- A+
所属分类:Web前端
这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助
说明
基于uni-app开发,调用官方蓝牙相关api实现连接蓝牙与向蓝牙热敏打印机发送字节流,可打印文字,二维码,图片,调整字体大小等,本文提供大概思路
结构
- bluetooth.js 蓝牙连接相关模块封装
- commands.js 打印十六进制相关代码库
- gbk.js 编码转换库地址
- printerjobs.js 打印实现库
bluetooth.js
蓝牙连接相关封装代码
class Bluetooth { constructor() { this.isOpenBle = false; this.deviceId = ""; this.serviceId = ""; this.writeId = ""; this.notifyId = ""; this.BluetoothConnectStatus = false this.printStatus = false this.init() } init() { this.closeBluetoothAdapter().then(() = >{ console.log("init初始关闭蓝牙模块") this.openBluetoothAdapter().then(() = >{ console.log("init初始化蓝牙模块") this.reconnect() //自动连接蓝牙设备 }) }) } showToast(title) { uni.showToast({ title: title, icon: 'none', 'duration': 2000 }); } openBluetoothAdapter() { return new Promise((resolve, reject) = >{ uni.openBluetoothAdapter({ success: res = >{ this.isOpenBle = true; resolve(res); }, fail: err = >{ this.showToast(`蓝牙未打开`); reject(err); }, }); }); } startBluetoothDevicesDiscovery() { if (!this.isOpenBle) { this.showToast(`初始化蓝牙模块失败`) return; } let self = this; uni.showLoading({ title: '蓝牙搜索中' }) return new Promise((resolve, reject) = >{ setTimeout(() = >{ uni.startBluetoothDevicesDiscovery({ success: res = >{ resolve(res) }, fail: res = >{ self.showToast(`搜索设备失败` + JSON.stringify(err)); reject(err); } }) }, 300); }); } stopBluetoothDevicesDiscovery() { let self = this; return new Promise((resolve, reject) = >{ uni.stopBluetoothDevicesDiscovery({ success: e = >{ uni.hideLoading(); }, fail: e = >{ uni.hideLoading(); self.showToast(`停止搜索蓝牙设备失败` + JSON.stringify(err)); } }) }); } createBLEConnection() { //设备deviceId let deviceId = this.deviceId; let self = this; // uni.showLoading({ // mask: true, // title: '设别连接中,请稍候...' // }) console.log(this.deviceId); return new Promise((resolve, reject) = >{ uni.createBLEConnection({ deviceId, success: (res) = >{ console.log("res:createBLEConnection " + JSON.stringify(res)); resolve(res) }, fail: err = >{ uni.hideLoading(); self.showToast(`停止搜索蓝牙设备失败` + JSON.stringify(err)); reject(err); } }) }); } //获取蓝牙设备所有服务(service) getBLEDeviceServices() { let _serviceList = []; let deviceId = this.deviceId; let self = this; return new Promise((resolve, reject) = >{ setTimeout(() = >{ uni.getBLEDeviceServices({ deviceId, success: res = >{ for (let service of res.services) { if (service.isPrimary) { _serviceList.push(service); } } uni.hideLoading(); console.log("_serviceList: " + JSON.stringify(_serviceList)); resolve(_serviceList) }, fail: err = >{ uni.hideLoading(); // self.showToast(`获取设备Services` + JSON.stringify(err)); reject(err); }, }) }, 500); }); } //获取蓝牙设备某个服务中所有特征值(characteristic) getBLEDeviceCharacteristics() { // console.log("getBLEDeviceCharacteristics") let deviceId = this.deviceId; let serviceId = this.serviceId; let self = this; return new Promise((resolve, reject) = >{ uni.getBLEDeviceCharacteristics({ deviceId, serviceId, success: res = >{ for (let _obj of res.characteristics) { //获取notify if (_obj.properties.notify) { self.notifyId = _obj.uuid; uni.setStorageSync('notifyId', self.notifyId); } //获取writeId if (_obj.properties.write) { self.writeId = _obj.uuid; uni.setStorageSync('writeId', self.writeId); } } //console.log("res:getBLEDeviceCharacteristics " + JSON.stringify(res)); let result = { 'notifyId': self.notifyId, 'writeId': self.writeId }; // self.showToast(`获取服务中所有特征值OK,${JSON.stringify(result)}`); this.BluetoothStatus = true resolve(result) }, fail: err = >{ self.showToast(`getBLEDeviceCharacteristics` + JSON.stringify(err)); reject(err); } }) }); } //断开联链接 closeBLEConnection() { let deviceId = this.deviceId; uni.closeBLEConnection({ deviceId, success(res) { console.log("closeBLEConnection" + res) } }) } notifyBLECharacteristicValue() { let deviceId = this.deviceId; let serviceId = this.serviceId; let characteristicId = this.notifyId; uni.notifyBLECharacteristicValueChange({ state: true, // 启用 notify 功能 deviceId, serviceId, characteristicId, success(res) { uni.onBLECharacteristicValueChange(function(res) { console.log('onBLECharacteristicValueChange', res); }); }, fail(res) { console.log('notifyBLECharacteristicValueChange failed:' + res.errMsg); } }); } writeBLECharacteristicValue(buffer) { let deviceId = this.deviceId; let serviceId = this.serviceId; let characteristicId = this.writeId; // console.log(deviceId); // console.log(serviceId); // console.log(characteristicId); return new Promise((resolve, reject) = >{ uni.writeBLECharacteristicValue({ deviceId, serviceId, characteristicId, value: buffer, success(res) { // console.log('message发送成功', JSON.stringify(res)); resolve(res); }, fail(err) { // console.log('message发送失败', JSON.stringify(err)); reject(err); } }); }); } closeBluetoothAdapter() { return new Promise((resolve, reject) = >{ uni.closeBluetoothAdapter({ success: res = >{ resolve() } }); }) } //若APP在之前已有搜索过某个蓝牙设备,并成功建立连接,可直接传入之前搜索获取的 deviceId 直接尝试连接该设备,无需进行搜索操作。 reconnect() { (async() = >{ try { this.deviceId = this.deviceId || uni.getStorageSync("deviceId"); this.serviceId = this.serviceId || uni.getStorageSync("serviceId"); console.log("this.deviceId", this.deviceId) console.log("this.serviceId", this.serviceId) let result1 = await this.createBLEConnection(); console.log("createBLEConnection: " + JSON.stringify(result1)); let result2 = await this.getBLEDeviceServices(); console.log("getBLEDeviceServices: " + JSON.stringify(result2)); let result3 = await this.getBLEDeviceCharacteristics(); console.log("getBLEDeviceCharacteristics: " + JSON.stringify(result3)); this.BluetoothConnectStatus = true this.showToast("蓝牙打印设备连接成功") // this.writeId = uni.getStorageSync("writeId"); // this.notifyId = uni.getStorageSync("notifyId"); } catch(err) { console.log("err: " + err); // this.showToast("蓝牙打印设备连接失败") } })(); } } export default Bluetooth;
commands.js
打印机ESC/POS十六进制编码库
/** * 修改自https://github.com/song940/node-escpos/blob/master/commands.js * ESC/POS _ (Constants) */ var _ = { LF: [0x0a], FS: [0x1c], FF: [0x0c], GS: [0x1d], DLE: [0x10], EOT: [0x04], NUL: [0x00], ESC: [0x1b], EOL: 'n', }; /** * [FEED_CONTROL_SEQUENCES Feed control sequences] * @type {Object} */ _.FEED_CONTROL_SEQUENCES = { CTL_LF: [0x0a], // Print and line feed CTL_GLF: [0x4a, 0x00], // Print and feed paper (without spaces between lines) CTL_FF: [0x0c], // Form feed CTL_CR: [0x0d], // Carriage return CTL_HT: [0x09], // Horizontal tab CTL_VT: [0x0b], // Vertical tab }; _.CHARACTER_SPACING = { CS_DEFAULT: [0x1b, 0x20, 0x00], CS_SET: [0x1b, 0x20] }; _.LINE_SPACING = { LS_DEFAULT: [0x1b, 0x32], LS_SET: [0x1b, 0x33] }; /** * [HARDWARE Printer hardware] * @type {Object} */ _.HARDWARE = { HW_INIT: [0x1b, 0x40], // Clear data in buffer and reset modes HW_SELECT: [0x1b, 0x3d, 0x01], // Printer select HW_RESET: [0x1b, 0x3f, 0x0a, 0x00], // Reset printer hardware Print:[0x1b, 0x64,0x01] //Print and feed paper }; /** * [CASH_DRAWER Cash Drawer] * @type {Object} */ _.CASH_DRAWER = { CD_KICK_2: [0x1b, 0x70, 0x00], // Sends a pulse to pin 2 [] CD_KICK_5: [0x1b, 0x70, 0x01], // Sends a pulse to pin 5 [] }; /** * [MARGINS Margins sizes] * @type {Object} */ _.MARGINS = { BOTTOM: [0x1b, 0x4f], // Fix bottom size LEFT: [0x1b, 0x6c], // Fix left size RIGHT: [0x1b, 0x51], // Fix right size }; /** * [PAPER Paper] * @type {Object} */ _.PAPER = { PAPER_FULL_CUT: [0x1d, 0x56, 0x00], // Full cut paper PAPER_PART_CUT: [0x1d, 0x56, 0x01], // Partial cut paper PAPER_CUT_A: [0x1d, 0x56, 0x41], // Partial cut paper PAPER_CUT_B: [0x1d, 0x56, 0x42], // Partial cut paper }; /** * [TEXT_FORMAT Text format] * @type {Object} */ _.TEXT_FORMAT = { TXT_NORMAL: [0x1b, 0x21, 0x00], // Normal text TXT_2HEIGHT: [0x1b, 0x21, 0x10], // Double height text TXT_2WIDTH: [0x1b, 0x21, 0x20], // Double width text TXT_4SQUARE: [0x1b, 0x21, 0x30], // Double width & height text TXT_UNDERL_OFF: [0x1b, 0x2d, 0x00], // Underline font OFF TXT_UNDERL_ON: [0x1b, 0x2d, 0x01], // Underline font 1-dot ON TXT_UNDERL2_ON: [0x1b, 0x2d, 0x02], // Underline font 2-dot ON TXT_BOLD_OFF: [0x1b, 0x45, 0x00], // Bold font OFF TXT_BOLD_ON: [0x1b, 0x45, 0x01], // Bold font ON TXT_ITALIC_OFF: [0x1b, 0x35], // Italic font ON TXT_ITALIC_ON: [0x1b, 0x34], // Italic font ON TXT_FONT_A: [0x1b, 0x4d, 0x00], // Font type A TXT_FONT_B: [0x1b, 0x4d, 0x01], // Font type B TXT_FONT_C: [0x1b, 0x4d, 0x02], // Font type C TXT_ALIGN_LT: [0x1b, 0x61, 0x00], // Left justification TXT_ALIGN_CT: [0x1b, 0x61, 0x01], // Centering TXT_ALIGN_RT: [0x1b, 0x61, 0x02], // Right justification }; /** * [BARCODE_FORMAT Barcode format] * @type {Object} */ _.BARCODE_FORMAT = { BARCODE_TXT_OFF: [0x1d, 0x48, 0x00], // HRI barcode chars OFF BARCODE_TXT_ABV: [0x1d, 0x48, 0x01], // HRI barcode chars above BARCODE_TXT_BLW: [0x1d, 0x48, 0x02], // HRI barcode chars below BARCODE_TXT_BTH: [0x1d, 0x48, 0x03], // HRI barcode chars both above and below BARCODE_FONT_A: [0x1d, 0x66, 0x00], // Font type A for HRI barcode chars BARCODE_FONT_B: [0x1d, 0x66, 0x01], // Font type B for HRI barcode chars BARCODE_HEIGHT: function (height) { // Barcode Height [1-255] return [0x1d, 0x68, height]; }, BARCODE_WIDTH: function (width) { // Barcode Width [2-6] return [0x1d, 0x77, width]; }, BARCODE_HEIGHT_DEFAULT: [0x1d, 0x68, 0x64], // Barcode height default:100 BARCODE_WIDTH_DEFAULT: [0x1d, 0x77, 0x01], // Barcode width default:1 BARCODE_UPC_A: [0x1d, 0x6b, 0x00], // Barcode type UPC-A BARCODE_UPC_E: [0x1d, 0x6b, 0x01], // Barcode type UPC-E BARCODE_EAN13: [0x1d, 0x6b, 0x02], // Barcode type EAN13 BARCODE_EAN8: [0x1d, 0x6b, 0x03], // Barcode type EAN8 BARCODE_CODE39: [0x1d, 0x6b, 0x04], // Barcode type CODE39 BARCODE_ITF: [0x1d, 0x6b, 0x05], // Barcode type ITF BARCODE_NW7: [0x1d, 0x6b, 0x06], // Barcode type NW7 BARCODE_CODE93: [0x1d, 0x6b, 0x48], // Barcode type CODE93 BARCODE_CODE128: [0x1d, 0x6b, 0x49], // Barcode type CODE128 }; /** * [IMAGE_FORMAT Image format] * @type {Object} */ _.IMAGE_FORMAT = { S_RASTER_N: [0x1d, 0x76, 0x30, 0x00], // Set raster image normal size S_RASTER_2W: [0x1d, 0x76, 0x30, 0x01], // Set raster image double width S_RASTER_2H: [0x1d, 0x76, 0x30, 0x02], // Set raster image double height S_RASTER_Q: [0x1d, 0x76, 0x30, 0x03], // Set raster image quadruple }; /** * [BITMAP_FORMAT description] * @type {Object} */ _.BITMAP_FORMAT = { BITMAP_S8: [0x1b, 0x2a, 0x00], BITMAP_D8: [0x1b, 0x2a, 0x01], BITMAP_S24: [0x1b, 0x2a, 0x20], BITMAP_D24: [0x1b, 0x2a, 0x21] }; /** * [GSV0_FORMAT description] * @type {Object} */ _.GSV0_FORMAT = { GSV0_NORMAL: [0x1d, 0x76, 0x30, 0x00], GSV0_DW: [0x1d, 0x76, 0x30, 0x01], GSV0_DH: [0x1d, 0x76, 0x30, 0x02], GSV0_DWDH: [0x1d, 0x76, 0x30, 0x03] }; /** * [BEEP description] * @type {string} */ _.BEEP = [0x1b, 0x42]; // Printer Buzzer pre hex /** * [COLOR description] * @type {Object} */ _.COLOR = { 0: [0x1b, 0x72, 0x00], // black 1: [0x1b, 0x72, 0x01] // red }; /** * [exports description] * @type {[type]} */ module.exports = _;
printerjobs.js
const commands = require('./commands'); const gbk = require('./gbk'); const printerJobs = function() { this._queue = Array.from(commands.HARDWARE.HW_INIT); this._enqueue = function(cmd) { this._queue.push.apply(this._queue, cmd); } }; /** * 增加打印内容 * @param {string} content 文字内容 */ printerJobs.prototype.text = function(content) { if (content) { let uint8Array = gbk.encode(content); let encoded = Array.from(uint8Array); this._enqueue(encoded); } return this; }; /** * 打印文字 * @param {string} content 文字内容 */ printerJobs.prototype.print = function(content) { this.text(content); // this._enqueue(commands.LF); return this; }; printerJobs.prototype.printL = function(content) { this.text(content); this._enqueue(commands.LF); return this; }; printerJobs.prototype.printImage = function(content) { if (content) { const cmds = [].concat([29, 118, 48, 0], content); // console.log("cmds",cmds) this._enqueue(cmds); this._enqueue(commands.LF); } return this; }; /** * 打印文字并换行 * @param {string} content 文字内容 */ printerJobs.prototype.println = function(content = '') { return this.print(content + commands.EOL); }; /** * 设置对齐方式 * @param {string} align 对齐方式 LT/CT/RT */ printerJobs.prototype.setAlign = function(align) { this._enqueue(commands.TEXT_FORMAT['TXT_ALIGN_' + align.toUpperCase()]); return this; }; /** * 设置字体 * @param {string} family A/B/C */ printerJobs.prototype.setFont = function(family) { this._enqueue(commands.TEXT_FORMAT['TXT_FONT_' + family.toUpperCase()]); return this; }; /** * 设定字体尺寸 * @param {number} width 字体宽度 1~2 * @param {number} height 字体高度 1~2 */ printerJobs.prototype.setSize = function(width, height) { if (2 >= width && 2 >= height) { this._enqueue(commands.TEXT_FORMAT.TXT_NORMAL); if (2 === width && 2 === height) { this._enqueue(commands.TEXT_FORMAT.TXT_4SQUARE); } else if (1 === width && 2 === height) { this._enqueue(commands.TEXT_FORMAT.TXT_2HEIGHT); } else if (2 === width && 1 === height) { this._enqueue(commands.TEXT_FORMAT.TXT_2WIDTH); } } return this; }; /** * 设定字体是否加粗 * @param {boolean} bold */ printerJobs.prototype.setBold = function(bold) { if (typeof bold !== 'boolean') { bold = true; } this._enqueue(bold ? commands.TEXT_FORMAT.TXT_BOLD_ON : commands.TEXT_FORMAT.TXT_BOLD_OFF); return this; }; /** * 设定是否开启下划线 * @param {boolean} underline */ printerJobs.prototype.setUnderline = function(underline) { if (typeof underline !== 'boolean') { underline = true; } this._enqueue(underline ? commands.TEXT_FORMAT.TXT_UNDERL_ON : commands.TEXT_FORMAT.TXT_UNDERL_OFF); return this; }; /** * 设置行间距为 n 点行,默认值行间距是 30 点 * @param {number} n 0≤n≤255 */ printerJobs.prototype.setLineSpacing = function(n) { if (n === undefined || n === null) { this._enqueue(commands.LINE_SPACING.LS_DEFAULT); } else { this._enqueue(commands.LINE_SPACING.LS_SET); this._enqueue([n]); } return this; }; /** * 打印空行 * @param {number} n */ printerJobs.prototype.lineFeed = function(n = 1) { return this.print(new Array(n).fill(commands.EOL).join('')); }; /** * 设置字体颜色,需要打印机支持 * @param {number} color - 0 默认颜色黑色 1 红色 */ printerJobs.prototype.setColor = function(color) { this._enqueue(commands.COLOR[color === 1 ? 1 : 0]); return this; }; /** * https://support.loyverse.com/hardware/printers/use-the-beeper-in-a-escpos-printers * 蜂鸣警报,需要打印机支持 * @param {number} n 蜂鸣次数,1-9 * @param {number} t 蜂鸣长短,1-9 */ printerJobs.prototype.beep = function(n, t) { this._enqueue(commands.BEEP); this._enqueue([n, t]); return this; }; /** * 清空任务 */ printerJobs.prototype.clear = function() { this._queue = Array.from(commands.HARDWARE.HW_RESET); // this._enqueue(commands.HARDWARE.Print); return this; }; /** * 返回ArrayBuffer */ printerJobs.prototype.buffer = function() { return new Uint8Array(this._queue).buffer; }; module.exports = printerJobs;
代码实现
封装蓝牙连接,搜索,断开相关操作模块
蓝牙搜索
startBluetoothDeviceDiscovery() { let self = this; self.tempDeviceList = []; uni.startBluetoothDevicesDiscovery({ success: res = >{ uni.onBluetoothDeviceFound(devices = >{ // console.log("发现设备: " + JSON.stringify(devices)); if (!self.tempDeviceList.some(item = >{ return item.deviceId === devices.devices[0].deviceId || item.name === devices.devices[0].name })) { // console.log("new", devices.devices) self.tempDeviceList.push(devices.devices[0]) } }); this.connect = false this.$refs.popup.open() }, fail: err = >{ uni.showToast({ title: '搜索设备失败' + JSON.stringify(err), icon: 'none' }) } }) },
搜索完成选择设备
async select_deviceId(item) { this.deviceId = item.deviceId; this.$bluetooth.deviceId = item.deviceId; uni.setStorageSync('deviceId', this.$bluetooth.deviceId); this.serviceList = []; try { //1.链接设备 let result = await this.$bluetooth.createBLEConnection(); //2.寻找服务 let result2 = null setTimeout(async() = >{ result2 = await this.$bluetooth.getBLEDeviceServices(); console.log("获取服务: " + JSON.stringify(result2)); this.serviceList = result2; console.log("serviceList", this.serviceList.length) if (this.serviceList[2].uuid) { this.select_service(this.serviceList[2].uuid) } else { uni.showToast({ title: '不是打印设备', icon: 'none' }) } }, 1000) } catch(e) { //TODO handle the exception console.log("e: " + JSON.stringify(e)); } },
选中服务
async select_service(res) { console.log("select_service", res) this.$bluetooth.serviceId = res; console.log("this.$bluetooth.serviceId", this.$bluetooth.serviceId) uni.setStorageSync('serviceId', res); try { let result = await this.$bluetooth.getBLEDeviceCharacteristics(); console.log("resultresult", result) this.$refs.popup.close() uni.showToast({ title: "连接成功" }) // this.pickUpOnce() } catch(e) { //TODO handle the exception console.log("e: " + JSON.stringify(e)); } },
打印内容组合
async writeBLECharacteristicValueTongzhishu() { let Qrcode_res = await this.get_Qrcode() let sign = await this.getSign() console.log("sign") let printerJobs = new PrinterJobs() let p = printerJobs.setAlign('ct') .setBold(true) .printL("打印测试") let buffer = printerJobs.buffer(); this.printbuffs(buffer); },
打印内容组合
主要是实现打印编码推送循环,手机蓝牙可能出现编码发送失败情况,这个时候就是要循环保证每个字节准确推送
printbuffs(buffer, fun) { console.log("printbuffs", buffer.byteLength) const maxChunk = 8; let p = Promise.resolve(); for (let i = 0, j = 0, length = buffer.byteLength; i < length; i += maxChunk, j++) { let subPackage = buffer.slice(i, i + maxChunk <= length ? (i + maxChunk) : length); p = p.then(() = >{ if (i == 0) { this.$bluetooth.printStatus = true this.$refs.loading.open(); } if ((i + maxChunk) >= length) { console.log("printEnd") setTimeout(() = >{ this.$bluetooth.printStatus = false this.$refs.loading.close(); }, 1000) } return this.printbuff(subPackage) }) } p = p.then(() = >{ console.log("printEve") fun() }) }, async printbuff(buffer) { while (true) { try { await this.$bluetooth.writeBLECharacteristicValue(buffer); break; } catch(e) {} } },