UniApp微信小程序-实现蓝牙功能
前言:UniApp提供了跨平台的蓝牙API,官方文档。最近有个需求,需要搜索并连接到对应的蓝牙设备,然后连接到对应的 wifi 网络,有同样需求的伙伴可以参考下。
本次功能主要使用到了以下几种蓝牙API:
本次功能涉及到两个页面:【本次ui页面的代码没有放出来,大家可以根据自己的实际需求进行调整】
一、第一个页面内涉及到的API:
页面初始值:
data() {return {bluetoothDeviceList: [],isShowLoading: false,};
},
onLoad() {this.searchBluetoothDevice()
},
1. 初始化蓝牙设备。uni.openBluetoothAdapter()
// 初始化蓝牙设备
searchBluetoothDevice(){let that = thisuni.openBluetoothAdapter({success(res) {console.log('openBluetoothAdapter success', res)that.startBluetoothDevicesDiscovery()},fail(err) {console.log('openBluetoothAdapter err=', err);if (err.errCode === 10001) {uni.$u.toast('请打开蓝牙')}}})
},
2. 开始搜索附近的蓝牙设备。uni.startBluetoothDevicesDiscovery()
(此操作比较耗费系统资源,请在搜索并连接到设备后调用 uni.stopBluetoothDevicesDiscovery 方法停止搜索。)
// 搜索蓝牙设备
startBluetoothDevicesDiscovery() {let that = this;if (this.isShowLoading) {this.stopBluetoothDevicesDiscovery()return}this.isShowLoading = trueuni.startBluetoothDevicesDiscovery({allowDuplicatesKey: true,// services: ['0000abcd-0000-1000-8000-00805f9bffff'], //传入这个参数,只搜索主服务为该UUID的设备success(res) {console.log('startBluetoothDevicesDiscovery success', res)that.onBluetoothDeviceFound()setTimeout(() => {console.log("----BluetoothDevicesDiscovery finish---- ");if (that.isShowLoading){that.stopBluetoothDevicesDiscovery()}}, 10000);},fail(err) {console.log('startBluetoothDevicesDiscovery err=', err);}})
},
3. 监听已经搜索到的蓝牙设备。uni.onBluetoothDeviceFound()
(并且展示到页面上)
// 监听搜索到的蓝牙设备
onBluetoothDeviceFound() {uni.onBluetoothDeviceFound((res) => {res.devices.forEach(device => {if (!device.name && !device.localName) {return}const idx = this.bluetoothDeviceList.findIndex(d => d.deviceId === device.deviceId)if (idx === -1) {this.bluetoothDeviceList.push(device)} else {this.bluetoothDeviceList[idx] = device}})})
},
4. 停止搜索蓝牙设备。uni.stopBluetoothDevicesDiscovery()
(搜索到之后或者搜索失败,都需要调用停止搜索蓝牙设备api)
// 停止蓝牙设备的搜索
stopBluetoothDevicesDiscovery() {this.isShowLoading = falseuni.stopBluetoothDevicesDiscovery()
},
5. 关闭蓝牙模块。uni.closeBluetoothAdapter()
( 页面关闭时,调用该api )
onUnload() {uni.closeBluetoothAdapter()
},
6. 连接低功耗蓝牙设备。uni.createBLEConnection()
// 点击列表里需要的蓝牙进行连接,蓝牙列表里可以拿到deviceId
createBLEConnection(deviceId){uni.showLoading({mask: true})let that = thisuni.createBLEConnection({// 这里的 deviceId 需要已经通过 createBLEConnection 与对应设备建立链接deviceId,success(res) {uni.hideLoading()uni.$u.toast('连接成功')setTimeout(()=>{uni.$u.route(`/pages3/bluetooth/form?deviceId=${deviceId}`)}, 1000)},fail(err) {uni.hideLoading()console.log('createBLEConnection err:', err)}})
},
二、第二个页面内涉及到的API:
页面初始值:
import { hexToUtf8Text, stringToArrayBuffer } from '@/util/hexUtils.js'
data() {return {deviceId: null,globalServiceuuId: '0000abcd-0000-1000-8000-00805f9b34fb', // 自己设备提供的serviceIdglobalWriteId: '0000abce-0000-1000-8000-00805f9b34fb', // 自己设备提供的写入需要的characteristicIdglobalNotifyId: '0000abd0-0000-1000-8000-00805f9b34fb', // 自己设备提供的notify需要的characteristicIdismy_service: false,characteristicId: '',serviceId: '',};
},
onLoad({deviceId}) {this.deviceId = deviceIdthis.getBLEDeviceServices(deviceId)
},
1. 获取蓝牙设备所有服务(service)。uni.getBLEDeviceServices()
// 获取serviceId
getBLEDeviceServices(deviceId) {let that = thisuni.getBLEDeviceServices({deviceId,success: (res) => {console.log("service size = ", res.services.length)for (let i = 0; i < res.services.length; i++) {console.log(res.services[i].uuid, 'res.services[i].uuid');if (this.globalServiceuuId.toUpperCase() == res.services[i].uuid){that.getBLEDeviceCharacteristics(deviceId, res.services[i].uuid)}}}})
},
2. 获取蓝牙设备某个服务中所有特征值(characteristic)。uni.getBLEDeviceCharacteristics()
注:因为 2 3 4 涉及到的三个api写到一个了方法里,所以这三个模块的代码粘贴的一样。
// 获取characteristicId
getBLEDeviceCharacteristics(deviceId, serviceId) {uni.getBLEDeviceCharacteristics({deviceId,serviceId,success: (res) => {var ismy_service = falseconsole.log("compute ", serviceId, this.globalServiceuuId.toUpperCase())if (serviceId == this.globalServiceuuId.toUpperCase()) {ismy_service = trueconsole.warn("this is my service ")}console.log('getBLEDeviceCharacteristics success', res.characteristics)for (let i = 0; i < res.characteristics.length; i++) {let item = res.characteristics[i]// 该特征值是否支持 write 操作if (item.properties.write) {if (ismy_service && (this.globalWriteId.toUpperCase() == item.uuid)){console.warn("find write uuid ready to ", item.uuid)this.characteristicId = item.uuidthis.serviceId = serviceId}}// 该特征值是否支持 notify 操作if (item.properties.notify || item.properties.indicate) {console.log("[Notify]", item.uuid)if (ismy_service && (this.globalNotifyId.toUpperCase() == item.uuid)){uni.notifyBLECharacteristicValueChange({ //开启通知deviceId,serviceId,characteristicId: item.uuid,state: true, success(res) {console.log('notifyBLECharacteristicValueChange success', res)},fail(err) {console.warn("notifyBLECharacteristicValueChange err", err)}})}}}},fail(err) {console.error('getBLEDeviceCharacteristics err', err)}})// 后端返回的数据-进行接收// 操作之前先监听,保证第一时间获取数据uni.onBLECharacteristicValueChange((res) => {uni.showLoading({mask: true})const buffer = res.value;if (buffer.byteLength > 0) {uni.hideLoading()const uint8Arr = new Uint8Array(buffer); // 转成 Uint8Array 查看二进制值const hexStr = Array.from(uint8Arr, byte => byte.toString(16).padStart(2, '0')).join(''); // 转16进制字符串let resData = hexToUtf8Text(hexStr) // 16进制转换成文本console.log(resData, '===resData');if(resData==='A1'){uni.$u.toast('连接成功')}else if(resData==='A0'){uni.$u.toast('连接失败')}else if(resData==='B1'){uni.$u.toast('检测联网成功')}else if(resData==='B0'){uni.$u.toast('检测联网失败')}else if(resData==='B2'){uni.$u.toast('检测联网超时')}else{uni.showToast({title: resData, icon:'none',duration: 3000})}}else{uni.hideLoading()}})
},
3. 启用 notify 功能,订阅特征值。uni.notifyBLECharacteristicValueChange()
// 获取characteristicId
getBLEDeviceCharacteristics(deviceId, serviceId) {uni.getBLEDeviceCharacteristics({deviceId,serviceId,success: (res) => {var ismy_service = falseconsole.log("compute ", serviceId, this.globalServiceuuId.toUpperCase())if (serviceId == this.globalServiceuuId.toUpperCase()) {ismy_service = trueconsole.warn("this is my service ")}console.log('getBLEDeviceCharacteristics success', res.characteristics)for (let i = 0; i < res.characteristics.length; i++) {let item = res.characteristics[i]// 该特征值是否支持 write 操作if (item.properties.write) {if (ismy_service && (this.globalWriteId.toUpperCase() == item.uuid)){console.warn("find write uuid ready to ", item.uuid)this.characteristicId = item.uuidthis.serviceId = serviceId}}// 该特征值是否支持 notify 操作if (item.properties.notify || item.properties.indicate) {console.log("[Notify]", item.uuid)if (ismy_service && (this.globalNotifyId.toUpperCase() == item.uuid)){uni.notifyBLECharacteristicValueChange({ //开启通知deviceId,serviceId,characteristicId: item.uuid,state: true, success(res) {console.log('notifyBLECharacteristicValueChange success', res)},fail(err) {console.warn("notifyBLECharacteristicValueChange err", err)}})}}}},fail(err) {console.error('getBLEDeviceCharacteristics err', err)}})// 后端返回的数据-进行接收// 操作之前先监听,保证第一时间获取数据uni.onBLECharacteristicValueChange((res) => {uni.showLoading({mask: true})const buffer = res.value;if (buffer.byteLength > 0) {uni.hideLoading()const uint8Arr = new Uint8Array(buffer); // 转成 Uint8Array 查看二进制值const hexStr = Array.from(uint8Arr, byte => byte.toString(16).padStart(2, '0')).join(''); // 转16进制字符串let resData = hexToUtf8Text(hexStr) // 16进制转换成文本console.log(resData, '===resData');if(resData==='A1'){uni.$u.toast('连接成功')}else if(resData==='A0'){uni.$u.toast('连接失败')}else if(resData==='B1'){uni.$u.toast('检测联网成功')}else if(resData==='B0'){uni.$u.toast('检测联网失败')}else if(resData==='B2'){uni.$u.toast('检测联网超时')}else{uni.showToast({title: resData, icon:'none',duration: 3000})}}else{uni.hideLoading()}})
},
4. 监听低功耗蓝牙设备的特征值变化事件。uni.onBLECharacteristicValueChange()
// 获取characteristicId
getBLEDeviceCharacteristics(deviceId, serviceId) {uni.getBLEDeviceCharacteristics({deviceId,serviceId,success: (res) => {var ismy_service = falseconsole.log("compute ", serviceId, this.globalServiceuuId.toUpperCase())if (serviceId == this.globalServiceuuId.toUpperCase()) {ismy_service = trueconsole.warn("this is my service ")}console.log('getBLEDeviceCharacteristics success', res.characteristics)for (let i = 0; i < res.characteristics.length; i++) {let item = res.characteristics[i]// 该特征值是否支持 write 操作if (item.properties.write) {if (ismy_service && (this.globalWriteId.toUpperCase() == item.uuid)){console.warn("find write uuid ready to ", item.uuid)this.characteristicId = item.uuidthis.serviceId = serviceId}}// 该特征值是否支持 notify 操作if (item.properties.notify || item.properties.indicate) {console.log("[Notify]", item.uuid)if (ismy_service && (this.globalNotifyId.toUpperCase() == item.uuid)){uni.notifyBLECharacteristicValueChange({ //开启通知deviceId,serviceId,characteristicId: item.uuid,state: true, success(res) {console.log('notifyBLECharacteristicValueChange success', res)},fail(err) {console.warn("notifyBLECharacteristicValueChange err", err)}})}}}},fail(err) {console.error('getBLEDeviceCharacteristics err', err)}})// 后端返回的数据-进行接收// 操作之前先监听,保证第一时间获取数据uni.onBLECharacteristicValueChange((res) => {uni.showLoading({mask: true})const buffer = res.value;if (buffer.byteLength > 0) {uni.hideLoading()const uint8Arr = new Uint8Array(buffer); // 转成 Uint8Array 查看二进制值const hexStr = Array.from(uint8Arr, byte => byte.toString(16).padStart(2, '0')).join(''); // 转16进制字符串let resData = hexToUtf8Text(hexStr) // 16进制转换成文本console.log(resData, '===resData'); // 这里是设备端返回的数据,根据情况进行提示if(resData==='A1'){uni.$u.toast('连接成功')}else if(resData==='A0'){uni.$u.toast('连接失败')}else if(resData==='B1'){uni.$u.toast('检测联网成功')}else if(resData==='B0'){uni.$u.toast('检测联网失败')}else if(resData==='B2'){uni.$u.toast('检测联网超时')}else{uni.showToast({title: resData, icon:'none',duration: 3000})}}else{uni.hideLoading()}})
},
5. 向低功耗蓝牙设备特征值中写入二进制数据。uni.writeBLECharacteristicValue()
(这里写入数据成功之后,onBLECharacteristicValueChange这个api里面可以接收到设备端返回的数据,然后进行判断或提示)
// 一键连接
writeBLECharacteristicValue() {uni.showLoading({mask: true})let body = {...this.form,msgTag: "wifi_connect", }const str = JSON.stringify(body) // 这是我们设备端要求的参数,其他人可根据需求情况定var buffer = stringToArrayBuffer(str)uni.writeBLECharacteristicValue({// 这里的 deviceId 需要在 getBluetoothDevices 或 onBluetoothDeviceFound 接口中获取deviceId: this.deviceId,// 这里的 serviceId 需要在 getBLEDeviceServices 接口中获取serviceId: this.serviceId,// 这里的 characteristicId 需要在 getBLEDeviceCharacteristics 接口中获取characteristicId: this.characteristicId,// 这里的value是ArrayBuffer类型value: buffer,success(res) {// uni.hideLoading()console.log('writeBLECharacteristicValue success', res)},fail(err) {uni.hideLoading()console.log('writeBLECharacteristicValue err', err)uni.$u.toast('连接失败')}})
},
6. 断开与低功耗蓝牙的连接。uni.closeBLEConnection()
onUnload() {if (this.deviceId) {uni.closeBLEConnection({deviceId: this.deviceId})}
},
提示:这里是转换二进制,或者16进制转换成可读文本用到的两个方法,单独封装到了utils里面。
// 1. 先定义 utf8Decode 函数(内部函数,无需导出,但需在调用前声明)
function utf8Decode(byteArray) {let str = '';let i = 0;const len = byteArray.length;while (i < len) {if (byteArray[i] < 0x80) {// 1字节字符(0xxxxxxx)str += String.fromCharCode(byteArray[i]);i++;} else if (byteArray[i] >= 0xC0 && byteArray[i] < 0xE0) {// 2字节字符(110xxxxx 10xxxxxx)if (i + 1 >= len) break;const charCode = ((byteArray[i] & 0x1F) << 6) | (byteArray[i + 1] & 0x3F);str += String.fromCharCode(charCode);i += 2;} else if (byteArray[i] >= 0xE0 && byteArray[i] < 0xF0) {// 3字节字符(1110xxxx 10xxxxxx 10xxxxxx)if (i + 2 >= len) break;const charCode = ((byteArray[i] & 0x0F) << 12) | ((byteArray[i + 1] & 0x3F) << 6) | (byteArray[i + 2] & 0x3F);str += String.fromCharCode(charCode);i += 3;} else if (byteArray[i] >= 0xF0 && byteArray[i] < 0xF8) {// 4字节字符(11110xxx 10xxxxxx 10xxxxxx 10xxxxxx)if (i + 3 >= len) break;let charCode = ((byteArray[i] & 0x07) << 18) | ((byteArray[i + 1] & 0x3F) << 12) | ((byteArray[i + 2] & 0x3F) << 6) | (byteArray[i + 3] & 0x3F);if (charCode > 0xFFFF) {// 处理UTF-16代理对charCode -= 0x10000;str += String.fromCharCode((charCode >> 10) + 0xD800, (charCode & 0x3FF) + 0xDC00);} else {str += String.fromCharCode(charCode);}i += 4;} else {// 无效字节,跳过i++;}}return str;
}// 2. 再定义 hexToUtf8Text 函数(调用 utf8Decode,此时函数已声明)
export function hexToUtf8Text(hexStr) {try {const cleanHex = hexStr.replace(/\s/g, '').toLowerCase();if (!/^[0-9a-f]+$/.test(cleanHex)) {throw new Error('16进制格式错误:仅允许0-9、a-f字符');}if (cleanHex.length % 2 !== 0) {throw new Error('16进制长度错误:需为偶数(每2位对应1个字节)');}// 16进制转字节数组const byteLength = cleanHex.length / 2;const byteArray = new Uint8Array(byteLength);for (let i = 0; i < byteLength; i++) {byteArray[i] = parseInt(cleanHex.substr(i * 2, 2), 16);}// 调用 utf8Decode(此时函数已存在,不会报“未找到”错误)return utf8Decode(byteArray);} catch (error) {console.error('16进制转文本失败:', error);return `转换失败:${error.message}`;}
}// 3. 转成二进制
export function stringToArrayBuffer(str) {var bytes = new Array();var len, c;len = str.length;for (var i = 0; i < len; i++) {c = str.charCodeAt(i);if (c >= 0x010000 && c <= 0x10ffff) {bytes.push(((c >> 18) & 0x07) | 0xf0);bytes.push(((c >> 12) & 0x3f) | 0x80);bytes.push(((c >> 6) & 0x3f) | 0x80);bytes.push((c & 0x3f) | 0x80);} else if (c >= 0x000800 && c <= 0x00ffff) {bytes.push(((c >> 12) & 0x0f) | 0xe0);bytes.push(((c >> 6) & 0x3f) | 0x80);bytes.push((c & 0x3f) | 0x80);} else if (c >= 0x000080 && c <= 0x0007ff) {bytes.push(((c >> 6) & 0x1f) | 0xc0);bytes.push((c & 0x3f) | 0x80);} else {bytes.push(c & 0xff);}}var array = new Int8Array(bytes.length);for (var i in bytes) {array[i] = bytes[i];}return array.buffer;
}
快乐学习!