/** * Renders a badge and prints it, showing everything on the way through. * * For working out why a printer will not accept a job, without going through the * kiosk or the admin console. Run inside the container: * * docker compose exec visitor-signin node scripts/print-test.mjs * docker compose exec visitor-signin node scripts/print-test.mjs --label 62x100 * docker compose exec visitor-signin node scripts/print-test.mjs --dry * * Options: * --site N which site to use (default: the first one) * --label ID override the roll id sent to the printer, without saving it * --rotate DEG override the rotation, without saving it * --dry render only, do not print */ import fs from 'node:fs'; 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'; 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 has = (name) => process.argv.includes(`--${name}`); const siteId = Number(arg('site', 0)); const site = siteId ? db.prepare('SELECT * FROM sites WHERE id = ?').get(siteId) : db.prepare('SELECT * FROM sites ORDER BY id LIMIT 1').get(); if (!site) { console.error('No sites exist yet.'); process.exit(1); } if (arg('label')) site.printer_label = arg('label'); if (arg('rotate')) site.printer_rotate = Number(arg('rotate')); const label = printer.labelFor(site); const target = `tcp://${site.printer_host}:${site.printer_port || 9100}`; console.log(''); console.log(` site ${site.name}`); console.log(` printer ${site.printer_model || 'QL-820NWB'} at ${target}`); console.log(` server printing ${site.printer_enabled ? 'on' : 'OFF — the kiosk would print instead'}`); console.log(` badge ${site.badge_width_mm} x ${site.badge_height_mm} mm, rotate ${site.printer_rotate || 0}`); console.log(` roll setting ${site.printer_label || '(unset)'} -> --label ${label}`); console.log(` red requested ${Boolean(site.badge_accent)}, will print ${printer.accentWillPrintRed(site)}`); const png = await printer.renderBadgePng(printer.sampleVisit(site), site); const file = '/tmp/print-test.png'; fs.writeFileSync(file, png); console.log(` rendered ${png.readUInt32BE(16)} x ${png.readUInt32BE(20)} dots -> ${file}`); if (!site.printer_host) { console.error('\n No printer address set for this site. Set one in Admin -> Sites -> Edit.'); process.exit(1); } if (has('dry')) { console.log('\n --dry given, so nothing was sent.\n'); process.exit(0); } const args = [ '--backend', 'network', '--model', site.printer_model || 'QL-820NWB', '--printer', target, 'print', '--label', label, ]; // Required on black/red tape even when the badge is entirely black. if (label === '62red') args.push('--red'); args.push(file); console.log(''); console.log(` running: ${config.printing.command} ${args.join(' ')}`); console.log(''); try { const out = execFileSync(config.printing.command, args, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], timeout: config.printing.timeoutMs, }); console.log(out.trim() || ' (no output)'); console.log('\n Sent. If nothing came out, the printer rejected it silently — check its display.\n'); } catch (err) { console.error(' FAILED\n'); console.error(`${err.stdout || ''}${err.stderr || ''}`.trim() || err.message); console.error(''); console.error(' Roll ids this printer understands:'); console.error(' 62 62 mm continuous, black only'); console.error(' 62red 62 mm continuous, black and red (DK-22251)'); console.error(' 62x100 62 x 100 mm DIE-CUT (pre-cut labels, not a continuous roll)'); console.error(' 62x29 62 x 29 mm die-cut'); console.error(''); console.error(' A continuous roll sent a die-cut id, or the reverse, is refused as a wrong roll.'); console.error(' Try: node scripts/print-test.mjs --label 62x100'); console.error(''); process.exit(1); }