|
|
// 数据包组装模块
|
|
|
const { calculateCRC } = require('./crc');
|
|
|
const CommonUtil = require('./CommonUtil');
|
|
|
const WeatherUtils = require('./WeatherUtils');
|
|
|
const HealthDataCache = require('./HealthDataCache.js');
|
|
|
|
|
|
|
|
|
const DataPacket = {
|
|
|
// 转换为 BCD 格式
|
|
|
toBCD(value) {
|
|
|
return ((value / 10) << 4) | (value % 10);
|
|
|
},
|
|
|
|
|
|
// 组装时间设置命令包
|
|
|
generateSetTimePacket() {
|
|
|
|
|
|
let arr = CommonUtil.getCurrenTime();
|
|
|
// 将时间参数转换为BCD格式
|
|
|
const bcdYear = this.toBCD(arr.year);
|
|
|
const bcdMonth = this.toBCD(arr.month);
|
|
|
const bcdDay = this.toBCD(arr.day);
|
|
|
const bcdHour = this.toBCD(arr.hours);
|
|
|
const bcdMinute = this.toBCD(arr.minutes);
|
|
|
const bcdSecond = this.toBCD(arr.seconds);
|
|
|
|
|
|
// 创建Uint8Array
|
|
|
const packet = new Uint8Array(16);
|
|
|
|
|
|
// 填充命令数据包
|
|
|
packet[0] = 0x01; // 命令字节
|
|
|
packet[1] = bcdYear;
|
|
|
packet[2] = bcdMonth;
|
|
|
packet[3] = bcdDay;
|
|
|
packet[4] = bcdHour;
|
|
|
packet[5] = bcdMinute;
|
|
|
packet[6] = bcdSecond;
|
|
|
|
|
|
// 填充其他字节为 0
|
|
|
packet.fill(0x00, 7, 15);
|
|
|
|
|
|
// 计算并设置 CRC 校验字节
|
|
|
const crc = calculateCRC(packet.subarray(0, 15));
|
|
|
packet[15] = crc;
|
|
|
|
|
|
return packet;
|
|
|
},
|
|
|
|
|
|
generateSetUserInfoPacket(gender, age, height, weight) {
|
|
|
// 输入参数验证
|
|
|
if (![0, 1].includes(gender)) {
|
|
|
throw new Error('性别只能为 0 或 1');
|
|
|
}
|
|
|
if (age < 0 || age > 150) {
|
|
|
throw new Error('年龄无效');
|
|
|
}
|
|
|
if (height < 0 || height > 300) {
|
|
|
throw new Error('身高无效');
|
|
|
}
|
|
|
if (weight < 0 || weight > 500) {
|
|
|
throw new Error('体重无效');
|
|
|
}
|
|
|
|
|
|
// 创建 Uint8Array 数据包
|
|
|
const packet = new Uint8Array(16);
|
|
|
|
|
|
// 填充命令数据包
|
|
|
packet[0] = 0x02; // 命令字节
|
|
|
packet[1] = gender; // 性别
|
|
|
packet[2] = age; // 年龄
|
|
|
packet[3] = height; // 身高
|
|
|
packet[4] = weight; // 体重
|
|
|
packet[5] = 70; // 步长
|
|
|
|
|
|
// 填充其他字节为 0
|
|
|
packet.fill(0x00, 6, 15);
|
|
|
|
|
|
// 计算并设置 CRC 校验字节
|
|
|
const crc = calculateCRC(packet.subarray(0, 15));
|
|
|
packet[15] = crc;
|
|
|
|
|
|
return packet;
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
* 设置手环基本参数
|
|
|
* @param {抬腕亮屏 1-开启,0-关闭} WRSFlag
|
|
|
* @param {屏幕亮度级别 分6个等级,参数传值:0-5,0:最暗,5:最亮。} SBLevel
|
|
|
*/
|
|
|
generateWatchBasicPacket(WRSFlag,SBLevel){
|
|
|
const packet = new Uint8Array(16);
|
|
|
|
|
|
// 填充命令数据包
|
|
|
packet[0] = 0x03; // 命令字节
|
|
|
packet[1] = 0x80; // 固定值,距离单位:0x81:MILE,0x80:KM
|
|
|
packet[2] = 0x80; // 固定值,12小时24小时显示:0x81: 12小时制, 0x80:24小时制度
|
|
|
packet[3] = WRSFlag==1 ? 0x81:0x80; // 固定值,抬腕亮屏 0x81:开启, 0x80:关闭
|
|
|
packet[4] = 0x80; // 固定值,温度单位切换 0x81:华摄氏度, 0x80:摄氏度,默认摄氏度
|
|
|
packet[5] = 0x80; // 固定值,夜间模式 0x81:开启, 0x80:关闭(夜间模式开启后晚上六点到凌晨7点,自动调节显示亮度到最暗)
|
|
|
packet[6] = 0x80; // 固定值,ANCS使能开关 0x81:开启, 0x80:关闭
|
|
|
packet.fill(0x00, 7, 10); // 填充其他字节为0
|
|
|
packet[11] = CommonUtil.sendSBLevelConv(SBLevel);// 屏幕亮度
|
|
|
packet[12] = 0x80;// 表盘界面更换80-8A
|
|
|
packet[13] = 0x80;// 社交距离开关 0x81 开 0x80 关
|
|
|
packet[14] = 0x81;//中英文切换,80代表英文(默认),81代表中文
|
|
|
|
|
|
// 计算并设置 CRC 校验字节
|
|
|
const crc = calculateCRC(packet.subarray(0, 15));
|
|
|
packet[15] = crc;
|
|
|
console.log("Generated Packet:", packet); // 调试最终数据包
|
|
|
return packet;
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
* 设置手环基本参数
|
|
|
* @param {抬腕亮屏 1-开启,0-关闭} WRSFlag
|
|
|
*/
|
|
|
getWatchBasic(){
|
|
|
const packet = new Uint8Array(16);
|
|
|
packet[0] = 0x04;
|
|
|
packet[1] = 0x00;
|
|
|
packet.fill(0x00, 2, 15);
|
|
|
// 计算并设置 CRC 校验字节
|
|
|
const crc = calculateCRC(packet.subarray(0, 15));
|
|
|
packet[15] = crc;
|
|
|
return packet;
|
|
|
},
|
|
|
|
|
|
// 获取电量
|
|
|
generateBatteryCheckPacket() {
|
|
|
// 创建 Uint8Array 数据包
|
|
|
const packet = new Uint8Array(16);
|
|
|
|
|
|
// 填充命令数据包
|
|
|
packet[0] = 0x13; // 命令字节
|
|
|
packet[1] = 0x00; // 固定值,表示电量检测
|
|
|
packet.fill(0x00, 2, 15); // 填充其他字节为0
|
|
|
|
|
|
// 计算并设置 CRC 校验字节
|
|
|
const crc = calculateCRC(packet.subarray(0, 15));
|
|
|
packet[15] = crc;
|
|
|
|
|
|
return packet;
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
*
|
|
|
* @param {指令} command
|
|
|
*/
|
|
|
generateReadDataPacket(command){
|
|
|
// 创建 Uint8Array 数据包
|
|
|
const packet = new Uint8Array(16);
|
|
|
|
|
|
packet[0] = command; // 命令字节
|
|
|
packet[1] = 0x00; //0x99: 删除数据,0x00:读取最近数据,0x01:读取指定位置数据,0x02:继续读取上一次位置
|
|
|
let field = CommonUtil.commandToField(command);// 根据指令获取字段
|
|
|
// console.log("field>>>:",field);
|
|
|
let lastTime = HealthDataCache.getLastTimeField(field);// 根据字段获取最近一次同步数据的时间戳
|
|
|
// console.log("lastTime1>>>:",lastTime);
|
|
|
|
|
|
let date = CommonUtil.getDateByTimestamp(lastTime);
|
|
|
packet[2] = 0x00; // 占位
|
|
|
packet[3] = 0x00; // 占位
|
|
|
|
|
|
packet[4] = "0x"+ date.year; //年
|
|
|
packet[5] = "0x"+ date.month; //月
|
|
|
packet[6] = "0x"+ date.day; //日
|
|
|
packet[7] = "0x"+ date.hours; //时
|
|
|
packet[8] = "0x"+ date.minutes; //分
|
|
|
packet[9] = "0x"+ date.seconds; //秒
|
|
|
|
|
|
packet.fill(0x00, 10, 15); // 填充其他字节为0
|
|
|
|
|
|
// 计算并设置 CRC 校验字节
|
|
|
const crc = calculateCRC(packet.subarray(0, 15));
|
|
|
packet[15] = crc;
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
// 获取步数总数
|
|
|
generateReadStepTotalDataPacket() {
|
|
|
// 创建 Uint8Array 数据包
|
|
|
const packet = new Uint8Array(16);
|
|
|
|
|
|
// 填充命令数据包
|
|
|
packet[0] = 0x51; // 命令字节
|
|
|
packet[1] = 0x00; // 读取记步总数据
|
|
|
packet.fill(0x00, 2, 15); // 填充其他字节为0
|
|
|
|
|
|
// 计算并设置 CRC 校验字节
|
|
|
const crc = calculateCRC(packet.subarray(0, 15));
|
|
|
packet[15] = crc;
|
|
|
|
|
|
return packet;
|
|
|
},
|
|
|
|
|
|
// 获取心率数据
|
|
|
generateReadHRDataPacket() {
|
|
|
// 创建 Uint8Array 数据包
|
|
|
const packet = new Uint8Array(16);
|
|
|
|
|
|
// 填充命令数据包
|
|
|
packet[0] = 0x55; // 命令字节
|
|
|
packet[1] = 0x00; // 读取记步总数据
|
|
|
packet.fill(0x00, 2, 15); // 填充其他字节为0
|
|
|
|
|
|
// 计算并设置 CRC 校验字节
|
|
|
const crc = calculateCRC(packet.subarray(0, 15));
|
|
|
packet[15] = crc;
|
|
|
|
|
|
return packet;
|
|
|
},
|
|
|
|
|
|
// 获取睡眠数据
|
|
|
generateReadSleepDataPacket() {
|
|
|
const packet = new Uint8Array(16);
|
|
|
// 填充命令数据包
|
|
|
packet[0] = 0x53;
|
|
|
packet[1] = 0x00;
|
|
|
packet.fill(0x00, 2, 15);
|
|
|
|
|
|
// 计算并设置 CRC 校验字节
|
|
|
const crc = calculateCRC(packet.subarray(0, 15));
|
|
|
packet[15] = crc;
|
|
|
|
|
|
return packet;
|
|
|
},
|
|
|
|
|
|
// 生成获取时间命令包
|
|
|
generateGetTimePacket() {
|
|
|
const packet = new Uint8Array(16);
|
|
|
|
|
|
// 填充数据
|
|
|
packet[0] = 0x41; // 命令字节
|
|
|
packet.fill(0x00, 1, 15); // 填充其他字节为 0
|
|
|
|
|
|
// 计算并设置 CRC 校验字节
|
|
|
const crc = calculateCRC(packet.subarray(0, 15));
|
|
|
packet[15] = crc;
|
|
|
|
|
|
return packet;
|
|
|
},
|
|
|
|
|
|
// 生成闹钟、吃药、喝水提示
|
|
|
generateAlarmClockPackets(maxLength, alarms) {
|
|
|
const commands = new Uint8Array(maxLength); // 创建固定长度的 Uint8Array
|
|
|
let offset = 0; // 当前写入位置
|
|
|
const packetLength = 39; // 单个指令长度
|
|
|
const maxAlarmsPerPacket = Math.floor(maxLength / packetLength); // 每条指令最大包含闹钟个数
|
|
|
const totalPackets = Math.ceil(alarms.length / maxAlarmsPerPacket); // 总共需要多少条指令
|
|
|
|
|
|
for (let i = 0; i < totalPackets; i++) {
|
|
|
const startIdx = i * maxAlarmsPerPacket;
|
|
|
const endIdx = Math.min(startIdx + maxAlarmsPerPacket, alarms.length);
|
|
|
|
|
|
for (let j = startIdx; j < endIdx; j++) {
|
|
|
const alarm = alarms[j];
|
|
|
|
|
|
if (offset + packetLength > maxLength) {
|
|
|
throw new Error("Maximum length exceeded, cannot fit all alarms.");
|
|
|
}
|
|
|
|
|
|
// 写入闹钟数据
|
|
|
commands[offset++] = 0x23; // 固定头部
|
|
|
commands[offset++] = alarms.length; // XX: 总闹钟个数
|
|
|
commands[offset++] = alarm.id; // AA: 闹钟编号
|
|
|
commands[offset++] = alarm.enabled ? 0x01 : 0x00; // BB: Enable/Disable
|
|
|
commands[offset++] = alarm.type; // CC: 闹钟类型 1:闹钟,2:吃药提示,3:喝水提示。
|
|
|
commands[offset++] = this.toBCD(alarm.hour); // DD: 小时 (BCD)
|
|
|
commands[offset++] = this.toBCD(alarm.minute); // EE: 分钟 (BCD)
|
|
|
commands[offset++] = alarm.weekEnable; // FF: 星期使能
|
|
|
commands[offset++] = alarm.text.length; // GG: 文本长度
|
|
|
|
|
|
// 写入文本内容或填充 0x00
|
|
|
for (let t = 0; t < 15; t++) {
|
|
|
// commands[offset++] = t < alarm.text.length ? alarm.text.charCodeAt(t) : 0x00;
|
|
|
if (t < alarm.text.length) {
|
|
|
const charCode = alarm.text.charCodeAt(t);
|
|
|
commands[offset++] = (charCode >> 8) & 0xFF; // 高字节
|
|
|
commands[offset++] = charCode & 0xFF; // 低字节
|
|
|
} else {
|
|
|
commands[offset++] = 0x00; // 填充 0x00
|
|
|
commands[offset++] = 0x00;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 尾巴
|
|
|
commands[offset++] = 0x23;
|
|
|
commands[offset++] = 0xFF;
|
|
|
return commands;
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
* 获取闹钟数据
|
|
|
*/
|
|
|
getAlarmClock(){
|
|
|
const packet = new Uint8Array(16);
|
|
|
packet[0] = 0x57;
|
|
|
packet[1] = 0x00;
|
|
|
packet.fill(0x00, 2, 15);
|
|
|
// 计算并设置 CRC 校验字节
|
|
|
const crc = calculateCRC(packet.subarray(0, 15));
|
|
|
packet[15] = crc;
|
|
|
return packet;
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
*
|
|
|
* @param {开始 时} startHour
|
|
|
* @param {开始 分} startMinute
|
|
|
* @param {结束 时} endHour
|
|
|
* @param {结束 分} endMinute
|
|
|
* @param {星期天 至 星期六周} weekDays
|
|
|
* @param {运动提醒周期,单位为分钟} reminderInterval
|
|
|
* @param {最少运动步数} minSteps
|
|
|
* @param {运动总开关} sportSwitch
|
|
|
*/
|
|
|
generateSedentaryPacket(startHour, startMinute, endHour, endMinute, weekDays, reminderInterval, minSteps, sportSwitch){
|
|
|
const packet = new Uint8Array(16);
|
|
|
// 填充命令数据包
|
|
|
packet[0] = 0x25; // 命令字节
|
|
|
|
|
|
// 开始运动时间
|
|
|
packet[1] = startHour; // AA: 开始运动时间的小时部分
|
|
|
packet[2] = startMinute; // BB: 开始运动时间的分钟部分
|
|
|
|
|
|
// 结束运动时间
|
|
|
packet[3] = endHour; // CC: 结束运动时间的小时部分
|
|
|
packet[4] = endMinute; // DD: 结束运动时间的分钟部分
|
|
|
|
|
|
let binaryWeek = CommonUtil.getWeekBinary(weekDays);
|
|
|
packet[5] = binaryWeek; // EE: 星期日到星期六
|
|
|
// 运动提醒周期(FF)
|
|
|
packet[6] = reminderInterval; // FF: 运动提醒周期,单位为分钟
|
|
|
// 最少运动步数(GG)
|
|
|
packet[7] = minSteps; // GG: 最少运动步数
|
|
|
// 运动总开关(HH)
|
|
|
packet[8] = sportSwitch === 1 ? 0x01 : 0x00; // HH: 运动总开关 1-开启 0-关闭
|
|
|
// 填充后续字节为0x00
|
|
|
packet.fill(0x00, 9, 15);
|
|
|
// 计算CRC并设置CRC字节(CRC算法需根据协议提供)
|
|
|
const crc = calculateCRC(packet.subarray(0, 15));
|
|
|
packet[15] = crc; // CRC校验字节
|
|
|
return packet;
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
* 获取久坐数据
|
|
|
*/
|
|
|
getSedentary(){
|
|
|
const packet = new Uint8Array(16);
|
|
|
packet[0] = 0x26;
|
|
|
packet[1] = 0x00;
|
|
|
packet.fill(0x00, 2, 15);
|
|
|
// 计算并设置 CRC 校验字节
|
|
|
const crc = calculateCRC(packet.subarray(0, 15));
|
|
|
packet[15] = crc;
|
|
|
return packet;
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
* 设置运动目标
|
|
|
* @param {步数目标} targetSteps
|
|
|
* @param {目标时间} targetTime
|
|
|
* @param {距离目标} targetDistance
|
|
|
* @param {卡路里目标} targetCalories
|
|
|
* @param {睡眠目标} targetSleep
|
|
|
*/
|
|
|
generateStepTargetPacket(targetSteps, targetTime, targetDistance, targetCalories, targetSleep){
|
|
|
// 1. 组装目标步数(4字节,低字节在前)
|
|
|
const stepBytes = [
|
|
|
targetSteps & 0xFF, // AA: 低字节
|
|
|
(targetSteps >> 8) & 0xFF, // BB: 次低字节
|
|
|
(targetSteps >> 16) & 0xFF, // CC: 次高字节
|
|
|
(targetSteps >> 24) & 0xFF // DD: 高字节
|
|
|
];
|
|
|
// 2. 组装目标步数时间(2字节,分钟,低字节在前)
|
|
|
const timeBytes = [
|
|
|
targetTime & 0xFF, // EE: 低字节
|
|
|
(targetTime >> 8) & 0xFF // FF: 高字节
|
|
|
];
|
|
|
// 3. 组装目标距离(2字节,低字节在前)
|
|
|
const distanceBytes = [
|
|
|
targetDistance & 0xFF, // GG: 低字节
|
|
|
(targetDistance >> 8) & 0xFF // HH: 高字节
|
|
|
];
|
|
|
// 4. 组装目标卡路里(2字节,低字节在前)
|
|
|
const caloriesBytes = [
|
|
|
targetCalories & 0xFF, // II: 低字节
|
|
|
(targetCalories >> 8) & 0xFF // JJ: 高字节
|
|
|
];
|
|
|
// 5. 组装目标睡眠时间(2字节,分钟,低字节在前)
|
|
|
const sleepBytes = [
|
|
|
targetSleep & 0xFF, // KK: 低字节
|
|
|
(targetSleep >> 8) & 0xFF // LL: 高字节
|
|
|
];
|
|
|
// 6. 组装命令字节(0x0B)
|
|
|
const commandByte = 0x0B;
|
|
|
// 7. 保留字节
|
|
|
const reservedBytes = [0x00, 0x00];
|
|
|
// 8. 拼接所有字节
|
|
|
const packet = [
|
|
|
commandByte, ...stepBytes, ...timeBytes, ...distanceBytes,
|
|
|
...caloriesBytes, ...sleepBytes, ...reservedBytes
|
|
|
];
|
|
|
// 9. 计算CRC校验字节
|
|
|
const crc = calculateCRC(packet); // 需要实现CRC校验算法
|
|
|
// 10. 将CRC字节添加到数据包末尾
|
|
|
packet.push(crc);
|
|
|
return new Uint8Array(packet);
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
* 获取运动目标
|
|
|
*/
|
|
|
getStepTarget(){
|
|
|
const packet = new Uint8Array(16);
|
|
|
packet[0] = 0x4B;
|
|
|
packet[1] = 0x00;
|
|
|
packet.fill(0x00, 2, 15);
|
|
|
// 计算并设置 CRC 校验字节
|
|
|
const crc = calculateCRC(packet.subarray(0, 15));
|
|
|
packet[15] = crc;
|
|
|
return packet;
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
*
|
|
|
* @param {当前天气} weather
|
|
|
* @param {当前气温} temperature
|
|
|
* @param {最大气温} maxTemperature
|
|
|
* @param {最小气温} minTemperature
|
|
|
* @param {空气质量} aqi
|
|
|
* @param {地址长度} length
|
|
|
* @param {地址,最大32位} address
|
|
|
*/
|
|
|
generateWeatherPacket(weather,temperature,maxTemperature,minTemperature,aqi,address){
|
|
|
let aqiInt = parseInt(aqi);
|
|
|
const packet = new Uint8Array(8);
|
|
|
packet[0] = 0x15; // 命令字节
|
|
|
const weatherCode = WeatherUtils.getWeatherCode(weather);
|
|
|
packet[1] = weatherCode;
|
|
|
packet[2] = parseInt(temperature);
|
|
|
packet[4] = parseInt(maxTemperature);
|
|
|
packet[3] = parseInt(minTemperature);
|
|
|
packet[5] = aqiInt & 0xFF;// 低字节
|
|
|
packet[6] = (aqiInt >> 8) & 0xFF; //高字节
|
|
|
// 将地址转换为ASCII字节数组
|
|
|
const addressBytes = CommonUtil.stringToBytes(address);
|
|
|
packet[7] = parseInt(addressBytes.length);
|
|
|
|
|
|
const text = new Uint8Array(addressBytes.length);
|
|
|
// 填充地址到数据包中
|
|
|
for (let i = 0; i < addressBytes.length; i++) {
|
|
|
text[i] = addressBytes[i];
|
|
|
}
|
|
|
// 拼接 header 和 payload
|
|
|
const fullPackage = new Uint8Array(packet.length + addressBytes.length);
|
|
|
fullPackage.set(packet, 0);
|
|
|
fullPackage.set(text, 8);
|
|
|
return fullPackage;
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
* 清空历史数据
|
|
|
*/
|
|
|
removeHistoryData(){
|
|
|
const packet = new Uint8Array(16);
|
|
|
packet[0] = 0x61;
|
|
|
packet[1] = 0x00;
|
|
|
packet.fill(0x00, 2, 15);
|
|
|
// 计算并设置 CRC 校验字节
|
|
|
const crc = calculateCRC(packet.subarray(0, 15));
|
|
|
packet[15] = crc;
|
|
|
return packet;
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
// 导出模块
|
|
|
module.exports = DataPacket;
|