From 8d2111ff41a774dd58b44573546c6ce38cc73cde Mon Sep 17 00:00:00 2001 From: jessikitty Date: Mon, 7 Sep 2026 14:25:53 +1000 Subject: [PATCH] Align status decoding with the manual MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Decodes the model code (byte 4) to a name, adds the cooling notification values, and rewords the error-information-2 bit 0 message. That bit is a media mismatch, which in practice means a monochrome job sent to a black/red roll — the exact failure seen on the QL-820NWB here. --- src/printing/ql-status.js | 55 ++++++++++++++++++++++++++++----------- 1 file changed, 40 insertions(+), 15 deletions(-) diff --git a/src/printing/ql-status.js b/src/printing/ql-status.js index 2085d82..537adfa 100644 --- a/src/printing/ql-status.js +++ b/src/printing/ql-status.js @@ -1,23 +1,26 @@ /** * Decoder for the 32-byte status frame the QL series sends back. * - * This is what the Python brother_ql path could never give us: real printer - * state rather than a parsed traceback. Cover open, roll empty and wrong roll - * width can all be reported before a label is wasted. + * Field layout and every code below is from Brother's "Raster Command + * Reference, QL-800/810W/820NWB v1.01", section 4, "Status information + * request". + * + * Note this is only useful over USB. Manual section 5.9 shows the network + * flow: over a TCP/IP port the print data is simply sent as-is and no status + * comes back. See probeStatus in ql-transport.js. * - * Layout (confirmed against Brother's reference and live QL-8xx captures): * 0 print head mark (0x80) * 1 size (0x20) * 2 fixed 'B' (0x42) - * 3-4 device dependent (model code) - * 5 fixed '0' (0x30) - * 6-7 fixed + * 3 series code, fixed '4' (0x34) + * 4 model code + * 5-7 fixed * 8 error information 1 * 9 error information 2 * 10 media width in mm * 11 media type * 12-13 fixed - * 14 reserved + * 14 fixed 0x3F * 15 mode * 16 fixed * 17 media length in mm @@ -41,13 +44,17 @@ const ERROR_BITS_1 = [ ]; const ERROR_BITS_2 = [ - [0x01, 'Wrong media for this job'], + [ + 0x01, + 'Media mismatch — the loaded roll does not match the job. A black/red ' + + 'roll rejects a monochrome job, and vice versa.', + ], [0x02, 'Expansion buffer full'], - [0x04, 'Transmission or communication error'], + [0x04, 'Communication error'], [0x08, 'Communication buffer full'], [0x10, 'Cover is open'], [0x20, 'Cancelled at the printer'], - [0x40, 'Media cannot be fed'], + [0x40, 'Media cannot be fed, or the end of the media was detected'], [0x80, 'System error'], ]; @@ -65,6 +72,20 @@ const PHASE_TYPES = { 0x01: 'printing', }; +/** Notification numbers. Cooling pauses printing but is not a failure. */ +const NOTIFICATIONS = { + 0x00: null, + 0x03: 'Print head cooling (started)', + 0x04: 'Print head cooling (finished)', +}; + +/** Byte 4 of the status frame. */ +const MODEL_CODES = { + 0x38: 'QL-800', + 0x39: 'QL-810W', + 0x41: 'QL-820NWB', +}; + const MEDIA_TYPES = { 0x00: 'none', 0x0a: 'continuous', @@ -94,7 +115,7 @@ function decodeStatus(buf) { const statusType = STATUS_TYPES[buf[18]] || `unknown_0x${buf[18].toString(16)}`; return { - modelCode: buf.readUInt16BE(3), + model: MODEL_CODES[buf[4]] || `unknown_0x${buf[4].toString(16)}`, errors, hasError: errors.length > 0 || buf[18] === 0x02, mediaWidthMm: buf[10], @@ -104,7 +125,8 @@ function decodeStatus(buf) { statusType, phaseType: PHASE_TYPES[buf[19]] || `unknown_0x${buf[19].toString(16)}`, phaseNumber: buf.readUInt16BE(20), - notification: buf[22], + notification: NOTIFICATIONS[buf[22]] ?? `unknown_0x${buf[22].toString(16)}`, + cooling: buf[22] === 0x03, raw: Buffer.from(buf.subarray(0, FRAME_LENGTH)), }; } @@ -140,8 +162,10 @@ function describeProblem(status) { /** * Check the loaded roll matches what the job expects. * - * Worth doing before sending: a two-colour job on a plain roll is refused with - * "wrong roll type", which gives nobody a clue that a colour setting caused it. + * Worth doing before sending where status is available: a monochrome job on a + * black/red roll is refused outright, and the printer's own message ("change + * it to Monochrome media") points at the driver rather than at the roll + * setting, which sends people the wrong way. * * @returns {string|null} a problem description, or null if it matches. */ @@ -163,6 +187,7 @@ function checkMediaMatches(status, media) { export { FRAME_LENGTH, + MODEL_CODES, decodeStatus, decodeAll, describeProblem,