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页面代码
src="http://f.zstjj.com/f/uniapp/280035/images/sliderbtn.png" class="dotImg zd">
src="http://f.zstjj.com/f/uniapp/280035/images/sliderbtn.png" class="dotImg dan">
src="http://f.zstjj.com/f/uniapp/280035/images/sliderbtn.png" class="dotImg zhong">
src="http://f.zstjj.com/f/uniapp/280035/images/sliderbtn.png" class="dotImg nong">
:class="fragranceInfo.CurrentFragranceWorkChannel ==1 ? 'selected' : ''"> :src="'http://f.zstjj.com/f/uniapp/280035/images/xf_'+fragranceInfo.FirstFragranceType+'.png'"> {{fragranceInfo.FirstFragranceResidue}}%
:class="fragranceInfo.CurrentFragranceWorkChannel == 2 ? 'selected' : ''"> :src="'http://f.zstjj.com/f/uniapp/280035/images/xf_'+fragranceInfo.SecondFragranceType+'.png'"> {{fragranceInfo.SecondFragranceResidue}}%
class="switch-style s-size" /> class="switch-style s-size" /> :value="fragranceInfo.LightFragranceModeStopTime" :range="array" range-key="name"> {{array[fragranceInfo.LightFragranceModeStopTime].name}} :value="fragranceInfo.LightFragranceModeWorkTime" :range="array" range-key="name"> {{array[fragranceInfo.LightFragranceModeWorkTime].name}} :value="fragranceInfo.MiddleFragranceModeStopTime" :range="array" range-key="name"> {{array[fragranceInfo.MiddleFragranceModeStopTime].name}} :value="fragranceInfo.MiddleFragranceModeWorkTime" :range="array" range-key="name"> {{array[fragranceInfo.MiddleFragranceModeWorkTime].name}} :value="fragranceInfo.StrongFragranceModeStopTime" :range="array" range-key="name"> {{array[fragranceInfo.StrongFragranceModeStopTime].name}} :value="fragranceInfo.StrongFragranceModeWorkTime" :range="array" range-key="name"> {{array[fragranceInfo.StrongFragranceModeWorkTime].name}} import { Fragrance } from '@/utils/adjust_fragrance.js' export default { options: { styleIsolation: "shared" }, data() { return { selected: 'A', value: 0, array: Array.from({ length: 125 }, (_, i) => i * 2 + 2).map(num => ({ //key: num, name: num //name: num })), range: [{ value: 0, text: "手动模式" }, // { // value: 1, // text: "自动模式" // }, ], fragranceMap: { 0: "未配置", 1: "青茶", 2: "鸢尾", 3: "茉莉", 4: "森林", 5: "蓝风铃", 6: "水蜜桃", 7: "海洋", 8: "浪漫", 9: "魅力", }, SecondFragranceMap: { }, selectedImage: 1, fragranceInfo: { //香氛机型(单香机型=0x01, 两香机型=0x02) FragranceType: 0, //当前香氛模式(自动模式=0x00,淡香模式=0x01,中香模式=0x02,浓香模式=0x03); FragranceMode: 1, //香氛工作状态(关闭状态=0x00, 打开状态=0x01) FragranceWorkState: 2, //等离子工作状态(关闭状态=0x00, 打开状态=0x01)。 IPCWorkState: 2, //当前香氛工作通道(1号香氛=0x00, 2号香氛=0x01) CurrentFragranceWorkChannel: 1, //Byte6= 1号香氛类型(未配置=0x00,青茶香氛=0x01,鸢尾香氛=0x02,茉莉香氛=0x03, //森林香氛=0x04,蓝风铃香氛=0x05,水蜜桃香氛=0x06,海洋香氛=0x07,浪漫香氛=0x08,魅力香氛=0x09) FirstFragranceType: 0, //2号香氛类型(未配置=0x00,青茶香氛=0x01,鸢尾香氛=0x02,茉莉香氛=0x03, //森林香氛=0x04,蓝风铃香氛=0x05,水蜜桃香氛=0x06,海洋香氛=0x07,浪漫香氛=0x08,魅力香氛=0x09) SecondFragranceType: 0, //1号香氛余量(0~100) FirstFragranceResidue: 0, //2号香氛余量(0~100) SecondFragranceResidue: 0, //空气质量等级(未配置=0x00,优=0x01, 良=0x02, 中=0x03, 差=0x04) AirQualityLevel: 0, //语音控制使能状态(关闭状态=0x00, 打开状态=0x01) VoiceControlState: 2, //淡香模式停止时间反馈(秒) LightFragranceModeStopTime: 0, //淡香模式工作时间反馈(秒) LightFragranceModeWorkTime: 0, //中香模式停止时间反馈(秒) MiddleFragranceModeStopTime: 0, //中香模式工作时间反馈(秒) MiddleFragranceModeWorkTime: 0, //浓香模式停止时间反馈(秒) StrongFragranceModeStopTime: 0, //浓香模式工作时间反馈(秒) StrongFragranceModeWorkTime: 0, }, isBluetoohConnect: false, refreshDataTP: 0, //刷新数据时间戳 hexDataStr: "", //数据 isClickBtn: false, //是否点击了按钮 isRefresh: false, isFirstRefresh:false, isDestory:false, } }, async onLoad(options) { // uni.showLoading({ // title: '准备连接中...', // mask: true // }); uni.hideHomeButton() // #ifdef MP-WEIXIN // 如果是微信小程序,首页默认弹窗指引授权 (2.23.3以下基础库不支持,所以判断方法是否存在) if (wx.requirePrivacyAuthorize instanceof Function) { await this.mpWeixinPrivacyAuthorization() } // #endif this.getLocation() var that=this setTimeout(function() { console.info('onload-----') that.initPage() }, 500); // 每秒钟输出一次当前时间 var that = this setInterval(function() { that.refreshDataInterval() }, 500); //this.hideLoading() //uni.hideLoading(); }, // onShow() { // uni.openBluetoothAdapter() //初始化蓝牙 // }, async destroyed() { console.log("销毁页面") this.isDestory=true await this.destroyedBluetooh() }, methods: { async initPage(){ try { this.fragrance = new Fragrance() // 连接蓝牙 await this.toConnect(true) // if (this.isFirstRefresh){ // await this.toConnect() // } } catch (e) { console.error('蓝牙连接错误', e) //uni.hideLoading() this.hideLoading() this.showToastError("蓝牙连接错误", () => { this.setBluetoothBreak() }) return } }, mpWeixinPrivacyAuthorization() { return new Promise((resolve, reject) => { if (uni.getStorageSync('mpweixin_disagree_authorization') == 1) { resolve() return } // 用户已拒绝,首页不再自动弹出询问,在用户完成微信登录后清除 wx.requirePrivacyAuthorize({ success: async () => { console.log('mpweixin_agree_authorization') // 用户同意授权 // 微信小程序自动登录代码 resolve() }, fail: () => { console.log("拒绝授权") // this.common.toast('用户拒绝授权') uni.setStorageSync('mpweixin_disagree_authorization', 1) resolve() }, // 用户拒绝授权 complete: () => { console.log("结束授权") } }) }) console.log("结束代码") }, getLocation() { // 获取位置权限 uni.getLocation({ type: 'wgs84', success: function(res) {}, fail: function(err) {} }) }, //获取授权 getAuthorize() { uni.showModal({ content: '授权蓝牙和位置权限才可以扫描蓝牙,是否去设置打开?', confirmText: "确认", cancelText: '取消', success: (res) => { if (res.confirm) { uni.openSetting({ // success: (res) => { // console.log("授权后请重新打开此页面,授权成功") // }, // fail: (err) => { // console.log(err) // } }) } else {} } }) }, //刷新当前数据 refreshDataInterval() { var timeNowTP = new Date().getTime() if (timeNowTP - this.refreshDataTP < 1500) { return } var tempStr = this.fragrance.getReturnDataStr() if (this.hexDataStr != tempStr && (!this.isRefresh)) { this.isRefresh = true var config = this.fragrance.HexToConfig(tempStr) if (config) { this.fragranceInfo = config this.refreshDataTP = timeNowTP this.hexDataStr = tempStr console.log("refreshDataInterval-刷新数据") this.isRefresh = false } } }, //修改淡香模式停止时间 changeLightFragranceModeStopTime: function(e) { this.fragranceInfo.LightFragranceModeStopTime = parseInt(e.detail.value); }, //修改淡香模式工作时间 changeLightFragranceModeWorkTime: function(e) { this.fragranceInfo.LightFragranceModeWorkTime = parseInt(e.detail.value); }, //修改中香模式停止时间 changeMiddleFragranceModeStopTime: function(e) { this.fragranceInfo.MiddleFragranceModeStopTime = parseInt(e.detail.value); }, //修改中香模式工作时间 changeMiddleFragranceModeWorkTime: function(e) { this.fragranceInfo.MiddleFragranceModeWorkTime = parseInt(e.detail.value); }, //修改浓香模式停止时间 changeStrongFragranceModeStopTime: function(e) { this.fragranceInfo.StrongFragranceModeStopTime = parseInt(e.detail.value); }, //修改浓香模式工作时间 changeStrongFragranceModeWorkTime: function(e) { this.fragranceInfo.StrongFragranceModeWorkTime = parseInt(e.detail.value); }, async changeBluetooh(e) { if (this.isBluetoohConnect) { //如果蓝牙已连接,点击按钮,断开蓝牙 uni.showLoading({ title: '断开蓝牙...', mask: true }); await this.destroyedBluetooh() this.isBluetoohConnect = false this.hideLoading() //uni.hideLoading() } else { //如果未连接,点击连接蓝牙 await this.toConnect() } }, // 蓝牙断开连接 lostBluetoothConnect(res) { if (!res.connected) { console.log("提示蓝牙已断开") if(!this.isDestory){ this.showToastError("蓝牙已断开", () => { this.setBluetoothBreak() }) } } }, // 标记蓝牙连接已断开 setBluetoothBreak() { this.isBluetoohConnect = false }, async destroyedBluetooh() { if (this.fragrance) { // 销毁蓝牙连接 await this.fragrance.close() } this.isBluetoohConnect = false }, //刷新页面数据 async refreshData(tryCount = 5) { //console.log("refreshData-刷新数据", tryCount) // console.log("this.isRefresh-刷新数据", this.isRefresh) if (this.isRefresh && tryCount == 5) { //如果正在刷新,那么不重新开启刷新线程 return false } this.isRefresh = true if (tryCount < 1) { this.hideLoading() this.isRefresh = false return false } try { var that = this if (tryCount == 3 && that.fragrance.getReturnData() == null) { that.fragrance.startNotice() } var tempStr = that.fragrance.getReturnDataStr() if (tempStr != that.hexDataStr) { var config = that.fragrance.HexToConfig(tempStr) if (config) { that.hexDataStr = tempStr that.fragranceInfo = config console.log("刷新数据成功") //uni.hideLoading() that.hideLoading() that.isRefresh = false return true } } setTimeout(function() { return that.refreshData(tryCount - 1) }, 100) } catch (e) { //TODO handle the exception } }, hideLoading() { setTimeout(function() { uni.hideLoading() }, 500) }, //操作香氛工作状态 async changeFragranceWorkState() { if (!this.checkBtnState()) { console.log("changeFragranceWorkState-两秒内重复点击") return } this.refreshDataTP = new Date().getTime() if (!this.isBluetoohConnect) { this.showToastError("请先连接蓝牙", () => { //this.setBluetoothBreak() }) return } uni.showLoading({ title: '设置香氛中...', mask: true }); var tempFragranceInfo = JSON.parse(JSON.stringify(this.fragranceInfo)) if (tempFragranceInfo.FragranceWorkState == 1) { //打开香氛 tempFragranceInfo.FragranceWorkState = 2 } else { //关闭香氛 tempFragranceInfo.FragranceWorkState = 1 } const state = await this.fragrance.WriteSwitchControlData(tempFragranceInfo) if (state) { //写入成功,刷新页面 await this.refreshData() } else { this.showToastError("调整香氛失败", () => { this.setBluetoothBreak() }) } }, checkBtnState() { if (this.isClickBtn) { return false } this.isClickBtn = true var that = this setTimeout(function() { that.isClickBtn = false }, 1500) return true }, //操作等离子状态 async changeIPCWorkState() { if (!this.checkBtnState()) { console.log("changeFragranceWorkState-两秒内重复点击") return } this.refreshDataTP = new Date().getTime() //console.log("!this.isBluetoohConnect", !this.isBluetoohConnect) if (!this.isBluetoohConnect) { this.showToastError("请先连接蓝牙", () => { //this.setBluetoothBreak() }) return } uni.showLoading({ title: '设置等离子中...', mask: true }); var tempFragranceInfo = JSON.parse(JSON.stringify(this.fragranceInfo)) if (tempFragranceInfo.IPCWorkState == 1) { //打开等离子 tempFragranceInfo.IPCWorkState = 2 } else { //关闭等离子 tempFragranceInfo.IPCWorkState = 1 } const state = await this.fragrance.WriteSwitchControlData(tempFragranceInfo) if (state) { //写入成功,刷新页面 await this.refreshData() //uni.hideLoading() this.hideLoading() } else { this.showToastError("设置等离子失败", () => { this.setBluetoothBreak() }) } }, //语音控制使能状态 async changeVoiceControlState() { if (!this.checkBtnState()) { console.log("changeFragranceWorkState-两秒内重复点击") return } this.refreshDataTP = new Date().getTime() if (!this.isBluetoohConnect) { this.showToastError("请先连接蓝牙", () => { //this.setBluetoothBreak() }) return } uni.showLoading({ title: '设置语音状态中...', mask: true }); var tempFragranceInfo = JSON.parse(JSON.stringify(this.fragranceInfo)) if (tempFragranceInfo.VoiceControlState == 1) { //打开语音控制 tempFragranceInfo.VoiceControlState = 2 } else { //关闭语音控制 tempFragranceInfo.VoiceControlState = 1 } //console.log("tempFragranceInfo.VoiceControlState", tempFragranceInfo.VoiceControlState) const state = await this.fragrance.WriteSwitchControlData(tempFragranceInfo) if (state) { //写入成功,刷新页面 await this.refreshData() //uni.hideLoading() this.hideLoading() } else { this.showToastError("设置等离子失败", () => { this.setBluetoothBreak() }) } }, //修改香氛模式时间 async changeFragranceModeTime() { if (!this.checkBtnState()) { console.log("changeFragranceWorkState-两秒内重复点击") return } this.refreshDataTP = new Date().getTime() if (!this.isBluetoohConnect) { this.showToastError("请先连接蓝牙", () => { //this.setBluetoothBreak() }) return } this.dialogClose() uni.showLoading({ title: '设置香氛模式时间...', mask: true }); var tempFragranceInfo = JSON.parse(JSON.stringify(this.fragranceInfo)) tempFragranceInfo.LightFragranceModeStopTime = (tempFragranceInfo.LightFragranceModeStopTime + 1) * 2 tempFragranceInfo.LightFragranceModeWorkTime = (tempFragranceInfo.LightFragranceModeWorkTime + 1) * 2 tempFragranceInfo.MiddleFragranceModeStopTime = (tempFragranceInfo.MiddleFragranceModeStopTime + 1) * 2 tempFragranceInfo.MiddleFragranceModeWorkTime = (tempFragranceInfo.MiddleFragranceModeWorkTime + 1) * 2 tempFragranceInfo.StrongFragranceModeStopTime = (tempFragranceInfo.StrongFragranceModeStopTime + 1) * 2 tempFragranceInfo.StrongFragranceModeWorkTime = (tempFragranceInfo.StrongFragranceModeWorkTime + 1) * 2 //console.log("tempFragranceInfo.LightFragranceModeStopTime",tempFragranceInfo.LightFragranceModeStopTime) const state = await this.fragrance.WriteFragranceSetData(tempFragranceInfo) if (state) { //写入成功,刷新页面 //console.log("selectFragranceMode-写入成功") //this.showToastSuccess('调整香氛浓度成功') this.refreshDataTP = new Date().getTime() await this.refreshData() //uni.hideLoading() this.hideLoading() } else { this.showToastError("调整香氛模式时间失败", () => { this.setBluetoothBreak() }) console.log("changeFragranceModeTime-写入失败") } }, // 连接按钮 async toConnect() { uni.showLoading({ title: '蓝牙搜索连接中...', mask: true }); if (this.isBluetoohConnect) { // 已连接 this.showToastSuccess('蓝牙已连接') return } var that = this try { // 连接蓝牙 const [isok, errCode] = await this.fragrance.BLEConnect(this.deviceId) if (!isok) { if (errCode == 100 ){ this.showToastError("没有找到指定设备", () => { this.setBluetoothBreak() }) } else if (errCode == 103) { this.getAuthorize() console.log("关掉loading") this.hideLoading() } else if (errCode == 10001) { this.showToastError("请打开本机蓝牙", () => { this.setBluetoothBreak() }) }else if ( errCode == 10002) { this.showToastError("蓝牙连接超时", () => { this.setBluetoothBreak() }) }else { this.showToastError("蓝牙连接错误", () => { this.setBluetoothBreak() }) } return } this.showToastSuccess('蓝牙连接成功') await this.fragrance.ReadFragranceInfo() this.refreshDataTP = new Date().getTime() await this.refreshData() this.isBluetoohConnect = true //设置为连接状态 // 监听连接状态 this.fragrance.onBLEConnectionStateChange((res) => { // console.log(`连接状态变化`, res) this.lostBluetoothConnect(res) }) } catch (e) { console.log('蓝牙连接错误', e) //uni.hideLoading() //this.hideLoading() this.showToastError("蓝牙连接错误", () => { this.setBluetoothBreak() }) return } //this.hideLoading() //uni.hideLoading() }, //开关 switch1Change: function(e) { console.log('switch1 发生 change 事件,携带值为', e.detail.value) }, //香氛浓度 async selectFragranceMode(index) { if (!this.checkBtnState()) { console.log("changeFragranceWorkState-两秒内重复点击") return } this.refreshDataTP = new Date().getTime() //console.log("!this.isBluetoohConnect", !this.isBluetoohConnect) if (!this.isBluetoohConnect) { this.showToastError("请先连接蓝牙", () => { //this.setBluetoothBreak() }) return } uni.showLoading({ title: '设置香氛浓度中...', mask: true }); var tempFragranceInfo = JSON.parse(JSON.stringify(this.fragranceInfo)) tempFragranceInfo.FragranceMode = index //console.log("tempFragranceInfo.FragranceMode", tempFragranceInfo.FragranceMode) const state = await this.fragrance.WriteSwitchControlData(tempFragranceInfo) if (state) { //写入成功,刷新页面 //console.log("selectFragranceMode-写入成功") //this.showToastSuccess('调整香氛浓度成功') await this.refreshData() //uni.hideLoading() this.hideLoading() } else { this.showToastError("调整香氛浓度失败", () => { this.setBluetoothBreak() }) console.log("selectFragranceMode-写入失败") } }, //香氛选择 handleClick(item) { this.selected = item; }, //香氛选择 async selectCurrentFragranceWorkChannel(index) { if (!this.checkBtnState()) { console.log("changeFragranceWorkState-两秒内重复点击") return } this.refreshDataTP = new Date().getTime() if (!this.isBluetoohConnect) { this.showToastError("请先连接蓝牙", () => { //this.setBluetoothBreak() }) return } if (this.fragranceInfo.FragranceWorkState != 1) { this.showToastError("请先打开香氛控制开关", () => { //this.setBluetoothBreak() }) return } uni.showLoading({ title: '设置香氛中...', mask: true }); var tempFragranceInfo = JSON.parse(JSON.stringify(this.fragranceInfo)) tempFragranceInfo.CurrentFragranceWorkChannel = index //console.log("tempFragranceInfo.CurrentFragranceWorkChannel", tempFragranceInfo //.CurrentFragranceWorkChannel) const state = await this.fragrance.WriteSwitchControlData(tempFragranceInfo) if (state) { //写入成功,刷新页面 await this.refreshData() this.hideLoading() //uni.hideLoading() } else { this.showToastError("调整香氛失败", () => { this.setBluetoothBreak() }) console.log("selectCurrentFragranceWorkChannel写入失败") } }, //设置弹窗 changeMode() { this.$refs.modeDialog.open() }, //关闭弹窗 dialogClose() { this.$refs.modeDialog.close() this.refreshDataTP = new Date().getTime() this.hexDataStr = "" }, change(e) { console.log("e:", e); }, /** * 操作成功提示 * @param {string} msg 消息内容 */ showToastSuccess(msg) { uni.showToast({ title: msg, icon: 'success', duration: 2000 }) }, /** * 操作错误提示 * @param {string} msg 消息内容 */ showToastError(msg, cb = null) { setTimeout(() => { uni.showToast({ title: msg, icon: 'none', duration: 2000, complete: () => { if (cb) { cb() } } }) }, 100) }, } } page { width: 100%; height: 100%; background: url('http://f.zstjj.com/f/uniapp/280035/images/xxbg.png') no-repeat; background-size: cover; box-sizing: border-box; } .content { height: 100%; position: relative; overflow: hidden; display: flex; flex-direction: column; justify-content: space-between; } .titlebox { margin: 20rpx 35rpx; position: relative; display: flex; justify-content: center; align-items: center; } .titlebox image { position: absolute; right: 0rpx; top: 0rpx; width: 56rpx; height: 56rpx; } .title { font-size: 46rpx; color: white; letter-spacing: 2rpx; margin-left: 40rpx; margin-top: 12rpx; } .lowerpart { position: absolute; width: 90%; bottom: 0px; margin: 20rpx 35rpx; } .subhead { color: white; font-weight: bold; letter-spacing: 1px; margin: 35rpx 44rpx 25rpx; } .elliptic { width: 100%; height: 99rpx; padding-top: 20rpx; border-radius: 100rpx; background: #2b2f3a; border: 2rpx solid #738ca187; display: flex; align-items: center; justify-content: center; flex-direction: column; } .strip { width: 85%; height: 22rpx; border-radius: 25rpx; margin-bottom: 20rpx; background: linear-gradient(93deg, #a4edf9, #70bbff, #ba8cff); } .strip-text { width: 85%; color: white; font-size: 28rpx; font-weight: lighter; position: relative; display: flex; justify-content: space-between; } .dot { margin: 0px 5rpx; position: relative; } .dot::after { content: ""; display: block; width: 14rpx; height: 14rpx; background-color: #ffffff; position: absolute; left: 8rpx; top: -37rpx; border-radius: 50%; } .dot::before { content: ""; display: block; width: 40rpx; height: 40rpx; background-color: #ffffff00; position: absolute; left: -5rpx; top: -50rpx; } .dotImg { width: 76rpx; height: 76rpx; position: absolute; bottom: 32rpx; } .zd { left: -18rpx; } .dan { left: 178rpx; } .zhong { right: 150rpx; } .nong { right: -24rpx; } .first-floor { display: flex; justify-content: space-between; } .fragrant-box { width: 47%; height: 190rpx; background: #2b2f3ad4; border: 2rpx solid #738ca187; border-radius: 40rpx; color: #ffffff; font-weight: lighter; letter-spacing: 1px; display: flex; align-items: center; justify-content: center; } .selected { color: #333333; font-weight: bold; // background: linear-gradient(177deg, #5fa6ff, #74ecff); background: linear-gradient(178deg, #3c92ff, #82eeff); } .fragrant-box image { width: 80rpx; height: 80rpx; margin-right: 25rpx; } .second-floor { margin: 40rpx 0 45rpx; display: flex; justify-content: space-between; } .switch-box { width: 30%; height: 180rpx; padding: 34rpx 0px 80rpx; background: #2b2f3af5; border: 2rpx solid #55697987; border-radius: 40rpx; position: relative; display: flex; align-items: center; justify-content: space-between; flex-direction: column; } .switch-box text { color: white; font-weight: bold; letter-spacing: 2rpx; } .switch-rotate { transform: rotate(270deg); } .switch-style { width: 92%; background: linear-gradient(92deg, #82eeff, #3c92ff); border-radius: 40rpx; } .s-size { transform: scale(1.3); } .popInfo { width: 600rpx; padding: 23rpx 40rpx 35rpx; border-radius: 38rpx; background: white; display: flex; flex-direction: column; align-items: flex-start; } .pop-title { width: 100%; color: #000000; margin: 0 0 25rpx; text-align: center; letter-spacing: 4rpx; font-weight: bolder; font-size: 34rpx; } .froms-strip { width: 100%; margin: 23rpx 0; padding-bottom: 20rpx; border-bottom: 1rpx solid #dddddd; position: relative; display: flex; justify-content: space-between; align-items: center; } .f-select /deep/ .uni-select { width: 28%; border: 2rpx solid #ffffff; position: absolute; right: 2rpx; top: -16rpx; font-size: 32rpx; appearance: none; } .f-select /deep/ .uni-select__selector-item { padding: 5rpx 10rpx; } .f-select /deep/ .uni-select__input-text { color: #787878; } .f-title { color: black; } .degree { margin: 0px 0px 40rpx 0px; } .d-title { color: black; margin: 0px 0px 16rpx 10rpx; } .degree-info { width: 540rpx; padding: 16rpx 30rpx; border-radius: 16rpx; background: #e4e4e6; display: flex; justify-content: space-around; } .degree-info view { color: black; letter-spacing: 2rpx; display: flex; align-items: center; } .uni-input { width: 100rpx; color: #028bfd; text-align: center; font-weight: bold; } .btnBox { width: 100%; margin-top: 30rpx; display: flex; justify-content: space-around; } .cancelBtn { width: 40%; background: #e5e5e5; font-size: 34rpx; color: black; letter-spacing: 4rpx; text-align: center; border-radius: 50rpx; padding: 20rpx; margin-right: 20rpx; } .confirmBtn { width: 40%; background: #028bfd; font-size: 34rpx; color: white; letter-spacing: 4rpx; text-align: center; border-radius: 50rpx; padding: 20rpx 10rpx; } .switch-box-2 { position: relative; display: inline-block; } .switch_shade { position: absolute; top: 0; right: 0; bottom: 0; left: 0; z-index: 10; } 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]); } 参考阅读
发表评论