Align status decoding with the manual

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.
This commit is contained in:
2026-09-07 14:25:53 +10:00
parent b4001c97c8
commit 8d2111ff41
+40 -15
View File
@@ -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,