微信小程序连接蓝牙打印机示例
小程序连接蓝牙打印机打印文本与二维码等示例在 github 上都能找到一些,唯独打印图片这个案例几乎没有。希望能帮助到有打印图片需求的小伙伴。
图片水平取模数据打印
指令打印位图数据)
关于找蓝牙设备中能用的 Characteristic,这里贴出对应的代码,关注注释部分
* 以Promise方式调用 微信api * @param { string } name 微信api的名称 ,如 wxAsyncPromise("getSystemInfo",options) * @param { object } options 除了success 和 fail 的其他参数 * @returns export function wxAsyncPromise ( name, options ) { return new Promise ( ( resolve, reject ) => { wx[name]({ ...(options || {}), success : function ( res ) { resolve (res); fail : function ( res ) { reject (res); //在多个服务services中递归查找能用的特征值 //deviceId : 已连接的蓝牙设备id //services : wx.getBLEDeviceServices()取得的服务 export function getDeviceCharacteristics ( deviceId, services = [], success, fail ) { services = services. slice ( 0 ); if (services. length ) { const serviceId = services. shift (). uuid ; wxAsyncPromise ( 'getBLEDeviceCharacteristics' , { deviceId, serviceId, . then ( ( res ) => { console . log ( 'getBLEDeviceCharacteristics' , deviceId, serviceId, res); let finished = false ; let write = false ; let notify = false ; let indicate = false ; //有斑马品牌的一款打印机中res.characteristics的所有uuid都是相同的, //找所有的properties存在(notify || indicate) && write(如果只写入数据只要write=true也可以)这种情况就说明这个uuid是可用的 //(不确保所有的打印机都能用这种方式取得uuid, //在凯盛诺打印机的res.characteristic只有一个uuid,所以也能用这个方式) for ( var i = 0 ; i < res. characteristics . length ; i++) { if (!notify) { notify = res. characteristics [i]. properties . notify ; if (!indicate) { indicate = res. characteristics [i]. properties . indicate ; if (!write) { write = res. characteristics [i]. properties . write ; if ((notify || indicate) && write) { /* 获取蓝牙特征值uuid */ success && success ({ serviceId, characteristicId : res. characteristics [i]. uuid , finished = true ; break ; if (!finished) { getDeviceCharacteristics (deviceId, services, success, fail); . catch ( ( res ) => { getDeviceCharacteristics (deviceId, services, success, fail); } else { fail && fail ();在 Demo 中手机选任意一张图片用 canvas 绘制,wx.canvasGetImageData()取得 Uint8ClampedArray 类型的图像像素点数据。
需要把 Uint8ClampedArray 类型的数据转成打印机识别的点阵位图数据(也可以让后台实现图片转位图数据,参考 热敏打印机编程 ESC/POS 指令 )。
不同打印机厂家的指令集可能不同,但打印图片的位图数据是一样的。
贴出 Uint8ClampedArray 转位图数据的代码
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) {
//灰度值大于threshold位 白色 为第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,
贴出递归发送二进制数据到蓝牙的代码
* sendDataToDevice
* @param {object} options
deviceId,
serviceId,
characteristicId,
value [ArrayBuffer],
lasterSuccess,
export function sendDataToDevice(options) {
let byteLength = options.value.byteLength;
//这里默认一次20个字节发送
const speed = options.onceByleLength || 20;
if (byteLength > 0) {
wxAsyncPromise('writeBLECharacteristicValue', {
...options,
value: options.value.slice(0, byteLength > speed ? speed : byteLength),
.then((res) => {
if (byteLength > speed) {
sendDataToDevice({
...options,
value: options.value.slice(speed, byteLength),
} else {
options.lasterSuccess && options.lasterSuccess();
.catch((res) => {
console.log(res);
贴出发送逐行图片数据的代码
// 使用的 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],
* printImage
* @param {object} opt
deviceId,//蓝牙设备id
serviceId,//服务id
characteristicId,//可用特征值uuid
lasterSuccess , //最后完成的回调
onProgress, //每段发送完成的回调
* @param {object} imageInfo // 由overwriteImageData返回的对象
export function printImage(opt = {}, imageInfo = {}) {
const { printAlign = 'left' } = opt;
let arr = imageInfo.array,
width = imageInfo.width;
const writeArray = [];
const xl = width % 256;
const xh = width / 256;
//分行发送图片数据
const command = []
.concat(printCommand.clear)
.concat(printCommand[printAlign])
.concat([29, 118, 48, 0, xl, xh, 1, 0]);
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));
const len = writeArray.length;
const print = (options, writeArray) => {
if (writeArray.length) {
sendDataToDevice({
...options,
value: writeArray.shift().buffer,
lasterSuccess: () => {
options.onProgress && options.onProgress(Math.floor(((len - writeArray.length) / len) * 100));
if (writeArray.length) {
print(options, writeArray);
} else {
options.lasterSuccess && options.lasterSuccess();
print(opt, writeArray);
打印机指令文档
在demo源码中的 PrintCommandDocs目录有 ESC-POS
和CPCL
两种类型的pdf指令文档
微信低功率蓝牙
小程序蓝牙打印 miniprogram-bluetoothprinter
热敏打印机编程 ESC/POS 指令
微信小程序组件库 wux-weapp