From 59dd973b864f2771f7043e4267552eba066b3f26 Mon Sep 17 00:00:00 2001 From: receiptline Date: Fri, 10 Jan 2025 00:39:09 +0900 Subject: [PATCH] Added cash drawer status --- CHANGELOG.md | 7 +++ README.md | 31 ++++++++++++ lib/receipt-serial.js | 85 +++++++++++++++++++++++++++++++-- test/print.html | 12 +++++ test/receipt-serial-console.js | 86 ++++++++++++++++++++++++++++++++-- 5 files changed, 215 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 55b59ef..0fb72a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [1.1.0] - 2025-01-10 +### Added +- Cash drawer status + +### Fixed +- Buffer length check + ## [1.0.5] - 2024-04-14 ### Fixed - README diff --git a/README.md b/README.md index cd641c4..d846d37 100644 --- a/README.md +++ b/README.md @@ -375,6 +375,31 @@ https://receiptline.github.io/receiptjs/test/print.html - `offline`: printer is off or offline - `disconnect`: printer is not connected +## receiptSerial.drawer + +The receiptSerial.drawer instance property is a string representing the cash drawer status. + +### Value + +- A string representing the cash drawer status. + - `drawerclosed`: drawer is closed + - `draweropen`: drawer is open + - `offline`: printer is off or offline + - `disconnect`: printer is not connected + +## receiptSerial.invertDrawerState(invert) + +The invertDrawerState() instance method inverts cash drawer state. + +### Parameters + +- `invert` <boolean> + - if true, invert drawer state + +### Return value + +- None. + ## receiptSerial.close() The close() instance method closes the connection. @@ -405,6 +430,9 @@ The on() instance method adds the `listener` function to the listeners array for - `error`: printer error (except cover open and paper empty) - `offline`: printer is off or offline - `disconnect`: printer is not connected + - `drawer`: drawer status updated + - `drawerclosed`: drawer is closed + - `draweropen`: drawer is open - `listener` - the listener function @@ -429,6 +457,9 @@ The off() instance method removes the `listener` function from the listeners arr - `error`: printer error (except cover open and paper empty) - `offline`: printer is off or offline - `disconnect`: printer is not connected + - `drawer`: drawer status updated + - `drawerclosed`: drawer is closed + - `draweropen`: drawer is open - `listener` - the listener function diff --git a/lib/receipt-serial.js b/lib/receipt-serial.js index 22f6c44..639cc7f 100644 --- a/lib/receipt-serial.js +++ b/lib/receipt-serial.js @@ -121,7 +121,9 @@ const ReceiptSerial = (() => { paperempty: 'paperempty', error: 'error', offline: 'offline', - disconnect: 'disconnect' + disconnect: 'disconnect', + drawerclosed: 'drawerclosed', + draweropen: 'draweropen' }; // control commands @@ -130,7 +132,7 @@ const ReceiptSerial = (() => { clear: '\x10\x14\x08\x01\x03\x14\x01\x06\x02\x08', // DLE DC4 n d1 d2 d3 d4 d5 d6 d7 siiasb: '\x1da\xff', // GS a n starasb: '\x1b\x1ea\x01\x17', // ESC RS a n ETB - escasb: '\x1dI\x42\x1dI\x43\x1da\xff' // GS I n GS I n GS a n + escasb: '\x10\x04\x01\x1dI\x42\x1dI\x43\x1da\xff' // DLE EOT n GS I n GS I n GS a n }; return { @@ -150,7 +152,10 @@ const ReceiptSerial = (() => { paperempty: [], error: [], offline: [], - disconnect: [] + disconnect: [], + drawer: [], + drawerclosed: [], + draweropen: [] }; // promise resolver let resolve = () => {}; @@ -176,6 +181,33 @@ const ReceiptSerial = (() => { } } }; + // drawer status + let drawer = state.offline; + // invert drawer status + let invertion = false; + // update drawer status + const updateDrawer = newstatus => { + let d = newstatus; + // invert drawer status + if (invertion) { + switch (d) { + case state.drawerclosed: + d = state.draweropen; + break; + case state.draweropen: + d = state.drawerclosed; + break; + default: + break; + } + } + if (d !== drawer) { + // status event + drawer = d; + dispatch(listeners.drawer, drawer); + dispatch(listeners[drawer]); + } + }; // timer let timeout = 0; // printer control language @@ -217,6 +249,7 @@ const ReceiptSerial = (() => { conn.on('close', () => { // disconnect event update(state.disconnect); + updateDrawer(state.disconnect); }); // data event conn.on('data', data => { @@ -245,6 +278,7 @@ const ReceiptSerial = (() => { const l = ((buffer[0] >> 2 & 0x18) | (buffer[0] >> 1 & 0x07)) + (buffer[1] >> 6 & 0x02); // check length if (l <= len) { + // printer if ((buffer[2] & 0x20) === 0x20) { // cover open event update(state.coveropen); @@ -260,6 +294,8 @@ const ReceiptSerial = (() => { else { // nothing to do } + // cash drawer + updateDrawer((buffer[2] & 0x04) === 0x04 ? state.draweropen : state.drawerclosed); // clear data buffer.splice(0, l); // clear timer @@ -346,6 +382,7 @@ const ReceiptSerial = (() => { else if ((buffer[0] & 0xf0) === 0xc0) { // sii: automatic status if (len > 7) { + // printer if ((buffer[1] & 0xf8) === 0xd8) { // cover open event update(state.coveropen); @@ -365,6 +402,8 @@ const ReceiptSerial = (() => { else { // nothing to do } + // cash drawer + updateDrawer((buffer[3] & 0xf8) === 0xd8 ? state.drawerclosed : state.draweropen); // clear data buffer.splice(0, 8); } @@ -382,6 +421,7 @@ const ReceiptSerial = (() => { const l = ((buffer[0] >> 2 & 0x08) | (buffer[0] >> 1 & 0x07)) + (buffer[1] >> 6 & 0x02); // check length if (l <= len) { + // printer if ((buffer[2] & 0x20) === 0x20) { // cover open event update(state.coveropen); @@ -405,6 +445,8 @@ const ReceiptSerial = (() => { else { // nothing to do } + // cash drawer + updateDrawer((buffer[2] & 0x04) === 0x04 ? state.draweropen : state.drawerclosed); // clear data buffer.splice(0, l); } @@ -430,6 +472,7 @@ const ReceiptSerial = (() => { else if ((buffer[0] & 0x93) === 0x10) { // escpos: automatic status if (len > 3 && (buffer[1] & 0x90) === 0 && (buffer[2] & 0x90) === 0 && (buffer[3] & 0x90) === 0) { + // printer if ((buffer[0] & 0x20) === 0x20) { // cover open event update(state.coveropen); @@ -449,6 +492,8 @@ const ReceiptSerial = (() => { else { // nothing to do } + // cash drawer + updateDrawer((buffer[0] & 0x04) === 0x04 ? state.drawerclosed : state.draweropen); // clear data buffer.splice(0, 4); } @@ -487,12 +532,22 @@ const ReceiptSerial = (() => { } // offline event update(state.offline); + updateDrawer(state.offline); } else { // nothing to do } } } + else if ((buffer[0] & 0x93) === 0x12) { + // escpos: realtime status + // clear timer + clearTimeout(timeout); + // cash drawer + updateDrawer((buffer[0] & 0x97) === 0x16 ? state.drawerclosed : state.draweropen); + // clear data + buffer.shift(); + } else { // escpos: other buffer.shift(); @@ -513,6 +568,30 @@ const ReceiptSerial = (() => { get status() { return status; }, + /** + * Cash drawer status. + * @type {string} cash drawer status + */ + get drawer() { + return drawer; + }, + /** + * Invert cash drawer state. + * @param {boolean} invert invert cash drawer state + */ + invertDrawerState(invert) { + invertion = !!invert; + switch (drawer) { + case state.drawerclosed: + drawer = state.draweropen; + break; + case state.draweropen: + drawer = state.drawerclosed; + break; + default: + break; + } + }, /** * Print receipt markdown. * @param {string} markdown receipt markdown diff --git a/test/print.html b/test/print.html index 008073a..776a930 100644 --- a/test/print.html +++ b/test/print.html @@ -16,6 +16,7 @@ const open = document.querySelector('#open'); const print = document.querySelector('#print'); const close = document.querySelector('#close'); + const invert = document.querySelector('#invert'); const body = document.querySelector('body'); const log = msg => { const div = document.createElement('div'); @@ -26,6 +27,9 @@ open.onclick = async () => { if (!conn) { conn = ReceiptSerial.connect(); + conn.on('drawer', status => { + log(status); + }); conn.on('status', status => { log(status); }); @@ -35,6 +39,7 @@ conn.on('disconnect', () => { conn = null; }); + conn.invertDrawerState(invert.checked); } }; print.onclick = async () => { @@ -47,6 +52,11 @@ conn.close(); } }; + invert.onchange = () => { + if (conn) { + conn.invertDrawerState(invert.checked); + } + }; } @@ -63,6 +73,8 @@ + +
diff --git a/test/receipt-serial-console.js b/test/receipt-serial-console.js index e83bfb4..3a1c6c8 100644 --- a/test/receipt-serial-console.js +++ b/test/receipt-serial-console.js @@ -126,7 +126,9 @@ const ReceiptSerial = (() => { paperempty: 'paperempty', error: 'error', offline: 'offline', - disconnect: 'disconnect' + disconnect: 'disconnect', + drawerclosed: 'drawerclosed', + draweropen: 'draweropen' }; // control commands @@ -135,7 +137,7 @@ const ReceiptSerial = (() => { clear: '\x10\x14\x08\x01\x03\x14\x01\x06\x02\x08', // DLE DC4 n d1 d2 d3 d4 d5 d6 d7 siiasb: '\x1da\xff', // GS a n starasb: '\x1b\x1ea\x01\x17', // ESC RS a n ETB - escasb: '\x1dI\x42\x1dI\x43\x1da\xff' // GS I n GS I n GS a n + escasb: '\x10\x04\x01\x1dI\x42\x1dI\x43\x1da\xff' // DLE EOT n GS I n GS I n GS a n }; return { @@ -155,7 +157,10 @@ const ReceiptSerial = (() => { paperempty: [], error: [], offline: [], - disconnect: [] + disconnect: [], + drawer: [], + drawerclosed: [], + draweropen: [] }; // promise resolver let resolve = () => {}; @@ -184,6 +189,34 @@ const ReceiptSerial = (() => { } console.log(new Date().toISOString(), 'status:', status); }; + // drawer status + let drawer = state.offline; + // invert drawer status + let invertion = false; + // update drawer status + const updateDrawer = newstatus => { + let d = newstatus; + // invert drawer status + if (invertion) { + switch (d) { + case state.drawerclosed: + d = state.draweropen; + break; + case state.draweropen: + d = state.drawerclosed; + break; + default: + break; + } + } + if (d !== drawer) { + // status event + drawer = d; + dispatch(listeners.drawer, drawer); + dispatch(listeners[drawer]); + } + console.log(new Date().toISOString(), 'status:', drawer); + }; // timer let timeout = 0; // printer control language @@ -225,6 +258,7 @@ const ReceiptSerial = (() => { conn.on('close', () => { // disconnect event update(state.disconnect); + updateDrawer(state.disconnect); }); // data event conn.on('data', data => { @@ -256,6 +290,7 @@ const ReceiptSerial = (() => { // check length if (l <= len) { console.log(new Date().toISOString(), 'star: automatic status'); + // printer if ((buffer[2] & 0x20) === 0x20) { // cover open event update(state.coveropen); @@ -271,6 +306,8 @@ const ReceiptSerial = (() => { else { // nothing to do } + // cash drawer + updateDrawer((buffer[2] & 0x04) === 0x04 ? state.draweropen : state.drawerclosed); // clear data buffer.splice(0, l); // clear timer @@ -366,6 +403,7 @@ const ReceiptSerial = (() => { // sii: automatic status console.log(new Date().toISOString(), 'sii: automatic status'); if (len > 7) { + // printer if ((buffer[1] & 0xf8) === 0xd8) { // cover open event update(state.coveropen); @@ -385,6 +423,8 @@ const ReceiptSerial = (() => { else { // nothing to do } + // cash drawer + updateDrawer((buffer[3] & 0xf8) === 0xd8 ? state.drawerclosed : state.draweropen); // clear data buffer.splice(0, 8); } @@ -404,6 +444,7 @@ const ReceiptSerial = (() => { // check length if (l <= len) { console.log(new Date().toISOString(), 'star: automatic status'); + // printer if ((buffer[2] & 0x20) === 0x20) { // cover open event update(state.coveropen); @@ -427,6 +468,8 @@ const ReceiptSerial = (() => { else { // nothing to do } + // cash drawer + updateDrawer((buffer[2] & 0x04) === 0x04 ? state.draweropen : state.drawerclosed); // clear data buffer.splice(0, l); } @@ -455,6 +498,7 @@ const ReceiptSerial = (() => { // escpos: automatic status if (len > 3 && (buffer[1] & 0x90) === 0 && (buffer[2] & 0x90) === 0 && (buffer[3] & 0x90) === 0) { console.log(new Date().toISOString(), 'escpos: automatic status'); + // printer if ((buffer[0] & 0x20) === 0x20) { // cover open event update(state.coveropen); @@ -474,6 +518,8 @@ const ReceiptSerial = (() => { else { // nothing to do } + // cash drawer + updateDrawer((buffer[0] & 0x04) === 0x04 ? state.drawerclosed : state.draweropen); // clear data buffer.splice(0, 4); } @@ -518,12 +564,22 @@ const ReceiptSerial = (() => { } // offline event update(state.offline); + updateDrawer(state.offline); } else { // nothing to do } } } + else if ((buffer[0] & 0x93) === 0x12) { + // escpos: realtime status + // clear timer + clearTimeout(timeout); + // cash drawer + updateDrawer((buffer[0] & 0x97) === 0x16 ? state.drawerclosed : state.draweropen); + // clear data + buffer.shift(); + } else { // escpos: other console.log(new Date().toISOString(), 'escpos: other'); @@ -545,6 +601,30 @@ const ReceiptSerial = (() => { get status() { return status; }, + /** + * Cash drawer status. + * @type {string} cash drawer status + */ + get drawer() { + return drawer; + }, + /** + * Invert cash drawer state. + * @param {boolean} invert invert cash drawer state + */ + invertDrawerState(invert) { + invertion = !!invert; + switch (drawer) { + case state.drawerclosed: + drawer = state.draweropen; + break; + case state.draweropen: + drawer = state.drawerclosed; + break; + default: + break; + } + }, /** * Print receipt markdown. * @param {string} markdown receipt markdown