├── .editorconfig ├── .github └── FUNDING.yml ├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── docs └── README.md ├── examples ├── barcode.js ├── bt_find_printer.js ├── bt_promise.js ├── codepage.js ├── color.js ├── encode.js ├── image.js ├── index.js ├── multiple.js ├── promise.js ├── qs_barcode.js ├── qs_qrcode.js ├── qs_text.js ├── server.js ├── star_printer_index.js ├── status.js ├── statuses.js ├── tux.png └── web │ ├── client.html │ ├── package.json │ └── server.js ├── lerna.json ├── package.json ├── packages ├── adapter │ ├── README.md │ ├── package.json │ ├── src │ │ └── index.ts │ ├── tsconfig.json │ └── tsconfig.ref.json ├── bluetooth │ ├── README.md │ ├── index.js │ ├── package.json │ └── rawbt.js ├── console │ ├── README.md │ ├── package.json │ ├── src │ │ └── index.ts │ ├── tsconfig.json │ └── tsconfig.ref.json ├── network │ ├── README.md │ ├── package.json │ ├── src │ │ └── index.ts │ ├── tsconfig.json │ └── tsconfig.ref.json ├── package.json ├── printer │ ├── README.md │ ├── package.json │ ├── src │ │ ├── commands.ts │ │ ├── image.ts │ │ ├── index.ts │ │ ├── statuses.ts │ │ └── utils.ts │ ├── tsconfig.json │ └── tsconfig.ref.json ├── screen │ ├── README.md │ ├── package.json │ ├── src │ │ ├── commands.ts │ │ └── index.ts │ ├── tsconfig.json │ └── tsconfig.ref.json ├── serialport │ ├── README.md │ ├── package.json │ ├── src │ │ └── index.ts │ ├── tsconfig.json │ └── tsconfig.ref.json ├── server │ └── server.js ├── tsconfig.json └── usb │ ├── README.md │ ├── package.json │ ├── src │ └── index.ts │ ├── tsconfig.json │ └── tsconfig.ref.json ├── screencasts └── IMG_1031.JPG └── test └── test.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | indent_size = 2 8 | indent_style = space 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: song940 2 | open_collective: node-escpos 3 | custom: https://git.io/fjRcB 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | 3 | *.log 4 | .idea/ 5 | yarn.lock 6 | package-lock.json 7 | 8 | node_modules/ 9 | dist/ 10 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_* 2 | node_modules 3 | *.sublime* 4 | psd 5 | thumb 6 | *.log -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "10" 4 | 5 | before_install: 6 | - sudo apt-get install build-essential libudev-dev -y 7 | 8 | install: 9 | - yarn global add node-pre-gyp-github && yarn install 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 lsong 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## ESCPOS PROJECT 2 | 3 | ESC/POS Printer driver for Node.js 4 | 5 | [![npm version](https://badge.fury.io/js/escpos.svg)](https://www.npmjs.com/package/escpos ) 6 | [![Build Status](https://travis-ci.org/song940/node-escpos.svg?branch=master)](https://travis-ci.org/song940/node-escpos) 7 | 8 | [![NPM](https://nodei.co/npm/escpos.png?downloads=true&downloadRank=true&stars=true)](https://npmjs.org/escpos ) 9 | 10 | Packages Available: 11 | 12 | + [escpos Printer](packages/printer/README.md) 13 | + [escpos Screen Display](packages/screen/README.md) 14 | + [escpos USB Adapter](packages/usb/README.md) 15 | + [escpos Network Adapter](packages/network/README.md) 16 | + [escpos Bluetooth Adapter](packages/bluetooth/README.md) 17 | + [escpos SerialPort Adapter](packages/serialport/README.md) 18 | 19 | ## Example 20 | 21 | ````javascript 22 | const escpos = require('escpos'); 23 | // install escpos-usb adapter module manually 24 | escpos.USB = require('escpos-usb'); 25 | // Select the adapter based on your printer type 26 | const device = new escpos.USB(); 27 | // const device = new escpos.Network('localhost'); 28 | // const device = new escpos.Serial('/dev/usb/lp0'); 29 | 30 | const options = { encoding: "GB18030" /* default */ } 31 | // encoding is optional 32 | 33 | const printer = new escpos.Printer(device, options); 34 | 35 | device.open(function(error){ 36 | printer 37 | .font('a') 38 | .align('ct') 39 | .style('bu') 40 | .size(1, 1) 41 | .text('The quick brown fox jumps over the lazy dog') 42 | .text('敏捷的棕色狐狸跳过懒狗') 43 | .barcode('1234567', 'EAN8') 44 | .table(["One", "Two", "Three"]) 45 | .tableCustom( 46 | [ 47 | { text:"Left", align:"LEFT", width:0.33, style: 'B' }, 48 | { text:"Center", align:"CENTER", width:0.33}, 49 | { text:"Right", align:"RIGHT", width:0.33 } 50 | ], 51 | { encoding: 'cp857', size: [1, 1] } // Optional 52 | ) 53 | .qrimage('https://github.com/song940/node-escpos', function(err){ 54 | this.cut(); 55 | this.close(); 56 | }); 57 | }); 58 | ```` 59 | - See `./examples` for more examples. 60 | 61 | ---- 62 | 63 | ## Screencast 64 | 65 | ![img_1031](https://user-images.githubusercontent.com/8033320/29250339-d66ce470-807b-11e7-89ce-9962da88ca18.JPG) 66 | 67 | ---- 68 | 69 | ## Contributing 70 | - Fork this repo 71 | - Clone your repo 72 | - Install dependencies 73 | - Checkout a feature branch 74 | - Feel free to add your features 75 | - Make sure your features are fully tested 76 | - Open a pull request, and enjoy <3 77 | 78 | ---- 79 | 80 | ## Contributors 81 | 82 | Thanks to our [contributors][contributors-href] 🎉👏 83 | 84 | + [Tao Yuan](https://github.com/taoyuan) 85 | + [Jose Vera](https://github.com/jor3l) 86 | + [Sébastien Vidal](https://github.com/Psychopoulet) 87 | + [Yu Yongwoo](https://github.com/uyu423) 88 | + [Attawit Kittikrairit](https://github.com/atton16) 89 | + [Michael Kuenzli](https://github.com/pfirpfel) 90 | 91 | [![](https://opencollective.com/node-escpos/contributors.svg?width=890&button=false)][contributors-href] 92 | 93 | ---- 94 | 95 | ### MIT license 96 | Copyright (c) 2015 ~ now Lsong 97 | 98 | Permission is hereby granted, free of charge, to any person obtaining a copy 99 | of this software and associated documentation files (the "Software"), to deal 100 | in the Software without restriction, including without limitation the rights 101 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 102 | copies of the Software, and to permit persons to whom the Software is 103 | furnished to do so, subject to the following conditions: 104 | 105 | The above copyright notice and this permission notice shall be included in 106 | all copies or substantial portions of the Software. 107 | 108 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 109 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 110 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 111 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 112 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 113 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 114 | THE SOFTWARE. 115 | 116 | --- 117 | 118 | [contributors-href]: https://github.com/song940/node-escpos/graphs/contributors 119 | 120 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /examples/barcode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const escpos = require('../'); 3 | 4 | const device = new escpos.USB(); 5 | 6 | const printer = new escpos.Printer(device); 7 | 8 | device.open(function() { 9 | printer 10 | .font('a') 11 | .align('ct') 12 | .size(1, 1) 13 | .text('EAN13 barcode example') 14 | .barcode('123456789012', 'EAN13') // code length 12 15 | .barcode('109876543210') // default type 'EAN13' 16 | .barcode('7654321', 'EAN8') // The EAN parity bit is automatically added. 17 | .cut() 18 | .close(); 19 | }); 20 | -------------------------------------------------------------------------------- /examples/bt_find_printer.js: -------------------------------------------------------------------------------- 1 | const { Bluetooth } = require('..'); 2 | 3 | (async () => { 4 | 5 | const printers = await Bluetooth.findPrinters(); 6 | console.log(printers); 7 | 8 | })(); 9 | -------------------------------------------------------------------------------- /examples/bt_promise.js: -------------------------------------------------------------------------------- 1 | const { Bluetooth, Printer } = require('..'); 2 | 3 | (async () => { 4 | 5 | try { 6 | const availablePrinters = await Bluetooth.findPrinters(); 7 | // Uncomment if you want to use a specific printer instead of the first bt device that responds: 8 | // const preferredPrinterName = 'MHT-P5801'; 9 | // const btPrinter = availablePrinters.filter(p => p && p.name === preferredPrinterName)[0]; 10 | const btPrinter = availablePrinters[0]; 11 | console.log('Connect to ' + btPrinter.name); 12 | const device = await Bluetooth.getDevice(btPrinter.address, btPrinter.channel); 13 | const printer = await Printer.create(device); 14 | 15 | await printer.text('hello'); 16 | await printer.cut(); 17 | await printer.close(); 18 | 19 | console.log('print job done'); 20 | } catch(error) { 21 | console.log('error', error); 22 | } 23 | })(); 24 | -------------------------------------------------------------------------------- /examples/codepage.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const escpos = require('../'); 3 | const path = require('path'); 4 | 5 | const device = new escpos.Serial('COM9', { baudRate: 9600, autoOpen: false }); 6 | // const device = new escpos.RawBT(); 7 | // const device = new escpos.Network('localhost'); 8 | // const device = new escpos.Serial('/dev/usb/lp0'); 9 | const printer = new escpos.Printer(device); 10 | 11 | device.open(function(err){ 12 | printer 13 | .encode('cp866') 14 | .setCharacterCodeTable(17) 15 | .text('Тест на кирилица.') 16 | .size(1, 1) 17 | .feed() 18 | .feed() 19 | .cut() 20 | .close(); 21 | }); -------------------------------------------------------------------------------- /examples/color.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const escpos = require('../'); 3 | 4 | // const device = new escpos.USB(0x0416, 0x5011); 5 | const device = new escpos.Network('192.168.0.90'); 6 | // const device = new escpos.Serial('/dev/usb/lp0'); 7 | const printer = new escpos.Printer(device); 8 | 9 | device.open(function(err){ 10 | 11 | printer 12 | .hardware('init') 13 | .font('a') 14 | .align('ct') 15 | .style('bu') 16 | .size(1, 1) 17 | .text('The quick brown fox jumps over the lazy dog') 18 | .color(1) 19 | .text('This line should be red') 20 | .color(0) 21 | .text('This line should be back to black') 22 | .color(1) 23 | .text('This line should be red') 24 | .close() 25 | }); 26 | 27 | -------------------------------------------------------------------------------- /examples/encode.js: -------------------------------------------------------------------------------- 1 | const escpos = require('../'); 2 | 3 | const device = new escpos.USB(); 4 | // const device = new escpos.RawBT(); 5 | // const device = new escpos.Network('localhost'); 6 | // const device = new escpos.Serial('/dev/usb/lp0'); 7 | const printer = new escpos.Printer(device); 8 | 9 | device.open(function(err){ 10 | 11 | printer 12 | .font('a') 13 | .align('ct') 14 | .size(1, 1) 15 | .text('敏捷的棕色狐狸跳过懒狗') // default encoding set is GB18030 16 | .encode('EUC-KR') // set encode globally 17 | .text('동해물과 백두산이 마르고 닳도록') 18 | .text('こんにちは', 'EUC-JP') // set encode functional 19 | .cut() 20 | .close(); 21 | }); 22 | -------------------------------------------------------------------------------- /examples/image.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const path = require('path'); 3 | const escpos = require('../'); 4 | 5 | const device = new escpos.USB(); 6 | const printer = new escpos.Printer(device); 7 | 8 | const tux = path.join(__dirname, 'tux.png'); 9 | escpos.Image.load(tux, function(image){ 10 | 11 | device.open(function(){ 12 | 13 | printer.align('ct') 14 | .image(image, 's8') 15 | .then(() => { 16 | printer.cut().close(); 17 | }); 18 | 19 | // OR non-async .raster(image, "mode") : printer.text("text").raster(image).cut().close(); 20 | 21 | }); 22 | 23 | }); -------------------------------------------------------------------------------- /examples/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const escpos = require('..'); 3 | 4 | const device = new escpos.USB(0x0416, 0x5011); 5 | // const device = new escpos.RawBT(); 6 | // const device = new escpos.Network('localhost'); 7 | // const device = new escpos.Serial('/dev/usb/lp0'); 8 | const printer = new escpos.Printer(device); 9 | 10 | device.open(function(err){ 11 | 12 | printer 13 | .font('a') 14 | .align('ct') 15 | .style('bu') 16 | .size(1, 1) 17 | .text('The quick brown fox jumps over the lazy dog') 18 | .text('敏捷的棕色狐狸跳过懒狗') 19 | .barcode('1234567', 'EAN8') 20 | .qrimage('https://github.com/song940/node-escpos', function(err){ 21 | this.cut(); 22 | this.close(); 23 | }); 24 | 25 | }); 26 | -------------------------------------------------------------------------------- /examples/multiple.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const escpos = require('..'); 3 | 4 | const devices = escpos.USB.findPrinter(); 5 | const network = new escpos.Network('localhost'); 6 | const lp0 = new escpos.Serial('/dev/usb/lp0'); 7 | const usb = escpos.USB(0x01, 0xff); 8 | 9 | const printer = new escpos.Printer([ 10 | devices[1], 11 | devices[3], 12 | network, 13 | lp0, 14 | usb 15 | ]); 16 | 17 | device.open(function(){ 18 | 19 | printer 20 | .font('a') 21 | .align('ct') 22 | .style('bu') 23 | .size(1, 1) 24 | .text('The quick brown fox jumps over the lazy dog') 25 | .text('敏捷的棕色狐狸跳过懒狗') 26 | .barcode('12345678', 'EAN8') 27 | .qrimage('https://github.com/song940/node-escpos', function(err){ 28 | this.cut(); 29 | this.close(); 30 | }); 31 | 32 | }); -------------------------------------------------------------------------------- /examples/promise.js: -------------------------------------------------------------------------------- 1 | const { USB, Printer } = require('..'); 2 | 3 | (async () => { 4 | 5 | const device = await USB.getDevice(); 6 | const printer = await Printer.create(device); 7 | 8 | await printer.text('hello'); 9 | await printer.cut(); 10 | await printer.close(); 11 | 12 | console.log('print job done'); 13 | 14 | })(); 15 | 16 | -------------------------------------------------------------------------------- /examples/qs_barcode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const escpos = require('../'); 3 | 4 | const device = new escpos.USB(0x0485, 0x7541); 5 | 6 | const printer = new escpos.Printer(device); 7 | 8 | device.open(function() { 9 | printer 10 | .model('qsprinter') 11 | .font('a') 12 | .align('ct') 13 | .size(1, 1) 14 | .encode('tis620') 15 | .text('EAN13 barcode example') 16 | .barcode('123456789012', 'EAN13') // code length 12 17 | // .barcode('109876543210') // default type 'EAN13' 18 | // .barcode('7654321', 'EAN8') // The EAN parity bit is automatically added. 19 | .close(); 20 | }); 21 | -------------------------------------------------------------------------------- /examples/qs_qrcode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const escpos = require('../'); 3 | 4 | const device = new escpos.USB(0x0485, 0x7541); 5 | 6 | const printer = new escpos.Printer(device); 7 | 8 | device.open(function() { 9 | printer 10 | .model('qsprinter') 11 | .font('a') 12 | .align('ct') 13 | .size(1, 1) 14 | .encode('utf8') 15 | .text('QR code example') 16 | // .qrcodeqs('http://agriex.market') 17 | .qrcode('ทดสอบ') 18 | // .barcode('123456789012', 'EAN13') // code length 12 19 | // .barcode('109876543210') // default type 'EAN13' 20 | // .barcode('7654321', 'EAN8') // The EAN parity bit is automatically added. 21 | .close(); 22 | }); 23 | -------------------------------------------------------------------------------- /examples/qs_text.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const escpos = require('../'); 3 | 4 | const device = new escpos.USB(0x0485, 0x7541); 5 | // const device = new escpos.Network('localhost'); 6 | // const device = new escpos.Serial('/dev/usb/lp0'); 7 | const printer = new escpos.Printer(device); 8 | 9 | device.open(function(err){ 10 | 11 | printer 12 | .model('qsprinter') 13 | .font('a') 14 | .align('ct') 15 | .style('bu') 16 | .size(1, 1) 17 | .encode('tis620') 18 | .text('The quick brown fox jumps over the lazy dog') 19 | .text('สวัสดีภาษาไทย') 20 | .close(); 21 | // .text('敏捷的棕色狐狸跳过懒狗') 22 | // .barcode('1234567', 'EAN8') 23 | // .qrimage('https://github.com/song940/node-escpos', function(err){ 24 | // this.cut(); 25 | // this.close(); 26 | // }); 27 | 28 | }); 29 | -------------------------------------------------------------------------------- /examples/server.js: -------------------------------------------------------------------------------- 1 | const escpos = require('..'); 2 | 3 | const device = new escpos.USB(); 4 | const server = new escpos.Server(device); 5 | 6 | device.open(() => { 7 | server.listen(6000, err => { 8 | console.log('Your printer is running at', server.address().port); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /examples/star_printer_index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const escpos = require('..'); 3 | 4 | // const device = new escpos.USB(0x0416, 0x5011); 5 | // const device = new escpos.RawBT(); 6 | const device = new escpos.Network('STARPrinterIPAddress'); 7 | // const device = new escpos.Serial('/dev/usb/lp0'); 8 | const printer = new escpos.Printer(device); 9 | 10 | device.open(function(err){ 11 | printer 12 | .font('a') 13 | .align("STAR_CA") 14 | .style('bu') 15 | .size(1, 1) 16 | .emphasize() 17 | .text('The quick brown fox jumps over the lazy dog') 18 | .cancelEmphasize() 19 | .align("STAR_LA") 20 | .text('敏捷的棕色狐狸跳过懒狗') 21 | .align("STAR_RA") 22 | .barcode('1234567', 'EAN8') 23 | .qrimage('https://github.com/song940/node-escpos', function(err){ 24 | this.fullCut() 25 | this.close(); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /examples/status.js: -------------------------------------------------------------------------------- 1 | const escpos = require('../packages/printer'); 2 | escpos.SerialPort = require('../packages/serialport/src'); 3 | const statuses = require('../packages/printer/src/statuses'); 4 | 5 | const device = new escpos.SerialPort('COM3'); 6 | const printer = new escpos.Printer(device); 7 | 8 | device.open(function (error) { 9 | if (error) { 10 | console.error(error); 11 | return; 12 | } 13 | 14 | printer 15 | // TODO: Update 16 | .getStatus('PrinterStatus', status => { 17 | console.log(status.toJSON()); 18 | }) 19 | .close(); 20 | }); 21 | -------------------------------------------------------------------------------- /examples/statuses.js: -------------------------------------------------------------------------------- 1 | const escpos = require('../packages/printer'); 2 | escpos.SerialPort = require('../packages/serialport/src'); 3 | 4 | const device = new escpos.SerialPort('COM3'); 5 | const printer = new escpos.Printer(device); 6 | 7 | device.open(function (error) { 8 | if (error) { 9 | console.error(error); 10 | return; 11 | } 12 | printer 13 | .getStatuses(statuses => { 14 | statuses.forEach(status => { 15 | console.log(status.toJSON()); 16 | }) 17 | }) 18 | .close(); 19 | }); 20 | -------------------------------------------------------------------------------- /examples/tux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lsongdev/node-escpos/465cd2653ce94ad4f8157f48dcbd3ec0aac0019c/examples/tux.png -------------------------------------------------------------------------------- /examples/web/client.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ESC POS 5 | 6 | 7 | 8 | 9 | 10 |
11 |

