// 数据包组装模块 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; }, // 获取睡眠数据 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;