You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

651 lines
22 KiB
JavaScript

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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") || device.name.startsWith("小柏智能手表")));
// 遍历并连接符合条件的设备
targetDevices.forEach(device => {
console.log("找到符合规则的蓝牙设备:", device);
console.log("device.advertisData:",new Uint8Array(device.advertisData))
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:5E:BE"){
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);
if(dataView.byteLength % 25 == 2){
console.log("收到步数结束符号,最后一条指令【结束】",dataView.byteLength)
}
// console.log("步数详情数据:",stepDetailList);
// 调用后台接口 todo ....
// 更新缓存
HealthDataCache.updateStepData(stepDetailList[0].startTime);
break;
case 0x55:
let heartRateList = ParseDataPacket.parseDataList(dataView,0);
let respList = this.getRespiratoryList(heartRateList);
if(dataView.byteLength % 10 == 2){
console.log("收到心率结束符号>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>",dataView.byteLength)
// 需要调用下一个指令(血氧)
this.sendHealthCommand(0x66)
}
console.log("心率数据:",heartRateList);
// console.log("呼吸率数据:",respList);
// 调用后台接口 todo ....
// 更新缓存
HealthDataCache.updateHeartRateData(heartRateList[0].time);
break;
case 0x56:
let parseBloodList = ParseDataPacket.parseBloodPressureList(dataView,0);
if(dataView.byteLength % 15 == 2){
console.log("收到血压结束符号>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>",dataView.byteLength)
// 需要调用下一个指令(体温)
this.sendHealthCommand(0x65)
}
// 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);
if(dataView.byteLength % 10 == 2){
console.log("收到血氧结束符号>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>",dataView.byteLength)
// 需要调用下一个指令(血压)
this.sendHealthCommand(0x56)
}
// console.log("血氧数据:",bloodOxygenList);
// 调用后台接口 todo ....
// 更新缓存
HealthDataCache.updateBloodOxygenData(bloodOxygenList[0].time);
break;
case 0x65:
let tempList = ParseDataPacket.parseTempList(dataView,0);
if(dataView.byteLength % 11 == 2){
console.log("收到体温结束符号>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>",dataView.byteLength)
// 需要调用下一个指令(体温)
this.sendHealthCommand(0x53)
}
// 目前返回的包括环境温度,后续固件改成只采集佩戴情况下的体温,暂时过滤掉 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){
console.log("收到睡眠结束符号>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>",dataView.byteLength)
// 需要调用下一个指令(体温)
// this.sendHealthCommand(0x52)
let list = this.data.sleepList;
if(list.length > 0){
let arr = CommonUtil.assembleSleepData(list);
arr.forEach((item,index) =>{
// 排序算法
item.sleepList.sort((a, b) => {
// 先比较 yyyyMMdd
if (a.yyyyMMdd !== b.yyyyMMdd) {
return a.yyyyMMdd.localeCompare(b.yyyyMMdd);
}
// 再比较 minute
return a.minute - b.minute;
});
item.sleepList[index].minute = item.sleepList[index].minute -1;
item.sleepList[index].status = 1
})
console.log("接口需要的睡眠数据:",arr);
// 更新缓存
HealthDataCache.updateSleepData(list[0].date);
// 调用接口传睡眠后,清空数据
this.setData({
sleepList: []
});
}
} else {
let sleepList = ParseDataPacket.parseSleepList(dataView,0);
// 排序minute 越大排前面
sleepList.sort((a, b) => {
return Number(a.minute) - Number(b.minute); // b.minute > a.minute 排前面
});
console.log("排序后数据:",sleepList)
// 获取当前已存在的 sleepList如果有
let currentSleepList = this.data.sleepList || [];
// 将 sleepList 放在前面currentSleepList 放在后面,实现新的数据插入在前
let updatedSleepList = sleepList.concat(currentSleepList);;
// 更新 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);
// const HRPacket = DataPacket.generateReadHRDataPacket();
// SendCommandUtil.sendPackage(this.data.deviceId,HRPacket);
this.sendHealthCommand(0x53)
},
getStep(){
// 获取步数(按天的总步数)
const stepPacket = DataPacket.generateReadStepTotalDataPacket();
SendCommandUtil.sendPackage(this.data.deviceId,stepPacket);
},
sendHealthCommand(cmd){
// 如果不传则默认发送心率
if(cmd == null){
cmd = 0x55;
}
if(cmd == 0x55){
// 获取当前时间
let currentTime = Math.floor(new Date().getTime() / 1000);
// 如果是第一次调用则不判断时间如果是第二次调用要判断本次调用与上次是否间隔18秒因手表机制问题不能频繁发送指令给手表端
console.log(this.data.initTime,this.data.initIndex)
if(this.data.initIndex === 0){
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 > 20){
this.setData({
initIndex: this.data.initIndex + 1,
initTime: currentTime
})
} else {
return;
}
}
}
let packet = DataPacket.generateReadDataPacket(cmd);
console.log("发送指令到设备端>>>:", this.uint8ArrayToHex(packet));
SendCommandUtil.sendPackage(this.data.deviceId, packet);
},
// sendHealthCommand2(){
// // 指令依次是:心率,血氧、血压、体温、睡眠、步数
// // 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(0x53);
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);
},
});