Public Access
CertZipDL
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
/**
|
||||
* Tries roll ids one at a time, waiting for you to say what came out.
|
||||
*
|
||||
* docker compose exec -it visitor-signin node scripts/print-probe.mjs
|
||||
*
|
||||
* Note the -it: this asks questions, so the container needs a terminal attached.
|
||||
*
|
||||
* Start with printer-status.mjs — if the printer answers, it tells you the right
|
||||
* id outright and this is unnecessary. Use this when the printer will not report
|
||||
* its status, or when it does and the job is still refused.
|
||||
*/
|
||||
import fs from 'node:fs';
|
||||
import readline from 'node:readline/promises';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import db from '../src/db.js';
|
||||
import config from '../src/config.js';
|
||||
import * as printer from '../src/printer.js';
|
||||
|
||||
// Ordered by how likely each is on a 62 mm machine, cheapest guesses first.
|
||||
const CANDIDATES = [
|
||||
['62', '62 mm continuous, black only'],
|
||||
['62x100', '62 x 100 mm die-cut'],
|
||||
['62red', '62 mm continuous, black and red (DK-22251)'],
|
||||
['62x29', '62 x 29 mm die-cut'],
|
||||
['29', '29 mm continuous'],
|
||||
['29x90', '29 x 90 mm die-cut'],
|
||||
['38', '38 mm continuous'],
|
||||
['50', '50 mm continuous'],
|
||||
['54', '54 mm continuous'],
|
||||
];
|
||||
|
||||
const site = db
|
||||
.prepare('SELECT * FROM sites WHERE printer_host IS NOT NULL ORDER BY id LIMIT 1')
|
||||
.get();
|
||||
|
||||
if (!site) {
|
||||
console.error('No site has a printer address set.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const target = `tcp://${site.printer_host}:${site.printer_port || 9100}`;
|
||||
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
||||
|
||||
console.log(`\n Printer: ${site.printer_model || 'QL-820NWB'} at ${target}`);
|
||||
console.log(' One label will be sent per attempt. After each, say whether anything came out.');
|
||||
console.log(' Press Ctrl+C at any point to stop.\n');
|
||||
|
||||
const results = [];
|
||||
|
||||
for (const [label, description] of CANDIDATES) {
|
||||
const answer = (await rl.question(` Try "${label}" (${description})? [Y/n/q] `)).trim().toLowerCase();
|
||||
if (answer === 'q') break;
|
||||
if (answer === 'n') {
|
||||
results.push([label, 'skipped']);
|
||||
continue;
|
||||
}
|
||||
|
||||
const png = await printer.renderBadgePng(printer.sampleVisit(site), { ...site, printer_label: label });
|
||||
const file = '/tmp/probe.png';
|
||||
fs.writeFileSync(file, png);
|
||||
|
||||
let sent = true;
|
||||
let detail = '';
|
||||
try {
|
||||
execFileSync(
|
||||
config.printing.command,
|
||||
[
|
||||
'--backend', 'network',
|
||||
'--model', site.printer_model || 'QL-820NWB',
|
||||
'--printer', target,
|
||||
'print', '--label', label, file,
|
||||
],
|
||||
{ stdio: ['ignore', 'pipe', 'pipe'], timeout: config.printing.timeoutMs }
|
||||
);
|
||||
} catch (err) {
|
||||
sent = false;
|
||||
detail = `${err.stdout || ''}${err.stderr || ''}`.trim().split('\n').pop() || err.message;
|
||||
}
|
||||
|
||||
if (!sent) {
|
||||
console.log(` could not send: ${detail}\n`);
|
||||
results.push([label, `send failed: ${detail}`]);
|
||||
continue;
|
||||
}
|
||||
|
||||
const came = (await rl.question(' Did a label print? [y/N] ')).trim().toLowerCase();
|
||||
if (came === 'y') {
|
||||
results.push([label, 'PRINTED']);
|
||||
console.log(`\n That is the one. Set "Roll loaded in the printer" so it sends ${label}.\n`);
|
||||
break;
|
||||
}
|
||||
results.push([label, 'nothing came out']);
|
||||
console.log(' Clear the error on the printer (open and close the cover) before the next try.\n');
|
||||
}
|
||||
|
||||
rl.close();
|
||||
|
||||
console.log(' Summary');
|
||||
for (const [label, outcome] of results) console.log(` ${label.padEnd(8)} ${outcome}`);
|
||||
console.log('');
|
||||
@@ -0,0 +1,151 @@
|
||||
/**
|
||||
* Asks the printer what it actually has loaded, and what it is complaining about.
|
||||
*
|
||||
* docker compose exec visitor-signin node scripts/printer-status.mjs
|
||||
* docker compose exec visitor-signin node scripts/printer-status.mjs --host 10.0.0.5
|
||||
*
|
||||
* brother_ql's network backend only writes to the socket; it never reads, which is
|
||||
* why a refused job still looks like a success. The Brother raster protocol has a
|
||||
* status request that returns a 32 byte block describing the media in the machine
|
||||
* and any error, so we ask directly.
|
||||
*/
|
||||
import net from 'node:net';
|
||||
import db from '../src/db.js';
|
||||
|
||||
function arg(name, fallback = null) {
|
||||
const i = process.argv.indexOf(`--${name}`);
|
||||
return i > -1 && process.argv[i + 1] && !process.argv[i + 1].startsWith('--')
|
||||
? process.argv[i + 1]
|
||||
: fallback;
|
||||
}
|
||||
|
||||
const site = arg('site')
|
||||
? db.prepare('SELECT * FROM sites WHERE id = ?').get(Number(arg('site')))
|
||||
: db.prepare('SELECT * FROM sites WHERE printer_host IS NOT NULL ORDER BY id LIMIT 1').get();
|
||||
|
||||
const host = arg('host', site?.printer_host);
|
||||
const port = Number(arg('port', site?.printer_port || 9100));
|
||||
|
||||
if (!host) {
|
||||
console.error('No printer address. Set one in Admin -> Sites, or pass --host.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------- decoding */
|
||||
|
||||
const MEDIA_TYPES = {
|
||||
0x00: 'no media loaded',
|
||||
0x0a: 'continuous roll',
|
||||
0x0b: 'die-cut labels',
|
||||
0x4a: 'continuous roll (cleaning)',
|
||||
0x4b: 'die-cut labels (cleaning)',
|
||||
};
|
||||
|
||||
const ERRORS_1 = [
|
||||
[0x01, 'no media loaded'],
|
||||
[0x02, 'end of media'],
|
||||
[0x04, 'cutter jam'],
|
||||
[0x08, 'weak batteries'],
|
||||
[0x10, 'printer in use'],
|
||||
[0x80, 'printer turned off'],
|
||||
];
|
||||
|
||||
const ERRORS_2 = [
|
||||
[0x01, 'wrong media — the job does not match the roll that is loaded'],
|
||||
[0x04, 'expansion buffer full'],
|
||||
[0x08, 'communication error'],
|
||||
[0x10, 'communication buffer full'],
|
||||
[0x20, 'cover is open'],
|
||||
[0x40, 'cancel key pressed'],
|
||||
[0x80, 'media cannot be fed'],
|
||||
];
|
||||
|
||||
function decode(buf) {
|
||||
if (buf.length < 32) return { error: `Short reply (${buf.length} bytes).` };
|
||||
const mediaWidth = buf[10];
|
||||
const mediaType = buf[11];
|
||||
const mediaLength = buf[17];
|
||||
return {
|
||||
mediaWidthMm: mediaWidth,
|
||||
mediaLengthMm: mediaLength,
|
||||
mediaType: MEDIA_TYPES[mediaType] || `unknown (0x${mediaType.toString(16)})`,
|
||||
mediaTypeRaw: mediaType,
|
||||
errors: [
|
||||
...ERRORS_1.filter(([bit]) => buf[8] & bit).map(([, text]) => text),
|
||||
...ERRORS_2.filter(([bit]) => buf[9] & bit).map(([, text]) => text),
|
||||
],
|
||||
raw: buf.subarray(0, 32).toString('hex').replace(/(..)/g, '$1 ').trim(),
|
||||
};
|
||||
}
|
||||
|
||||
/** The label id brother_ql should be given, worked out from what is loaded. */
|
||||
function suggestLabel(status) {
|
||||
if (status.mediaTypeRaw === 0x00) return null;
|
||||
const continuous = status.mediaTypeRaw === 0x0a || status.mediaTypeRaw === 0x4a;
|
||||
if (continuous) {
|
||||
return String(status.mediaWidthMm); // 62, 29, 12 ...
|
||||
}
|
||||
return status.mediaLengthMm
|
||||
? `${status.mediaWidthMm}x${status.mediaLengthMm}`
|
||||
: `${status.mediaWidthMm} (die-cut, length unknown)`;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------- asking */
|
||||
|
||||
console.log(`\n Asking ${host}:${port} what it has loaded...\n`);
|
||||
|
||||
const socket = net.createConnection({ host, port, timeout: 8000 });
|
||||
const chunks = [];
|
||||
|
||||
socket.on('connect', () => {
|
||||
// 200 null bytes clears any half-finished job, then initialise, then ask.
|
||||
socket.write(Buffer.alloc(200, 0x00));
|
||||
socket.write(Buffer.from([0x1b, 0x40]));
|
||||
socket.write(Buffer.from([0x1b, 0x69, 0x53]));
|
||||
});
|
||||
|
||||
socket.on('data', (d) => {
|
||||
chunks.push(d);
|
||||
if (Buffer.concat(chunks).length >= 32) socket.end();
|
||||
});
|
||||
|
||||
socket.on('timeout', () => {
|
||||
socket.destroy();
|
||||
if (!chunks.length) {
|
||||
console.error(' The printer accepted the connection but sent nothing back.');
|
||||
console.error(' Some firmware only answers when idle — make sure it is not mid-job,');
|
||||
console.error(' and that nothing else is holding port 9100 open.\n');
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('error', (err) => {
|
||||
console.error(` Could not reach it: ${err.message}\n`);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
socket.on('close', () => {
|
||||
const buf = Buffer.concat(chunks);
|
||||
if (!buf.length) process.exit(1);
|
||||
|
||||
const status = decode(buf);
|
||||
if (status.error) {
|
||||
console.error(` ${status.error}\n raw: ${buf.toString('hex')}\n`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(` media loaded ${status.mediaWidthMm} mm ${status.mediaType}`);
|
||||
if (status.mediaLengthMm) console.log(` label length ${status.mediaLengthMm} mm`);
|
||||
console.log(` errors ${status.errors.length ? status.errors.join('; ') : 'none reported'}`);
|
||||
console.log(` raw status ${status.raw}`);
|
||||
|
||||
const suggested = suggestLabel(status);
|
||||
console.log('');
|
||||
if (!suggested) {
|
||||
console.log(' No media detected. Open and close the cover to make it re-read the roll.');
|
||||
} else {
|
||||
console.log(` Use this roll id: --label ${suggested}`);
|
||||
console.log(` Try it with: node scripts/print-test.mjs --label ${suggested}`);
|
||||
}
|
||||
console.log('');
|
||||
});
|
||||
Reference in New Issue
Block a user