ESC POS

12 |
13 |
14 | 15 | 16 |
17 |
18 | 19 | 20 |
21 | 22 |
23 |
24 | 25 | 26 | 27 | 28 | 29 | 43 | -------------------------------------------------------------------------------- /examples/web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "body-parser": "^1.19.0", 4 | "cors": "^2.8.5", 5 | "dotenv": "^8.2.0", 6 | "escpos": "^3.0.0-alpha.3", 7 | "escpos-usb": "^3.0.0-alpha.2", 8 | "express": "^4.17.1" 9 | }, 10 | "devDependencies": { 11 | "nodemon": "^2.0.4" 12 | }, 13 | "scripts": { 14 | "dev": "nodemon server.js" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/web/server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const path = require('path') 3 | const escpos = require('escpos') 4 | escpos.USB = require('escpos-usb') 5 | 6 | const device = new escpos.USB(); 7 | const options = { encoding: "GB18030" } 8 | const printer = new escpos.Printer(device, options) 9 | 10 | var bodyParser = require('body-parser') 11 | var app = require('express')() 12 | var http = require('http').Server(app) 13 | var cors = require('cors') 14 | app.use(cors()) 15 | app.use(bodyParser.json()) 16 | 17 | const port = 4000; 18 | 19 | app.post('/print', (req, res) => { 20 | res.json( 21 | { status: 'success' } 22 | ) 23 | console.log(req.body) 24 | print(req.body.text) 25 | }); 26 | 27 | http.listen(port, () => { 28 | console.log(`Printer: http://localhost:${port}`); 29 | }); 30 | 31 | const print = (text) => { 32 | device.open(function () { 33 | printer 34 | .font('a') 35 | .align('ct') 36 | .style('bu') 37 | .size(1, 1) 38 | .text(text) 39 | .cut() 40 | .close(); 41 | }); 42 | } 43 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [ 3 | "packages/*" 4 | ], 5 | "version": "3.0.0-alpha.6" 6 | } 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "root", 3 | "private": true, 4 | "devDependencies": { 5 | "lerna": "^3.20.2" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/adapter/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Adapter 3 | 4 | ### Constructors 5 | 6 | You can choose your adapter type as USB, Serial, Bluetooth, Network, or Console. 7 | 8 | ### Methods 9 | 10 | #### open(function callback[err]) 11 | 12 | Claims the current device USB (or other device type), if the printer is already in use by other process this will fail and return the error parameter. 13 | 14 | By default, the USB adapter will set the first printer found . 15 | 16 | Triggers the callback function when done. 17 | 18 | #### close(function callback) 19 | 20 | Closes the current device and releases its USB interface. 21 | 22 | ---- 23 | -------------------------------------------------------------------------------- /packages/adapter/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "escpos-adapter", 3 | "version": "3.0.0-alpha.4", 4 | "description": "escpos adapter base class", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "scripts": { 8 | "build": "tsc", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "keywords": [ 12 | "escpos" 13 | ], 14 | "author": "Lsong (https://lsong.org)", 15 | "license": "MIT", 16 | "gitHead": "a3a65c61c0b990298258331f54546a93d6875ddb", 17 | "devDependencies": { 18 | "@tsconfig/node10": "^1.0.8", 19 | "@types/node": "^10.0.0", 20 | "typescript": "^4.5.2" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/adapter/src/index.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const EventEmitter = require('events'); 3 | 4 | export class NotImplementedException extends Error {} 5 | 6 | export abstract class Adapter extends EventEmitter { 7 | abstract open(callback?: (error: Error | null) => void): this; 8 | abstract write(data: Buffer | string, callback?: (error: Error | null) => void): this; 9 | abstract close(callback?: (error: Error | null) => void, ...closeArgs: CloseArgs): this; 10 | abstract read(callback?: (data: Buffer) => void): void; 11 | } 12 | -------------------------------------------------------------------------------- /packages/adapter/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/node10/tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "sourceMap": true, 6 | "declaration": true, 7 | "declarationMap": true 8 | }, 9 | "include": ["src"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/adapter/tsconfig.ref.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "composite": true, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /packages/bluetooth/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## escpos-bluetooth 3 | 4 | #### Bluetooth(address, channel) 5 | ```javascript 6 | const escpos = require('escpos'); 7 | escpos.Bluetooth = require('escpos-bluetooth'); 8 | 9 | const address = '01:23:45:67:89:AB'; 10 | const channel = 1; 11 | const bluetoothDevice = new escpos.Bluetooth(address, channel); 12 | ``` 13 | You can scan for printers using the `escpos.Bluetooth.findPrinters()` method. Check out the examples (bt_promise and bt_find_printer) for more information. 14 | -------------------------------------------------------------------------------- /packages/bluetooth/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const EventEmitter = require('events'); 3 | const util = require('util'); 4 | 5 | let bluetooth = null; 6 | let device = null; 7 | let connection = null; 8 | 9 | /** 10 | * Bluetooth adapter 11 | * @param {string} address Bluetooth Address 12 | * @param {number} channel Bluetooth channel 13 | * @return {Bluetooth} 14 | * @constructor 15 | */ 16 | function Bluetooth(address, channel){ 17 | EventEmitter.call(this); 18 | this.address = address; 19 | this.channel = channel; 20 | loadBluetoothDependency(); 21 | device = new bluetooth.DeviceINQ(); 22 | return this; 23 | }; 24 | 25 | util.inherits(Bluetooth, EventEmitter); 26 | 27 | /** 28 | * Load bluetooth dependency only if actually needed 29 | */ 30 | function loadBluetoothDependency(){ 31 | if (!bluetooth) { 32 | bluetooth = require('node-bluetooth'); 33 | } 34 | } 35 | 36 | /** 37 | * Returns an array of all available bluetooth devices with a serial port 38 | * @return {Promise} Device objects with address, name, channel 39 | */ 40 | Bluetooth.findPrinters = async function(){ 41 | loadBluetoothDependency(); 42 | if (device === null) { 43 | device = new bluetooth.DeviceINQ(); 44 | } 45 | const devices = await device.scan(); 46 | const printers = await Promise.all(devices.map(({address, name}) => { 47 | return new Promise((resolve, reject) => { 48 | device.findSerialPortChannel(address, function(channel){ 49 | if (channel === -1) { 50 | resolve(undefined); 51 | } else { 52 | resolve({ 53 | address, 54 | name, 55 | channel 56 | }) 57 | } 58 | }); 59 | }); 60 | })); 61 | return printers; 62 | }; 63 | 64 | /** 65 | * Returns a connected Blueetooth device 66 | * @param {string} address Bluetooth Address 67 | * @param {number} channel Bluetooth channel 68 | * @return {Promise} connected device if everything is ok, error otherwise 69 | */ 70 | Bluetooth.getDevice = async function(address, channel){ 71 | return new Promise((resolve, reject) => { 72 | const device = new Bluetooth(address, channel); 73 | device.open(err => { 74 | if(err) return reject(err); 75 | resolve(device); 76 | }); 77 | }); 78 | }; 79 | 80 | /** 81 | * Open connection to bluetooth device 82 | * @param callback 83 | */ 84 | Bluetooth.prototype.open = function(callback){ 85 | bluetooth.connect(this.address, this.channel, (err, conn) => { 86 | if(err) { 87 | callback && callback(err); 88 | } else { 89 | connection = conn; 90 | this.emit('connect', connection); 91 | callback && callback(); 92 | } 93 | }); 94 | return this; 95 | }; 96 | 97 | /** 98 | * Close bluetooth connection 99 | * @param callback 100 | */ 101 | Bluetooth.prototype.close = function(callback){ 102 | if (connection === null) { 103 | callback && callback(); 104 | } else { 105 | connection.close((err) => { 106 | if(err) { 107 | callback && callback(err); 108 | } else { 109 | this.emit('disconnect', connection); 110 | connection = null; 111 | callback && callback(); 112 | } 113 | }); 114 | } 115 | return this; 116 | }; 117 | 118 | /** 119 | * Write data to the printer 120 | * @param data 121 | * @param callback 122 | */ 123 | Bluetooth.prototype.write = function(data, callback) { 124 | if (connection === null) { 125 | callback && callback(new Error('No open bluetooth connection.')); 126 | } else { 127 | connection.write(data, () => { 128 | this.emit('write', data); 129 | callback && callback(); 130 | }); 131 | } 132 | return this; 133 | }; 134 | 135 | /** 136 | * expose 137 | */ 138 | module.exports = Bluetooth; 139 | -------------------------------------------------------------------------------- /packages/bluetooth/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "escpos-bluetooth", 3 | "version": "3.0.0-alpha.2", 4 | "description": "bluetooth adapter for escpos", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [ 10 | "escpos", 11 | "escpos-adapter" 12 | ], 13 | "dependencies": { 14 | "node-bluetooth": "*" 15 | }, 16 | "author": "Lsong (https://lsong.org)", 17 | "license": "MIT", 18 | "gitHead": "a3a65c61c0b990298258331f54546a93d6875ddb" 19 | } 20 | -------------------------------------------------------------------------------- /packages/bluetooth/rawbt.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const { MutableBuffer } = require('mutable-buffer'); 3 | 4 | 5 | function stdout(data){ 6 | console.log(data); 7 | } 8 | 9 | /** 10 | * RawBT for print on client side 11 | * 12 | * It is a solution consisting of an android application and a simple server socket for personal computers. 13 | * This adapter generates data in the desired representation. It is based on base64. 14 | * A site visitor requests data with a simple ajax request. 15 | * The received data is transferred to the Android application or sent to the local print server socket on the visitor's computer. 16 | * 17 | * @see https://RawBT.ru/ 18 | * @see https://github.com/402d/RawBT_ws_server 19 | * 20 | * Front example: 21 | * @see https://RawBT.ru/mike42/example_RawBT/ 22 | * 23 | * 24 | * @param {[type]} handler 25 | * @constructor 26 | */ 27 | function RawBT(handler){ 28 | this.handler = handler || stdout; 29 | this.buffer = new MutableBuffer(); 30 | return this; 31 | } 32 | /** 33 | * Doing nothing 34 | * @param {Function} callback [description] 35 | * @return {[type]} [description] 36 | */ 37 | RawBT.prototype.open = function(callback){ 38 | callback && callback(); 39 | return this; 40 | }; 41 | /** 42 | * write to buffer 43 | * 44 | * @param {[type]} data [description] 45 | * @param {[type]} callback [description] 46 | */ 47 | RawBT.prototype.write = function(data,callback){ 48 | this.buffer.write(data); 49 | callback && callback(); 50 | return this; 51 | }; 52 | /** 53 | * Close the underlying buffer. With rawbt connector, the 54 | * job will not actually be sent to the printer until this is called. 55 | * 56 | * Real output all buffered data to client in base64 format. 57 | * 58 | * @param {[type]} callback [description] 59 | */ 60 | RawBT.prototype.close = function(callback){ 61 | let data = "intent:base64,"; 62 | let buf = this.buffer.flush(); 63 | data = data + Buffer.from(buf).toString('base64'); 64 | data = data + "#Intent;scheme=RawBT;package=ru.a402d.RawBTprinter;end;"; 65 | this.handler && this.handler(data); 66 | callback && callback(); 67 | return this; 68 | }; 69 | 70 | /** 71 | * [exports description] 72 | * @type {[type]} 73 | */ 74 | module.exports = RawBT; 75 | -------------------------------------------------------------------------------- /packages/console/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## escpos-console 3 | 4 | #### Console(handler = stdout) 5 | ```javascript 6 | const escpos = require('escpos'); 7 | escpos.Console = require('escpos-console'); 8 | 9 | const debugDevice = new escpos.Console(); 10 | ``` 11 | -------------------------------------------------------------------------------- /packages/console/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "escpos-console", 3 | "version": "3.0.0-alpha.2", 4 | "description": "console adapter for escpos", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "scripts": { 8 | "build": "tsc", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "keywords": [ 12 | "escpos", 13 | "escpos-adapter" 14 | ], 15 | "author": "Lsong (https://lsong.org)", 16 | "license": "MIT", 17 | "gitHead": "a3a65c61c0b990298258331f54546a93d6875ddb", 18 | "devDependencies": { 19 | "@tsconfig/node10": "^1.0.8", 20 | "@types/node": "^10.0.0", 21 | "typescript": "^4.5.2" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/console/src/index.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import {Adapter, NotImplementedException} from "escpos-adapter"; 3 | 4 | /** 5 | * [stdout description] 6 | * @param {[type]} data [description] 7 | * @return {[type]} [description] 8 | */ 9 | function stdout(data: string | Buffer) { 10 | const bit = 8; 11 | for (let i=0; i < data.length; i += bit){ 12 | let arr = []; 13 | for (let j = 0; j < bit && i + j < data.length; j++) arr.push(data[i + j]); 14 | arr = arr 15 | .map((b) => b.toString(16).toUpperCase()) 16 | .map((b) => { 17 | if (b.length == 1) b = '0' + b; 18 | return b; 19 | }) 20 | console.log(arr.join(' ')); 21 | } 22 | console.log(); 23 | } 24 | 25 | /** 26 | * [Console description] 27 | */ 28 | export default class Console extends Adapter<[]> { 29 | public handler: (data: (string | Buffer)) => void; 30 | 31 | constructor(handler: (data: string | Buffer) => void = stdout) { 32 | super(); 33 | this.handler = handler; 34 | } 35 | 36 | /** 37 | * [open description] 38 | * @param {Function} callback [description] 39 | * @return {[type]} [description] 40 | */ 41 | open(callback?: (error: Error | null) => void) { 42 | if (callback) callback(null); 43 | return this; 44 | }; 45 | 46 | /** 47 | * [write description] 48 | * @param {[type]} data [description] 49 | * @param {Function} callback [description] 50 | * @return {[type]} [description] 51 | */ 52 | write(data: string | Buffer, callback?: (error: Error | null) => void) { 53 | this.handler(data); 54 | if (callback) callback(null); 55 | return this; 56 | }; 57 | 58 | close(callback?: (error: Error | null) => void) { 59 | if (callback) callback(null); 60 | return this; 61 | } 62 | 63 | read() { 64 | return NotImplementedException; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /packages/console/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/node10/tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "sourceMap": true, 6 | "declaration": true, 7 | "declarationMap": true 8 | }, 9 | "include": ["src"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/console/tsconfig.ref.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "composite": true, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /packages/network/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## escpos-network 3 | 4 | #### Network(address, port = 9100) 5 | ```javascript 6 | const escpos = require('escpos'); 7 | escpos.Network = require('escpos-network'); 8 | 9 | const networkDevice = new escpos.Network('localhost', 3000); 10 | ``` 11 | The default port number is 9100. 12 | -------------------------------------------------------------------------------- /packages/network/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "escpos-network", 3 | "version": "3.0.0-alpha.5", 4 | "description": "network adapter for escpos", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "scripts": { 8 | "build": "tsc", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "keywords": [ 12 | "escpos", 13 | "escpos-adapter" 14 | ], 15 | "author": "Lsong (https://lsong.org)", 16 | "license": "MIT", 17 | "gitHead": "a3a65c61c0b990298258331f54546a93d6875ddb", 18 | "devDependencies": { 19 | "@tsconfig/node10": "^1.0.8", 20 | "@types/node": "^10.0.0", 21 | "typescript": "^4.5.2" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/network/src/index.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import net from "net"; 3 | import {Adapter} from "escpos-adapter"; 4 | 5 | /** 6 | * Network Adapter 7 | */ 8 | export default class Network extends Adapter<[device: net.Socket]> { 9 | private readonly address: string; 10 | private readonly port: number; 11 | private readonly timeout: number; 12 | private readonly device: net.Socket; 13 | 14 | /** 15 | * @param {[type]} address 16 | * @param {[type]} port 17 | */ 18 | constructor(address: string, port = 9100, timeout = 30000) { 19 | super(); 20 | this.address = address; 21 | this.port = port; 22 | this.timeout = timeout; 23 | this.device = new net.Socket(); 24 | } 25 | 26 | /** 27 | * connect to remote device 28 | * @praram {[type]} callback 29 | * @return 30 | */ 31 | open(callback?: (error: Error | null, device: net.Socket) => void) { 32 | // start timeout on open 33 | const connection_timeout = setTimeout(() => { 34 | this.device.destroy(); 35 | callback && callback( 36 | new Error(`printer connection timeout after ${this.timeout}ms`), this.device 37 | ); 38 | }, this.timeout); 39 | 40 | // connect to net printer by socket (port, ip) 41 | this.device.on("error", (err) => { 42 | callback && callback(err, this.device); 43 | }).on('data', buf => { 44 | // console.log('printer say:', buf); 45 | }).connect(this.port, this.address, (err?: Error | null) => { 46 | clearInterval(connection_timeout); 47 | this.emit('connect', this.device); 48 | callback && callback(err ?? null, this.device); 49 | }); 50 | return this; 51 | }; 52 | 53 | /** 54 | * write data to printer 55 | * @param {[type]} data -- byte data 56 | * @param {Function} callback 57 | * @return 58 | */ 59 | write(data: string | Buffer, callback?: (error: Error | null) => void) { 60 | const handler: Function = (error?: Error | null) => { 61 | if (callback) callback(error ?? null); 62 | }; 63 | if (typeof data === 'string') this.device.write(data, undefined, handler); 64 | else this.device.write(data, handler); 65 | return this; 66 | }; 67 | 68 | read(callback?: (data: Buffer) => void) { 69 | this.device.on('data', buf => { 70 | if (callback) callback(buf); 71 | }) 72 | return this; 73 | } 74 | 75 | /** 76 | * [close description] 77 | * @param {Function} callback [description] 78 | * @return {[type]} [description] 79 | */ 80 | close(callback?: (error: Error | null, device: net.Socket) => void) { 81 | this.device.destroy(); 82 | this.emit('disconnect', this.device); 83 | callback && callback(null, this.device); 84 | return this; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /packages/network/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/node10/tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "sourceMap": true, 6 | "declaration": true, 7 | "declarationMap": true 8 | }, 9 | "include": ["src"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/network/tsconfig.ref.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "composite": true, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /packages/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "escpos-packages", 3 | "private": true, 4 | "workspaces": { 5 | "packages": [ 6 | "adapter", 7 | "printer", 8 | "usb", 9 | "console", 10 | "serialport", 11 | "screen" 12 | ] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/printer/README.md: -------------------------------------------------------------------------------- 1 | # ESCPOS 2 | 3 | ESC/POS Printer driver for node 4 | 5 | [![NPM](https://nodei.co/npm/escpos.png?downloads=true&downloadRank=true&stars=true)](https://npmjs.org/escpos ) 6 | [![npm version](https://badge.fury.io/js/escpos.svg)](https://www.npmjs.com/package/escpos ) 7 | [![Build Status](https://travis-ci.org/song940/node-escpos.svg?branch=master)](https://travis-ci.org/song940/node-escpos) 8 | 9 | ## Installation 10 | 11 | ### npm 12 | ```bash 13 | $ npm i escpos --save 14 | ``` 15 | 16 | ### yarn 17 | ```bash 18 | $ yarn add escpos 19 | ``` 20 | 21 | ## Example 22 | 23 | ````javascript 24 | const escpos = require('escpos'); 25 | // install escpos-usb adapter module manually 26 | escpos.USB = require('escpos-usb'); 27 | // Select the adapter based on your printer type 28 | const device = new escpos.USB(); 29 | // const device = new escpos.Network('localhost'); 30 | // const device = new escpos.Serial('/dev/usb/lp0'); 31 | 32 | const options = { encoding: "GB18030" /* default */ } 33 | // encoding is optional 34 | 35 | const printer = new escpos.Printer(device, options); 36 | 37 | device.open(function(error){ 38 | printer 39 | .font('a') 40 | .align('ct') 41 | .style('bu') 42 | .size(1, 1) 43 | .text('The quick brown fox jumps over the lazy dog') 44 | .text('敏捷的棕色狐狸跳过懒狗') 45 | .barcode('1234567', 'EAN8') 46 | .table(["One", "Two", "Three"]) 47 | .tableCustom( 48 | [ 49 | { text:"Left", align:"LEFT", width:0.33, style: 'B' }, 50 | { text:"Center", align:"CENTER", width:0.33}, 51 | { text:"Right", align:"RIGHT", width:0.33 } 52 | ], 53 | { encoding: 'cp857', size: [1, 1] } // Optional 54 | ) 55 | .qrimage('https://github.com/song940/node-escpos', function(err){ 56 | this.cut(); 57 | this.close(); 58 | }); 59 | }); 60 | ```` 61 | - See `./examples` for more examples. 62 | 63 | ---- 64 | 65 | ## Printer 66 | 67 | ### Constructors 68 | 69 | #### Printer(device) 70 | 71 | ```javascript 72 | const usbDevice = new escpos.USB(); 73 | const usbPrinter = new escpos.Printer(usbDevice); 74 | 75 | const serialDevice = new escpos.Serial('/dev/usb/lp0'); 76 | const serialPrinter = new escpos.Printer(serialDevice); 77 | 78 | const bluetoothDevice = new escpos.Bluetooth('01:23:45:67:89:AB', 1); 79 | const bluetoothPrinter = new escpos.Printer(bluetoothDevice); 80 | 81 | const networkDevice = new escpos.Network('localhost'); 82 | const networkPrinter = new escpos.Printer(networkDevice); 83 | ``` 84 | 85 | ### Methods 86 | 87 | Escpos inherits its methods to the printers. the following methods are defined: 88 | 89 | #### text("text", encodeType) 90 | 91 | Prints raw text. Raises TextError exception. 92 | 93 | For the encode type, see the [iconv-lite wiki document](https://github.com/ashtuchkin/iconv-lite/wiki/Supported-Encodings). Escpos uses `iconv-lite` for encoding. 94 | 95 | If the type is undefined, the default type is GB18030. 96 | 97 | #### async image(imagePath, "density") 98 | 99 | Prints an image at a set density. 100 | 101 | Density consists of a character signaling the base density setting, `s` for single, `d` for double, followed by an integer for image sampling - `8` (8-bit) or `24`. 102 | 103 | ```javascript 104 | printer.align('ct') 105 | .image(image, 's8') 106 | .then(() => { 107 | printer.cut() 108 | .close(); 109 | }); 110 | ``` 111 | 112 | #### encode("encodeType") 113 | 114 | Sets the encoding value globally. default type is GB18030 (Chinese) 115 | 116 | ```javascript 117 | printer 118 | .encode('EUC-KR') 119 | .text('동해물과 백두산이 마르고 닳도록'); 120 | ``` 121 | 122 | #### control("align") 123 | 124 | Carrier feed and tabs. 125 | 126 | align is a string which takes any of the following values: 127 | 128 | + LF for Line Feed 129 | + FF for Form Feed 130 | + CR for Carriage Return 131 | + HT for Horizontal Tab 132 | + VT for Vertical Tab 133 | 134 | 135 | #### align("align") 136 | 137 | Set text properties. 138 | 139 | align set horizontal position for text, the possible values are: 140 | 141 | + CT is the Center alignment 142 | + LT is the Left alignment 143 | + RT is the Right alignment 144 | 145 | Default: LT 146 | 147 | #### font("type") 148 | font type could be A or B. Default: A 149 | 150 | #### size(width, heigth) 151 | 152 | width is a numeric value, 1 is for regular size, and 2 is twice the standard size. Default: 1 153 | 154 | height is a numeric value, 1 is for regular size and 2 is twice the standard size. Default: 1 155 | 156 | 157 | 158 | #### barcode("code", "barcodeType", "options") 159 | 160 | Prints a barcode. 161 | 162 | "code" is an alphanumeric code to be printed as bar code 163 | "barcodeType" must be one of the following type of codes: 164 | 165 | + UPC-A 166 | + UPC-E 167 | + EAN13 168 | + EAN8 169 | + CODE39 170 | + ITF 171 | + NW7 172 | 173 | The EAN type automatically calculates the last parity bit. For the EAN13 type, the length of the string is limited to 12, and EAN8 is limited to 7. (#57) 174 | If you wish to disable the parity bit you must set `"includeParity": false` in the options provided to the command. 175 | 176 | **"options"** 177 | 178 | - "options.width" (default=1) is a numeric value ranging between 1 up to 5. 179 | - "options.height" (default=100) is a numeric value ranging between 1 up to 255. 180 | - "options.includeParity" (default=true) When true parity bit is calculated for EAN13/EAN8 bar code 181 | - "options.position" (default=BLW) where to place the barcode numeric value: OFF|ABV|BLW|BTH 182 | 183 | + ABV = ABOVE 184 | + BLW = BELOW 185 | + BTH = BOTH 186 | + OFF = OFF 187 | 188 | - "options.font" (default=A) the font size: A|B 189 | 190 | 191 | Raises BarcodeTypeError, BarcodeSizeError, BarcodeCodeError exceptions. 192 | 193 | For backward compatibility the old method interface is still supported: 194 | 195 | 196 | barcode("code", "barcodeType", width, height, "position", "font") 197 | 198 | #### cut("mode") 199 | 200 | Cut paper. 201 | 202 | mode set a full or partial cut. Default: full 203 | Partial cut is not implemented in all printers. 204 | 205 | *** Don't foget this, because cut will flush buffer to printer *** 206 | 207 | #### cashdraw(pin) 208 | 209 | Sends a pulse to the cash drawer in the specified pin. 210 | 211 | pin is a numeric value which defines the pin to be used to send the pulse, it could be 2 or 5. 212 | Raises `CashDrawerError()`` 213 | 214 | #### beep(n,t) 215 | 216 | Printer Buzzer (Beep sound). 217 | 218 | "n" Refers to the number of buzzer times. 219 | "t" Refers to the buzzer sound length in (t * 100) milliseconds. 220 | 221 | ---- 222 | 223 | ## Screencast 224 | 225 | ![img_1031](https://user-images.githubusercontent.com/8033320/29250339-d66ce470-807b-11e7-89ce-9962da88ca18.JPG) 226 | 227 | ---- 228 | 229 | ## Contributing 230 | - Fork this repo 231 | - Clone your repo 232 | - Install dependencies 233 | - Checkout a feature branch 234 | - Feel free to add your features 235 | - Make sure your features are fully tested 236 | - Open a pull request, and enjoy <3 237 | 238 | ---- 239 | 240 | ## Contributors 241 | 242 | Thanks to our [contributors][contributors-href] 🎉👏 243 | 244 | + [Tao Yuan](https://github.com/taoyuan) 245 | + [Jose Vera](https://github.com/jor3l) 246 | + [Sébastien Vidal](https://github.com/Psychopoulet) 247 | + [Yu Yongwoo](https://github.com/uyu423) 248 | + [Attawit Kittikrairit](https://github.com/atton16) 249 | + [Michael Kuenzli](https://github.com/pfirpfel) 250 | 251 | [![](https://opencollective.com/node-escpos/contributors.svg?width=890&button=false)][contributors-href] 252 | 253 | ---- 254 | 255 | ### MIT license 256 | Copyright (c) 2015 lsong 257 | 258 | Permission is hereby granted, free of charge, to any person obtaining a copy 259 | of this software and associated documentation files (the "Software"), to deal 260 | in the Software without restriction, including without limitation the rights 261 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 262 | copies of the Software, and to permit persons to whom the Software is 263 | furnished to do so, subject to the following conditions: 264 | 265 | The above copyright notice and this permission notice shall be included in 266 | all copies or substantial portions of the Software. 267 | 268 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 269 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 270 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 271 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 272 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 273 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 274 | THE SOFTWARE. 275 | 276 | --- 277 | 278 | [contributors-href]: https://github.com/song940/node-escpos/graphs/contributors 279 | -------------------------------------------------------------------------------- /packages/printer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "escpos", 3 | "version": "3.0.0-alpha.6", 4 | "description": "ESC/POS Printer driver for nodejs", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "scripts": { 8 | "build": "tsc", 9 | "test": "mocha" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/song940/node-escpos.git" 14 | }, 15 | "keywords": [ 16 | "escpos", 17 | "printer" 18 | ], 19 | "author": "Lsong (https://lsong.org)", 20 | "contributors": [ 21 | "Jose Vera ", 22 | "Sébastien Vidal ", 23 | "Yu Yongwoo ", 24 | "Attawit Kittikrairit ", 25 | "Risley Lima ", 26 | "Michael Kuenzli " 27 | ], 28 | "license": "MIT", 29 | "bugs": { 30 | "url": "https://github.com/song940/node-escpos/issues" 31 | }, 32 | "homepage": "https://github.com/song940/node-escpos#readme", 33 | "dependencies": { 34 | "get-pixels": "*", 35 | "iconv-lite": "*", 36 | "mutable-buffer": "^2.0.3", 37 | "qr-image": "*" 38 | }, 39 | "devDependencies": { 40 | "@tsconfig/node10": "1.0.8", 41 | "@types/get-pixels": "^3.3.2", 42 | "@types/node": "^10.0.0", 43 | "@types/qr-image": "^3.2.4", 44 | "mocha": "*", 45 | "typescript": "^4.5.2" 46 | }, 47 | "engines": { 48 | "node": ">=8.x" 49 | }, 50 | "directories": { 51 | "doc": "docs", 52 | "example": "examples", 53 | "test": "test" 54 | }, 55 | "gitHead": "a3a65c61c0b990298258331f54546a93d6875ddb" 56 | } 57 | -------------------------------------------------------------------------------- /packages/printer/src/commands.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Utility function that converts numbers into hex values 3 | * 4 | * @usage: 5 | * numToHex(256) => '0100' 6 | * numToHex(0) => '00' 7 | */ 8 | const numToHexString = function (value: number | string) { 9 | value = +value; 10 | if (!isNaN(value)) { 11 | value = (value).toString(16); 12 | while(value.length % 2 !== 0) { 13 | value = '0' + value; 14 | } 15 | } 16 | return value; 17 | }; 18 | 19 | export const LF = '\x0a'; 20 | export const FS = '\x1c'; 21 | export const FF = '\x0c'; 22 | export const GS = '\x1d'; 23 | export const DLE = '\x10'; 24 | export const EOT = '\x04'; 25 | export const NUL = '\x00'; 26 | export const ESC = '\x1b'; 27 | export const TAB = '\x74'; 28 | export const EOL = '\n'; 29 | 30 | /** 31 | * [FEED_CONTROL_SEQUENCES Feed control sequences] 32 | * @type {Object} 33 | */ 34 | export const FEED_CONTROL_SEQUENCES = { 35 | CTL_LF: '\x0a', // Print and line feed 36 | CTL_GLF: '\x4a\x00', // Print and feed paper (without spaces between lines) 37 | CTL_FF: '\x0c', // Form feed 38 | CTL_CR: '\x0d', // Carriage return 39 | CTL_HT: '\x09', // Horizontal tab 40 | CTL_VT: '\x0b', // Vertical tab 41 | }; 42 | 43 | export const CHARACTER_SPACING = { 44 | CS_DEFAULT: '\x1b\x20\x00', 45 | CS_SET: '\x1b\x20' 46 | } 47 | 48 | export const LINE_SPACING = { 49 | LS_DEFAULT: '\x1b\x32', 50 | LS_SET: '\x1b\x33' 51 | }; 52 | 53 | /** 54 | * [HARDWARE Printer hardware] 55 | * @type {Object} 56 | */ 57 | export const HARDWARE = { 58 | HW_INIT: '\x1b\x40', // Clear data in buffer and reset modes 59 | HW_SELECT: '\x1b\x3d\x01', // Printer select 60 | HW_RESET: '\x1b\x3f\x0a\x00', // Reset printer hardware 61 | }; 62 | 63 | /** 64 | * [CASH_DRAWER Cash Drawer] 65 | * @type {Object} 66 | */ 67 | export const CASH_DRAWER = { 68 | CD_KICK_2: '\x1b\x70\x00\x19\x78', // Sends a pulse to pin 2 [] 69 | CD_KICK_5: '\x1b\x70\x01\x19\x78', // Sends a pulse to pin 5 [] 70 | }; 71 | 72 | /** 73 | * [MARGINS Margins sizes] 74 | * @type {Object} 75 | */ 76 | export const MARGINS = { 77 | BOTTOM: '\x1b\x4f', // Fix bottom size 78 | LEFT: '\x1b\x6c', // Fix left size 79 | RIGHT: '\x1b\x51', // Fix right size 80 | }; 81 | 82 | /** 83 | * [PAPER Paper] 84 | * @type {Object} 85 | */ 86 | export const PAPER = { 87 | PAPER_FULL_CUT: '\x1d\x56\x00', // Full cut paper 88 | PAPER_PART_CUT: '\x1d\x56\x01', // Partial cut paper 89 | PAPER_CUT_A: '\x1d\x56\x41', // Partial cut paper 90 | PAPER_CUT_B: '\x1d\x56\x42', // Partial cut paper 91 | STAR_FULL_CUT: '\x1B\x64\x02' , // STAR printer - Full cut 92 | }; 93 | 94 | /** 95 | * [TEXT_FORMAT Text format] 96 | * @type {Object} 97 | */ 98 | export const TEXT_FORMAT = { 99 | 100 | TXT_NORMAL: '\x1b\x21\x00', // Normal text 101 | TXT_2HEIGHT: '\x1b\x21\x10', // Double height text 102 | TXT_2WIDTH: '\x1b\x21\x20', // Double width text 103 | TXT_4SQUARE: '\x1b\x21\x30', // Double width & height text 104 | STAR_TXT_EMPHASIZED: '\x1B\x45', // STAR printer - Select emphasized printing 105 | STAR_CANCEL_TXT_EMPHASIZED: '\x1B\x46', // STAR printer - Cancel emphasized printing 106 | 107 | TXT_CUSTOM_SIZE: function(width: number, height: number) { // other sizes 108 | width = width > 8 ? 8 : width; 109 | width = width < 1 ? 1 : width; 110 | height = height > 8 ? 8 : height; 111 | height = height < 1 ? 1 : height; 112 | 113 | var widthDec = (width - 1) * 16; // Values between 1-8 114 | var heightDec = height - 1; // Values between 1-8 115 | var sizeDec = widthDec + heightDec; 116 | /* 117 | * @todo I would suggest replacing the return line by the code below since 118 | * `String.fromCharCode()` can generate undesirable results. 119 | * 120 | * return Buffer.from('1d21' + numToHexString(sizeDec), 'hex'); 121 | * */ 122 | return '\x1d\x21' + String.fromCharCode(sizeDec); 123 | }, 124 | 125 | TXT_HEIGHT: { 126 | 1: '\x00', 127 | 2: '\x01', 128 | 3: '\x02', 129 | 4: '\x03', 130 | 5: '\x04', 131 | 6: '\x05', 132 | 7: '\x06', 133 | 8: '\x07' 134 | }, 135 | TXT_WIDTH: { 136 | 1: '\x00', 137 | 2: '\x10', 138 | 3: '\x20', 139 | 4: '\x30', 140 | 5: '\x40', 141 | 6: '\x50', 142 | 7: '\x60', 143 | 8: '\x70' 144 | }, 145 | 146 | TXT_UNDERL_OFF: '\x1b\x2d\x00', // Underline font OFF 147 | TXT_UNDERL_ON: '\x1b\x2d\x01', // Underline font 1-dot ON 148 | TXT_UNDERL2_ON: '\x1b\x2d\x02', // Underline font 2-dot ON 149 | TXT_BOLD_OFF: '\x1b\x45\x00', // Bold font OFF 150 | TXT_BOLD_ON: '\x1b\x45\x01', // Bold font ON 151 | TXT_ITALIC_OFF: '\x1b\x35', // Italic font ON 152 | TXT_ITALIC_ON: '\x1b\x34', // Italic font ON 153 | 154 | TXT_FONT_A: '\x1b\x4d\x00', // Font type A 155 | TXT_FONT_B: '\x1b\x4d\x01', // Font type B 156 | TXT_FONT_C: '\x1b\x4d\x02', // Font type C 157 | 158 | TXT_ALIGN_LT: '\x1b\x61\x00', // Left justification 159 | TXT_ALIGN_CT: '\x1b\x61\x01', // Centering 160 | TXT_ALIGN_RT: '\x1b\x61\x02', // Right justification 161 | 162 | STAR_TXT_ALIGN_LA: '\x1B\x1D\x61\x00', // STAR printer - Left alignment 163 | STAR_TXT_ALIGN_CA: '\x1B\x1D\x61\x01', // STAR printer - Center alignment 164 | STAR_TXT_ALIGN_RA: '\x1B\x1D\x61\x02', // STAR printer - Right alignment 165 | }; 166 | 167 | /** 168 | * Qsprinter-compatible 169 | * Added by Attawit Kittikrairit 170 | * [MODEL Model-specific commands] 171 | * @type {Object} 172 | */ 173 | export const MODEL = { 174 | QSPRINTER: { 175 | BARCODE_MODE: { 176 | ON: '\x1d\x45\x43\x01', // Barcode mode on 177 | OFF: '\x1d\x45\x43\x00', // Barcode mode off 178 | }, 179 | BARCODE_HEIGHT_DEFAULT: '\x1d\x68\xA2', // Barcode height default:162 180 | CODE2D_FORMAT: { 181 | PIXEL_SIZE: { 182 | CMD: '\x1b\x23\x23\x51\x50\x49\x58', 183 | MIN: 1, 184 | MAX: 24, 185 | DEFAULT: 12, 186 | }, 187 | VERSION: { 188 | CMD: '\x1d\x28\x6b\x03\x00\x31\x43', 189 | MIN: 1, 190 | MAX: 16, 191 | DEFAULT: 3, 192 | }, 193 | LEVEL: { 194 | CMD: '\x1d\x28\x6b\x03\x00\x31\x45', 195 | OPTIONS: { 196 | L: 48, 197 | M: 49, 198 | Q: 50, 199 | H: 51, 200 | } 201 | }, 202 | LEN_OFFSET: 3, 203 | SAVEBUF: { 204 | // Format: CMD_P1{LEN_2BYTE}CMD_P2{DATA} 205 | // DATA Max Length: 256*256 - 3 (65533) 206 | CMD_P1: '\x1d\x28\x6b', 207 | CMD_P2: '\x31\x50\x30', 208 | }, 209 | PRINTBUF: { 210 | // Format: CMD_P1{LEN_2BYTE}CMD_P2 211 | CMD_P1: '\x1d\x28\x6b', 212 | CMD_P2: '\x31\x51\x30', 213 | } 214 | }, 215 | }, 216 | }; 217 | 218 | /** 219 | * [BARCODE_FORMAT Barcode format] 220 | * @type {Object} 221 | */ 222 | export const BARCODE_FORMAT = { 223 | BARCODE_TXT_OFF: '\x1d\x48\x00', // HRI barcode chars OFF 224 | BARCODE_TXT_ABV: '\x1d\x48\x01', // HRI barcode chars above 225 | BARCODE_TXT_BLW: '\x1d\x48\x02', // HRI barcode chars below 226 | BARCODE_TXT_BTH: '\x1d\x48\x03', // HRI barcode chars both above and below 227 | 228 | BARCODE_FONT_A: '\x1d\x66\x00', // Font type A for HRI barcode chars 229 | BARCODE_FONT_B: '\x1d\x66\x01', // Font type B for HRI barcode chars 230 | 231 | BARCODE_HEIGHT: function (height: number) { // Barcode Height [1-255] 232 | return Buffer.from('1d68'+ numToHexString(height), 'hex'); 233 | }, 234 | // Barcode Width [2-6] 235 | BARCODE_WIDTH: { 236 | 1: '\x1d\x77\x02', 237 | 2: '\x1d\x77\x03', 238 | 3: '\x1d\x77\x04', 239 | 4: '\x1d\x77\x05', 240 | 5: '\x1d\x77\x06', 241 | }, 242 | BARCODE_HEIGHT_DEFAULT: '\x1d\x68\x64', // Barcode height default:100 243 | BARCODE_WIDTH_DEFAULT: '\x1d\x77\x01', // Barcode width default:1 244 | 245 | BARCODE_UPC_A: '\x1d\x6b\x00', // Barcode type UPC-A 246 | BARCODE_UPC_E: '\x1d\x6b\x01', // Barcode type UPC-E 247 | BARCODE_EAN13: '\x1d\x6b\x02', // Barcode type EAN13 248 | BARCODE_EAN8: '\x1d\x6b\x03', // Barcode type EAN8 249 | BARCODE_CODE39: '\x1d\x6b\x04', // Barcode type CODE39 250 | BARCODE_ITF: '\x1d\x6b\x05', // Barcode type ITF 251 | BARCODE_NW7: '\x1d\x6b\x06', // Barcode type NW7 252 | BARCODE_CODE93: '\x1d\x6b\x48', // Barcode type CODE93 253 | BARCODE_CODE128: '\x1d\x6b\x49', // Barcode type CODE128 254 | }; 255 | 256 | /** 257 | * [CODE2D_FORMAT description] 258 | * @type {Object} 259 | */ 260 | export const CODE2D_FORMAT = { 261 | TYPE_PDF417: GS + 'Z' + '\x00', 262 | TYPE_DATAMATRIX: GS + 'Z' + '\x01', 263 | TYPE_QR: GS + 'Z' + '\x02', 264 | CODE2D: ESC + 'Z', 265 | QR_LEVEL_L: 'L', // correct level 7% 266 | QR_LEVEL_M: 'M', // correct level 15% 267 | QR_LEVEL_Q: 'Q', // correct level 25% 268 | QR_LEVEL_H: 'H' // correct level 30% 269 | }; 270 | 271 | /** 272 | * [IMAGE_FORMAT Image format] 273 | * @type {Object} 274 | */ 275 | export const IMAGE_FORMAT = { 276 | S_RASTER_N: '\x1d\x76\x30\x00', // Set raster image normal size 277 | S_RASTER_2W: '\x1d\x76\x30\x01', // Set raster image double width 278 | S_RASTER_2H: '\x1d\x76\x30\x02', // Set raster image double height 279 | S_RASTER_Q: '\x1d\x76\x30\x03', // Set raster image quadruple 280 | }; 281 | 282 | /** 283 | * [BITMAP_FORMAT description] 284 | * @type {Object} 285 | */ 286 | export const BITMAP_FORMAT = { 287 | BITMAP_S8: '\x1b\x2a\x00', 288 | BITMAP_D8: '\x1b\x2a\x01', 289 | BITMAP_S24: '\x1b\x2a\x20', 290 | BITMAP_D24: '\x1b\x2a\x21' 291 | }; 292 | 293 | /** 294 | * [GSV0_FORMAT description] 295 | * @type {Object} 296 | */ 297 | export const GSV0_FORMAT = { 298 | GSV0_NORMAL: '\x1d\x76\x30\x00', 299 | GSV0_DW: '\x1d\x76\x30\x01', 300 | GSV0_DH: '\x1d\x76\x30\x02', 301 | GSV0_DWDH: '\x1d\x76\x30\x03' 302 | }; 303 | 304 | /** 305 | * [BEEP description] 306 | * @type {string} 307 | */ 308 | export const BEEP = '\x1b\x42'; // Printer Buzzer pre hex 309 | 310 | /** 311 | * [COLOR description] 312 | * @type {Object} 313 | */ 314 | export const COLOR = { 315 | 0: '\x1b\x72\x00', // black 316 | 1: '\x1b\x72\x01', // red 317 | REVERSE: '\x1dB1', // Reverses the colors - white text on black background 318 | UNREVERSE: '\x1dB0' // Default: undo the reverse - black text on white background 319 | }; 320 | -------------------------------------------------------------------------------- /packages/printer/src/image.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import getPixels from "get-pixels"; 3 | import {NdArray} from "ndarray"; 4 | 5 | export type ImageMimeType = 'image/png' | 'image/jpg' | 'image/jpeg' | 'image/gif' | 'image/bmp'; 6 | 7 | /** 8 | * [Image description] 9 | * @param {[type]} pixels [description] 10 | */ 11 | export default class Image { 12 | private readonly pixels: NdArray; 13 | private readonly data: boolean[] = []; 14 | 15 | constructor(pixels: NdArray) { 16 | this.pixels = pixels; 17 | 18 | const rgbaData: number[][] = []; 19 | 20 | for (let i = 0; i < this.pixels.data.length; i += this.size.colors) { 21 | rgbaData.push( 22 | new Array(this.size.colors).fill(0).map((_, b) => this.pixels.data[i + b]) 23 | ); 24 | } 25 | 26 | this.data = rgbaData.map( 27 | ([r, g, b, a]) => a != 0 && r > 200 && g > 200 && b > 200, 28 | ); 29 | } 30 | 31 | private get size() { 32 | return { 33 | width : this.pixels.shape[0], 34 | height: this.pixels.shape[1], 35 | colors: this.pixels.shape[2], 36 | }; 37 | } 38 | 39 | /** 40 | * [toBitmap description] 41 | * @param {[type]} density [description] 42 | * @return {[type]} [description] 43 | */ 44 | toBitmap(density: number = 24) { 45 | const result: number[][] = []; 46 | let x, y, b, l, i; 47 | const c = density / 8; 48 | 49 | // n blocks of lines 50 | let n = Math.ceil(this.size.height / density); 51 | 52 | for (y = 0; y < n; y++) { 53 | // line data 54 | const ld: number[] = result[y] = []; 55 | 56 | for (x = 0; x < this.size.width; x++) { 57 | for (b = 0; b < density; b++) { 58 | i = x * c + (b >> 3); 59 | 60 | if (ld[i] === undefined) ld[i] = 0; 61 | 62 | l = y * density + b; 63 | if (l < this.size.height) { 64 | if (this.data[l * this.size.width + x]) { 65 | ld[i] += (0x80 >> (b & 0x7)); 66 | } 67 | } 68 | } 69 | } 70 | } 71 | 72 | return { 73 | data: result, 74 | density: density 75 | }; 76 | }; 77 | 78 | /** 79 | * [toRaster description] 80 | * @return {[type]} [description] 81 | */ 82 | toRaster() { 83 | const result = []; 84 | const {width, height} = this.size; 85 | 86 | // n blocks of lines 87 | const n = Math.ceil(width / 8); 88 | 89 | for (let y = 0; y < height; y++) { 90 | for (let x = 0; x < n; x++) { 91 | for (let b = 0; b < 8; b++) { 92 | const i = x * 8 + b; 93 | 94 | if (result[y * n + x] === undefined) { 95 | result[y * n + x] = 0; 96 | } 97 | 98 | const c = x * 8 + b; 99 | if (c < width) { 100 | if (this.data[y * width + i]) { 101 | result[y * n + x] += (0x80 >> (b & 0x7)); 102 | } 103 | } 104 | } 105 | } 106 | } 107 | return { 108 | data: result, 109 | width: n, 110 | height: height 111 | }; 112 | } 113 | 114 | /** 115 | * Load image from URL 116 | * @param {[string]} url [description] 117 | * @param {[type]} type [description] 118 | * @return {[Promise]} [description] 119 | */ 120 | static load(url: string, type: ImageMimeType | null = null): Promise { 121 | return new Promise((resolve, reject) => { 122 | getPixels(url, type ?? '', (error, pixels) => { 123 | if (error) reject(error); 124 | else resolve(new Image(pixels as NdArray)); 125 | }); 126 | }) 127 | } 128 | }; 129 | -------------------------------------------------------------------------------- /packages/printer/src/index.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import {Adapter} from "escpos-adapter"; 3 | 4 | import { 5 | DeviceStatus, 6 | ErrorCauseStatus, 7 | OfflineCauseStatus, 8 | PrinterStatus, 9 | RollPaperSensorStatus, 10 | StatusClassConstructor 11 | } from "./statuses"; 12 | import * as _ from "./commands"; 13 | import * as utils from "./utils"; 14 | import Image from "./image"; 15 | import EventEmitter from "events"; 16 | import {MutableBuffer} from "mutable-buffer"; 17 | import getPixels from "get-pixels"; 18 | import iconv from "iconv-lite"; 19 | import qr from "qr-image"; 20 | import {AnyCase} from "./utils"; 21 | import {type} from "os"; 22 | 23 | export interface PrinterOptions { 24 | encoding?: string | undefined, 25 | width?: number | undefined, 26 | } 27 | 28 | export type PrinterModel = null | 'qsprinter'; 29 | 30 | /** 31 | * 'dhdw', 'dwh' and 'dhw' are treated as 'dwdh' 32 | */ 33 | export type RasterMode = AnyCase<'normal' | 'dw' | 'dh' | 'dwdh' | 'dhdw' | 'dwh' | 'dhw'>; 34 | 35 | export interface QrImageOptions extends qr.Options { 36 | mode: RasterMode 37 | } 38 | 39 | export type BitmapDensity = AnyCase<'s8' | 'd8' | 's24' | 'd24'>; 40 | 41 | export type StyleString = AnyCase<'normal' | `${'b'|''}${'i'|''}${'u'|'u2'|''}`>; 42 | 43 | export type FeedControlSequence = AnyCase<'lf' | 'glf' | 'ff' | 'cr' | 'ht' | 'vt'>; 44 | 45 | export type Alignment = AnyCase<'lt' | 'ct' | 'rt'>; 46 | 47 | export type FontFamily = AnyCase<'a' | 'b' | 'c'>; 48 | 49 | export type HardwareCommand = AnyCase<'init' | 'select' | 'reset'>; 50 | 51 | export type BarcodeType = AnyCase<'UPC_A' | 'UPC-A' | 'UPC-E' | 'UPC_E' | 'EAN13' | 'EAN8' | 'CODE39' | 'ITF' | 'NW7' | 'CODE93' | 'CODE128'>; 52 | export type BarcodePosition = AnyCase<'off' | 'abv' | 'blw' | 'bth'>; 53 | export type BarcodeFont = AnyCase<'a' | 'b'>; 54 | export interface BarcodeOptions { 55 | width: number; 56 | height: number; 57 | position?: BarcodePosition | undefined; 58 | font?: BarcodeFont | undefined; 59 | includeParity?: boolean | undefined; 60 | } 61 | export type LegacyBarcodeArguments = [ 62 | width: number, 63 | height: number, 64 | position?: BarcodePosition | undefined, 65 | font?: BarcodeFont | undefined, 66 | ]; 67 | 68 | export type QRLevel = AnyCase<'l' | 'm' | 'q' | 'h'>; 69 | 70 | export type TableAlignment = AnyCase<'left' | 'center' | 'right'>; 71 | export type CustomTableItem = { 72 | text: string; 73 | align?: TableAlignment; 74 | style?: StyleString | undefined; 75 | } & ({ width: number } | { cols: number }); 76 | 77 | export interface CustomTableOptions { 78 | size: [number, number]; 79 | encoding: string; 80 | } 81 | 82 | export class Printer extends EventEmitter { 83 | public adapter: Adapter; 84 | public buffer = new MutableBuffer(); 85 | protected options: PrinterOptions; 86 | protected encoding: string; 87 | protected width: number; 88 | protected _model: PrinterModel = null; 89 | 90 | /** 91 | * [function ESC/POS Printer] 92 | * @param {[Adapter]} adapter [eg: usb, network, or serialport] 93 | * @param {[PrinterOptions]} options 94 | * @return {[Printer]} printer [the escpos printer instance] 95 | */ 96 | constructor(adapter: Adapter, options: PrinterOptions) { 97 | super(); 98 | this.adapter = adapter; 99 | this.options = options; 100 | this.encoding = options.encoding ?? 'GB18030'; 101 | this.width = options.width ?? 48; 102 | } 103 | 104 | /** 105 | * Set printer model to recognize model-specific commands. 106 | * Supported models: [ null, 'qsprinter' ] 107 | * 108 | * For generic printers, set model to null 109 | * 110 | * [function set printer model] 111 | * @param {[String]} model [mandatory] 112 | * @return {[Printer]} printer [the escpos printer instance] 113 | */ 114 | model(model: PrinterModel) { 115 | this._model = model; 116 | return this; 117 | } 118 | 119 | /** 120 | * Set character code table 121 | * @param {[Number]} codeTable 122 | * @return {[Printer]} printer [the escpos printer instance] 123 | */ 124 | setCharacterCodeTable(codeTable: number) { 125 | this.buffer.write(_.ESC); 126 | this.buffer.write(_.TAB); 127 | this.buffer.writeUInt8(codeTable); 128 | return this; 129 | }; 130 | 131 | /** 132 | * Fix bottom margin 133 | * @param {[String]} size 134 | * @return {[Printer]} printer [the escpos printer instance] 135 | */ 136 | marginBottom(size: number) { 137 | this.buffer.write(_.MARGINS.BOTTOM); 138 | this.buffer.writeUInt8(size); 139 | return this; 140 | }; 141 | 142 | /** 143 | * Fix left margin 144 | * @param {[String]} size 145 | * @return {[Printer]} printer [the escpos printer instance] 146 | */ 147 | marginLeft(size: number) { 148 | this.buffer.write(_.MARGINS.LEFT); 149 | this.buffer.writeUInt8(size); 150 | return this; 151 | }; 152 | 153 | /** 154 | * Fix right margin 155 | * @param {[String]} size 156 | * @return {[Printer]} printer [the escpos printer instance] 157 | */ 158 | marginRight(size: number) { 159 | this.buffer.write(_.MARGINS.RIGHT); 160 | this.buffer.writeUInt8(size); 161 | return this; 162 | }; 163 | 164 | /** 165 | * [function print] 166 | * @param {[String]} content [mandatory] 167 | * @return {[Printer]} printer [the escpos printer instance] 168 | */ 169 | print(content: string | Buffer) { 170 | this.buffer.write(content); 171 | return this; 172 | }; 173 | /** 174 | * [function print pure content with End Of Line] 175 | * @param {[String]} content [mandatory] 176 | * @return {[Printer]} printer [the escpos printer instance] 177 | */ 178 | println(content: string) { 179 | return this.print(content + _.EOL); 180 | }; 181 | 182 | /** 183 | * [function print End Of Line] 184 | * @return {[Printer]} printer [the escpos printer instance] 185 | */ 186 | newLine() { 187 | return this.print(_.EOL); 188 | }; 189 | 190 | /** 191 | * [function Print encoded alpha-numeric text with End Of Line] 192 | * @param {[String]} content [mandatory] 193 | * @param {[String]} encoding [optional] 194 | * @return {[Printer]} printer [the escpos printer instance] 195 | */ 196 | text(content: string, encoding = this.encoding) { 197 | return this.print(iconv.encode(`${content}${_.EOL}`, encoding)); 198 | }; 199 | 200 | 201 | /** 202 | * [function Print draw line End Of Line] 203 | * @param {[String]} character [optional] 204 | * @return {[Printer]} printer [the escpos printer instance] 205 | */ 206 | drawLine(character = '-') { 207 | for (let i = 0; i < this.width; i++) { 208 | this.buffer.write(Buffer.from(character)); 209 | } 210 | this.newLine(); 211 | 212 | return this; 213 | }; 214 | 215 | 216 | 217 | /** 218 | * [function Print table with End Of Line] 219 | * @param {[data]} data [mandatory] 220 | * @param {[String]} encoding [optional] 221 | * @return {[Printer]} printer [the escpos printer instance] 222 | */ 223 | table(data: (string | number)[], encoding = this.encoding) { 224 | const cellWidth = this.width / data.length; 225 | let lineTxt = ""; 226 | 227 | for (let i = 0; i < data.length; i++) { 228 | lineTxt += data[i].toString(); 229 | 230 | const spaces = cellWidth - data[i].toString().length; 231 | for (let j = 0; j < spaces; j++) lineTxt += " "; 232 | } 233 | this.buffer.write(iconv.encode(lineTxt + _.EOL, encoding)); 234 | return this; 235 | }; 236 | 237 | /** 238 | * [function Print custom table with End Of Line] 239 | * @param {[data]} data [mandatory] 240 | * @param {[String]} options [optional] 241 | * @return {[Printer]} printer [the escpos printer instance] 242 | */ 243 | tableCustom(data: CustomTableItem[], options: CustomTableOptions = { size: [1, 1], encoding: this.encoding }): this { 244 | let [width, height] = options.size; 245 | let baseWidth = Math.floor(this.width / width) 246 | let cellWidth = Math.floor(baseWidth / data.length) 247 | let leftoverSpace = baseWidth - cellWidth * data.length // by only data[].width 248 | let lineStr = '' 249 | let secondLineEnabled = false 250 | let secondLine = [] 251 | 252 | for (let i = 0; i < data.length; i++) { 253 | const obj = data[i] 254 | const align = utils.upperCase(obj.align || 'left'); 255 | 256 | const textLength = utils.textLength(obj.text); 257 | 258 | if ("width" in obj) { 259 | cellWidth = baseWidth * obj.width 260 | } else if (obj.cols) { 261 | cellWidth = obj.cols / width 262 | leftoverSpace = 0; 263 | } 264 | 265 | let originalText: string | null = null; 266 | if (cellWidth < textLength) { 267 | originalText = obj.text; 268 | obj.text = utils.textSubstring(obj.text, 0, cellWidth) 269 | } 270 | 271 | if (align === 'CENTER') { 272 | let spaces = (cellWidth - textLength) / 2 273 | for (let s = 0; s < spaces; s++) lineStr += ' '; 274 | 275 | if (obj.text !== '') { 276 | if (obj.style) lineStr += `${this._getStyle(obj.style)}${obj.text}${this._getStyle("NORMAL")}`; 277 | else lineStr += obj.text 278 | } 279 | 280 | for (let s = 0; s < spaces - 1; s++) lineStr += ' '; 281 | } else if (align === 'RIGHT') { 282 | let spaces = cellWidth - textLength 283 | if (leftoverSpace > 0) { 284 | spaces += leftoverSpace 285 | leftoverSpace = 0 286 | } 287 | 288 | for (let s = 0; s < spaces; s++) lineStr += ' '; 289 | 290 | if (obj.text !== '') { 291 | if (obj.style) lineStr += `${this._getStyle(obj.style)}${obj.text}${this._getStyle("NORMAL")}`; 292 | else lineStr += obj.text 293 | } 294 | } else { 295 | if (obj.text !== '') { 296 | if (obj.style) lineStr += `${this._getStyle(obj.style)}${obj.text}${this._getStyle("NORMAL")}`; 297 | else lineStr += obj.text 298 | } 299 | 300 | let spaces = Math.floor(cellWidth - textLength) 301 | if (leftoverSpace > 0) { 302 | spaces += leftoverSpace 303 | leftoverSpace = 0 304 | } 305 | 306 | for (let s = 0; s < spaces; s++) lineStr += ' ' 307 | } 308 | 309 | if (originalText !== null) { 310 | secondLineEnabled = true 311 | obj.text = utils.textSubstring(originalText, cellWidth) 312 | secondLine.push(obj) 313 | } else { 314 | obj.text = '' 315 | secondLine.push(obj) 316 | } 317 | } 318 | 319 | // Set size to line 320 | if (width > 1 || height > 1) { 321 | lineStr = ( 322 | _.TEXT_FORMAT.TXT_CUSTOM_SIZE(width, height) + 323 | lineStr + 324 | _.TEXT_FORMAT.TXT_NORMAL 325 | ) 326 | } 327 | 328 | // Write the line 329 | this.buffer.write( 330 | iconv.encode(lineStr + _.EOL, options.encoding || this.encoding) 331 | ) 332 | 333 | if (secondLineEnabled) { 334 | // Writes second line if has 335 | return this.tableCustom(secondLine, options) 336 | } else { 337 | return this 338 | } 339 | } 340 | 341 | 342 | /** 343 | * [function Print encoded alpha-numeric text without End Of Line] 344 | * @param {[String]} content [mandatory] 345 | * @param {[String]} encoding [optional] 346 | * @return {[Printer]} printer [the escpos printer instance] 347 | */ 348 | pureText(content: string, encoding = this.encoding) { 349 | return this.print(iconv.encode(content, encoding)); 350 | }; 351 | 352 | /** 353 | * [function encode text] 354 | * @param {[String]} encoding [mandatory] 355 | * @return {[Printer]} printer [the escpos printer instance] 356 | */ 357 | encode(encoding: string) { 358 | this.encoding = encoding; 359 | return this; 360 | } 361 | 362 | /** 363 | * [line feed] 364 | * @param {[type]} n Number of lines 365 | * @return {[Printer]} printer [the escpos printer instance] 366 | */ 367 | feed(n = 1) { 368 | this.buffer.write(new Array(n).fill(_.EOL).join('')); 369 | return this; 370 | }; 371 | 372 | /** 373 | * [feed control sequences] 374 | * @param {[type]} ctrl [description] 375 | * @return {[Printer]} printer [the escpos printer instance] 376 | */ 377 | control(ctrl: FeedControlSequence) { 378 | this.buffer.write(_.FEED_CONTROL_SEQUENCES[ 379 | `CTL_${utils.upperCase(ctrl)}` as const 380 | ]); 381 | return this; 382 | }; 383 | /** 384 | * [text align] 385 | * @param {[type]} align [description] 386 | * @return {[Printer]} printer [the escpos printer instance] 387 | */ 388 | align(align: Alignment) { 389 | this.buffer.write(_.TEXT_FORMAT[ 390 | `TXT_ALIGN_${utils.upperCase(align)}` as const 391 | ]); 392 | return this; 393 | }; 394 | /** 395 | * [font family] 396 | * @param {[type]} family [description] 397 | * @return {[Printer]} printer [the escpos printer instance] 398 | */ 399 | font(family: FontFamily) { 400 | this.buffer.write(_.TEXT_FORMAT[ 401 | `TXT_FONT_${utils.upperCase(family)}` as const 402 | ]); 403 | if (family.toUpperCase() === 'A') 404 | this.width = this.options && this.options.width || 42; 405 | else 406 | this.width = this.options && this.options.width || 56; 407 | return this; 408 | }; 409 | 410 | /** 411 | * [font style] 412 | * @return {[Printer]} printer [the escpos printer instance] 413 | */ 414 | _getStyle(string: StyleString): string; 415 | _getStyle(bold: boolean, italic: boolean, underline: boolean | 0 | 1 | 2): string; 416 | _getStyle(boldOrString: boolean | StyleString, italic?: boolean, underline?: boolean | 0 | 1 | 2) { 417 | if (typeof boldOrString === 'string') { 418 | switch (utils.upperCase(boldOrString)) { 419 | case 'B': 420 | return this._getStyle(true, false, 0); 421 | case 'I': 422 | return this._getStyle(false, true, 0); 423 | case 'U': 424 | return this._getStyle(false, false, 1); 425 | case 'U2': 426 | return this._getStyle(false, false, 2); 427 | case 'BI': 428 | return this._getStyle(true, true, 0); 429 | case 'BIU': 430 | return this._getStyle(true, true, 1); 431 | case 'BIU2': 432 | return this._getStyle(true, true, 2); 433 | case 'BU': 434 | return this._getStyle(true, false, 1); 435 | case 'BU2': 436 | return this._getStyle(true, false, 2); 437 | case 'IU': 438 | return this._getStyle(false, true, 1); 439 | case 'IU2': 440 | return this._getStyle(false, true, 2); 441 | case 'NORMAL': 442 | default: 443 | return this._getStyle(false, false, 0); 444 | } 445 | } else { 446 | let styled = `${ 447 | boldOrString ? _.TEXT_FORMAT.TXT_BOLD_ON : _.TEXT_FORMAT.TXT_BOLD_OFF 448 | }${ 449 | italic ? _.TEXT_FORMAT.TXT_ITALIC_ON : _.TEXT_FORMAT.TXT_ITALIC_OFF 450 | }`; 451 | if (underline === 0 || underline === false) styled += _.TEXT_FORMAT.TXT_UNDERL_OFF; 452 | else if (underline === 1 || underline === true) styled += _.TEXT_FORMAT.TXT_UNDERL_ON; 453 | else if (underline === 2) styled += _.TEXT_FORMAT.TXT_UNDERL2_ON; 454 | return styled; 455 | } 456 | } 457 | 458 | /** 459 | * [font style] 460 | * @return {[Printer]} printer [the escpos printer instance] 461 | */ 462 | style(string: StyleString): this; 463 | style(bold: boolean, italic: boolean, underline: boolean | 0 | 1 | 2): this; 464 | style(boldOrString: boolean | StyleString, italic?: boolean, underline?: boolean | 0 | 1 | 2) { 465 | const style = (typeof boldOrString === 'string') 466 | ? this._getStyle(boldOrString) 467 | : this._getStyle(boldOrString, italic as boolean, underline as boolean); 468 | this.buffer.write(style); 469 | return this; 470 | }; 471 | 472 | /** 473 | * [font size] 474 | * @param {[String]} width [description] 475 | * @param {[String]} height [description] 476 | * @return {[Printer]} printer [the escpos printer instance] 477 | */ 478 | size(width: number, height: number) { 479 | this.buffer.write(_.TEXT_FORMAT.TXT_CUSTOM_SIZE(width, height)); 480 | return this; 481 | }; 482 | 483 | /** 484 | * [set character spacing] 485 | * @param {[type]} n [description] 486 | * @return {[Printer]} printer [the escpos printer instance] 487 | */ 488 | spacing(n?: number | null) { 489 | if (n === undefined || n === null) { 490 | this.buffer.write(_.CHARACTER_SPACING.CS_DEFAULT); 491 | } else { 492 | this.buffer.write(_.CHARACTER_SPACING.CS_SET); 493 | this.buffer.writeUInt8(n); 494 | } 495 | return this; 496 | } 497 | 498 | /** 499 | * [set line spacing] 500 | * @param {[type]} n [description] 501 | * @return {[Printer]} printer [the escpos printer instance] 502 | */ 503 | lineSpace(n?: number | null) { 504 | if (n === undefined || n === null) { 505 | this.buffer.write(_.LINE_SPACING.LS_DEFAULT); 506 | } else { 507 | this.buffer.write(_.LINE_SPACING.LS_SET); 508 | this.buffer.writeUInt8(n); 509 | } 510 | return this; 511 | }; 512 | 513 | /** 514 | * [hardware] 515 | * @param {[type]} hw [description] 516 | * @return {[Printer]} printer [the escpos printer instance] 517 | */ 518 | hardware(hw: HardwareCommand) { 519 | this.buffer.write(_.HARDWARE[`HW_${utils.upperCase(hw)}` as const]); 520 | return this; 521 | }; 522 | 523 | private static isLegacyBarcodeOptions( 524 | optionsOrLegacy: [BarcodeOptions] | LegacyBarcodeArguments 525 | ): optionsOrLegacy is LegacyBarcodeArguments { 526 | return typeof optionsOrLegacy[0] === 'object'; 527 | } 528 | 529 | /** 530 | * [barcode] 531 | * @param {[type]} code [description] 532 | * @param {[type]} type [description] 533 | * @param {[type]} options [description] 534 | * @return {[Printer]} printer [the escpos printer instance] 535 | */ 536 | barcode(code: number, type: BarcodeType, options: BarcodeOptions): this; 537 | barcode(code: number, type: BarcodeType, ...optionsOrLegacy: [BarcodeOptions] | LegacyBarcodeArguments) { 538 | let options: BarcodeOptions; 539 | if (Printer.isLegacyBarcodeOptions(optionsOrLegacy)) { 540 | options = { 541 | width: optionsOrLegacy[0], 542 | height: optionsOrLegacy[1], 543 | position: optionsOrLegacy[2], 544 | font: optionsOrLegacy[3], 545 | includeParity: true, 546 | }; 547 | } else [options] = optionsOrLegacy; 548 | options.font = options.font ?? 'a'; 549 | options.position = options.position ?? 'blw'; 550 | options.includeParity = options.includeParity ?? true; 551 | 552 | const convertCode = code.toString(10); 553 | let parityBit = ''; 554 | let codeLength = ''; 555 | if (typeof type === 'undefined' || type === null) { 556 | throw new TypeError('barcode type is required'); 557 | } 558 | if (type === 'EAN13' && convertCode.length !== 12) { 559 | throw new Error('EAN13 Barcode type requires code length 12'); 560 | } 561 | if (type === 'EAN8' && convertCode.length !== 7) { 562 | throw new Error('EAN8 Barcode type requires code length 7'); 563 | } 564 | if (this._model === 'qsprinter') { 565 | this.buffer.write(_.MODEL.QSPRINTER.BARCODE_MODE.ON); 566 | } 567 | if (this._model === 'qsprinter') { 568 | // qsprinter has no BARCODE_WIDTH command (as of v7.5) 569 | } else if (utils.isKey(options.width, _.BARCODE_FORMAT.BARCODE_WIDTH)) { 570 | this.buffer.write(_.BARCODE_FORMAT.BARCODE_WIDTH[options.width]); 571 | } else { 572 | this.buffer.write(_.BARCODE_FORMAT.BARCODE_WIDTH_DEFAULT); 573 | } 574 | if (options.height >= 1 && options.height <= 255) { 575 | this.buffer.write(_.BARCODE_FORMAT.BARCODE_HEIGHT(options.height)); 576 | } else { 577 | if (this._model === 'qsprinter') { 578 | this.buffer.write(_.MODEL.QSPRINTER.BARCODE_HEIGHT_DEFAULT); 579 | } else { 580 | this.buffer.write(_.BARCODE_FORMAT.BARCODE_HEIGHT_DEFAULT); 581 | } 582 | } 583 | if (this._model === 'qsprinter') { 584 | // Qsprinter has no barcode font 585 | } else { 586 | this.buffer.write(_.BARCODE_FORMAT[ 587 | `BARCODE_FONT_${utils.upperCase(options.font)}` as const 588 | ]); 589 | } 590 | this.buffer.write(_.BARCODE_FORMAT[ 591 | `BARCODE_TXT_${utils.upperCase(options.position)}` as const 592 | ]); 593 | 594 | let normalizedType = utils.upperCase(type); 595 | if (normalizedType === 'UPC-A') normalizedType = 'UPC_A'; 596 | else if (normalizedType === 'UPC-E') normalizedType = 'UPC_E'; 597 | 598 | this.buffer.write(_.BARCODE_FORMAT[ 599 | `BARCODE_${normalizedType}` as const 600 | ]); 601 | if (options.includeParity) { 602 | if (type === 'EAN13' || type === 'EAN8') { 603 | parityBit = utils.getParityBit(convertCode); 604 | } 605 | } 606 | if (type == 'CODE128' || type == 'CODE93') { 607 | codeLength = utils.codeLength(convertCode); 608 | } 609 | this.buffer.write(codeLength + convertCode + (options.includeParity ? parityBit : '') + '\x00'); // Allow to skip the parity byte 610 | if (this._model === 'qsprinter') { 611 | this.buffer.write(_.MODEL.QSPRINTER.BARCODE_MODE.OFF); 612 | } 613 | return this; 614 | }; 615 | 616 | /** 617 | * [print qrcode] 618 | * @param {[type]} content [description] 619 | * @param {[type]} version [description] 620 | * @param {[type]} level [description] 621 | * @param {[type]} size [description] 622 | * @return {[Printer]} printer [the escpos printer instance] 623 | */ 624 | qrcode(content: string, version?: number | undefined, level?: QRLevel | undefined, size?: number | undefined) { 625 | if (this._model !== 'qsprinter') { 626 | this.buffer.write(_.CODE2D_FORMAT.TYPE_QR); 627 | this.buffer.write(_.CODE2D_FORMAT.CODE2D); 628 | this.buffer.writeUInt8(version ?? 3); 629 | this.buffer.write(_.CODE2D_FORMAT[ 630 | `QR_LEVEL_${utils.upperCase(level ?? 'L')}` as const 631 | ]); 632 | this.buffer.writeUInt8(size ?? 6); 633 | this.buffer.writeUInt16LE(content.length); 634 | this.buffer.write(content); 635 | } else { 636 | const dataRaw = iconv.encode(content, 'utf8'); 637 | if (dataRaw.length < 1 && dataRaw.length > 2710) { 638 | throw new Error('Invalid code length in byte. Must be between 1 and 2710'); 639 | } 640 | 641 | // Set pixel size 642 | if (!size || (size && typeof size !== 'number')) 643 | size = _.MODEL.QSPRINTER.CODE2D_FORMAT.PIXEL_SIZE.DEFAULT; 644 | else if (size && size < _.MODEL.QSPRINTER.CODE2D_FORMAT.PIXEL_SIZE.MIN) 645 | size = _.MODEL.QSPRINTER.CODE2D_FORMAT.PIXEL_SIZE.MIN; 646 | else if (size && size > _.MODEL.QSPRINTER.CODE2D_FORMAT.PIXEL_SIZE.MAX) 647 | size = _.MODEL.QSPRINTER.CODE2D_FORMAT.PIXEL_SIZE.MAX; 648 | this.buffer.write(_.MODEL.QSPRINTER.CODE2D_FORMAT.PIXEL_SIZE.CMD); 649 | this.buffer.writeUInt8(size); 650 | 651 | // Set version 652 | if (!version || (version && typeof version !== 'number')) 653 | version = _.MODEL.QSPRINTER.CODE2D_FORMAT.VERSION.DEFAULT; 654 | else if (version && version < _.MODEL.QSPRINTER.CODE2D_FORMAT.VERSION.MIN) 655 | version = _.MODEL.QSPRINTER.CODE2D_FORMAT.VERSION.MIN; 656 | else if (version && version > _.MODEL.QSPRINTER.CODE2D_FORMAT.VERSION.MAX) 657 | version = _.MODEL.QSPRINTER.CODE2D_FORMAT.VERSION.MAX; 658 | this.buffer.write(_.MODEL.QSPRINTER.CODE2D_FORMAT.VERSION.CMD); 659 | this.buffer.writeUInt8(version); 660 | 661 | // Set level 662 | this.buffer.write(_.MODEL.QSPRINTER.CODE2D_FORMAT.LEVEL.CMD); 663 | this.buffer.write(_.MODEL.QSPRINTER.CODE2D_FORMAT.LEVEL.OPTIONS[ 664 | utils.upperCase(level ?? 'L') 665 | ]); 666 | 667 | // Transfer data(code) to buffer 668 | this.buffer.write(_.MODEL.QSPRINTER.CODE2D_FORMAT.SAVEBUF.CMD_P1); 669 | this.buffer.writeUInt16LE(dataRaw.length + _.MODEL.QSPRINTER.CODE2D_FORMAT.LEN_OFFSET); 670 | this.buffer.write(_.MODEL.QSPRINTER.CODE2D_FORMAT.SAVEBUF.CMD_P2); 671 | this.buffer.write(dataRaw); 672 | 673 | // Print from buffer 674 | this.buffer.write(_.MODEL.QSPRINTER.CODE2D_FORMAT.PRINTBUF.CMD_P1); 675 | this.buffer.writeUInt16LE(dataRaw.length + _.MODEL.QSPRINTER.CODE2D_FORMAT.LEN_OFFSET); 676 | this.buffer.write(_.MODEL.QSPRINTER.CODE2D_FORMAT.PRINTBUF.CMD_P2); 677 | } 678 | return this; 679 | }; 680 | 681 | /** 682 | * [print qrcode image] 683 | * @param {[type]} text [description] 684 | * @param {[type]} options [description] 685 | * @return {[Promise]} 686 | */ 687 | qrimage(text: string, options: QrImageOptions = { type: 'png', mode: 'dhdw' }): Promise { 688 | return new Promise((resolve, reject) => { 689 | const buffer = qr.imageSync(text, options); 690 | const type = ['image', options.type].join('/'); 691 | getPixels(buffer, type, (err, pixels) => { 692 | if (err) reject(err); 693 | this.raster(new Image(pixels), options.mode); 694 | resolve(this); 695 | }); 696 | }) 697 | }; 698 | 699 | /** 700 | * [image description] 701 | * @param {[type]} image [description] 702 | * @param {[type]} density [description] 703 | * @return {[Printer]} printer [the escpos printer instance] 704 | */ 705 | async image(image: Image, density: BitmapDensity = 'd24') { 706 | if (!(image instanceof Image)) throw new TypeError('Only escpos.Image supported'); 707 | const n = !!~['D8', 'S8'].indexOf(utils.upperCase(density)) ? 1 : 3; 708 | const header = _.BITMAP_FORMAT[`BITMAP_${utils.upperCase(density)}` as const]; 709 | const bitmap = image.toBitmap(n * 8); 710 | 711 | this.lineSpace(0); // set line spacing to 0 712 | bitmap.data.forEach((line) => { 713 | this.buffer.write(header); 714 | this.buffer.writeUInt16LE(line.length / n); 715 | this.buffer.write(line); 716 | this.buffer.write(_.EOL); 717 | }); 718 | // added a delay so the printer can process the graphical data 719 | // when connected via slower connection ( e.g.: Serial) 720 | await new Promise((resolve) => { 721 | setTimeout(() => { resolve() }, 200); 722 | }); 723 | return this.lineSpace(); 724 | }; 725 | 726 | /** 727 | * [raster description] 728 | * @param {[type]} image [description] 729 | * @param {[type]} mode Raster mode ( 730 | * @return {[Printer]} printer [the escpos printer instance] 731 | */ 732 | raster(image: Image, mode: RasterMode = 'NORMAL') { 733 | if (!(image instanceof Image)) 734 | throw new TypeError('Only escpos.Image supported'); 735 | mode = utils.upperCase(mode); 736 | if (mode === 'DHDW' || 737 | mode === 'DWH' || 738 | mode === 'DHW') mode = 'DWDH'; 739 | const raster = image.toRaster(); 740 | const header = _.GSV0_FORMAT[`GSV0_${mode}` as const]; 741 | this.buffer.write(header); 742 | this.buffer.writeUInt16LE(raster.width); 743 | this.buffer.writeUInt16LE(raster.height); 744 | this.buffer.write(raster.data); 745 | return this; 746 | }; 747 | 748 | /** 749 | * [function Send pulse to kick the cash drawer] 750 | * @param {[type]} pin [description] 751 | * @return {[Printer]} printer [the escpos printer instance] 752 | */ 753 | cashdraw(pin: 2 | 5 = 2) { 754 | this.buffer.write(_.CASH_DRAWER[ 755 | pin === 5 ? 'CD_KICK_5' : 'CD_KICK_2' 756 | ]); 757 | return this; 758 | }; 759 | 760 | /** 761 | * Printer Buzzer (Beep sound) 762 | * @param {[Number]} n Refers to the number of buzzer times 763 | * @param {[Number]} t Refers to the buzzer sound length in (t * 100) milliseconds. 764 | */ 765 | beep(n: number, t: number) { 766 | this.buffer.write(_.BEEP); 767 | this.buffer.writeUInt8(n); 768 | this.buffer.writeUInt8(t); 769 | return this; 770 | }; 771 | 772 | /** 773 | * Send data to hardware and flush buffer 774 | * @return {[Promise]} 775 | */ 776 | flush(): Promise { 777 | return new Promise((resolve, reject) => { 778 | const buf = this.buffer.flush(); 779 | this.adapter.write(buf, (error) => { 780 | if (error) reject(error); 781 | else resolve(this); 782 | }); 783 | }); 784 | }; 785 | 786 | /** 787 | * Cut paper 788 | * @param {[boolean]} partial set a full or partial cut. Default: full Partial cut is not implemented in all printers 789 | * @param {[number]} feed Number of lines to feed before cutting 790 | * @return {[Printer]} printer [the escpos printer instance] 791 | */ 792 | cut(partial = false, feed = 3) { 793 | this.feed(feed); 794 | this.buffer.write(_.PAPER[ 795 | partial ? 'PAPER_PART_CUT' : 'PAPER_FULL_CUT' 796 | ]); 797 | return this; 798 | }; 799 | 800 | /** 801 | * [close description] 802 | * @param closeArgs Arguments passed to adapter's close function 803 | */ 804 | async close(...closeArgs: AdapterCloseArgs): Promise { 805 | await this.flush(); 806 | return new Promise((resolve, reject) => { 807 | this.adapter.close((error) => { 808 | if (error) reject(error); 809 | resolve(this); 810 | }, ...closeArgs); 811 | }); 812 | }; 813 | 814 | /** 815 | * [color select between two print color modes, if your printer supports it] 816 | * @param {Number} color - 0 for primary color (black) 1 for secondary color (red) 817 | * @return {[Printer]} printer [the escpos printer instance] 818 | */ 819 | color(color: 0 | 1) { 820 | if (color !== 0 && color !== 1) { 821 | console.warn(`Unknown color ${color}`); 822 | this.buffer.write(_.COLOR[0]); 823 | } else this.buffer.write(_.COLOR[color]); 824 | return this; 825 | }; 826 | 827 | /** 828 | * [reverse colors, if your printer supports it] 829 | * @param {Boolean} reverse - True for reverse, false otherwise 830 | * @return {[Printer]} printer [the escpos printer instance] 831 | */ 832 | setReverseColors(reverse: boolean) { 833 | this.buffer.write(reverse ? _.COLOR.REVERSE : _.COLOR.UNREVERSE); 834 | return this; 835 | }; 836 | 837 | 838 | /** 839 | * [writes a low level command to the printer buffer] 840 | * 841 | * @usage 842 | * 1) raw('1d:77:06:1d:6b:02:32:32:30:30:30:30:32:30:30:30:35:30:35:00:0a') 843 | * 2) raw('1d 77 06 1d 6b 02 32 32 30 30 30 30 32 30 30 30 35 30 35 00 0a') 844 | * 3) raw(Buffer.from('1d77061d6b0232323030303032303030353035000a','hex')) 845 | * 846 | * @param data {Buffer|string} 847 | * @returns {Printer} 848 | */ 849 | raw(data: Buffer | string) { 850 | if (Buffer.isBuffer(data)) { 851 | this.buffer.write(data); 852 | } else if (typeof data === 'string') { 853 | data = data.toLowerCase(); 854 | this.buffer.write(Buffer.from(data.replace(/(\s|:)/g, ''), 'hex')); 855 | } 856 | return this; 857 | }; 858 | 859 | /** 860 | * get one specific status from the printer using it's class 861 | * @param {string} statusClass 862 | * @return {Promise} promise returning given status 863 | */ 864 | getStatus(statusClass: StatusClassConstructor): Promise { 865 | return new Promise((resolve) => { 866 | this.adapter.read(data => { 867 | const byte = data.readInt8(0); 868 | resolve(new statusClass(byte)); 869 | }) 870 | 871 | statusClass.commands().forEach((c) => { 872 | this.buffer.write(c); 873 | }); 874 | }); 875 | } 876 | 877 | /** 878 | * get statuses from the printer 879 | * @return {Promise} 880 | */ 881 | getStatuses() { 882 | return new Promise((resolve) => { 883 | this.adapter.read(data => { 884 | let buffer: number[] = []; 885 | for (let i = 0; i < data.byteLength; i++) buffer.push(data.readInt8(i)); 886 | if (buffer.length < 4) return; 887 | 888 | let statuses = [ 889 | new PrinterStatus(buffer[0]), 890 | new RollPaperSensorStatus(buffer[1]), 891 | new OfflineCauseStatus(buffer[2]), 892 | new ErrorCauseStatus(buffer[3]), 893 | ]; 894 | resolve(statuses); 895 | }); 896 | 897 | [PrinterStatus, RollPaperSensorStatus, OfflineCauseStatus, ErrorCauseStatus].forEach((statusClass) => { 898 | statusClass.commands().forEach((command) => { 899 | this.adapter.write(command); 900 | }) 901 | }); 902 | }); 903 | } 904 | 905 | /** 906 | * STAR printer - Paper cut instruction 907 | * @return {[Printer]} printer [the escpos printer instance] 908 | */ 909 | starFullCut() { 910 | this.buffer.write(_.PAPER.STAR_FULL_CUT); 911 | return this; 912 | }; 913 | 914 | /** 915 | * STAR printer - Select emphasized printing 916 | * @return {[Printer]} printer [the escpos printer instance] 917 | */ 918 | emphasize() { 919 | this.buffer.write(_.TEXT_FORMAT.STAR_TXT_EMPHASIZED); 920 | return this; 921 | }; 922 | 923 | /** 924 | * STAR printer - Cancel emphasized printing 925 | * @return {[Printer]} printer [the escpos printer instance] 926 | */ 927 | cancelEmphasize() { 928 | this.buffer.write(_.TEXT_FORMAT.STAR_CANCEL_TXT_EMPHASIZED); 929 | return this; 930 | } 931 | } 932 | 933 | export default Printer; 934 | export { default as Image } from './image'; 935 | export const command = _; 936 | -------------------------------------------------------------------------------- /packages/printer/src/statuses.ts: -------------------------------------------------------------------------------- 1 | import * as _ from "./commands"; 2 | 3 | enum Status { 4 | Ok = 'ok', 5 | Warning = 'warning', 6 | Error = 'error', 7 | } 8 | 9 | interface StatusJSONElementSingle { 10 | bit: number; 11 | value: 0 | 1; 12 | label: string; 13 | status: Status; 14 | } 15 | 16 | interface StatusJSONElementMultiple { 17 | bit: string; 18 | value: string; 19 | label: string; 20 | status: Status; 21 | } 22 | 23 | type StatusJSONElement = StatusJSONElementSingle | StatusJSONElementMultiple; 24 | 25 | interface StatusJSON { 26 | className: T, 27 | byte: number, 28 | bits: string, 29 | statuses: StatusJSONElement[] 30 | } 31 | 32 | // noinspection JSBitwiseOperatorUsage 33 | export abstract class DeviceStatus { 34 | byte; 35 | bits: (0 | 1)[] = []; 36 | bitsAsc: (0 | 1)[] = []; 37 | public constructor(byte: number) { 38 | this.byte = byte; 39 | for (let j = 7; j >= 0; j--) { 40 | const bit = byte & (1 << j) ? 1 : 0; 41 | this.bits.push(bit); 42 | } 43 | 44 | this.bitsAsc = this.bits.slice(); 45 | this.bitsAsc.reverse(); 46 | } 47 | 48 | private getBits() { 49 | return this.bits.join(''); 50 | } 51 | 52 | protected toBaseJSON(name: T): StatusJSON { 53 | return { 54 | className: name, 55 | byte: this.byte, 56 | bits: this.getBits(), 57 | statuses: [] 58 | }; 59 | } 60 | } 61 | 62 | export class PrinterStatus extends DeviceStatus { 63 | static commands() { 64 | return [_.DLE, _.EOT, String.fromCharCode(1)]; 65 | } 66 | 67 | toJSON() { 68 | let result = super.toBaseJSON('PrinterStatus'); 69 | for (let i = 0; i < 8; i++) { 70 | let label = ''; 71 | let status = Status.Ok; 72 | switch (i) { 73 | case 2: 74 | if (this.bitsAsc[i] === 1) { 75 | label = 'Drawer kick-out connector pin 3 is HIGH'; 76 | } else { 77 | label = 'Drawer kick-out connector pin 3 is LOW'; 78 | } 79 | break; 80 | case 3: 81 | if (this.bitsAsc[i] === 1) { 82 | status = Status.Error 83 | label = 'Offline'; 84 | } else { 85 | label = 'Online'; 86 | } 87 | break; 88 | case 5: 89 | if (this.bitsAsc[i] === 1) { 90 | status = Status.Error; 91 | label = 'Waiting for online recovery'; 92 | } else { 93 | label = 'Not waiting for online recovery'; 94 | } 95 | break; 96 | case 6: 97 | if (this.bitsAsc[i] === 1) { 98 | label = 'Paper feed button is being pressed'; 99 | } else { 100 | label = 'Paper feed button is not being pressed'; 101 | } 102 | break; 103 | default: 104 | label = 'Fixed'; 105 | break; 106 | } 107 | 108 | result.statuses.push({ 109 | bit: i, 110 | value: this.bitsAsc[i], 111 | label: label, 112 | status: status 113 | }); 114 | } 115 | 116 | return result; 117 | } 118 | } 119 | 120 | export class OfflineCauseStatus extends DeviceStatus { 121 | static commands() { 122 | return [_.DLE, _.EOT, String.fromCharCode(2)]; 123 | } 124 | 125 | toJSON() { 126 | let result = super.toBaseJSON('OfflineCauseStatus'); 127 | for (let i = 0; i < 8; i++) { 128 | let label = ''; 129 | let status = Status.Error; 130 | switch (i) { 131 | case 2: 132 | if (this.bitsAsc[i] === 1) { 133 | status = Status.Error; 134 | label = 'Cover is open'; 135 | } else { 136 | label = 'Cover is closed'; 137 | } 138 | break; 139 | case 3: 140 | if (this.bitsAsc[i] === 1) { 141 | status = Status.Error; 142 | label = 'Paper is being fed by the paper feed button'; 143 | } else { 144 | label = 'Paper is not being fed by the paper feed button'; 145 | } 146 | break; 147 | case 5: 148 | if (this.bitsAsc[i] === 1) { 149 | status = Status.Error; 150 | label = 'Printing stops due to a paper-end'; 151 | } else { 152 | label = 'No paper-end stop'; 153 | } 154 | break; 155 | case 6: 156 | if (this.bitsAsc[i] === 1) { 157 | status = Status.Error; 158 | label = 'Error occurred'; 159 | } else { 160 | label = 'No error'; 161 | } 162 | break; 163 | default: 164 | label = 'Fixed'; 165 | break; 166 | } 167 | 168 | result.statuses.push({ 169 | bit: i, 170 | value: this.bitsAsc[i], 171 | label, 172 | status, 173 | }); 174 | } 175 | 176 | return result; 177 | } 178 | } 179 | 180 | export class ErrorCauseStatus extends DeviceStatus { 181 | static commands() { 182 | return [_.DLE, _.EOT, String.fromCharCode(3)]; 183 | } 184 | 185 | toJSON() { 186 | let result = super.toBaseJSON('ErrorCauseStatus'); 187 | for (let i = 0; i < 8; i++) { 188 | let label = ''; 189 | let status = Status.Ok; 190 | switch (i) { 191 | case 2: 192 | if (this.bitsAsc[i] === 1) { 193 | status = Status.Error; 194 | label = 'Recoverable error occurred'; 195 | } else { 196 | label = 'No recoverable error'; 197 | } 198 | break; 199 | case 3: 200 | if (this.bitsAsc[i] === 1) { 201 | status = Status.Error; 202 | label = 'Autocutter error occurred'; 203 | } else { 204 | label = 'No autocutter error'; 205 | } 206 | break; 207 | case 5: 208 | if (this.bitsAsc[i] === 1) { 209 | status = Status.Error; 210 | label = 'Unrecoverable error occurred'; 211 | } else { 212 | label = 'No unrecoverable error'; 213 | } 214 | break; 215 | case 6: 216 | if (this.bitsAsc[i] === 1) { 217 | status = Status.Error; 218 | label = 'Auto-recoverable error occurred'; 219 | } else { 220 | label = 'No auto-recoverable error'; 221 | } 222 | break; 223 | default: 224 | label = 'Fixed'; 225 | break; 226 | } 227 | 228 | result.statuses.push({ 229 | bit: i, 230 | value: this.bitsAsc[i], 231 | label: label, 232 | status: status 233 | }); 234 | } 235 | 236 | return result; 237 | } 238 | } 239 | 240 | export class RollPaperSensorStatus extends DeviceStatus { 241 | static commands() { 242 | return [_.DLE, _.EOT, String.fromCharCode(4)]; 243 | } 244 | 245 | toJSON() { 246 | let result = super.toBaseJSON('RollPaperSensorStatus'); 247 | 248 | for (let i = 0; i <= 1; i++) { 249 | result.statuses.push({ 250 | bit: i, 251 | value: this.bitsAsc[i], 252 | label: 'Fixed', 253 | status: Status.Ok 254 | }); 255 | } 256 | 257 | let label = ''; 258 | let status = Status.Ok; 259 | if (this.bitsAsc[2] === 1 && this.bitsAsc[3] === 1) { 260 | status = Status.Warning; 261 | label = 'Roll paper near-end sensor: paper near-end'; 262 | } else if (this.bitsAsc[2] === 0 && this.bitsAsc[3] === 0) { 263 | label = 'Roll paper near-end sensor: paper adequate'; 264 | } 265 | 266 | result.statuses.push({ 267 | bit: '2,3', 268 | value: `${this.bitsAsc[2]}${this.bitsAsc[3]}`, 269 | label: label, 270 | status: status 271 | }); 272 | 273 | result.statuses.push({ 274 | bit: 4, 275 | value: this.bitsAsc[4], 276 | label: 'Fixed', 277 | status: Status.Ok 278 | }); 279 | 280 | label = ''; 281 | status = Status.Ok; 282 | if (this.bitsAsc[5] === 1 && this.bitsAsc[6] === 1) { 283 | status = Status.Error; 284 | label = 'Roll paper end sensor: paper not present'; 285 | } else if (this.bitsAsc[5] === 0 && this.bitsAsc[6] === 0) { 286 | label = 'Roll paper end sensor: paper present'; 287 | } 288 | 289 | result.statuses.push({ 290 | bit: '5,6', 291 | value: `${this.bitsAsc[5]}${this.bitsAsc[6]}`, 292 | label: label, 293 | status, 294 | }); 295 | 296 | for (let i = 7; i <= 8; i++) { 297 | result.statuses.push({ 298 | bit: i, 299 | value: this.bitsAsc[i], 300 | label: 'Fixed', 301 | status: Status.Ok 302 | }); 303 | } 304 | 305 | return result; 306 | } 307 | } 308 | 309 | export const statusClasses = { 310 | PrinterStatus, 311 | OfflineCauseStatus, 312 | ErrorCauseStatus, 313 | RollPaperSensorStatus, 314 | } 315 | export type Statuses = typeof statusClasses; 316 | export type StatusClassName = keyof Statuses; 317 | export type StatusClassConstructor = { 318 | new(byte: number): T; 319 | commands(): string[]; 320 | } 321 | -------------------------------------------------------------------------------- /packages/printer/src/utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * [getParityBit description] 3 | * @return {[type]} [description] 4 | */ 5 | export function getParityBit(str: string) { 6 | let parity = 0; 7 | let reversedCode = str.split('').reverse().join(''); 8 | for (let counter = 0; counter < reversedCode.length; counter += 1) { 9 | parity += parseInt(reversedCode.charAt(counter), 10) * Math.pow(3, ((counter + 1) % 2)); 10 | } 11 | return ((10 - (parity % 10)) % 10).toString(); 12 | } 13 | 14 | export function codeLength(str: string) { 15 | const hex = Number(str.length).toString(16).padStart(2, '0') 16 | let buff = Buffer.from(hex, 'hex'); 17 | return buff.toString(); 18 | } 19 | 20 | export function charLength(char: string) { 21 | const code = char.charCodeAt(0); 22 | return code > 0x7f && code <= 0xffff ? 2 : 1; // More than 2bytes count as 2 23 | } 24 | 25 | export function textLength(str: string) { 26 | return str.split('').reduce((accLen, char) => { 27 | return accLen + charLength(char); 28 | }, 0) 29 | } 30 | 31 | export function textSubstring(str: string, start: number, end?: number) { 32 | let accLen = 0; 33 | return str.split('').reduce((accStr, char) => { 34 | accLen = accLen + charLength(char); 35 | return accStr + (accLen > start && (!end || accLen <= end) ? char : ''); 36 | }, '') 37 | } 38 | 39 | export function upperCase(string: T): Uppercase { 40 | return string.toUpperCase() as Uppercase; 41 | } 42 | 43 | export type AnyCase = Uppercase | Lowercase; 44 | 45 | export function isKey(key: string | number | symbol, of: T): key is keyof T { 46 | return key in of; 47 | } 48 | -------------------------------------------------------------------------------- /packages/printer/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/node10/tsconfig.json", 3 | "include": ["src"], 4 | "compilerOptions": { 5 | "outDir": "dist", 6 | "sourceMap": true, 7 | "declaration": true, 8 | "declarationMap": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/printer/tsconfig.ref.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "composite": true, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /packages/screen/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## ESCPOS Screen 3 | 4 | ## Installation 5 | 6 | ## Example 7 | 8 | ```js 9 | ``` 10 | 11 | ### Constructors 12 | 13 | #### Screen(device) 14 | 15 | ```javascript 16 | const usbDevice = new escpos.USB(); 17 | const usbScreen = new escpos.Screen(usbDevice); 18 | 19 | const serialDevice = new escpos.Serial('/dev/ttyUSB0'); 20 | const serialScreen = new escpos.Screen(serialDevice); 21 | 22 | const bluetoothDevice = new escpos.Bluetooth('01:23:45:67:89:AB', 1); 23 | const bluetoothScreen = new escpos.Screen(bluetoothDevice); 24 | 25 | const networkDevice = new escpos.Network('localhost'); 26 | const networkScreen = new escpos.Screen(networkDevice); 27 | ``` 28 | 29 | ### Methods 30 | 31 | #### clear() 32 | 33 | Clears all displayed characters. 34 | 35 | #### clearLine() 36 | 37 | Clears the line containing the cursor. 38 | 39 | #### moveUp() 40 | 41 | Moves the cursor up one line. 42 | 43 | #### moveLeft() 44 | 45 | Moves the cursor one character position to the left. 46 | 47 | #### moveRight() 48 | 49 | Moves the cursor one character position to the right. 50 | 51 | #### moveDown() 52 | 53 | Moves the cursor down one line. 54 | 55 | #### moveHome() 56 | 57 | Moves the cursor to the left-most position on the upper line (home position). 58 | 59 | #### moveMaxRight() 60 | 61 | Moves the cursor to the right-most position on the current line. 62 | 63 | #### moveMaxLeft() 64 | 65 | Moves the cursor to the left-most position on the current line. 66 | 67 | #### moveBottom() 68 | 69 | Moves the cursor to the bottom position. 70 | 71 | #### move(n, m) 72 | 73 | Moves the cursor to the nth position on the mth line. 74 | 75 | #### overwrite() 76 | 77 | Selects overwrite mode as the screen display mode. 78 | 79 | #### verticalScroll() 80 | 81 | Selects vertical scroll mode as the screen display mode. 82 | 83 | #### horizontalScroll() 84 | 85 | Selects horizontal scroll mode as the display screen mode. 86 | 87 | #### cursor(display) 88 | 89 | Turn cursor display mode on/off. 90 | 91 | "display" Refers to the on/off boolean state. 92 | 93 | #### blink(step) 94 | 95 | Sets display screen blank interval. 96 | "step" Refers to the blink interval. The interval [ON= step × 50 ms] / [OFF = step × 50 ms] is repeated. 97 | 98 | #### timer(h, m) 99 | 100 | Sets the counter time and displays it in the bottom right of the screen. 101 | "h" Refers to hours: 0≤ h ≤ 23 102 | "m" Refers to minutes: 0≤ m ≤ 59 103 | 104 | #### displayTimer() 105 | 106 | Displays the time counter at the right side of the bottom line. 107 | 108 | #### brightness(level) 109 | 110 | Sets the brightness of the fluorescent character display tube. 111 | "level" Refers to brightness level: 1 ≤ level ≤ 4 112 | 113 | #### reverse(n) 114 | 115 | Selects or cancels reverse display of the characters received after this command. 116 | "n" Refers selects/cancels reverse boolean state. 117 | 118 | #### text("text", encodeType) 119 | 120 | Prints raw text. Raises TextError exception. 121 | 122 | For the encode type, see the [iconv-lite wiki document](https://github.com/ashtuchkin/iconv-lite/wiki/Supported-Encodings). Escpos uses `iconv-lite` for encoding. 123 | 124 | If the type is undefined, the default type is GB18030. 125 | 126 | #### encode("encodeType") 127 | 128 | Sets the encoding value globally. default type is GB18030 (Chinese) 129 | 130 | #### close(function callback) 131 | 132 | Flush buffer and closes the current device. 133 | 134 | ---- 135 | -------------------------------------------------------------------------------- /packages/screen/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "escpos-screen", 3 | "version": "3.0.0-alpha.6", 4 | "description": "Escpos Screen API", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "scripts": { 8 | "build": "tsc", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "keywords": [ 12 | "escpos", 13 | "escpos-screen" 14 | ], 15 | "author": "Lsong (https://lsong.org)", 16 | "license": "MIT", 17 | "gitHead": "a3a65c61c0b990298258331f54546a93d6875ddb", 18 | "dependencies": { 19 | "iconv-lite": "^0.6.2", 20 | "mutable-buffer": "^2.1.1" 21 | }, 22 | "devDependencies": { 23 | "@tsconfig/node10": "1.0.8", 24 | "@types/get-pixels": "^3.3.2", 25 | "@types/node": "^10.0.0" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/screen/src/commands.ts: -------------------------------------------------------------------------------- 1 | export const SCREEN = { 2 | BS: '\x08', // Moves the cursor one character position to the left 3 | HT: '\x09', // Moves the cursor one character position to the right 4 | LF: '\x0a', // Moves the cursor down one line 5 | US_LF: '\x1f\x0a', // Moves the cursor up one line 6 | HOM: '\x0b', // Moves the cursor to the left-most position on the upper line (home position) 7 | CR: '\x0d', // Moves the cursor to the left-most position on the current line 8 | US_CR: '\x1f\x0d', // Moves the cursor to the right-most position on the current line 9 | US_B: '\x1f\x42', // Moves the cursor to the bottom position 10 | US_$: '\x1f\x24', // Moves the cursor to the nth position on the mth line 11 | CLR: '\x0c', // Clears all displayed characters 12 | CAN: '\x18', // Clears the line containing the cursor 13 | US_MD1: '\x1f\x01', // Selects overwrite mode as the screen display mode 14 | US_MD2: '\x1f\x02', // Selects vertical scroll mode as the screen display mode 15 | US_MD3: '\x1f\x03', // Selects horizontal scroll mode as the display screen mode 16 | US_C: '\x1f\x43', // Turn cursor display mode on/off 17 | US_E: '\x1f\x45', // Sets or cancels the blink interval of the display screen 18 | US_T: '\x1f\x54', // Sets the counter time and displays it in the bottom right of the screen 19 | US_U: '\x1f\x55', // Displays the time counter at the right side of the bottom line 20 | US_X: '\x1f\x58', // Sets the brightness of the fluorescent character display tube 21 | US_r: '\x1f\x72', // Selects or cancels reverse display of the characters received after this command 22 | US_v: '\x1f\x76' // Sets the DTR signal in the host interface to the MARK or SPACE state 23 | } 24 | -------------------------------------------------------------------------------- /packages/screen/src/index.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import iconv from "iconv-lite"; 3 | import {MutableBuffer} from "mutable-buffer"; 4 | import * as _ from "./commands"; 5 | import {Adapter} from "escpos-adapter"; 6 | 7 | export interface ScreenOptions { 8 | encoding?: string | undefined; 9 | } 10 | 11 | /** 12 | * [function ESC/POS Screen] 13 | */ 14 | export default class Screen { 15 | private adapter: Adapter; 16 | private buffer: MutableBuffer; 17 | private encoding: string; 18 | 19 | /** 20 | * @param {[Adapter]} adapter [eg: usb, network, or serialport] 21 | * @param {[ScreenOptions]} options 22 | * @return {[Screen]} Screen [the escpos Screen instance] 23 | */ 24 | constructor(adapter: Adapter, options?: ScreenOptions) { 25 | this.adapter = adapter; 26 | this.buffer = new MutableBuffer(); 27 | this.encoding = options?.encoding ?? 'GB18030'; 28 | } 29 | 30 | /** 31 | * Clears all displayed characters 32 | * @return {[Screen]} Screen [the escpos Screen instance] 33 | */ 34 | clear() { 35 | this.buffer.write(_.SCREEN.CLR); 36 | return this; 37 | }; 38 | 39 | /** 40 | * Clears the line containing the cursor 41 | * @return {[Screen]} Screen [the escpos Screen instance] 42 | */ 43 | clearLine() { 44 | this.buffer.write(_.SCREEN.CAN); 45 | return this; 46 | }; 47 | 48 | /** 49 | * Moves the cursor up one line 50 | * @return {[Screen]} Screen [the escpos Screen instance] 51 | */ 52 | moveUp() { 53 | this.buffer.write(_.SCREEN.US_LF); 54 | return this; 55 | }; 56 | 57 | /** 58 | * Moves the cursor one character position to the left 59 | * @return {[Screen]} Screen [the escpos Screen instance] 60 | */ 61 | moveLeft() { 62 | this.buffer.write(_.SCREEN.BS); 63 | return this; 64 | }; 65 | 66 | /** 67 | * Moves the cursor one character position to the right 68 | * @return {[Screen]} Screen [the escpos Screen instance] 69 | */ 70 | moveRight() { 71 | this.buffer.write(_.SCREEN.HT); 72 | return this; 73 | }; 74 | 75 | /** 76 | * Moves the cursor down one line 77 | * @return {[Screen]} Screen [the escpos Screen instance] 78 | */ 79 | moveDown() { 80 | this.buffer.write(_.SCREEN.LF); 81 | return this; 82 | }; 83 | 84 | /** 85 | * Moves the cursor to the left-most position on the upper line (home position) 86 | * @return {[Screen]} Screen [the escpos Screen instance] 87 | */ 88 | moveHome() { 89 | this.buffer.write(_.SCREEN.HOM); 90 | return this; 91 | }; 92 | 93 | /** 94 | * Moves the cursor to the right-most position on the current line 95 | * @return {[Screen]} Screen [the escpos Screen instance] 96 | */ 97 | moveMaxRight() { 98 | this.buffer.write(_.SCREEN.US_CR); 99 | return this; 100 | }; 101 | 102 | /** 103 | * Moves the cursor to the left-most position on the current line 104 | * @return {[Screen]} Screen [the escpos Screen instance] 105 | */ 106 | moveMaxLeft() { 107 | this.buffer.write(_.SCREEN.CR); 108 | return this; 109 | }; 110 | 111 | /** 112 | * Moves the cursor to the bottom position 113 | * @return {[Screen]} Screen [the escpos Screen instance] 114 | */ 115 | moveBottom() { 116 | this.buffer.write(_.SCREEN.US_B); 117 | return this; 118 | }; 119 | 120 | /** 121 | * Moves the cursor to the nth position on the mth line 122 | * @param {[type]} n [description] 123 | * @param {[type]} m [description] 124 | * @return {[Screen]} Screen [the escpos Screen instance] 125 | */ 126 | move(n: number, m: number) { 127 | this.buffer.write(_.SCREEN.US_$); 128 | this.buffer.writeUInt8(n); 129 | this.buffer.writeUInt8(m); 130 | return this; 131 | }; 132 | 133 | /** 134 | * Selects overwrite mode as the screen display mode 135 | * @return {[Screen]} Screen [the escpos Screen instance] 136 | */ 137 | overwrite() { 138 | this.buffer.write(_.SCREEN.US_MD1); 139 | return this; 140 | }; 141 | 142 | /** 143 | * Selects vertical scroll mode as the screen display mode 144 | * @return {[Screen]} Screen [the escpos Screen instance] 145 | */ 146 | verticalScroll() { 147 | this.buffer.write(_.SCREEN.US_MD2); 148 | return this; 149 | }; 150 | 151 | /** 152 | * Selects horizontal scroll mode as the display screen mode 153 | * @return {[Screen]} Screen [the escpos Screen instance] 154 | */ 155 | horizontalScroll() { 156 | this.buffer.write(_.SCREEN.US_MD3); 157 | return this; 158 | }; 159 | 160 | /** 161 | * Turn cursor display mode on/off 162 | * @param {[type]} visible [description] 163 | * @return {[Screen]} Screen [the escpos Screen instance] 164 | */ 165 | cursor(visible: boolean) { 166 | this.buffer.write(_.SCREEN.US_C); 167 | this.buffer.writeUInt8(visible ? 1 : 0); 168 | return this; 169 | }; 170 | 171 | /** 172 | * Sets display screen blank interval 173 | * @param {[type]} step [description] 174 | * @return {[Screen]} Screen [the escpos Screen instance] 175 | */ 176 | blink(step: number) { 177 | this.buffer.write(_.SCREEN.US_E); 178 | this.buffer.writeUInt8(step); 179 | return this; 180 | }; 181 | 182 | /** 183 | * Sets the counter time and displays it in the bottom right of the screen 184 | * @param {[type]} h [description] 185 | * @param {[type]} m [description] 186 | * @return {[Screen]} Screen [the escpos Screen instance] 187 | */ 188 | timer(h: number, m: number) { 189 | this.buffer.write(_.SCREEN.US_T); 190 | this.buffer.writeUInt8(h); 191 | this.buffer.writeUInt8(m); 192 | return this; 193 | }; 194 | 195 | /** 196 | * Displays the time counter at the right side of the bottom line 197 | * @return {[Screen]} Screen [the escpos Screen instance] 198 | */ 199 | displayTimer() { 200 | this.buffer.write(_.SCREEN.US_U); 201 | return this; 202 | }; 203 | 204 | /** 205 | * Set brightness 206 | * @param {[type]} level [description] 207 | * @return {[Screen]} Screen [the escpos Screen instance] 208 | */ 209 | brightness(level: number) { 210 | this.buffer.write(_.SCREEN.US_X); 211 | this.buffer.writeUInt8(level); 212 | return this; 213 | }; 214 | 215 | /** 216 | * Selects or cancels reverse display of the characters received after this command 217 | * @param {[type]} enable [description] 218 | * @return {[Screen]} Screen [the escpos Screen instance] 219 | */ 220 | reverse(enable: boolean) { 221 | this.buffer.write(_.SCREEN.US_r); 222 | this.buffer.writeUInt8(enable ? 1 : 0); 223 | return this; 224 | }; 225 | 226 | /** 227 | * Set status confirmation for DTR signal 228 | * @param {[type]} n [description] 229 | * @return {[Screen]} Screen [the escpos Screen instance] 230 | */ 231 | DTR(n: boolean) { 232 | this.buffer.write(_.SCREEN.US_v); 233 | this.buffer.writeUInt8(n ? 1 : 0); 234 | return this; 235 | }; 236 | 237 | /** 238 | * [function print] 239 | * @param {[String]} content [mandatory] 240 | * @return {[Screen]} Screen [the escpos Screen instance] 241 | */ 242 | print(content: string | Buffer) { 243 | this.buffer.write(content); 244 | return this; 245 | }; 246 | 247 | /** 248 | * [function Print encoded alpha-numeric text with End Of Line] 249 | * @param {[String]} content [mandatory] 250 | * @param {[String]} encoding [optional] 251 | * @return {[Screen]} Screen [the escpos Screen instance] 252 | */ 253 | text(content: string, encoding = this.encoding) { 254 | return this.print(iconv.encode(content, encoding)); 255 | }; 256 | 257 | /** 258 | * [function encode text] 259 | * @param {[String]} encoding [mandatory] 260 | * @return {[Screen]} Screen [the escpos Screen instance] 261 | */ 262 | encode(encoding: string) { 263 | this.encoding = encoding; 264 | return this; 265 | }; 266 | 267 | /** 268 | * Send data to hardware and flush buffer 269 | * @return {[Screen]} printer [the escpos printer instance] 270 | */ 271 | flush() { 272 | const buf = this.buffer.flush(); 273 | this.adapter.write(buf); 274 | return this; 275 | }; 276 | 277 | /** 278 | * [close description] 279 | * @param {Function} callback [description] 280 | * @param {[type]} closeArgs [description] 281 | * @return {[type]} [description] 282 | */ 283 | close(callback: (error: Error | null) => void, ...closeArgs: T) { 284 | this.flush(); 285 | this.adapter.close(callback, ...closeArgs); 286 | }; 287 | } 288 | -------------------------------------------------------------------------------- /packages/screen/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/node10/tsconfig.json", 3 | "include": ["src"], 4 | "compilerOptions": { 5 | "outDir": "dist", 6 | "sourceMap": true, 7 | "declaration": true, 8 | "declarationMap": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/screen/tsconfig.ref.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "composite": true, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /packages/serialport/README.md: -------------------------------------------------------------------------------- 1 | 2 | #### Serial("port", options) 3 | ```javascript 4 | const escpos = require('escpos'); 5 | escpos.SerialPort = require('escpos-serialport'); 6 | 7 | const serialDeviceOnWindows = new escpos.SerialPort('COM10'); 8 | const serialDeviceOnLinux = new escpos.SerialPort('/dev/usb/lp0', { 9 | baudRate: 14400, 10 | stopBit: 2 11 | }); 12 | ``` 13 | Check the [serialport package documentation](https://github.com/EmergingTechnologyAdvisors/node-serialport#serialportopenoptions--object) for more options. 14 | -------------------------------------------------------------------------------- /packages/serialport/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "escpos-serialport", 3 | "version": "3.0.0-alpha.4", 4 | "description": "serialport adapter for escpos", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "scripts": { 8 | "build": "tsc", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "dependencies": { 12 | "serialport": "10" 13 | }, 14 | "devDependencies": { 15 | "@tsconfig/node10": "^1.0.8", 16 | "@types/node": "^10.0.0", 17 | "@types/serialport": "^8.0.2", 18 | "typescript": "^4.5.2" 19 | }, 20 | "keywords": [ 21 | "escpos", 22 | "escpos-adapter" 23 | ], 24 | "author": "Lsong (https://lsong.org)", 25 | "license": "MIT", 26 | "gitHead": "a3a65c61c0b990298258331f54546a93d6875ddb" 27 | } 28 | -------------------------------------------------------------------------------- /packages/serialport/src/index.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import { Adapter } from "escpos-adapter/src"; 3 | import { SerialPort } from 'serialport'; 4 | 5 | /** 6 | * SerialPort device 7 | * @param {[type]} port 8 | * @param {[type]} options 9 | */ 10 | export default class Serial extends Adapter<[timeout?: number]> { 11 | private device: SerialPort | null; 12 | constructor(port: string, options: any) { 13 | super(); 14 | this.device = new SerialPort({ path: port, ...options }); 15 | this.device.on('close', () => { 16 | this.emit('disconnect', this.device); 17 | this.device = null; 18 | }); 19 | } 20 | 21 | /** 22 | * List Printers 23 | * @returns {[Array]} 24 | */ 25 | async list() { 26 | const ports = await SerialPort.list(); 27 | return ports; 28 | } 29 | 30 | /** 31 | * open deivce 32 | * @param {Function} callback 33 | * @return {[type]} 34 | */ 35 | open(callback?: (error: Error | null) => void) { 36 | if (this.device === null) throw new Error('Serial port device disconnected'); 37 | this.device.open((error: any) => { 38 | if (callback) callback(error ?? null); 39 | }); 40 | return this; 41 | }; 42 | 43 | /** 44 | * write data to serialport device 45 | * @param {[type]} data [description] 46 | * @param {Function} callback [description] 47 | * @return {[type]} [description] 48 | */ 49 | write(data: Buffer | string, callback?: (error: Error | null) => void) { 50 | if (this.device === null) throw new Error('Serial port device disconnected'); 51 | this.device.write(data, (error) => { 52 | if (callback) callback(error ?? null); 53 | }); 54 | return this; 55 | }; 56 | 57 | /** 58 | * close device 59 | * @param {Function} callback [description] 60 | * @param {int} timeout [allow manual timeout for emulated COM ports (bluetooth, ...)] 61 | * @return {[type]} [description] 62 | */ 63 | close(callback?: (error: Error | null, device: SerialPort) => void, timeout = 0) { 64 | const device = this.device; 65 | if (device === null) return this; 66 | 67 | device.drain(() => { 68 | device.flush((err) => { 69 | setTimeout(() => { 70 | err ? callback && callback(err, device) : device.close((err) => { 71 | this.device = null; 72 | if (callback) callback(err ?? null, device); 73 | }); 74 | 75 | }, Math.max(timeout, 0)); 76 | }); 77 | }); 78 | return this; 79 | 80 | }; 81 | 82 | /** 83 | * read buffer from the printer 84 | * @param {Function} callback 85 | * @return {Serial} 86 | */ 87 | read(callback?: (data: Buffer) => void) { 88 | if (this.device === null) throw new Error('Serial port device disconnected'); 89 | this.device.on('data', function (data) { 90 | if (callback) callback(data); 91 | }); 92 | return this; 93 | }; 94 | } 95 | -------------------------------------------------------------------------------- /packages/serialport/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/node10/tsconfig.json", 3 | "include": ["src"], 4 | "compilerOptions": { 5 | "outDir": "dist", 6 | "sourceMap": true, 7 | "declaration": true, 8 | "declarationMap": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/serialport/tsconfig.ref.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "composite": true, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /packages/server/server.js: -------------------------------------------------------------------------------- 1 | const tcp = require('net'); 2 | 3 | class Server extends tcp.Server { 4 | constructor(device){ 5 | super(); 6 | this.device = device; 7 | this.on('connection', this.request); 8 | } 9 | request(client){ 10 | client.pipe(this.device, { 11 | end: false 12 | }); 13 | } 14 | } 15 | 16 | module.exports = Server; 17 | -------------------------------------------------------------------------------- /packages/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { "path": "./adapter/tsconfig.ref.json" }, 5 | { "path": "./printer/tsconfig.ref.json" }, 6 | { "path": "./usb/tsconfig.ref.json" }, 7 | { "path": "./console/tsconfig.ref.json" }, 8 | { "path": "./serialport/tsconfig.ref.json" }, 9 | { "path": "./screen/tsconfig.ref.json" } 10 | ], 11 | } 12 | -------------------------------------------------------------------------------- /packages/usb/README.md: -------------------------------------------------------------------------------- 1 | ## escpos-usb 2 | 3 | If you use usb as an adapter : 4 | 5 | + On Linux, you'll need `libudev` to build libusb. 6 | + On Ubuntu/Debian: `sudo apt-get install build-essential libudev-dev`. 7 | + On Windows, Use [Zadig](http://sourceforge.net/projects/libwdi/files/zadig/) to install the WinUSB driver for your USB device. 8 | 9 | Otherwise you will get `LIBUSB_ERROR_NOT_SUPPORTED` when attempting to open devices. 10 | 11 | 12 | #### USB(vid, pid) 13 | ```javascript 14 | const escpos = require('escpos'); 15 | escpos.USB = require('escpos-usb'); 16 | 17 | const usbDevice = new escpos.USB(0x01, 0xff); 18 | ``` 19 | vid(Vendor Id) and pid (Product Id) can be checked with the `lsusb` command or `escpos.USB.findPrinter()` method. 20 | -------------------------------------------------------------------------------- /packages/usb/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "escpos-usb", 3 | "version": "3.0.0-alpha.4", 4 | "description": "usb adapter for escpos", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "scripts": { 8 | "build": "tsc", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "dependencies": { 12 | "escpos-adapter": "^3.0.0-alpha.4", 13 | "usb": "*" 14 | }, 15 | "keywords": [], 16 | "author": "Lsong (https://lsong.org)", 17 | "license": "MIT", 18 | "gitHead": "a3a65c61c0b990298258331f54546a93d6875ddb", 19 | "devDependencies": { 20 | "@tsconfig/node10": "^1.0.8", 21 | "@types/node": "^10.0.0", 22 | "@types/usb": "^1.5.4", 23 | "typescript": "^4.5.2" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/usb/src/index.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const os = require('os'); 3 | import { Adapter } from "escpos-adapter"; 4 | import { usb, getDeviceList, findByIds } from 'usb'; 5 | 6 | /** 7 | * [USB Class Codes ] 8 | * @type {Object} 9 | * @docs http://www.usb.org/developers/defined_class 10 | */ 11 | const IFACE_CLASS = { 12 | AUDIO: 0x01, 13 | HID: 0x03, 14 | PRINTER: 0x07, 15 | HUB: 0x09 16 | }; 17 | 18 | export default class USBAdapter extends Adapter<[timeout?: number]> { 19 | constructor(vid?: number, pid?: number) { 20 | super(); 21 | let self = this; 22 | this.device = null; 23 | if (vid && pid) { 24 | this.device = findByIds(vid, pid); 25 | } else if (vid) { 26 | // Set spesific USB device from devices array as coming from USB.findPrinter() function. 27 | // for example 28 | // let devices = escpos.USB.findPrinter(); 29 | // => devices [ Device1, Device2 ]; 30 | // And Then 31 | // const device = new escpos.USB(Device1); OR device = new escpos.USB(Device2); 32 | this.device = vid; 33 | } else { 34 | let devices = USBAdapter.findPrinter(); 35 | if (devices && devices.length) 36 | this.device = devices[0]; 37 | } 38 | if (!this.device) 39 | throw new Error('Can not find printer'); 40 | 41 | usb.usb.on('detach', function (device) { 42 | if (device == self.device) { 43 | self.emit('detach', device); 44 | self.emit('disconnect', device); 45 | self.device = null; 46 | } 47 | }); 48 | 49 | return this; 50 | } 51 | static findPrinter() { 52 | return getDeviceList().filter(function (device) { 53 | try { 54 | return device.configDescriptor?.interfaces.filter(function (iface) { 55 | return iface.filter(function (conf) { 56 | return conf.bInterfaceClass === IFACE_CLASS.PRINTER; 57 | }).length; 58 | }).length; 59 | } catch (e) { 60 | // console.warn(e) 61 | return false; 62 | } 63 | }); 64 | } 65 | static getDevice(vid: number, pid: number) { 66 | return new Promise((resolve, reject) => { 67 | try { 68 | const device = findByIds(vid, pid); 69 | device?.open(); 70 | resolve(device); 71 | } catch (err) { 72 | reject(err); 73 | } 74 | }); 75 | }; 76 | open(callback?: ((error: Error | null) => void) | undefined): this { 77 | let self = this, counter = 0, index = 0; 78 | this.device.open(); 79 | this.device.interfaces.forEach(function (iface: any) { 80 | (function (iface) { 81 | iface.setAltSetting(iface.altSetting, function () { 82 | try { 83 | // http://libusb.sourceforge.net/api-1.0/group__dev.html#gab14d11ed6eac7519bb94795659d2c971 84 | // libusb_kernel_driver_active / libusb_attach_kernel_driver / libusb_detach_kernel_driver : "This functionality is not available on Windows." 85 | if ("win32" !== os.platform()) { 86 | if (iface.isKernelDriverActive()) { 87 | try { 88 | iface.detachKernelDriver(); 89 | } catch (e) { 90 | console.error("[ERROR] Could not detatch kernel driver: %s", e) 91 | } 92 | } 93 | } 94 | iface.claim(); // must be called before using any endpoints of this interface. 95 | iface.endpoints.filter(function (endpoint: any) { 96 | if (endpoint.direction == 'out' && !self.endpoint) { 97 | self.endpoint = endpoint; 98 | } 99 | if (endpoint.direction == 'in' && !self.deviceToPcEndpoint) { 100 | self.deviceToPcEndpoint = endpoint; 101 | } 102 | }); 103 | if (self.endpoint) { 104 | self.emit('connect', self.device); 105 | callback && callback(null); 106 | } else if (++counter === self.device.interfaces.length && !self.endpoint) { 107 | callback && callback(new Error('Can not find endpoint from printer')); 108 | } 109 | } catch (err: any) { 110 | // Try/Catch block to prevent process from exit due to uncaught exception. 111 | // i.e LIBUSB_ERROR_ACCESS might be thrown by claim() if USB device is taken by another process 112 | // example: MacOS Parallels 113 | callback && callback(err); 114 | } 115 | }); 116 | })(iface); 117 | }); 118 | return this; 119 | } 120 | read(callback?: ((data: Buffer) => void) | undefined): void { 121 | this.deviceToPcEndpoint.transfer(64, (error: any, data: Buffer) => { 122 | callback && callback(data); 123 | }); 124 | } 125 | write(data: string | Buffer, callback?: ((error: Error | null) => void) | undefined): this { 126 | this.emit('data', data); 127 | this.endpoint.transfer(data, callback); 128 | return this; 129 | } 130 | close(callback?: ((error: Error | null) => void) | undefined, timeout?: number | undefined): this { 131 | if (!this.device) callback && callback(null); 132 | try { 133 | this.device.close(); 134 | usb.removeAllListeners('detach'); 135 | callback && callback(null); 136 | this.emit('close', this.device); 137 | } 138 | catch (err: any) { 139 | callback && callback(err); 140 | } 141 | return this; 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /packages/usb/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/node10/tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "sourceMap": true, 6 | "declaration": true, 7 | "declarationMap": true 8 | }, 9 | "include": ["src"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/usb/tsconfig.ref.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "composite": true, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /screencasts/IMG_1031.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lsongdev/node-escpos/465cd2653ce94ad4f8157f48dcbd3ec0aac0019c/screencasts/IMG_1031.JPG -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | const escpos = require('..'); 2 | const assert = require('assert'); 3 | 4 | describe('ESC/POS printing test', function() { 5 | 6 | it('device#write', function(done){ 7 | var device = new escpos.Console(function(data){ 8 | assert.equal(data.length, 3); 9 | done(); 10 | }); 11 | device.write(Buffer.alloc(3)); 12 | }) 13 | 14 | it('printer#print', function(done){ 15 | var device = new escpos.Console(function(data){ 16 | assert.deepEqual(data, Buffer.from('hello world')); 17 | done(); 18 | }); 19 | var printer = new escpos.Printer(device); 20 | printer.print('hello world').flush(); 21 | }) 22 | 23 | it('printer#tableCustom', function (done){ 24 | var output = null 25 | var device = new escpos.Console(function(data) { 26 | assert.deepEqual(data, Buffer.from("hello world")); 27 | done(); 28 | }); 29 | 30 | var printer = new escpos.Printer(device) 31 | output = printer 32 | .font('A') 33 | .encode('utf8') 34 | .tableCustom([ 35 | { text: "Check:13", align: "LEFT" }, 36 | { text: "Table:A1", align: "RIGHT" } 37 | ], { size: [2, 1] }) 38 | .newLine() 39 | .align("CT") 40 | .size(1, 1) 41 | .text("Misafir") 42 | .size(2, 1) 43 | .text("Check") 44 | .resetStyle() 45 | .drawLine() 46 | .cut() 47 | .close() 48 | }) 49 | }); 50 | --------------------------------------------------------------------------------