2024-05-11 16:46:09 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//微信小程序向蓝牙打印机发送数据进行打印的坑:
|
|
|
|
|
|
//小程序api向蓝牙打印机发送数据打印,发送的任何内容都应该要转成二进制数据,而且蓝牙打印的文本编码是GBK的,发送中文需转成GBK编码再转成二进制数据发送
|
|
|
|
|
|
//发送打印机指令也要转成二进制数据发送
|
|
|
|
|
|
//蓝牙打印机一次接收的二级制数据有限制,不同的系统不同的蓝牙设备限制可能不同,微信建议一次20个字节,需做递归分包发送
|
|
|
|
|
|
//发送完要打印的内容后,一定要发送一个打印的指令才能顺利打印 (有些指令就不需要)
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
*
|
|
|
|
|
|
*
|
|
|
|
|
|
* @export
|
|
|
|
|
|
* @param {object} options
|
|
|
|
|
|
* {
|
|
|
|
|
|
deviceId,
|
|
|
|
|
|
serviceId,
|
|
|
|
|
|
characteristicId,
|
|
|
|
|
|
value [ArrayBuffer],
|
|
|
|
|
|
lasterSuccess,
|
|
|
|
|
|
}
|
|
|
|
|
|
*/
|
2024-06-05 23:51:19 +00:00
|
|
|
|
|
2024-05-11 16:46:09 +00:00
|
|
|
|
export function sendDataToDevice(options) {
|
|
|
|
|
|
let byteLength = options.value.byteLength;
|
2024-05-11 18:21:02 +00:00
|
|
|
|
// 这里默认一次20个字发送
|
2024-05-11 16:46:09 +00:00
|
|
|
|
const speed = options.onceByleLength || 20;
|
2024-05-11 18:21:02 +00:00
|
|
|
|
console.log('byteLength====', byteLength)
|
2024-05-11 16:46:09 +00:00
|
|
|
|
if (byteLength > 0) {
|
2024-05-11 18:21:02 +00:00
|
|
|
|
console.log('speed====', speed)
|
|
|
|
|
|
console.log('value====', options.value)
|
2024-05-11 16:46:09 +00:00
|
|
|
|
uni.writeBLECharacteristicValue({
|
|
|
|
|
|
...options,
|
|
|
|
|
|
// writeType:"writeNoResponse",
|
|
|
|
|
|
value: options.value.slice(0, byteLength > speed ? speed : byteLength),
|
|
|
|
|
|
success: function (res) {
|
2024-05-11 18:21:02 +00:00
|
|
|
|
// 【writeBLECharacteristicValue】安卓存在频繁调用失败的情况,下次调用前必须加上延迟
|
|
|
|
|
|
setTimeout(()=> {
|
|
|
|
|
|
if (byteLength > speed) {
|
|
|
|
|
|
sendDataToDevice({
|
|
|
|
|
|
...options,
|
|
|
|
|
|
value: options.value.slice(speed, byteLength),
|
|
|
|
|
|
});
|
|
|
|
|
|
} else {
|
|
|
|
|
|
options.lasterSuccess && options.lasterSuccess();
|
|
|
|
|
|
}
|
|
|
|
|
|
},10)
|
2024-05-11 16:46:09 +00:00
|
|
|
|
},
|
|
|
|
|
|
fail: function (res) {
|
2024-05-11 18:21:02 +00:00
|
|
|
|
console.log('writeBLECharacteristicValue:fail====', res)
|
2024-05-11 16:46:09 +00:00
|
|
|
|
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;
|
2024-05-11 18:21:02 +00:00
|
|
|
|
console.log("sendDataToPrint:allLenth=======", allLenth)
|
|
|
|
|
|
console.log("sendDataToPrint:allUint8Array-len=======", allUint8Array.length)
|
2024-05-11 16:46:09 +00:00
|
|
|
|
const writeArrayCopyer = allUint8Array.slice(0);
|
|
|
|
|
|
const print = (options, writeArray) => {
|
2024-05-11 18:21:02 +00:00
|
|
|
|
console.log("===writeArray.length===", writeArray.length)
|
2024-05-11 16:46:09 +00:00
|
|
|
|
if (writeArray.length) {
|
2024-05-11 18:21:02 +00:00
|
|
|
|
let curBuffer = writeArray.shift().buffer
|
2024-05-11 16:46:09 +00:00
|
|
|
|
sendDataToDevice({
|
|
|
|
|
|
...options,
|
2024-05-11 18:21:02 +00:00
|
|
|
|
value: curBuffer,
|
2024-05-11 16:46:09 +00:00
|
|
|
|
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,
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|