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.

456 lines
15 KiB
JavaScript

5 months ago
// 数据包组装模块
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;
},
// 获取睡眠数据
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;
},
}
// 导出模块
module.exports = DataPacket;