uniapp小程序连接蓝牙设备
一、初始化蓝牙模块
这一步是必须的,在开发项目过程中,初始化蓝牙模块之后,紧接着就要开启一些监听的api,供后续设备读写数据时监听变化。
bash
initBLE(callback) {
//提前定义变量,直接在uni的api中使用this是不可以的
var self = this;
// `第一步:初始化蓝牙模块`
uni.openBluetoothAdapter({
success: function (e) {
utils.toast("初始化蓝牙适配器成功");
},
fail: function (e) {
utils.toast("初始化蓝牙适配器失败 : " + JSON.stringify(e));
},
});
// `检查蓝牙适配器状态变化`
// `这一步主要是为了在连接成功蓝牙之后关闭蓝牙搜索,蓝牙搜索非常耗费性能`
uni.onBluetoothAdapterStateChange(function (e) {
self.bluetooth.available = e.available;
if (!e.available) {
utils.toast("蓝牙适配器不可用");
if (self.bluetooth.startDiscovery) {
self.bluetooth.startDiscovery = false;
self.stopBluetoothDevicesDiscovery();
}
}
});
// `监听搜索到设备`
uni.onBluetoothDeviceFound(function (e) {
// `当搜索到设备后,可以在e.devices中获取到设备信息列表`
if (!e.devices) return;
for (var i = 0; i < e.devices.length; i++) {
var device = e.devices[i];
for (var j = 0; j < self.discoveryList.length; j++) {
var item = self.discoveryList[j];
// 去重
if (item.deviceId === device.deviceId) {
return;
}
}
self.discoveryList.push(device);
}
});
// `监听蓝牙设备连接变化`
uni.onBLEConnectionStateChange(function (e) {
// 该方法回调中可以用于处理连接意外断开等异常情况
self.currentDeviceStatus = e.connected ? 1 : 2;
// 在连续盘点过程中是不允许断开的,万一因为其他什么原因断开了,则尝试停止连续盘点
if (!e.connected && self.inventoryLabelForm.looping) {
self.sendStopLoopInventoryCmd(); //停止盘点
}
});
// `读数据`
uni.onBLECharacteristicValueChange(function (e) {
if (e.deviceId != self.currentDevice.deviceId) return;
var value = self.buffer2Hex(e.value);
console.log(e); //查看是否有R20固定返回值
utils.toast("读数据178:" + JSON.stringify(e));
self.lastRcvData += value;
self.doRcvData();
});
self.historyList = utils.getHistoryList();
// 如果有cb回调函数,则需要停止下拉刷新
if (callback) {
// setTimeout这里等待一下上面的异步初始化
setTimeout(() => {
callback();
}, 1000);
}
},
二、开始搜索
以上只是蓝牙的初始化操作,要想真正实现蓝牙连接从第二步开始
bash
// 开启蓝牙搜索服务
startBluetoothDevicesDiscovery() {
var self = this;
// 小程序环境需要等待一下,不然会报错
setTimeout(() => {
// 开始搜寻附近的蓝牙外围设备
uni.startBluetoothDevicesDiscovery({
allowDuplicatesKey: false,
success: function (e) {
self.bluetooth.discoverying = true;
//开启搜索成功,此时初始化中定义的onBluetoothDeviceFound会自动执行
},
fail: function (e) {
utils.toast("开始搜索蓝牙设备失败 : " + JSON.stringify(e));
},
});
}, 1000);
},
三、连接蓝牙
目前为止我们已经搜索到蓝牙设备了,下面要做的就是在蓝牙列表中选择对应的蓝牙设备进行连接
bash
// 连接设备
onConnectDevice(device) {
// `device是选择的设备对象,在这可以取到设备的deviceId供后续使用`
var self = this;
this.onCloseDiscoveryDialog();
this.onCloseHistoryDialog();
this.currentDevice = device;
this.currentDeviceStatus = 3;
this.lastRcvData = "";
// 创建一个BLE连接
uni.createBLEConnection({
deviceId: device.deviceId,//这里使用设备id来创建连接
success: function (e) {
// 创建完成后获取服务(这在ios中是必须的,否则会导致后面读取数据失败)
uni.getBLEDeviceServices({
// 这里的 deviceId 需要已经通过 createBLEConnection 与对应设备建立链接
deviceId: device.deviceId,
success(res) {
// 获取特征值(这在ios中是必须的,否则会导致后面读取数据失败)
uni.getBLEDeviceCharacteristics({
// 这里的 deviceId 需要已经通过 createBLEConnection 与对应设备建立链接
deviceId: device.deviceId,
// 这里的 serviceId 需要在 getBLEDeviceServices 接口中获取
serviceId: RX_SERVICE_UUID, //因为这里使用的是固定服务所以事先定义过常量了,这里直接使用
success(res) {
// 创建连接-获取服务-获取特征值之后就可以开启nofity功能了
self.enableBleNotify(device);
},
fail() {},
});
},
});
// 开启nofity功能
self.enableBleNotify(device);
},
fail: function (e) {
utils.toast("连接蓝牙设备失败 : " + JSON.stringify(e));
self.currentDeviceStatus = 2;
},
});
// 保存设备到历史记录里
var flag = true;
for (var i = 0; i < this.historyList.length; i++) {
var item = this.historyList[i];
if (item.deviceId === device.deviceId) {
this.historyList[i] = device;
flag = false;
}
}
if (flag) {
this.historyList.push(device);
}
utils.saveHistoryList(this.historyList);
},
四、监听特征值变化
bash
enableBleNotify(device) {
// 这里监听时,ios测试需要有点延迟,安卓的话,只在测试机上测试过,不需要延迟
// 这里延迟1秒兼容,反正差距不大
setTimeout(function () {
uni.notifyBLECharacteristicValueChange({
state: true,
deviceId: device.deviceId,
serviceId: RX_SERVICE_UUID,
characteristicId: R_UUID,
success: function (e) {
//到了此时蓝牙才算真正的连接成功
},
fail: function (e) {},
});
}, 1000);
},
五、调用示例
这里以单次盘点为例
bash
// 单步盘点标签
async onOneStepInventory() {
var self = this;
if (!this.checkDeviceConnect()) return;
this.inventoryLabelForm.startTime = utils.currentTimeMillis(); //获取盘点时间的
// 单步盘点关闭enableRssi
this.inventoryLabelForm.enableRssi = false;
// 发送固定值
await this.sendR20Hex(); //这里是定制需要没有可以省去
var cmd = this.generateUHFProtocl(0x80, utils.number2Hex(5000, 2));
self.sendData(cmd);
},
//发送固定值
async sendR20Hex() {
await this.sendData(
"A55A0029F011D75238EE46C3ECFFCE3B9AFC093ACC13F711A6ADF3FF76ACE59A8DF1BA704E22EC0D0A"
);
},
//写入操作(发送数据,连续存盘指令自行实现了,不调用该方法)
async sendData(hexStr, serviceId, characteristicId) {
if (utils.isBlank(hexStr)) return;
serviceId = serviceId || RX_SERVICE_UUID;
characteristicId = characteristicId || W_UUID;
var self = this;
var sendData;
// ble发送数据时,需要分包,最多20字节
if (hexStr.length > 40) {
sendData = hexStr.substr(0, 40);
hexStr = hexStr.substr(40, hexStr.length - 40);
} else {
sendData = hexStr;
hexStr = null;
}
this.logSend(sendData);
var buffer = new ArrayBuffer(sendData.length / 2);
var bufView = new Uint8Array(buffer);
for (var i = 0; i < sendData.length; i += 2) {
bufView[i / 2] = parseInt(sendData.substr(i, 2), 16);
}
//这里写了一个promise是为了在执行正常操作之前,先执行固定值操作
return new Promise((resolve, reject) => {
uni.writeBLECharacteristicValue({
deviceId: self.currentDevice.deviceId,
serviceId: serviceId,
characteristicId: characteristicId,
value: buffer,
success: async function (e) {
if (hexStr) {
//超过20字节 递归循环写入
await self.sendData(hexStr);
}
console.log("写入数据成功391" + sendData);
// 由于写字节限制,如果还有未发送完的数据,接着继续发送
resolve();
},
fail: function (e) {},
});
});
},
utils.js文件
这里主要存放一些转换方法,上面用到的utils.xx方法可以在此处查看
bash
export default {
alert: function (content, title = "提示") {
uni.showModal({
title,
content,
showCancel: false,
success: function (res) {},
});
},
confirm: function (content, title = "提示") {
uni.showModal({
title,
content,
success: function (res) {
if (res.confirm) {
console.log("用户点击确定");
} else if (res.cancel) {
console.log("用户点击取消");
}
},
});
},
toast: function (title, duration = 1000) {
uni.showToast({
title,
icon: "none",
duration,
});
},
showWaiting: function (title = "加载中") {
uni.showLoading({
title,
});
},
closeWaiting: function () {
uni.hideLoading();
},
actionSheet: function (itemList) {
uni.showActionSheet({
itemList,
success: function (res) {
console.log("选中了第" + (res.tapIndex + 1) + "个按钮");
},
fail: function (res) {
console.log(res.errMsg);
},
});
},
saveHistoryList: function (list) {
try {
uni.setStorageSync("history_list", JSON.stringify(list));
} catch (e) {
// error
}
},
getHistoryList: function () {
try {
const value = uni.getStorageSync("history_list");
if (value) {
return JSON.parse(value);
} else {
return [];
}
} catch (e) {
// error
}
},
clearHistoryList: function () {
try {
uni.removeStorageSync("history_list");
} catch (e) {
// error
}
},
leftPad: function (value, length, fill) {
while (length - value.length > 0) {
value = fill + value;
}
return value;
},
number2Hex: function (value, byteLength) {
value = parseInt(value);
var hex = value.toString(16).toUpperCase();
byteLength = byteLength || hex.length / 2 + (hex.length % 2);
return this.leftPad(hex, byteLength * 2, "0");
},
bin2Hex: function (value, byteLength) {
byteLength = byteLength || 1;
byteLength = Math.max(
value.length / 8 + (value.length % 8 > 0 ? 1 : 0),
byteLength
);
value = this.leftPad(value, byteLength * 8, "0");
var hex = "";
for (var i = 0; i < value.length; i += 8) {
hex += this.number2Hex(parseInt(value.substr(i, 8), 2), 1);
}
return hex;
},
isNull: function (value) {
if (value == null) return true;
if ("undefined" == typeof value) return true;
return false;
},
isString: function (value) {
return "[object String]" === Object.prototype.toString.call(value);
},
isBlank: function (value) {
if (this.isNull(value)) return true;
if (!this.isString(value)) return true;
if (value.trim().length == 0) return true;
return false;
},
str2Hex: function (str) {
var hex = "";
for (var i = 0; i < str.length; i++) {
hex += this.number2Hex(str.charCodeAt(i), 1);
}
return hex;
},
currentTimeMillis: function () {
return new Date().getTime();
},
hex2ByteArray: function (hex) {
var result = [];
for (var i = 0; i < hex.length; i += 2) {
var value = parseInt(hex.substr(i, 2), 16);
if (value > 127) {
value -= 0x100;
}
result.push(value);
}
return result;
},
/**
* hex转字符串
* @param {Object} hex
* @param {Object} charset 编码格式,默认为utf-8
*/
hex2Str: function (hex, charset = "utf-8") {
var bytesArray = this.hex2ByteArray(hex);
try {
var str;
// 创建一个TextDecoder对象,指定所需的编码格式
const decoder = new TextDecoder(charset);
// 将ByteArray转换为字符串
str = decoder.decode(bytesArray);
return str;
} catch (e) {
this.toast("错误的编码格式");
}
},
};