withTimeout方法可以在搜寻设备时等待指定的秒数,如果30秒内未搜索到则取消搜索
/**
* 超时控制函数
* @param {Promise} promise 回调函数
* @param {number} timeout 超时时间, 默认10s
*/
export function withTimeout(promise, timeout = 10000) {
let timeoutEvent = null
const logicPromise = new Promise((resolve, reject) => {
promise.then((data) => {
if (timeoutEvent) {
// 清理超时
clearTimeout(timeoutEvent)
timeoutEvent = null
}
resolve(data)
})
})
// 创建一个新的 Promise 对象,用于处理超时情况
const timeoutPromise = new Promise((resolve, reject) => {
timeoutEvent = setTimeout(() => {
reject(`执行超时`);
}, timeout);
});
// 将两个 Promise 对象(原始的 promise 和超时的 promise)合并为一个新的 Promise 对象
return Promise.race([logicPromise, timeoutPromise]);
}
计算数据校验和:
校验字节等于命令字节与所有数据字节之和的反码。求和按带进位加 (ADDC)方式计算,每个进位都被加到本次结果的最低位(LSB)。
举例:如命令字节=0x01;
数据=0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF,0x00,0x01;则校验字节0x01+0xF0+0xF1+0xF2+0xF3+0xF4+0xF5+0xF6+0xF7+0xF8+0xF9+0xFA
+0xFB+0xFC+0xFD+0xFE+0xFF+0x00+0x01 = 0x0F79;
0x0F+0x79 = 0x88;
校验字节 = 0xFF – 0x88 = 0x77。
getAddc(str) {
let itotal = 0,
len = str.length,
num = 0;
var tempTotal = "";
while (num < len) {
let s = str.substring(num, num + 2);
itotal += parseInt(s, 16);
num = num + 2;
if (itotal >= 256) {
itotal = parseInt((itotal - itotal % 256) / 256) + itotal % 256
}
}
itotal = 255 - itotal
return itotal.toString(16).padStart(2, '0')
}
vue页面代码
空气净化香氛系统
香氛浓度
自动
淡
中
浓
香氛选择
{{fragranceMap[fragranceInfo.FirstFragranceType]}}
{{fragranceInfo.FirstFragranceResidue}}%
{{fragranceMap[fragranceInfo.SecondFragranceType]}}
{{fragranceInfo.SecondFragranceResidue}}%
香氛开关
等离子开关
蓝牙
设置
语音开关
淡香
每隔
{{array[fragranceInfo.LightFragranceModeStopTime].name}}
秒
工作
{{array[fragranceInfo.LightFragranceModeWorkTime].name}}
秒
中香
每隔
{{array[fragranceInfo.MiddleFragranceModeStopTime].name}}
秒
工作
{{array[fragranceInfo.MiddleFragranceModeWorkTime].name}}
秒
浓香
每隔
{{array[fragranceInfo.StrongFragranceModeStopTime].name}}
秒
工作
{{array[fragranceInfo.StrongFragranceModeWorkTime].name}}
秒
取消
确定
js函数代码
export class Fragrance {
// 读UUID:0000FEC1-0000-1000-8000-XXXXX
#appointNotifyCharacteristicId = '0000FEC1-0000-1000-8000-XXXXX'
// 写UUID:0000FEC1-0000-1000-8000-XXXXX
#appointWriteCharacteristicId = '0000FEC1-0000-1000-8000-XXXXX'
// 指定服务ID
#appointServiceId = '0000FEE7-0000-1000-8000-XXXXX'
// 指定设备ID
#appointDeviceId = '60E962AD-434B-F6EF-D82C-XXXXX'
// 读UUID:0000fec1-0000-1000-8000-XXXXX
#notifyCharacteristicId = '0000FEC1-0000-1000-8000-XXXXX'
// 写UUID:0000fec1-0000-1000-8000-XXXXX
#writeCharacteristicId = '0000FEC1-0000-1000-8000-XXXXX'
//
#serviceId = '0000FEE7-0000-1000-8000-XXXXX'
// 设备ID
#deviceId = ''
#deviceReturnData = ''
#connected = false // 连接状态标志位
isConnected() {
return this.#connected
}
/**
* 关闭蓝牙连接, 关闭蓝牙模块
*/
async close() {
try {
this.#connected = false
// 关闭蓝牙连接
await uni.closeBLEConnection({
deviceId: this.#deviceId
})
// 关闭蓝牙模块
await uni.closeBluetoothAdapter()
} catch (e) {
// 这里错误 不处理
console.error('关闭蓝牙错误', e)
}
}
// 开始搜索蓝牙uni.startBluetoothDevicesDiscovery(),使用uni.onBluetoothDeviceFound 注册搜索到的设备,
async ScanDevice() {
var that = this
return new Promise((resolve, reject) => {
console.log("开始搜索")
try {
uni.startBluetoothDevicesDiscovery({
interval: 0, // 上报设备的间隔。0 表示找到新设备立即上报,其他数值根据传入的间隔上报
allowDuplicatesKey: true,
// services: ['0000FEE7'], // 只搜索主服务 UUID 为 0000FEE7 的设备
// 扫描模式,越高扫描越快,也越耗电。仅安卓微信客户端 7.0.12 及以上支持。
// low:低,medium:中,high:高
powerLevel: 'high',
success(res) {
uni.onBluetoothDeviceFound(function(devices) {
var d = {}
if (typeof devices == Array) {
d = devices[0]
} else {
d = devices.devices[0]
}
// console.log('onBluetoothDeviceFound-', d.name)
// 找到名称 KLOVEF-AirFragranceSystem 的设备是代表已搜索到
if (d.name == 'KLOVEF-AirFragranceSystem') {
// console.log('onBluetoothDeviceFound-', devices)
that.#deviceId = d.deviceId
resolve(d.deviceId)
///console.log("this.#deviceId-aaa", that.#deviceId)
// uni.stopBluetoothDevicesDiscovery({
// success(res) {
// console.log('关闭成功')
// },
// fail(res) {
// console.log('关闭失败' + res.errMsg)
// }
// })
}
})
},
fail(res) {
console.log("搜索失败", res.errMsg)
}
})
} catch (e) {
console.log("结束搜索-111")
console.error('ScanDevice' + e)
}
console.log("结束搜索-222")
})
}
HexToConfig(HexStr) {
if (HexStr.length != 40) { //校验数据长度
return null
}
if (!this.checkAddc(HexStr)) { //校验ADDC
return null
}
var FragranceType = parseInt(HexStr.substring(2, 4), 16)
var FragranceMode = parseInt(HexStr.substring(4, 6), 16) + 1
//返回和写入时一致的数据,旧返回数据 (关闭状态=0x00, 打开状态=0x01)
//目标返回数据 (未控制=0x00, 打开香氛=0x01, 关闭香氛=0x02)
var FragranceWorkState = parseInt(HexStr.substring(6, 8), 16)
if (FragranceWorkState != 1) {
FragranceWorkState = 2
}
//返回和写入时一致的数据,旧返回数据 等离子工作状态(关闭状态=0x00, 打开状态=0x01)
//目标返回数据 (未控制=0x00, 打开等离子=0x01, 关闭等离子=0x02)
var IPCWorkState = parseInt(HexStr.substring(8, 10), 16)
if (IPCWorkState != 1) {
IPCWorkState = 2
}
var CurrentFragranceWorkChannel = parseInt(HexStr.substring(10, 12), 16) + 1
var FirstFragranceType = parseInt(HexStr.substring(12, 14), 16)
var SecondFragranceType = parseInt(HexStr.substring(14, 16), 16)
var FirstFragranceResidue = parseInt(HexStr.substring(16, 18), 16)
//console.log("FirstFragranceResidue",HexStr.substring(16, 18), 16)
var SecondFragranceResidue = parseInt(HexStr.substring(18, 20), 16)
var AirQualityLevel = parseInt(HexStr.substring(20, 22), 16)
//返回和写入时一致的数据,旧返回数据 等离子工作状态((关闭状态=0x00, 打开状态=0x01)
//目标返回数据 (未控制=0x00, 打开语音控制=0x01, 关闭语音控制=0x02)
var VoiceControlState = parseInt(HexStr.substring(22, 24), 16)
if (VoiceControlState != 1) {
VoiceControlState = 2
}
//这里为了方便前端显示,转换成索引
var LightFragranceModeStopTime = parseInt(HexStr.substring(24, 26), 16) / 2 - 1
var LightFragranceModeWorkTime = parseInt(HexStr.substring(26, 28), 16) / 2 - 1
var MiddleFragranceModeStopTime = parseInt(HexStr.substring(28, 30), 16) / 2 - 1
var MiddleFragranceModeWorkTime = parseInt(HexStr.substring(30, 32), 16) / 2 - 1
var StrongFragranceModeStopTime = parseInt(HexStr.substring(32, 34), 16) / 2 - 1
var StrongFragranceModeWorkTime = parseInt(HexStr.substring(34, 36), 16) / 2 - 1
return {
//香氛机型(单香机型=0x01, 两香机型=0x02)
FragranceType: FragranceType,
//当前香氛模式(自动模式=0x00,淡香模式=0x01,中香模式=0x02,浓香模式=0x03);
FragranceMode: FragranceMode,
//香氛工作状态(关闭状态=0x00, 打开状态=0x01)
FragranceWorkState: FragranceWorkState,
//等离子工作状态(关闭状态=0x00, 打开状态=0x01)。
IPCWorkState: IPCWorkState,
//当前香氛工作通道(1号香氛=0x00, 2号香氛=0x01)
CurrentFragranceWorkChannel: CurrentFragranceWorkChannel,
//Byte6= 1号香氛类型(未配置=0x00,青茶香氛=0x01,鸢尾香氛=0x02,茉莉香氛=0x03,
//森林香氛=0x04,蓝风铃香氛=0x05,水蜜桃香氛=0x06,海洋香氛=0x07,浪漫香氛=0x08,魅力香氛=0x09)
FirstFragranceType: FirstFragranceType,
//2号香氛类型(未配置=0x00,青茶香氛=0x01,鸢尾香氛=0x02,茉莉香氛=0x03,
//森林香氛=0x04,蓝风铃香氛=0x05,水蜜桃香氛=0x06,海洋香氛=0x07,浪漫香氛=0x08,魅力香氛=0x09)
SecondFragranceType: SecondFragranceType,
//1号香氛余量(0~100)
FirstFragranceResidue: FirstFragranceResidue,
//2号香氛余量(0~100)
SecondFragranceResidue: SecondFragranceResidue,
//空气质量等级(未配置=0x00,优=0x01, 良=0x02, 中=0x03, 差=0x04)
AirQualityLevel: AirQualityLevel,
//语音控制使能状态(关闭状态=0x00, 打开状态=0x01)
VoiceControlState: VoiceControlState,
//淡香模式停止时间反馈(秒)
LightFragranceModeStopTime: LightFragranceModeStopTime,
//淡香模式工作时间反馈(秒)
LightFragranceModeWorkTime: LightFragranceModeWorkTime,
//中香模式停止时间反馈(秒)
MiddleFragranceModeStopTime: MiddleFragranceModeStopTime,
//中香模式工作时间反馈(秒)
MiddleFragranceModeWorkTime: MiddleFragranceModeWorkTime,
//浓香模式停止时间反馈(秒)
StrongFragranceModeStopTime: StrongFragranceModeStopTime,
//浓香模式工作时间反馈(秒)
StrongFragranceModeWorkTime: StrongFragranceModeWorkTime,
}
}
/**
* 开始通知
*/
startNotice() {
console.log("startNotice-监听数据")
var that = this;
uni.notifyBLECharacteristicValueChange({
state: true, // 启用 notify 功能
// 这里的 deviceId 需要已经通过 createBLEConnection 与对应设备建立链接
deviceId: that.#deviceId,
// 这里的 serviceId 需要在 getBLEDeviceServices 接口中获取
serviceId: that.#serviceId,
// 这里的 characteristicId 需要在 getBLEDeviceCharacteristics 接口中获取
characteristicId: that.#writeCharacteristicId,
success(res) {
//接收蓝牙返回消息
uni.onBLECharacteristicValueChange((sjRes) => {
// 此时可以拿到蓝牙设备返回来的数据是一个ArrayBuffer类型数据,
//所以需要通过一个方法转换成字符串
that.#deviceReturnData = that.ab2hex(sjRes.value) //10.0
console.log("startNotice-that.#deviceReturnData", that.#deviceReturnData)
})
},
fail(err) {
// uni.showToast({
// title: '通知失败',
// icon: 'error',
// duration: 2000
// })
console.log("startNotice", err)
}
})
}
//手机写入开关控制数据
async WriteSwitchControlData(f) {
var hexStr = "02" + f.FragranceMode.toString().padStart(2, '0') + f.CurrentFragranceWorkChannel.toString()
.padStart(2, '0') + f.FragranceWorkState.toString().padStart(2, '0') + f.IPCWorkState.toString()
.padStart(2, '0') + f.VoiceControlState.toString().padStart(2, '0')
//console.log("hexStr", hexStr)
hexStr = hexStr + "00000000000000"
hexStr = this.addAddc(hexStr)
var isOK = await this.SetFragranceInfo(hexStr)
//console.log("手机写入香氛设置数据", isOK)
return isOK
}
//手机写入香氛设置数据
async WriteFragranceSetData(f) {
var hexStr = "03" + f.LightFragranceModeStopTime.toString(16).padStart(2, '0') + f
.LightFragranceModeWorkTime.toString(16).padStart(2, '0') + f.MiddleFragranceModeStopTime.toString(16)
.padStart(2, '0') + f.MiddleFragranceModeWorkTime.toString(16).padStart(2, '0') + f
.StrongFragranceModeStopTime.toString(16).padStart(2, '0') + f.StrongFragranceModeWorkTime.toString(16)
.padStart(2, '0')
hexStr = hexStr + "000000000000"
hexStr = this.addAddc(hexStr)
var isOK = await this.SetFragranceInfo(hexStr)
//console.log("手机写入香氛设置数据", isOK)
return await this.SetFragranceInfo(hexStr)
}
//设置香氛数据
async SetFragranceInfo(hexStr) {
//console.log("增加addc 后 SetFragranceInfo-hexStr", hexStr)
var that = this
var buffer = that.string2buffer(hexStr); //9.0
return new Promise((resolve, reject) => {
try {
uni.writeBLECharacteristicValue({
// 这里的 deviceId 需要在 getBluetoothDevices 或 onBluetoothDeviceFound 接口中获取
deviceId: that.#deviceId,
// 这里的 serviceId 需要在 getBLEDeviceServices 接口中获取
serviceId: that.#serviceId,
// 这里的 characteristicId 需要在 getBLEDeviceCharacteristics 接口中获取
characteristicId: that.#writeCharacteristicId,
// 这里的 value 是ArrayBuffer类型
value: buffer,
success(res) {
//that.startNotice()
//console.log('SetFragranceInfo success', res)
if (res.errCode == 0) {
//console.log("返回写入成功")
resolve(true)
} else {
resolve(false)
}
},
fail(res) {
console.log('writeBLECharacteristicValue fail', res.errMsg)
console.log("返回写入失败")
//return false
resolve(false)
},
complete(res) {
console.log('writeBLECharacteristicValue ', JSON.stringify(res))
}
})
} catch (e) {
console.error('uni.onBLECharacteristicValueChange' + e)
}
})
}
//设置香氛数据
async SetFragranceInfoByCode(code) {
var that = this
//向蓝牙设备发送一个0x00的16进制数据
//打开香氛
var hexStr = "02" + code + "0101010300000000000000"
hexStr = this.addAddc(hexStr)
//console.log(code + "写入开关控制数据-hexStr", hexStr)
var buffer = that.string2buffer(hexStr); //9.0
//console.log("开始发送数据-写入开关控制数据")
uni.writeBLECharacteristicValue({
// 这里的 deviceId 需要在 getBluetoothDevices 或 onBluetoothDeviceFound 接口中获取
deviceId: that.#deviceId,
// 这里的 serviceId 需要在 getBLEDeviceServices 接口中获取
serviceId: that.#serviceId,
// 这里的 characteristicId 需要在 getBLEDeviceCharacteristics 接口中获取
characteristicId: that.#writeCharacteristicId,
// 这里的 value 是ArrayBuffer类型
value: buffer,
success(res) {
//that.startNotice()
console.log('SetFragranceInfo success', res.errMsg)
},
fail(res) {
console.log('writeBLECharacteristicValue fail', res.errMsg)
},
complete(res) {
console.log('writeBLECharacteristicValue ', JSON.stringify(res))
}
})
}
//增加ADDC校验
addAddc(str) {
return str + this.getAddc(str)
}
getAddc(str) {
let itotal = 0,
len = str.length,
num = 0;
var tempTotal = "";
while (num < len) {
let s = str.substring(num, num + 2);
itotal += parseInt(s, 16);
num = num + 2;
if (itotal >= 256) {
itotal = parseInt((itotal - itotal % 256) / 256) + itotal % 256
}
}
itotal = 255 - itotal
return itotal.toString(16).padStart(2, '0')
}
checkAddc(str) {
if (str.length != 40) {
return false
}
var data = str.substring(0, 38)
var allData = this.addAddc(data)
return str == allData
}
//读取香氛设备信息
async ReadFragranceInfo() {
var that = this
// 向蓝牙设备发送一个0x00的16进制数据
var buffer = this.string2buffer('01000000000000000000000000FE'); //9.0
console.log("this.#deviceId", this.#deviceId)
console.log("this.#serviceId", this.#serviceId)
console.log("this.#writeCharacteristicId", this.#writeCharacteristicId)
var that = this
uni.writeBLECharacteristicValue({
// 这里的 deviceId 需要在 getBluetoothDevices 或 onBluetoothDeviceFound 接口中获取
deviceId: that.#deviceId,
// 这里的 serviceId 需要在 getBLEDeviceServices 接口中获取
serviceId: that.#serviceId,
// 这里的 characteristicId 需要在 getBLEDeviceCharacteristics 接口中获取
characteristicId: that.#writeCharacteristicId,
// 这里的 value 是ArrayBuffer类型
value: buffer,
success(res) {
that.startNotice()
},
fail(res) {
console.log('writeBLECharacteristicValue fail', res.errMsg)
},
complete(res) {
console.log('writeBLECharacteristicValue ', JSON.stringify(res))
}
})
}
getReturnDataStr() {
let returnData = this.#deviceReturnData
return returnData
}
getReturnData() {
//console.log("that.#deviceReturnData-999", this.#deviceReturnData)
var config = this.HexToConfig(this.#deviceReturnData)
//console.log("config", config)
return config
}
// ArrayBuffer转16进度字符串示例
ab2hex(buffer) {
if (!buffer) return ""
const hexArr = Array.prototype.map.call(
new Uint8Array(buffer),
function(bit) {
return ('00' + bit.toString(16)).slice(-2)
}
)
return hexArr.join('')
}
/**
* 将字符串转换成ArrayBufer
*/
string2buffer(str) {
let val = ""
if (!str) return;
let length = str.length;
let index = 0;
let array = []
while (index < length) {
array.push(str.substring(index, index + 2));
index = index + 2;
}
val = array.join(",");
// 将16进制转化为ArrayBuffer
return new Uint8Array(val.match(/[\da-f]{2}/gi).map(function(h) {
return parseInt(h, 16)
})).buffer
}
/**
* 连接低功耗蓝牙设备
* @param {string} deviceId 设备ID
* @param {number} timeout 蓝牙连接超时时间, 单位ms, 默认3000ms(3秒)
* @param {number} mut 每次传输的数据大小, 默认244bytes(C385协议上就是默认每次传一帧数据, 总大小为244byte)
* @param {number} retry 错误重试, 默认2次
* @param {number} first 是否首次进入,如果是则重置 #deviceId
*/
async BLEConnect(deviceId, timeout = 3000, mtu = 244, retry = 3, first = true) {
console.log("开始连接蓝牙" + retry)
if (retry < 0) {
// 重试次数已达到, 执行回调反馈给调用端
return [false, 0]
}
try {
//1. 检查适配器是否启动 uni.getBluetoothAdapterState(),没有启动则启动蓝牙适配器 uni.openBluetoothAdapter
// this.openBluetoothAdapter()
if (first) {
this.#deviceId = ''
}
const [bluetooth, state] = await uni.openBluetoothAdapter()
console.log("bluetooth", bluetooth)
console.log("state", state)
if (bluetooth) {
//console.log("typeof(bluetooth)",bluetooth)
if (bluetooth.errno == 103 || bluetooth.errMsg.indexOf("fail auth deny") != -1) {
return [false, 103] //103需要打开蓝牙权限
}
return [false, bluetooth.errCode] //10001需要打开蓝牙
}
const [adapter, adapterState] = await uni.getBluetoothAdapterState()
console.log("adapter", adapter)
console.log("adapterState", adapterState)
if (typeof(adapterState) == "undefined") {
//return showModal()
}
if (!adapterState.discovering && retry == 0) {
return [false, 10002] //未开启定位权限
}
if (!adapterState.available) {
return [false, 10001] //蓝牙未打开
}
if (first || this.#deviceId == '') { // 如果没有搜到过设备则重新进入搜索,如果已经搜到过就无需搜索了
try {
const deviceId = await withTimeout(this.ScanDevice(), 3000)
console.log('deviceId', deviceId)
} catch (e) {
console.log('ScanDevice', err)
return [false, 100]
}
uni.stopBluetoothDevicesDiscovery({
success(res) {
console.log('关闭成功')
},
fail(res) {
console.log('关闭失败' + res.errMsg)
}
})
// await this.ScanDevice()
//console.log("this.#deviceId",this.#deviceId)
if (this.#deviceId == "" && retry < 0) { //需要打开设备
return [false, 100]
}
if (this.#deviceId == "") {
console.log('deviceId not value')
retry = retry - 1
return that.BLEConnect(this.#deviceId, timeout, mtu, retry)
}
}
const [createErr, createSccess] = await uni.createBLEConnection({
deviceId: this.#deviceId,
timeout
})
//console.log('createBLEConnection', createErr, createSccess)
if (createErr) {
try {
// 需要成对出现,如果连接错误也需要退出
await uni.closeBLEConnection({
deviceId: this.#deviceId
})
} catch {}
// 连接错误
console.log("连接错误", createErr)
if (createErr.errCode == 10012 && retry == 0) {
return [false, createErr.errCode] //10012,需要打开设备
} else if (createErr.errCode != -1) {
retry = retry - 1
return await this.BLEConnect(this.#deviceId, timeout + 2000, mtu, retry, false)
}
}
var that = this
//setTimeout(() => {
var isok = await this.getBLEDeviceServices()
console.log("isok-111", isok)
if (!isok) {
retry = retry - 1
return await this.BLEConnect(deviceId, timeout, mtu, retry, false)
}
isok = await this.getBLEDeviceCharacteristics()
console.log("isok-222", isok)
if (!isok) {
retry = retry - 1
return await this.BLEConnect(deviceId, timeout, mtu, retry, false)
}
return [true, 0]
} catch (e) {
console.log("重试", e)
// 重试
retry = retry - 1
//console.log("连接蓝牙失败-catch-333")
return await this.BLEConnect(deviceId, timeout, mtu, retry, false)
}
return [false, 0]
}
async getBLEDeviceServices() {
console.log("开始执行-getBLEDeviceServices")
var that = this
return new Promise((resolve, reject) => {
uni.getBLEDeviceServices({
deviceId: that.#deviceId,
success(res) {
// 5.启用notify
//console.log('getBLEDeviceServices', res)
// this.#connected = true
resolve(true)
console.log("开始执行-getBLEDeviceServices-执行成功")
// console.log("getBLEDeviceServices-res", res)
// if (res.errno == 0 && res.services.length > 0) {
// //console.log('getBLEDeviceServices-成功')
// if (res.services[0].uuid != that.#appointServiceId) {
// resolve(false)
// }
// that.#serviceId = res.services[0].uuid
// // that.#connected = true
// // that.startNotice()
// // console.log("蓝牙连接成功")
// resolve(true)
// console.log("开始执行-getBLEDeviceServices-执行成功")
// //})
// } else {
// resolve(false)
// }
},
fail(err) {
resolve(false)
}
})
})
}
async getBLEDeviceCharacteristics() {
var that = this
console.log("开始执行-getBLEDeviceCharacteristics")
return new Promise((resolve, reject) => {
uni.getBLEDeviceCharacteristics({
deviceId: that.#deviceId,
serviceId: that.#serviceId,
success(res) {
//console.log("getBLEDeviceCharacteristics-res", res)
if (res.characteristics.length > 0) {
// console.log("连接蓝牙成功-222")
// console.log("res.characteristics", res.characteristics)
if (res.characteristics[0].uuid != that
.#appointNotifyCharacteristicId) {
resolve(false)
}
that.#notifyCharacteristicId = res.characteristics[0].uuid
that.#writeCharacteristicId = res.characteristics[0].uuid
that.#connected = true
console.log("开始执行-成功-getBLEDeviceCharacteristics")
that.startNotice()
// console.log("this.#deviceId-999", that.#deviceId)
// console.log("this.#writeCharacteristicId ", that
// .#writeCharacteristicId)
console.log("蓝牙连接成功")
resolve(true)
}
},
fail() {
resolve(false)
}
})
})
}
/**
* 发送数据
* @param {ArrayBuffer} bytes
*/
async #sendData(bytes) {
await uni.writeBLECharacteristicValue({
deviceId: this.#deviceId, // 蓝牙设备 id
serviceId: this.#serviceId, // 蓝牙特征值对应服务的 uuid
characteristicId: this.#writeCharacteristicId, // 蓝牙特征值的 uuid
value: bytes, // 这里的value是ArrayBuffer类型
fail: (err) => {
console.log('writeBLECharacteristicValue', err)
},
})
}
// notify 接收消息回调函数
#sendDataCallback = null
// notify 接收消息回调函数消息队列(防止漏接收消息)
#sendDataCallbackMsgQueue = null
/**
* 监听低功耗蓝牙设备的特征值变化事件。必须先启用 notifyBLECharacteristicValueChange 接口才能接收到设备推送的 notification。
* @param {Function} callback 回调函数
*/
onBLEConnectionStateChange(callback) {
uni.onBLEConnectionStateChange((res) => {
// 该方法回调中可以用于处理连接意外断开等异常情况
// console.log(`设备 ${res.deviceId} 状态已经改变, connected: ${res.connected}`)
this.#connected = res.connected // 更新连接状态
callback(res)
})
}
}
/**
* 16进制字符串转16进制 ArrayBuffer
* @param {string} hexString 16进制字符串
*/
function string2HexArray(hexString) {
if (!hexString) return;
let length = hexString.length;
let index = 0;
let array = []
while (index < length) {
array.push(hexString.substring(index, index + 2));
index = index + 2;
}
const val = array.join(",");
return new Uint8Array(val.match(/[\da-f]{2}/gi).map((h) => parseInt(h, 16))).buffer
}
/**
* ArrayBuffer转16进度字符串示例
* @param {Array} buffer 字节数组
* @return {string} 转换后的字符串
*/
export function ab2hex(buffer) {
const hexArr = Array.prototype.map.call(
new Uint8Array(buffer),
(bit) => {
return ('00' + bit.toString(16)).slice(-2)
}
)
return hexArr.join('')
}
/**
* 判断是不是要连接的蓝牙设备 是不是以 OTA- 开头
* @param {string} deviceName 设备名称
*/
export function isOTADevice(deviceName) {
const prefix = "OTA-"
return deviceName.toUpperCase().startsWith(prefix)
}
function crc16(str) {
const data = Buffer.from(str, "hex")
let crcValue = 0xFFFF;
for (let i = 0; i < data.length; i++) {
crcValue ^= data[i] & 0xFFFF
for (let j = 0; j < 8; j++) {
if (crcValue & 0x0001) {
crcValue >>= 1
crcValue ^= 0xA001
} else {
crcValue >>= 1
}
}
}
crcValue = crcValue.toString(16).toUpperCase().padStart(4, '0')
return crcValue.substring(0, 2) + crcValue.substring(2, 4)
}
/**
* 字符串转ASCII码函数
* @param {string} str
*/
function toASCII(str) {
return str.split('').map(char => char.charCodeAt(0)).join('');
}
/**
* 十六进制字符串转ASCII码
* @param {string} hexStr
*/
function hexToASCII(hexStr) {
let result = '';
for (let i = 0; i < hexStr.length; i += 2) {
let hex = hexStr.substr(i, 2);
result += String.fromCharCode(parseInt(hex, 16));
}
return result;
}
/**
* 格式化长度
* @param {number} fileLen
*/
function formatLen(fileLen) {
return fileLen.toLocaleString('en-US', {
minimumIntegerDigits: 6,
useGrouping: false
}).padStart(6, '0');
}
/**
* 搜索监听C385升级设备
* @param {Function} notifier 回调函数
* @param {Function} callback 回调函数
* @param {Function} cancelCallback 取消回调函数
*/
export async function listenUpdateDevice(notifier, callback, cancelCallback = null) {
const showModal = () => {
uni.showModal({
title: '提示!',
content: '初始化蓝牙失败,请打开本机蓝牙!',
showCancel: true,
cancelText: "取消",
confirmText: "确定",
success: (res) => {
if (res.confirm) {
// 用户点击了确定
return listenUpdateDevice(notifier, callback, cancelCallback)
} else if (res.cancel) {
// 用户点击了取消
if (cancelCallback) {
cancelCallback()
}
return
}
}
});
}
try {
// 1.初始化蓝牙模块
const res = await uni.openBluetoothAdapter()
// 2.获取本机蓝牙适配器状态(正常状态下是一定能获取的)
let [_, state] = await uni.getBluetoothAdapterState()
if (!state) {
return listenUpdateDevice(notifier, callback, cancelCallback)
}
// console.log(state)
if (!state.available) {
return showModal()
}
// 3.开启搜寻附近的蓝牙外围设备
await uni.startBluetoothDevicesDiscovery()
// 通知已经开始搜索设备了
notifier()
// 4.开始监听搜索设备
uni.onBluetoothDeviceFound(async found => {
const d = found.devices[0]
if (isOTADevice(d.name) && d.connectable) {
const device = {
name: d.name, // 名称
id: d.advertisServiceUUIDs.length > 0 ? d.advertisServiceUUIDs[0] : '', // 设备ID
MAC: d.deviceId, // MAC地址
advertisData: ab2hex(d.advertisData).trim(), // 广播数据
RSSI: d.RSSI, // RSSI
connectable: d.connectable // 是否能连接
}
callback(device)
// 搜索到了要升级的设备旧关闭(startBluetoothDevicesDiscovery会很耗电, 所以搜索到蓝牙设备后要及时关毕)
// await uni.stopBluetoothDevicesDiscovery()
}
})
} catch (e) {
console.error('listenUpdateDevice错误', e)
return showModal()
}
}
/**
* 关闭搜索监听C385升级设备
* @param {Function} callback 回调函数
*/
export async function closeListenUpdateDevice() {
// 搜索到了要升级的设备旧关闭(startBluetoothDevicesDiscovery会很耗电, 所以搜索到蓝牙设备后要及时关毕)
await uni.stopBluetoothDevicesDiscovery()
}
/**
* ASCII 转 ArrayBuffer
* @param {string} str ASCII 字符串
*/
function textEncode(str) {
const bytes = [];
for (let i = 0; i < str.length; i++) {
let charcode = str.charCodeAt(i);
if (charcode < 0x80) {
bytes.push(charcode);
} else if (charcode < 0x800) {
bytes.push(0xC0 | (charcode >> 6),
0x80 | (charcode & 0x3F));
} else {
bytes.push(0xE0 | (charcode >> 12),
0x80 | ((charcode >> 6) & 0x3F),
0x80 | (charcode & 0x3F));
}
}
return new Uint8Array(bytes).buffer;
}
/**
* 超时控制函数
* @param {Promise} promise 回调函数
* @param {number} timeout 超时时间, 默认10s
*/
export function withTimeout(promise, timeout = 10000) {
let timeoutEvent = null
const logicPromise = new Promise((resolve, reject) => {
promise.then((data) => {
if (timeoutEvent) {
// 清理超时
clearTimeout(timeoutEvent)
timeoutEvent = null
}
resolve(data)
})
})
// 创建一个新的 Promise 对象,用于处理超时情况
const timeoutPromise = new Promise((resolve, reject) => {
timeoutEvent = setTimeout(() => {
reject(`执行超时`);
}, timeout);
});
// 将两个 Promise 对象(原始的 promise 和超时的 promise)合并为一个新的 Promise 对象
return Promise.race([logicPromise, timeoutPromise]);
}