小程序
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

582 lines
17 KiB

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,
}