//微信小程序向蓝牙打印机发送数据进行打印的坑: //小程序api向蓝牙打印机发送数据打印,发送的任何内容都应该要转成二进制数据,而且蓝牙打印的文本编码是GBK的,发送中文需转成GBK编码再转成二进制数据发送 //发送打印机指令也要转成二进制数据发送 //蓝牙打印机一次接收的二级制数据有限制,不同的系统不同的蓝牙设备限制可能不同,微信建议一次20个字节,需做递归分包发送 //发送完要打印的内容后,一定要发送一个打印的指令才能顺利打印 (有些指令就不需要) export function senBlData(uint8Array) { let that = this var uint8Buf = Array.from(uint8Array); function split_array(datas, size) { var result = {}; var j = 0 for (var i = 0; i < datas.length; i += size) { result[j] = datas.slice(i, i + size) j++ } return result } var sendloop = split_array(uint8Buf, 20); function realWriteData(sendloop, i) { var data = sendloop[i] if (typeof(data) == "undefined") { uni.showLoading({ title: '数据发送完毕,准备打印', icon: 'loading', }) setTimeout(function() { uni.hideLoading() }, 1000); return } // console.log("第【" + i + "】次写数据"+data) var buffer = new ArrayBuffer(data.length) var dataView = new DataView(buffer) for (var j = 0; j < data.length; j++) { dataView.setUint8(j, data[j]); } uni.writeBLECharacteristicValue({ deviceId: that.deviceId, serviceId: that.writeServiceId, characteristicId: that.writeCharaterId, value: buffer, success(res) { realWriteData(sendloop, i + 1); }, fail() { uni.showLoading({ title: '发送数据失败', icon: 'loading', }) setTimeout(function() { uni.hideLoading() }, 1000); } }) } var i = 0; realWriteData(sendloop, i); } // 发送数据(递归分包发送) /** * * * @export * @param {object} options * { deviceId, serviceId, characteristicId, value [ArrayBuffer], lasterSuccess, } */ export function sendDataToDevice(options) { let byteLength = options.value.byteLength; // 这里默认一次20个字发送 const speed = options.onceByleLength || 20; console.log('byteLength====', byteLength) if (byteLength > 0) { console.log('speed====', speed) console.log('value====', options.value) uni.writeBLECharacteristicValue({ ...options, // writeType:"writeNoResponse", value: options.value.slice(0, byteLength > speed ? speed : byteLength), success: function (res) { // 【writeBLECharacteristicValue】安卓存在频繁调用失败的情况,下次调用前必须加上延迟 setTimeout(()=> { if (byteLength > speed) { sendDataToDevice({ ...options, value: options.value.slice(speed, byteLength), }); } else { options.lasterSuccess && options.lasterSuccess(); } },10) }, fail: function (res) { console.log('writeBLECharacteristicValue:fail====', res) options.onError && options.onError(res); }, }); } } export function charToArrayBuffer(str) { var out = new ArrayBuffer(str.length); var uint8 = new Uint8Array(out); var strs = str.split(''); for (var i = 0; i < strs.length; i++) { uint8[i] = strs[i].charCodeAt(); } return uint8; } export function charToArray(str) { var arr = []; var strs = str.split(''); for (var i = 0; i < strs.length; i++) { arr[i] = strs[i].charCodeAt(); } return arr; } // 使用的 ESC/POS指令, 十进制方式 // 更多指令请查看 ./PrintCommandDocs/ESC-POS指令文档(凯盛诺打印机代表).pdf export const printCommand = { left: [27, 97, 0], //居左 center: [27, 97, 1], //居中 right: [27, 97, 2], //居右 clear: [27, 64], //初始化 enter: [10], }; //打印二维码 /** * * * @export * @param {object} options * { deviceId, serviceId, characteristicId, value,//ArrayBuffer:二维码的数据 } */ export function printQR(options) { //打印二维码的十进制指令data: let data = [...printCommand.clear, 29, 107, 97, 7, 4, options.value.byteLength, 0]; sendDataToDevice({ ...options, value: new Uint8Array(data).buffer, lasterSuccess: () => { //指令发送成功后,发送二维码的数据 sendDataToDevice(options); }, }); } function grayPixle(pix) { return pix[0] * 0.299 + pix[1] * 0.587 + pix[2] * 0.114; } /** * overwriteImageData 图片数据转 位图数据 * @param {object} data * { width,//图片宽度 height,//图片高度 imageData,//Uint8ClampedArray threshold,//阈值, 越大,打印点数越多,图形越黑 } */ export function overwriteImageData(data) { let sendWidth = data.width, sendHeight = data.height; const threshold = data.threshold || 180; let sendImageData = new ArrayBuffer((sendWidth * sendHeight) / 8); sendImageData = new Uint8Array(sendImageData); let pix = data.imageData; const part = []; let index = 0; for (let i = 0; i < pix.length; i += 32) { //横向每8个像素点组成一个字节(8位二进制数)。 for (let k = 0; k < 8; k++) { const grayPixle1 = grayPixle(pix.slice(i + k * 4, i + k * 4 + (4 - 1))); //阈值调整 if (grayPixle1 > threshold) { //灰度值大于128位 白色 为第k位0不打印 part[k] = 0; } else { part[k] = 1; } } let temp = 0; for (let a = 0; a < part.length; a++) { temp += part[a] * Math.pow(2, part.length - 1 - a); } //开始不明白以下算法什么意思,了解了字节才知道,一个字节是8位的二进制数,part这个数组存的0和1就是二进制的0和1,传输到打印的位图数据的一个字节是0-255之间的十进制数,以下是用相权相加法转十进制数,理解了这个就用上面的for循环替代了 // const temp = // part[0] * 128 + // part[1] * 64 + // part[2] * 32 + // part[3] * 16 + // part[4] * 8 + // part[5] * 4 + // part[6] * 2 + // part[7] * 1; sendImageData[index++] = temp; } return { array: Array.from(sendImageData), width: sendWidth / 8, height: sendHeight, }; } /** * 分段发送二进制数据,按allUint8Array的length产生发送的进度 * @export * @param {object} options * { deviceId, serviceId, characteristicId, lasterSuccess, onProgress, } * * @param {array} allUint8Array 所有的Uint8Array */ export function sendDataToPrint(options, allUint8Array) { const allLenth = allUint8Array.length; console.log("sendDataToPrint:allLenth=======", allLenth) console.log("sendDataToPrint:allUint8Array-len=======", allUint8Array.length) const writeArrayCopyer = allUint8Array.slice(0); const print = (options, writeArray) => { console.log("===writeArray.length===", writeArray.length) if (writeArray.length) { let curBuffer = writeArray.shift().buffer sendDataToDevice({ ...options, value: curBuffer, lasterSuccess: () => { options.onProgress && options.onProgress(Math.floor(((allLenth - writeArray.length) / allLenth) * 100)); if (writeArray.length) { print(options, writeArray); } else { options.lasterSuccess && options.lasterSuccess(); } }, }); } }; print(options, writeArrayCopyer); } /** * 获取打印图片的指令 * * @export * @param {object} options * { lineByLine, // 是否逐行打印,默认true } * @param {object} imageInfo overwriteImageData 得到的位图数据数组和宽高信息 { array, width, height } */ export function getImageCommandArray(opt = {}, imageInfo = {}) { const lineByLine = typeof opt.lineByLine !== 'boolean' ? true : opt.lineByLine; const width = imageInfo.width; const h = imageInfo.height; const xl = width % 256; const xh = (width - xl) / 256; const yl = h % 256; const yh = (h - yl) / 256; //打印图片的十进制指令数组 let command = []; if (lineByLine) { // 分段逐行的指令 command = command.concat([29, 118, 48, 0, xl, xh, 1, 0]); } else { // 非分段逐行的指令 command = command.concat([29, 118, 48, 0, xl, xh, yl, yh]); } return command; } /** * 获取一张图片数据与指令整合的writeArray,结合 sendDataToPrint 发送二进制数据到蓝牙打印机 * @param {object} options * { lineByLine, // 是否逐行打印,默认true printAlign, // 打印的位置 } * @param {object} imageInfo overwriteImageData 得到的位图数据数组和宽高信息 { array, width, height } */ export function getPrintImageWriteArray(opt = {}, imageInfo = {}) { const lineByLine = typeof opt.lineByLine !== 'boolean' ? true : opt.lineByLine; const width = imageInfo.width; let arr = imageInfo.array; let writeArray = []; const iniTcommand = [].concat(printCommand.clear).concat(printCommand[opt.printAlign || 'left']); const command = getImageCommandArray(opt, imageInfo); writeArray.push(new Uint8Array(iniTcommand)); // 分段逐行打印的数据 if (lineByLine) { for (let i = 0; i < arr.length / width; i++) { const subArr = arr.slice(i * width, i * width + width); const tempArr = command.concat(subArr); writeArray.push(new Uint8Array(tempArr)); } } else { // 非逐行打印 writeArray.push(new Uint8Array(command.concat(arr))); } // writeArray.push(new Uint8Array([27, 74, 3])); return writeArray; } /** * 只打印一张图片数据的方法 * @param {object} options * { deviceId, serviceId, characteristicId, lasterSuccess, onProgress, printAlign, "left"|"right"|"center" } * @param {object} imageInfo overwriteImageData 得到的位图数据数组和宽高信息 { array, width, height } */ export function printImage(opt, imageInfo) { sendDataToPrint( opt, getPrintImageWriteArray( { printAlign: opt.printAlign, lineByLine: opt.lineByLine, }, imageInfo, ), ); }