const SendCommandUtil = require('../utils/SendCommandUtil.js'); // 引用 发送指令工具类 const DataPacket = require('../utils/DataPacket.js'); const ParseDataPacket = require('../utils/ParseDataPacket.js'); const DynamicArrayBuffer = require('../utils/DynamicArrayBuffer.js'); const CommonUtil = require('../utils/CommonUtil.js'); const HealthDataCache = require('../utils/HealthDataCache.js'); Page({ // 数据定义区域,存储蓝牙设备 ID、服务 ID 和特征 ID data: { deviceId: '', // 存储蓝牙设备的ID(IOS是UUID ,安卓是MAC) serviceId: '0000FFF0-0000-1000-8000-00805F9B34FB',// 存储蓝牙设备的服务 UUID notifyCharacteristicId:'0000FFF7-0000-1000-8000-00805F9B34FB',// 通知特征 initIndex: 0, initTime: Math.floor(new Date().getTime() / 1000), // 定义蓝牙数据缓存 sleepDataBuffer: new DynamicArrayBuffer(), // 创建缓存对象 isDataComplete: false, // 判断数据是否上传完毕 sleepList:[], }, // 页面加载时触发的函数,用于初始化蓝牙适配器 onLoad() { wx.getLocation({ type: 'gcj02', success(res) { console.log("开启位置授权:", res) }, fail(err) { console.error('获取位置失败', err); } }); wx.openBluetoothAdapter({ // 开启蓝牙适配器 success: (res) => { console.log('蓝牙适配器初始化成功'); // 蓝牙适配器初始化成功的回调 this.startBluetoothDevicesDiscovery(); // 开始扫描附近的蓝牙设备 }, fail: (err) => { console.error('蓝牙适配器初始化失败', err); // 蓝牙适配器初始化失败的回调 } }); }, getLocation(){ wx.getLocation({ type: 'gcj02', success(res) { console.log("开启位置授权:", res) }, fail(err) { console.error('获取位置失败', err); } }); }, // 扫描蓝牙设备 startBluetoothDevicesDiscovery() { wx.startBluetoothDevicesDiscovery({ // 启动设备扫描 allowDuplicatesKey: false, // 是否允许重复设备 success: (res) => { console.log('开始扫描附近的蓝牙设备'); // 扫描成功回调 this.getBluetoothDevices(); // 调用函数获取蓝牙设备列表 }, fail: (res) => { console.log('开始扫描附近的蓝牙设备', res); }, }); }, // 获取附近的蓝牙设备列表,并尝试连接到目标设备 getBluetoothDevices() { wx.onBluetoothDeviceFound((res) => { // 过滤出名称以 "GJ-SH-01" 开头的设备 const targetDevices = res.devices.filter(device => device.name && device.name.startsWith("J2203")); // 遍历并连接符合条件的设备 targetDevices.forEach(device => { console.log("找到符合规则的蓝牙设备:", device); const hexStr = this.uint8ArrayToMac(new Uint8Array(device.advertisData)) console.log("advertisData: ",hexStr) // 连接指定设备 // if(device.deviceId=="03:02:02:07:65:98"){ // if(device.deviceId=="03:02:02:07:5F:AF"){ if(device.deviceId=="03:02:02:07:64:B4"){ this.connectToDevice(device.deviceId); } }); }); }, // 连接指定的蓝牙设备 connectToDevice(deviceId) { wx.createBLEConnection({ // 建立与蓝牙设备的连接 deviceId, // 设备的 ID success: () => { this.setData({ deviceId }); // 将设备 ID 保存到 data 中 console.log('蓝牙设备连接成功'); // 连接成功的回调 // 获取服务IDs和特征值 this.getCharacteristicId() }, fail: (err) => { console.error('蓝牙设备连接失败', err); // 连接失败的回调 } }); }, // 获取服务下的特征 UUID getCharacteristicId() { // 获取设备服务列表 wx.getBLEDeviceServices({ deviceId: this.data.deviceId, success: (res) => { res.services.forEach(service => { // 获取每个服务的特征值 wx.getBLEDeviceCharacteristics({ deviceId: this.data.deviceId, serviceId: service.uuid, success: (res) => { res.characteristics.forEach(characteristic => { if(characteristic.uuid.startsWith("0000FFF7")){ // 开始订阅特征 this.subscribeAndReceiveData() } }); } }); }); } }); }, // 连接指定的蓝牙设备 connectToDevice(deviceId) { wx.createBLEConnection({ // 建立与蓝牙设备的连接 deviceId, // 设备的 ID success: () => { this.setData({ deviceId }); // 将设备 ID 保存到 data 中 console.log('蓝牙设备连接成功'); // 连接成功的回调 // 获取服务IDs和特征值 this.getCharacteristicId() }, fail: (err) => { console.error('蓝牙设备连接失败', err); // 连接失败的回调 } }); }, // 获取数据(健康数据、运动数据) getData() { SendCommandUtil.getHealthData(this.data.deviceId); }, // 订阅 并 接收数据 subscribeAndReceiveData() { wx.notifyBLECharacteristicValueChange ({ deviceId: this.data.deviceId, // 蓝牙设备 ID serviceId: this.data.serviceId, // 蓝牙服务 UUID characteristicId: this.data.notifyCharacteristicId, // 健康数据 state: true, success: (res) => { console.log('订阅手表向手机端发送数据事件', res); }, fail: (err) => { console.error('手表向手机端发送数据订阅失败', err); }, }); wx.onBLECharacteristicValueChange((res) => { const hexStr = this.uint8ArrayToHex(new Uint8Array(res.value)) console.log("收到手表端发送的数据>>>>>>:",hexStr); const dataView = new DataView(res.value); // 获取第一个字节 let cmdType = dataView.getUint8(0); console.log("cmdType",cmdType); switch (cmdType) { case 0x01: console.log("同步时间成功"); break; case 0x02: console.log("同步用户信息成功"); break; case 0x04: let basicInfo = ParseDataPacket.parseWatchBasic(dataView,0); console.log("手环基本信息:",basicInfo); break; case 0x13: let power = dataView.getUint8(1); console.log("获得电量成功:",power); break; case 0x23: wx.showToast({ title: '设置成功', // 提示的内容 icon: 'success', // 图标,可选值为 'success', 'loading', 'none' duration: 2000 // 提示的持续时间,单位为毫秒 }); break; case 0x25: wx.showToast({ title: '设置成功', icon: 'success', duration: 2000 }); break; case 0x26: let sedentaryInfo = ParseDataPacket.parseSedentary(dataView,0); console.log("久坐信息:",sedentaryInfo); break; case 0x4B: let targetInfo = ParseDataPacket.parseStepTarget(dataView,0); console.log("运动目标信息:",targetInfo); break; case 0x51: let stepList = ParseDataPacket.parseStepList(dataView,0); console.log("步数总数据:",stepList); break; case 0x52: let stepDetailList = ParseDataPacket.parseStepDetailList(dataView,0); console.log("步数详情数据:",stepDetailList); // 调用后台接口 todo .... // 更新缓存 HealthDataCache.updateStepData(stepDetailList[0].startTime); break; case 0x55: let heartRateList = ParseDataPacket.parseDataList(dataView,0); let respList = this.getRespiratoryList(heartRateList); console.log("心率数据:",heartRateList); // console.log("呼吸率数据:",respList); // 调用后台接口 todo .... // 更新缓存 HealthDataCache.updateHeartRateData(heartRateList[0].time); break; case 0x56: let parseBloodList = ParseDataPacket.parseBloodPressureList(dataView,0); console.log("血压数据:",parseBloodList); // 调用后台接口 todo .... // 更新缓存 HealthDataCache.updateBloodPressureData(parseBloodList[0].time); break; case 0x57: let alarmClockList = ParseDataPacket.parseAlarmClockList(dataView,0); console.log("获取闹钟数据成功:",alarmClockList); break; case 0x66: let bloodOxygenList = ParseDataPacket.parseDataList(dataView,0); console.log("血氧数据:",bloodOxygenList); // 调用后台接口 todo .... // 更新缓存 HealthDataCache.updateBloodOxygenData(bloodOxygenList[0].time); break; case 0x65: let tempList = ParseDataPacket.parseTempList(dataView,0); // 目前返回的包括环境温度,后续固件改成只采集佩戴情况下的体温,暂时过滤掉 oneValue 小于 35.0 的数据 const filteredData = tempList.filter(item => item.oneValue >= 35.0); console.log("体温数据:",tempList); if(filteredData.length > 0){ // todo 调用后端接口 // 更新缓存 HealthDataCache.updateTemperatureData(filteredData[0].time); } break; case 0x53: let id = dataView.getUint8(1); // 睡眠数据结束符 if(dataView.byteLength == 2 && id == 0xff){ let list = this.data.sleepList; let arr = CommonUtil.assembleSleepData(list); console.log("接口需要的睡眠数据:",arr); // // 更新缓存 // HealthDataCache.updateSleepData(list[0].time); // 调用接口传睡眠后,清空数据 this.setData({ sleepList: [] }); } else { let sleepList = ParseDataPacket.parseSleepList(dataView,0); // 排序,minute 越大排前面 sleepList.sort((a, b) => { return Number(b.minute) - Number(a.minute); // b.minute > a.minute 排前面 }); console.log("排序后数据:",sleepList) // 获取当前已存在的 sleepList(如果有) let currentSleepList = this.data.sleepList || []; // 将新的 sleepList 追加到现有的 sleepList 中 let updatedSleepList = currentSleepList.concat(sleepList); // 更新 data 中的 sleepList this.setData({ sleepList: updatedSleepList }); } break; default: console.warn('未知数据类型', cmdType); break; } }); }, init(){ // 同步时间 const setTimePacket = DataPacket.generateSetTimePacket(); SendCommandUtil.sendPackage(this.data.deviceId,setTimePacket); // 同步用户性别、身高、体重、年龄 const userInfoPacket = DataPacket.generateSetUserInfoPacket(1,30,168,55); SendCommandUtil.sendPackage(this.data.deviceId,userInfoPacket); }, getPower(){ // 获取电量 const batteryPacket = DataPacket.generateBatteryCheckPacket(); SendCommandUtil.sendPackage(this.data.deviceId,batteryPacket); }, getStep(){ // 获取步数(按天的总步数) const stepPacket = DataPacket.generateReadStepTotalDataPacket(); SendCommandUtil.sendPackage(this.data.deviceId,stepPacket); }, sendHealthCommand(){ // 指令依次是:心率,血氧、血压、体温、睡眠、步数 // 0x55,0x66,0x56,0x65,0x53,0x52 const commands = [0x55]; // 递归发送命令并加上 5 秒延迟 function sendNextCommand(index = 0) { if (index >= commands.length) return; // 所有命令发送完毕,结束 const cmd = commands[index]; const packet = DataPacket.generateReadDataPacket(cmd); console.log("hexStr", this.uint8ArrayToHex(packet)); // 发送命令包 SendCommandUtil.sendPackage(this.data.deviceId, packet); // 每发送完一个命令后,等待 3 秒继续发送下一个 setTimeout(() => { sendNextCommand.call(this, index + 1); }, 3500); // 延迟 3.5 秒 } // 开始发送第一个命令,并保持 `this` 上下文 sendNextCommand.call(this,0); }, // 获取健康数据 getHealthData(){ // 睡眠数据 // const sleepPacket = DataPacket.generateReadSleepDataPacket(); // SendCommandUtil.sendPackage(this.data.deviceId,sleepPacket); // 获取当前时间 let currentTime = Math.floor(new Date().getTime() / 1000); // 如果是第一次调用,则不判断时间,如果是第二次调用,要判断本次调用与上次是否间隔18秒(因手表机制问题,不能频繁发送指令给手表端) console.log(this.data.initTime,this.data.initIndex) if(this.data.initIndex === 0){ this.sendHealthCommand(); this.setData({ initIndex: this.data.initIndex + 1, initTime: currentTime }) } else { // 初始时间 let initTime = this.data.initTime let timeDifference = currentTime - initTime; if(this.data.initIndex > 0 && timeDifference > 18){ console.log(timeDifference,this.data.initIndex) this.setData({ initIndex: this.data.initIndex + 1, initTime: currentTime }) this.sendHealthCommand(); } } }, /** * 清除历史数据 */ removeHistoryData(){ let basicPacket = DataPacket.removeHistoryData(); let hex = this.uint8ArrayToHex(basicPacket); console.log('绑定时清除历史数据:', hex); SendCommandUtil.sendPackage(this.data.deviceId,basicPacket); }, /** * 佩戴管理设置 */ wearManageSetting() { // 测试用例 > 手表基础信息设置(抬腕亮屏,屏幕亮度)------------------------------------------------------------ // let basicPacket = DataPacket.generateWatchBasicPacket(0,0); // let hex = this.uint8ArrayToHex(basicPacket); // console.log('发送抬腕亮屏,屏幕亮度指令:', hex); // SendCommandUtil.sendPackage(this.data.deviceId,basicPacket); // 测试用例 > 闹钟、吃药、喝水------------------------------------------------------------------------------ // const week = [0,1,1,1,1,1,0];// 表示周天、周一、周二、周三、周四、周五、周六 // let binaryWeek = CommonUtil.getWeekBinary(week); // const alarms = [ // { // id: 0, // 闹钟编号 // enabled: true, // 是否启用 // type: 1, // 闹钟类型:1:闹钟,2:吃药提示,3:喝水提示 // hour: 14, // 小时 // minute: 11, // 分钟 // weekEnable: binaryWeek, // 星期使能:每天都启用 // text: "闹钟", // 文本内容 // }, // { // id: 1, // enabled: true, // type: 3, // hour: 14, // minute: 13, // weekEnable: 0b01111111, // 启用周一、周三、周五 // text: "喝水", // }, // { // id: 2, // enabled: true, // type: 2, // hour: 10, // minute: 28, // weekEnable: 0b01111111, // 启用周一、周三、周五 // text: "吃药", // }, // ]; // // 这里的公式写死,不可修改 // let maxLength = 39 * alarms.length + 2; // let alarmClockPackets = DataPacket.generateAlarmClockPackets(maxLength,alarms); // let hex = this.uint8ArrayToHex(alarmClockPackets); // console.log('发送闹钟指令:', hex); // SendCommandUtil.sendPackage(this.data.deviceId,alarmClockPackets); // 测试用例 > 久坐------------------------------------------------------------------------------------- let sportTimePackets = DataPacket.generateSedentaryPacket(9, 0, 20, 0, [0,1,1,1,1,1,0], 60, 100, 1); let hex1 = this.uint8ArrayToHex(sportTimePackets); console.log('发送久坐指令:', hex1); SendCommandUtil.sendPackage(this.data.deviceId,sportTimePackets); // 测试用例 > 运动目标------------------------------------------------------------------------------------- let targetPacket = DataPacket.generateStepTargetPacket(5000,0,4,160,480); let hex2 = this.uint8ArrayToHex(sportTimePackets); console.log('发送运动目标指令:', hex2); SendCommandUtil.sendPackage(this.data.deviceId,targetPacket); }, // 获取佩戴管理各项设置值 wearManageGetting() { // 获取手环基本信息 // let basic = DataPacket.getWatchBasic(); // SendCommandUtil.sendPackage(this.data.deviceId,basic); // 获取闹钟数据(闹钟、吃药、喝水) // let alarmClockPacket = DataPacket.getAlarmClock(); // SendCommandUtil.sendPackage(this.data.deviceId,alarmClockPacket); // 获取久坐数据 // let sedentaryPacket = DataPacket.getSedentary(); // SendCommandUtil.sendPackage(this.data.deviceId,sedentaryPacket); // 获得运动目标 // let stepTargetPacket = DataPacket.getStepTarget(); // SendCommandUtil.sendPackage(this.data.deviceId,stepTargetPacket); }, /** * 下发天气指令 */ downWeather(){ let packet = DataPacket.generateWeatherPacket("晴","20","30","10","66","长沙市") let hex = this.uint8ArrayToHex(packet); console.log('发送天气指令:', hex); SendCommandUtil.sendPackage(this.data.deviceId,packet); }, removeCache(){ wx.removeStorageSync('healthDataCache'); this.setData({ index1:0, index2:0, index3:0, index4:0, index5:0 }) }, // 字节转16进制 uint8ArrayToHex(array) { return Array.from(array) .map(byte => byte.toString(16).padStart(2, '0')) // 转换每个字节为 2 位的 16 进制,并补零 .join(' '); // 用空格连接所有字节 }, // IOS MAC地址转换 uint8ArrayToMac(array) { return Array.from(array.slice(2, 8)) .map(byte => byte.toString(16).padStart(2, '0').toUpperCase()) // 转换为两位 16 进制 .join(':'); // 用冒号连接 }, /** * 根据心率批量计算呼吸率 * @param {解析后的心率列表} heartList */ getRespiratoryList(heartList){ let respList = []; heartList.forEach(item => { let respValue = this.calculateRespiratory(item.oneValue); let respItem = { type:5, oneValue:respValue, time:item.time } respList.push(respItem); }) return respList; }, /** * 根据心率计算呼吸率 * @param {心率} heartValue */ calculateRespiratory(heartValue){ //心率值 let X = heartValue; //呼吸率 let Y = 0; //正常心率计算法 if(X >= 50 && X <= 100){ Y= ( ( X - 2 ) / 4 ); //心率大于100的计算法 } else if ( X >100 ){ Y= ( 25 + ( X - 100 ) / 4 ); //心率小于50的计算法 } else if ( X < 50 ){ //计算结果向下取整 Y = Math.floor(12 - (50 - X) / 4.0); } else { Y = 0; } return parseInt(Y); }, });