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.

487 lines
16 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 { 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-50:最暗5:最亮。} SBLevel
*/
generateWatchBasicPacket(WRSFlag,SBLevel){
const packet = new Uint8Array(16);
// 填充命令数据包
packet[0] = 0x03; // 命令字节
packet[1] = 0x80; // 固定值距离单位0x81MILE0x80:KM
packet[2] = 0x80; // 固定值12小时24小时显示0x81: 12小时制 0x8024小时制度
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;