近期调试现有的项目,涉及到了微信蓝牙打印,因此特意总结一下开发思路以及实现逻辑。
一、开发环境
- 微信开发者工具 V1.06.2303220;调试基础库 3.3.4
- 低功耗蓝牙打印机(设备的特征支持 write)蓝牙版本 4.2 ;中心设备/主机 模式;中文格式为 gbk 编码
- iOS 6.5.6,Android 6.5.7以上
二、测试机型
- OPPO Android 13 =》可正常连接打印
- HarmonyOs 4.0.0 =》 可正常连接打印
- 型号 iPhone14 Pro Max,版本 iPhone 17.1.2
注意事项
在 iPhone14 Pro Max,版本 iPhone 17.1.2 中,手机打开蓝牙在其他设备列表 无法显示该打印机设备,但是通过小程序中搜索蓝牙设备是能找到该设备的 可直接打印。
需要注意的是,该iPhone 机型下,写入特征值数据与关闭蓝牙逻辑流程跟安卓机不同;因此在处理逻辑时需要注意,否则iPhone 在未完成打印之前就会关闭蓝牙导致数据无法正常打印。
安卓:
iPhone
三、说明
1、角色/工作模式
蓝牙低功耗协议给设备定义了若干角色,或称工作模式;本次讲解中,使用的是中心设备/主机 (Central)
概念 :
中心设备可以扫描外围设备,并在发现有外围设备存在后与之建立连接,之后就可以使用外围设备提供的服务(Service)。
一般而言,手机会担任中心设备的角色,利用外围设备提供的数据进行处理或展示等等。小程序提供低功耗蓝牙接口是默认设定手机为中心设备的。
2、整体思路
首先我先用思维导图,简单描述一下蓝牙打印的处理逻辑,让大家了解一下整体的路线,
正常情况下,基础路线:
四、功能实现
本文中我将蓝牙打印分为两部分:正常连接指定设备的蓝牙 、传递数据信息进行打印;
1、蓝牙功能实现
(1)初始化蓝牙模块
我们在进行蓝牙相关操作的时候,首先就是要:初始化蓝牙模块。
注意事项:
- 用户是否打开了蓝牙,进行提示,并监听蓝牙适配器状态变化,当用户开启蓝牙后再次触发后续逻辑
- 要防止用户拒绝使用蓝牙申请,并针对用户的误操作引导用户再次授权蓝牙
- iOS 上开启主机/从机(外围设备)模式时需分别调用一次,并指定对应的
mode
js
/**
* 初始化蓝牙模块
*/
function openBlueTooth() {
wx.openBluetoothAdapter({
mode: 'central', // 蓝牙模式,可作为主/从设备,仅 iOS 需要。
success: (res) => {
wx.showLoading({
title: '蓝牙已开启,扫描设备',
});
getBlueToothDevices();
},
fail: (err) => {
let {
errno, errCode
} = err;
if (errno === 103) {
wx.showModal({
title: '提示',
content: '用户未授权使用蓝牙申请,请点击右上角三个点-设置-蓝牙,设置为允许',
})
} else if(errCode === 10001) {
// 用户蓝牙开关未开启或者手机不支持蓝牙功能
// 此时小程序蓝牙模块已经初始化完成
wx.showModal({
title: '提示',
content: '请确认手机支持蓝牙,并将蓝牙打开',
complete: () => {
wx.onBluetoothAdapterStateChange(function (res) {
let {
available
} = res;
if(available) {
// 蓝牙适配器可用
wx.showLoading({
title: '蓝牙已开启,扫描设备',
});
// 开始搜寻附近的蓝牙外围设备
getBlueToothDevices();
}
})
}
})
} else {
wx.showToast({
title: '请打开手机蓝牙并开启微信定位授权',
duration: 3000,
icon: 'none'
});
}
}
});
}
(2)搜索蓝牙设备
js
/**
* 开始搜寻附近的蓝牙外围设备。
*/
function getBlueToothDevices() {
wx.startBluetoothDevicesDiscovery({
// 上报设备的间隔,单位 ms。0 表示找到新设备立即上报,其他数值根据传入的间隔上报。
interval: 1000,
success: () => {
/**
* 监听搜索到新设备的事件
*/
wx.onBluetoothDeviceFound((res) => {
// 扫描到设备停止扫描
wx.stopBluetoothDevicesDiscovery();
let devices = res.devices;
let deviceId = "";
devices.forEach(item => {
// 根据自己的设备标识来判断是否搜索到了
// 这里采用的标识是 "Jucsan"
if (item.name.indexOf("Jucsan") > -1) {
deviceId = item.deviceId;
}
});
if (deviceId != "") {
// 连接蓝牙设备
connectDevice(deviceId);
} else {
wx.showToast({
title: '未找到设备',
icon: 'error',
duration: 2000
});
}
});
},
fail: () => {
wx.hideLoading();
wx.showToast({
title: '搜寻蓝牙设备失败',
duration: 4000,
})
}
})
}
(3)连接蓝牙设备
js
/**
* 连接蓝牙设备
*/
function connectDevice(deviceId) {
wx.showLoading({
title: '设备连接中...'
})
/**
* 连接 蓝牙低功耗中心设备
*/
wx.createBLEConnection({
deviceId,
success: () => {
// 获得设备服务
getDeviceService(deviceId);
},
fail: function () {
wx.hideLoading();
wx.showToast({
title: '连接设备失败',
icon: 'error',
duration: 4000
});
}
});
}
(4)获得设备服务
说明:在这一环节,我们获取蓝牙低功耗设备所有服务 (service),然后获取到我们需要的对应 蓝牙设备服务的UUID
js
function getDeviceService(deviceId) {
wx.showLoading({
title: '获取已连接设备服务...'
})
/**
* 获取蓝牙低功耗设备所有服务 (service)。
* 注意:根据自己真实的设备服务进行调试
*/
wx.getBLEDeviceServices({
deviceId,
success: (res) => {
let services = res.services;
if (services.length > 2) {
let serviceId = services[2].uuid;
// 获取设备服务特征值
getDeviceServiceCharacteristic(deviceId, serviceId);
}
}
});
}
(5)获取设备服务特征值
js
function getDeviceServiceCharacteristic(deviceId, serviceId) {
wx.getBLEDeviceCharacteristics({
deviceId,
serviceId,
success: (res) => {
let characteristics = res.characteristics;
characteristics.forEach((character) => {
if (character.properties.write) {
writeCharacterId = character.uuid;
}
});
if (writeCharacterId != "") {
// 找到写入特征值
printDeviceId = deviceId;
printServiceId = serviceId;
writeCharacterId = writeCharacterId;
startPrint();
}
}
});
}
2、进行数据打印
注意事项:
- 目前采用的是 gbk 编码打印中文,使用的蓝牙打印机默认为 gbk编码。
js
/**
* 进行打印操作
*/
function startPrint() {
wx.showLoading({
title: '打印数据中...'
})
writeCharacteristicValue("\r\n");
writeCharacteristicValue(" JUCSAN智能物联网终端数据报表\r\n");
writeCharacteristicValue(" 设备ID:123456 \r\n");
writeCharacteristicValue("\r\n");
writeCharacteristicValue("\r\n", true);
closeBlueToothPrint();
}
(1)写入特征值
注意事项:
- 小程序不会对写入数据包大小做限制,但系统与蓝牙设备会限制蓝牙 4.0 单次传输的数据大小,超过最大字节数后会发生写入错误,建议每次写入不超过 20 字节
js
/**
* 写入特征值
*/
function writeCharacteristicValue(printValue, isCloseBlueTooth = false) {
let printValueTarget = gbkToArray(printValue);
let printUnitLength = 20;
let printStartIndex = 0;
let printEndIndex = 0;
while (printStartIndex < printValueTarget.byteLength) {
// 结束索引
printEndIndex = printStartIndex + printUnitLength;
if (printEndIndex > printValueTarget.byteLength) {
printEndIndex = printValueTarget.byteLength;
}
let printValueUnit = printValueTarget.slice(printStartIndex, printEndIndex);
writeUnit(printValueUnit, isCloseBlueTooth);
// 开始索引
printStartIndex = printEndIndex;
}
}
转换代码
js
function gbkToArray(content) {
/**
* gb2312 是中文映射表 可显示 2000多个汉字
* TextEncoder 文本编码器: 文本 =》 二进制字节流
* TextDecoder 文本译码器: 字节流 =》 文本
*/
var _encoder = new TextEncoder("gb2312", {
NONSTANDARD_allowLegacyEncoding: true
});
// content 需要打印的字符串
const val = _encoder.encode(content);
return val.buffer;
}
写入特征值
注意事项:
- 在实际测试中要注意当前手机的蓝牙是否与打印机蓝牙兼容:
例如: 在使用 iPhone 14, IOS 17.1.2 测试中,无法搜索到 蓝牙4.2版本的打印机,但可以正常连接到打印机。我们需要注意的是安卓机与iPhone中关闭蓝牙逻辑触发的时机不同,要对该情况进行兼容。 - 在实际项目中,我们为了防止打印大量数据,发送打印指令过快而导致设备打印数据冲突,打印格式错误,可以打印50条然后延时1秒继续打印。
js
function writeUnit(value, isCloseBlueTooth) {
wx.writeBLECharacteristicValue({
deviceId: printDeviceId,
serviceId: printServiceId,
characteristicId: writeCharacterId,
value: value,
// 蓝牙特征值的写模式设置,有两种模式,iOS 优先 write,安卓优先 writeNoResponse 。(基础库 2.22.0 开始支持)
writeType: isIos ? 'write' : 'writeNoResponse',
success: function () {
if (isCloseBlueTooth) {
setTimeout(() => {
closeBlueToothPrint();
}, 500);
}
},
fail: function (res) {
wx.hideLoading();
let {
errCode
} = res;
if (errCode === 10005) {
wx.showModal({
title: '提示',
content: '没有找到指定特征',
})
} else if (errCode === 10012) {
wx.showModal({
title: '提示',
content: '连接超时',
})
} else {
wx.showModal({
title: '提示',
content: '写入失败',
})
}
},
complete: (res) => {
console.log('写入二进制数据 complete - res', res);
}
});
}
3、关闭蓝牙
js
/**
* 打印完毕,关闭蓝牙
* @param {*} content
*/
function closeBlueToothPrint() {
wx.hideLoading();
wx.showModal({
title: '数据打印完毕!',
})
// 关闭蓝牙
wx.closeBluetoothAdapter({
success(res) {
console.log('关闭蓝牙', res)
}
})
}
总结
到这里,我们就了解了微信小程序中蓝牙打印的基础流程了。对于这些涉及不同型号机型、硬件的功能,我们需要多进行真机调试,因为难免在部分机型中会出现一些错误需要进行兼容。
其实回顾起来整体流程也并不复杂,实际项目中,我们只需要通过后端接口获取打印的数据,然后将蓝牙打印的逻辑进行封装调用即可。完整的代码可以看我github仓库。
积累开发过程中的点点滴滴,持续成长,让我们不断地精进提升。与君共勉!!!