uniapp 蓝牙小程序-兼容安卓和iOS

withTimeout方法可以在搜寻设备时等待指定的秒数,如果30秒内未搜索到则取消搜索

javascript 复制代码
/**
 * 超时控制函数
 * @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)。

javascript 复制代码
举例:如命令字节=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页面代码

javascript 复制代码
<template>
	<view class="content">
		<view class="titlebox">
			<view class="title">空气净化香氛系统</view>
			<image src="http://f.zstjj.com/f/uniapp/280035/images/sz-icon.png" @click="changeMode"></image>
		</view>
		<view class="lowerpart">
			<view>
				<view class="subhead">香氛浓度</view>
				<view class="elliptic">
					<view class="strip"></view>
					<view class="strip-text">
						<view class="dot" @click="selectFragranceMode(1)">自动</view>
						<image v-if="fragranceInfo.FragranceMode == 1"
							src="http://f.zstjj.com/f/uniapp/280035/images/sliderbtn.png" class="dotImg zd"></image>
						<view class="dot" @click="selectFragranceMode(2)">淡</view>
						<image v-if="fragranceInfo.FragranceMode == 2"
							src="http://f.zstjj.com/f/uniapp/280035/images/sliderbtn.png" class="dotImg dan"></image>
						<view class="dot" @click="selectFragranceMode(3)">中</view>
						<image v-if="fragranceInfo.FragranceMode == 3"
							src="http://f.zstjj.com/f/uniapp/280035/images/sliderbtn.png" class="dotImg zhong"></image>
						<view class="dot" @click="selectFragranceMode(4)">浓</view>
						<image v-if="fragranceInfo.FragranceMode == 4"
							src="http://f.zstjj.com/f/uniapp/280035/images/sliderbtn.png" class="dotImg nong"></image>
					</view>
				</view>
			</view>
			<view>
				<view class="subhead">香氛选择</view>
				<view class="first-floor">
					<view class="fragrant-box" @click="selectCurrentFragranceWorkChannel(1)"
						:class="fragranceInfo.CurrentFragranceWorkChannel ==1 ? 'selected' : ''">
						<image
							:src="'http://f.zstjj.com/f/uniapp/280035/images/xf_'+fragranceInfo.FirstFragranceType+'.png'">
						</image>
						<view style="margin-right:20rpx;">
							<view>{{fragranceMap[fragranceInfo.FirstFragranceType]}}</view>
							<view style="color: #989393;margin-top: 10rpx;letter-spacing: 0px;font-weight: normal;">
								{{fragranceInfo.FirstFragranceResidue}}%
							</view>
						</view>
					</view>
					<view class="fragrant-box" @click="selectCurrentFragranceWorkChannel(2)"
						:class="fragranceInfo.CurrentFragranceWorkChannel == 2 ? 'selected' : ''">
						<image
							:src="'http://f.zstjj.com/f/uniapp/280035/images/xf_'+fragranceInfo.SecondFragranceType+'.png'">
						</image>
						<view style="margin-right:20rpx;">
							<view>{{fragranceMap[fragranceInfo.SecondFragranceType]}}</view>
							<view style="color: #989393;margin-top: 10rpx;letter-spacing: 0px;font-weight: normal;">
								{{fragranceInfo.SecondFragranceResidue}}%
							</view>
						</view>
					</view>
				</view>
				<view class="second-floor">
					<view class="switch-box">
						<text>香氛开关</text>
						<view class="switch-rotate">
							<switch :checked="fragranceInfo.FragranceWorkState==1" color="#ffffff00"
								class="switch-style s-size" />
							<view class="switch_shade" @tap='changeFragranceWorkState()'></view>
						</view>
					</view>
					<view class="switch-box">
						<text>等离子开关</text>
						<view class="switch-rotate">
							<switch :checked="fragranceInfo.IPCWorkState==1" color="#ffffff00"
								class="switch-style s-size" />
							<view class="switch_shade" @tap='changeIPCWorkState()'></view>
						</view>
					</view>
					<view class="switch-box">
						<text>蓝牙</text>
						<view class="switch-rotate">
							<switch :checked="isBluetoohConnect" color="#ffffff00" class="switch-style s-size" />
							<view class="switch_shade" @tap='changeBluetooh()'></view>
						</view>
						<!-- <view "switch-rotate" >
							<switch :checked='item.is_Checked'  Color='#ffffff00' style="transform: scale(0.4);" />
							<view class="switch_shade" @tap='changeChecked()'></view>
							</view> -->
					</view>
				</view>
			</view>
		</view>

		<!-- 设置弹窗 -->
		<uni-popup ref="modeDialog" type="center">
			<view class="popInfo">
				<view class="pop-title">设置</view>
				<view class="froms-strip">
					<view class="f-title">语音开关</view>
					<switch :checked="fragranceInfo.VoiceControlState==1" style="width: 15%;transform:scale(0.7);background: linear-gradient(92deg, #a4edf9, #2999ff);
		border-radius: 40rpx;" color="#ffffff00" />
					<view class="switch_shade" @tap='changeVoiceControlState()'></view>
				</view>
				<!-- <view class="froms-strip">
					<view class="f-title">香氛模式</view>
					<view class="f-select">
						<uni-data-select v-model="value" :localdata="range" @change="change" :clear="false">
						</uni-data-select>
					</view>
				</view> -->
				<view class="degree">
					<view class="d-title">淡香</view>
					<view class="degree-info">
						<view class="hang">
							<view>每隔</view>
							<picker @change="changeLightFragranceModeStopTime"
								:value="fragranceInfo.LightFragranceModeStopTime" :range="array" range-key="name">
								<view class="pickerValue">
									{{array[fragranceInfo.LightFragranceModeStopTime].name}}
								</view>
							</picker>
							<view>秒</view>
						</view>
						<view class="hang">
							<view>工作</view>
							<picker @change="changeLightFragranceModeWorkTime"
								:value="fragranceInfo.LightFragranceModeWorkTime" :range="array" range-key="name">
								<view class="pickerValue">
									{{array[fragranceInfo.LightFragranceModeWorkTime].name}}
								</view>
							</picker>
							<view>秒</view>
						</view>
					</view>
				</view>
				<view class="degree">
					<view class="d-title">中香</view>
					<view class="degree-info">
						<view class="hang">
							<view>每隔</view>
							<picker @change="changeMiddleFragranceModeStopTime"
								:value="fragranceInfo.MiddleFragranceModeStopTime" :range="array" range-key="name">
								<view class="pickerValue">
									{{array[fragranceInfo.MiddleFragranceModeStopTime].name}}
								</view>
							</picker>
							<view>秒</view>
						</view>
						<view class="hang">
							<view>工作</view>
							<picker @change="changeMiddleFragranceModeWorkTime"
								:value="fragranceInfo.MiddleFragranceModeWorkTime" :range="array" range-key="name">
								<view class="pickerValue">
									{{array[fragranceInfo.MiddleFragranceModeWorkTime].name}}
								</view>
							</picker>
							<view>秒</view>
						</view>
					</view>
				</view>
				<view class="degree">
					<view class="d-title">浓香</view>
					<view class="degree-info">
						<view class="hang">
							<view>每隔</view>
							<picker @change="changeStrongFragranceModeStopTime"
								:value="fragranceInfo.StrongFragranceModeStopTime" :range="array" range-key="name">
								<view class="pickerValue">
									{{array[fragranceInfo.StrongFragranceModeStopTime].name}}
								</view>
							</picker>
							<view>秒</view>
						</view>
						<view class="hang">
							<view>工作</view>
							<picker @change="changeStrongFragranceModeWorkTime"
								:value="fragranceInfo.StrongFragranceModeWorkTime" :range="array" range-key="name">
								<view class="pickerValue">
									{{array[fragranceInfo.StrongFragranceModeWorkTime].name}}
								</view>
							</picker>
							<view>秒</view>
						</view>
					</view>
				</view>
				<view class="btnBox">
					<view class="cancelBtn" @click="dialogClose">取消</view>
					<view class="confirmBtn" @click="changeFragranceModeTime()">确定</view>
				</view>
			</view>
		</uni-popup>
		<!-- #ifdef MP-WEIXIN -->
		<ws-wx-privacy id="privacy-popup"></ws-wx-privacy>
		<!-- #endif -->
	</view>
</template>

<script>
	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)
			},
		}
	}
</script>

<style lang="scss">
	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;
	}
</style>

js函数代码

javascript 复制代码
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]);
}
相关推荐
尚梦38 分钟前
uni-app 封装刘海状态栏(适用小程序, h5, 头条小程序)
前端·小程序·uni-app
paopaokaka_luck5 小时前
基于Spring Boot+Vue的助农销售平台(协同过滤算法、限流算法、支付宝沙盒支付、实时聊天、图形化分析)
java·spring boot·小程序·毕业设计·mybatis·1024程序员节
尚学教辅学习资料7 小时前
基于SSM+uniapp的营养食谱系统+LW参考示例
java·uni-app·ssm·菜谱
Bessie2347 小时前
微信小程序eval无法使用的替代方案
微信小程序·小程序·uni-app
蜕变菜鸟7 小时前
小程序跳转另一个小程序
小程序
11 小时前
躺平成长-代码开发,利用kimi开发小程序(09)
小程序
14 小时前
微信小程序运营日记(第四天)
微信小程序·小程序
guanpinkeji14 小时前
旧衣回收小程序:提高回收效率,扩大服务范围
大数据·小程序·团队开发·软件开发·小程序开发·旧衣回收·旧衣回收小程序
说私域16 小时前
完美日记营销模式对开源 AI 智能名片 2 + 1 链动模式 S2B2C 商城小程序的启示
人工智能·小程序
qq229511650216 小时前
小程序Android系统 校园二手物品交换平台APP
微信小程序·uni-app