import { log } from './log.js' const TAG = 'bleUtil-' const logEnable = false let isAndroid = false let ecBluetoothAdapterStateChangeCallback = () => { } let ecBLEConnectionStateChangeCallback = () => { } let ecBLECharacteristicValueChangeCallBack = () => { } let ecDeviceId = '' // 主服务 let serverUUID = '' const serverUUID1 = '0000FFF0-0000-1000-8000-00805F9B34FB' const serverUUID2 = 'FFF0' // 写 let gattCharacteristicWriteUUID = '' const writeUUID1 = '0000FFF3-0000-1000-8000-00805F9B34FB' const writeUUID2 = 'FFF3' // 读(电池电量) let gattCharacteristicReadUUID = '' const readUUID = '0000FFF5-0000-1000-8000-00805F9B34FB' const readUUID2 = 'FFF5' /** errCode: 30001:'请打开系统蓝牙开关', errCode: 30002:'请打开系统定位开关', errCode: 30003:'请打开微信定位权限,允许微信使用您的位置信息', errCode: 30004:'请打开小程序蓝牙开关,点击右上角三个点,然后点击设置', errCode: 30005: '蓝牙适配器不可用', errCode: 30006: '微信小程序蓝牙权限未获取', errCode: 30000: '微信小程序设置页面打开失败', errCode: 30007: '微信小程序授权失败', errCode: 30008:'openBluetoothAdapter 失败', errCode: 30009:'通知失败', errCode: 30010: '发送指令为空', errCode: 30011: 'deviceId为空', errCode: 10013: '连接失败' */ const bleLog = data => { if (logEnable) { log(TAG + JSON.stringify(data)) } } const onBluetoothAdapterStateChange = cb => { ecBluetoothAdapterStateChangeCallback = cb } const onBLECharacteristicValueChange = cb => { ecBLECharacteristicValueChangeCallBack = cb } const getSetting = () => { return new Promise(function (resolve, reject) { wx.getSetting({ success(res) { if (res.authSetting && res.authSetting['scope.bluetooth']) { resolve({ ok: true, errCode: 0, errMsg: '' }) } else { resolve({ ok: false, errCode: 30006, errMsg: 'scope.bluetooth false', }) } }, fail(res) { resolve({ ok: false, errCode: res.errCode ? res.errCode : 30000, errMsg: res.errMsg ? res.errMsg : 'getSetting fail', }) }, }) }) } const authorize = () => { return new Promise(function (resolve, reject) { wx.authorize({ scope: 'scope.bluetooth', success(res) { resolve({ ok: true, errCode: 0, errMsg: '' }) }, fail(res) { // {"errMsg":"authorize:fail:auth deny"} resolve({ ok: false, errCode: 30007, errMsg: res.errMsg }) }, }) }) } const _openBluetoothAdapter = () => { return new Promise(function (resolve, reject) { wx.openBluetoothAdapter({ success(res) { // {errno: 0, errMsg: "openBluetoothAdapter:ok"} resolve({ ok: true, errCode: 0, errMsg: '' }) }, fail(res) { resolve({ ok: false, errCode: res.errCode ? res.errCode : 30008, errMsg: res.errMsg, }) }, }) }) } const openBluetoothAdapter = async () => { await _openBluetoothAdapter() // 平台 let platform // 蓝牙开关 let bluetoothEnabled // 位置开关 let locationEnabled // 定位权限 let locationAuthorized if (wx.canIUse('getSystemInfoSync')) { const systemInfo = wx.getSystemInfoSync() platform = systemInfo.platform.toLowerCase() bluetoothEnabled = systemInfo.bluetoothEnabled locationEnabled = systemInfo.locationEnabled locationAuthorized = systemInfo.locationAuthorized } else { const deviceInfo = wx.getDeviceInfo() platform = deviceInfo.platform const systemSetting = wx.getSystemSetting() bluetoothEnabled = systemSetting.bluetoothEnabled locationEnabled = systemSetting.locationEnabled const authorizedSetting = wx.getAppAuthorizeSetting() locationAuthorized = authorizedSetting.locationAuthorized } //log(`platform = ${platform} , bluetoothEnabled = ${bluetoothEnabled} , locationEnabled = ${locationEnabled} , locationAuthorized = ${locationAuthorized}`) if (platform === 'android') { isAndroid = true } if (!bluetoothEnabled) { ecBluetoothAdapterStateChangeCallback({ ok: false, errCode: 30001, errMsg: '请打开系统蓝牙开关', }) return } if (isAndroid && !locationEnabled) { ecBluetoothAdapterStateChangeCallback({ ok: false, errCode: 30002, errMsg: '请打开系统定位开关', }) return } if (isAndroid && !locationAuthorized) { ecBluetoothAdapterStateChangeCallback({ ok: false, errCode: 30003, errMsg: '请打开微信定位权限,允许微信使用您的位置信息', }) return } const setting = await getSetting() //小程序蓝牙权限 if (!setting.ok) { const authRes = await authorize() if (!authRes.ok) { ecBluetoothAdapterStateChangeCallback({ ok: false, errCode: 30004, errMsg: '请打开小程序蓝牙开关,点击右上角三个点,然后点击设置', }) return } } wx.offBluetoothAdapterStateChange() wx.onBluetoothAdapterStateChange(res => { //log(res) // {available: true, discovering: true} if (!res.available) { ecBluetoothAdapterStateChangeCallback({ ok: false, errCode: 30005, errMsg: '蓝牙适配器不可用', }) } }) const openRes = await _openBluetoothAdapter() ecBluetoothAdapterStateChangeCallback(openRes) } const onBluetoothDeviceFound = cb => { wx.offBluetoothDeviceFound() wx.onBluetoothDeviceFound(res => { // log(res) const device = res.devices[0] const name = device.name ? device.name : device.localName if (!name) { return } let id = device.deviceId let rssi = device.RSSI cb({ id, name, rssi }) }) } let scanFlag = false const startBluetoothDevicesDiscovery = () => { if (scanFlag === false) { log(TAG + 'start scan') wx.startBluetoothDevicesDiscovery({ //services: [serverId], // 传入这个参数,只搜索主服务 UUID 为 serverId 的设备 allowDuplicatesKey: true, powerLevel: 'high', complete(res) { // log(res) }, }) scanFlag = true } } const stopBluetoothDevicesDiscovery = () => { if (scanFlag) { wx.stopBluetoothDevicesDiscovery({ complete(res) { log(TAG + 'stop scan') }, }) scanFlag = false } } const onBLEConnectionStateChange = cb => { ecBLEConnectionStateChangeCallback = cb } /** * 连接蓝牙低功耗设备 * */ const _createBLEConnection = () => { return new Promise(function (resolve, reject) { wx.createBLEConnection({ deviceId: ecDeviceId, success(res) { // {"errno":0,"errCode":0,"errMsg":"createBLEConnection:ok"} resolve({ ok: true, errCode: 0, errMsg: '' }) }, fail(res) { // {"errno":1001,"errMsg":"createBLEConnection:fail parameter error: parameter.deviceId should be String instead of Undefined;"} resolve({ ok: false, errCode: res.errCode ? res.errCode : res.errno, errMsg: res.errMsg, }) }, }) }) } /** * 获取蓝牙低功耗设备所有服务 (service) * @param { deviceId } 蓝牙设备 id。需要已经通过 wx.createBLEConnection 建立连接 */ const getBLEDeviceServices = () => { return new Promise(function (resolve, reject) { wx.getBLEDeviceServices({ deviceId: ecDeviceId, success(res) { log(res) //{"services":[{"uuid":"0000FFF0-0000-1000-8000-00805F9B34FB","isPrimary":true}],"errCode":0,"errno":0,"errMsg":"getBLEDeviceServices:ok"} // {"errno":0,"deviceId":"7C7E20F2-CB75-6DA8-F8DF-FFF702B0D63F","services":[{"isPrimary":true,"uuid":"0000FFF0-0000-1000-8000-00805F9B34FB"}],"errMsg":"getBLEDeviceServices:ok","errCode":0} resolve({ ok: true, errCode: 0, errMsg: '', services: res.services, }) }, fail(res) { log(res) resolve({ ok: false, errCode: res.errCode, errMsg: res.errMsg }) }, }) }) } /** * 获取蓝牙低功耗设备某个服务中所有特征 (characteristic) * @param { deviceId } 蓝牙设备 id。需要已经通过 wx.createBLEConnection 建立连接 * @param { serviceId } 蓝牙服务 UUID。需要先调用 wx.getBLEDeviceServices 获取 */ const getBLEDeviceCharacteristics = serviceId => { return new Promise(function (resolve, reject) { wx.getBLEDeviceCharacteristics({ deviceId: ecDeviceId, serviceId, success(res) { log(TAG + res) // {"characteristics":[{"uuid":"0000FFF2-0000-1000-8000-00805F9B34FB","handle":3,"properties":{"read":false,"write":true,"notify":false,"indicate":false,"writeNoResponse":true,"writeDefault":true}},{"uuid":"0000FFF1-0000-1000-8000-00805F9B34FB","handle":5,"properties":{"read":true,"write":true,"notify":true,"indicate":false,"writeNoResponse":true,"writeDefault":true}}],"errCode":0,"errno":0,"errMsg":"getBLEDeviceCharacteristics:ok"} // {"characteristics":[{"properties":{"writeDefault":true,"notify":false,"write":true,"indicate":false,"read":false,"writeNoResponse":true},"uuid":"0000FFF2-0000-1000-8000-00805F9B34FB"},{"properties":{"writeDefault":true,"notify":true,"write":true,"indicate":false,"read":true,"writeNoResponse":true},"uuid":"0000FFF1-0000-1000-8000-00805F9B34FB"}],"deviceId":"7C7E20F2-CB75-6DA8-F8DF-FFF702B0D63F","serviceId":"0000FFF0-0000-1000-8000-00805F9B34FB","errno":0,"errMsg":"getBLEDeviceCharacteristics:ok","errCode":0} resolve({ ok: true, errCode: 0, errMsg: '', characteristics: res.characteristics, }) }, fail(res) { log(TAG + res) resolve({ ok: false, errCode: res.errCode, errMsg: res.errMsg }) }, }) }) } // 启用蓝牙低功耗设备特征值变化时的 notify 功能,订阅特征。注意:必须设备的特征支持 notify 或者 indicate 才可以成功调用。 // 另外,必须先启用 wx.notifyBLECharacteristicValueChange 才能监听到设备 characteristicValueChange 事 const notifyBLECharacteristicValueChange = (serviceId, characteristicId) => { return new Promise(function (resolve, reject) { wx.notifyBLECharacteristicValueChange({ state: true, deviceId: ecDeviceId, serviceId, characteristicId, success(res) { bleLog(res) // {"errCode":0,"errno":0,"errMsg":"notifyBLECharacteristicValueChange:ok"} resolve({ ok: true, errCode: 0, errMsg: '' }) }, fail(res) { bleLog(res) resolve({ ok: false, errCode: res.errCode, errMsg: res.errMsg }) }, }) }) } const setBLEMTU = mtu => { return new Promise(function (resolve, reject) { wx.setBLEMTU({ deviceId: ecDeviceId, mtu, success(res) { bleLog(res) // {"errMsg":"setBLEMTU:ok","errno":0,"errCode":0,"mtu":50} resolve({ ok: true, errCode: 0, errMsg: '' }) }, fail(res) { bleLog(res) // {"errCode":-1,"errno":1500104,"errMsg":"setBLEMTU:fail:internal error"} resolve({ ok: false, errCode: res.errCode, errMsg: res.errMsg }) }, }) }) } //和设备建立连接 const createBLEConnection = async id => { bleLog('id = ' +id) ecDeviceId = id wx.offBLEConnectionStateChange() wx.onBLEConnectionStateChange(async res => { // {"deviceId":"EC:22:05:13:78:49","connected":true} if (res.connected) { // 获取所有服务 const servicesResult = await getBLEDeviceServices() if (!servicesResult.ok) { // 服务获取失败 ecBLEConnectionStateChangeCallback(servicesResult) closeBLEConnection() return } // 遍历服务 for (const service of servicesResult.services) { if ((service.uuid.toUpperCase() === serverUUID1) || (service.uuid.toUpperCase() === serverUUID2)) { // 获得服务的uuid serverUUID = service.uuid } // 通过服务的uuid,获取特征值的uuid const characteristicsResult = await getBLEDeviceCharacteristics(service.uuid) if (!characteristicsResult.ok) { // 特征值获取失败 ecBLEConnectionStateChangeCallback(characteristicsResult) closeBLEConnection() return } for (const characteristic of characteristicsResult.characteristics) { // 通知 if (characteristic.properties && characteristic.properties.notify) { const notifyResult = await notifyBLECharacteristicValueChange(service.uuid, characteristic.uuid) bleLog('通知结果 = ' + notifyResult) if (!notifyResult.ok) { ecBLEConnectionStateChangeCallback({ ok: false, errCode: 30009, errMsg: 'notify error', }) closeBLEConnection() return } } // 读(电池电量) if ((characteristic.uuid.toUpperCase() === readUUID) || (characteristic.uuid.toUpperCase() === readUUID2)) { gattCharacteristicReadUUID = characteristic.uuid } // 写 if ((characteristic.uuid.toUpperCase() === writeUUID1) || (characteristic.uuid.toUpperCase() === writeUUID2)) { gattCharacteristicWriteUUID = characteristic.uuid } } } if (isAndroid) { await setBLEMTU(247) } ecBLEConnectionStateChangeCallback({ ok: true, errCode: 0, errMsg: '', }) } else { ecBLEConnectionStateChangeCallback({ ok: false, errCode: 10001, errMsg: 'disconnect', }) } }) const res = await _createBLEConnection() if (!res.ok) { ecBLEConnectionStateChangeCallback({ ok: false, errCode: 10013, errMsg: res.errMsg, }) } } //关闭当前连接 const closeBLEConnection = id => { if (isValueEmpty(id)) { bleLog('id is empty') return } wx.closeBLEConnection({ deviceId: id, complete(res) { bleLog(res) }, }) } // 向蓝牙低功耗设备特征值中写入二进制数据 const writeBLECharacteristicValue = (str) => { if (str.length === 0) { return { ok: false, errCode: 30010, errMsg: 'data is null' } } if (isValueEmpty(ecDeviceId)) { return { ok: false, errCode: 30011, errMsg: 'ecDeviceId is null' } } let buffer = new Uint8Array(hexStrToBytes(str)).buffer return _writeBLECharacteristicValue(buffer) } const _writeBLECharacteristicValue = buffer => { return new Promise(function (resolve, reject) { wx.writeBLECharacteristicValue({ deviceId: ecDeviceId, serviceId: serverUUID, characteristicId: gattCharacteristicWriteUUID, value: buffer, writeType: 'writeNoResponse', success(res) { // console.log(res) resolve({ ok: true, errCode: 0, errMsg: '' }) }, fail(res) { //console.log(res) resolve({ ok: false, errCode: res.errCode, errMsg: res.errMsg }) }, }) }) } // 读取蓝牙设备中的数据 const readBLECharacteristicValue = () => { wx.onBLECharacteristicValueChange((characteristic) => { console.log(characteristic) // 获取特征值的 ArrayBuffer const buffer = characteristic.value; const value = new Uint8Array(buffer) const hex = arrayBufferToHex(value) ecBLECharacteristicValueChangeCallBack({ ok: true, errCode: 10014, errMsg: hex, }) }) wx.readBLECharacteristicValue({ characteristicId: gattCharacteristicReadUUID, deviceId: ecDeviceId, serviceId: serverUUID, success(res) { bleLog(res) }, fail(res) { bleLog(res) }, }) } // 转换 ArrayBuffer 为 16 进制字符串 function arrayBufferToHex(buffer) { const uint8Array = new Uint8Array(buffer); let hexString = ''; uint8Array.forEach(byte => { hexString += byte.toString(16).padStart(2, '0'); }); return hexString; } // bytes 转 16进制字符窜 const bytesToHexStr = bytes => { let strHex = '' for (let i = 0; i < bytes.length; i++) { strHex = strHex + bytes[i].toString(16).padStart(2, '0').toUpperCase() } return strHex } /** * 16进制字符串转bytes * @param hexStr 16进制字符串 */ const hexStrToBytes = hexStr => { let bytes = [] for (let i = 0; i < (hexStr.length / 2); i++) { bytes.push(parseInt(hexStr.substr(2 * i, 2), 16)) } return bytes } // int 转16进制 function intToHexString(number) { let hexString = number.toString(16) // toString(16).padStart(2, '0') 这个api需要传入转16进制的指定长度 if (hexString.length % 2 != 0) { hexString = '0' + hexString } return hexString } // 判空 function isValueEmpty(value) { if (value === null || value === undefined) { return true; } if (typeof value === 'string') { return !value.trim().length; } if (Array.isArray(value)) { return value.length === 0; } if (typeof value === 'object') { return Object.keys(value).length === 0; } return false; } // 16进制字符串倒序输出 function reverseHexString(hexString) { // 将16进制字符串按字节分割并倒序 let reversedHexString = hexString.match(/.{1,2}/g).reverse().join(''); return reversedHexString; } module.exports = { onBluetoothAdapterStateChange, openBluetoothAdapter, onBluetoothDeviceFound, startBluetoothDevicesDiscovery, stopBluetoothDevicesDiscovery, onBLEConnectionStateChange, createBLEConnection, closeBLEConnection, onBLECharacteristicValueChange, writeBLECharacteristicValue, readBLECharacteristicValue, intToHexString, reverseHexString, }