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