/** * HBY102J 晶全应用层协议:下行 FA … FF,上行 FB/FC … FF。 * 与 HBY100-J 使用同一颗蓝牙模组 / 同一套 GATT(AE30 + AE03 写 + AE02 通知), * 见 `api/100J/HBY100-J.js` 中 SERVICE_UUID / WRITE / NOTIFY;仅帧语义与 100J 不同。 */ export const HBY102J_SERVICE = '0000AE30-0000-1000-8000-00805F9B34FB' export const HBY102J_WRITE = '0000AE03-0000-1000-8000-00805F9B34FB' export const HBY102J_NOTIFY = '0000AE02-0000-1000-8000-00805F9B34FB' function buildFaFrame(func, dataBytes = []) { return [0xfa, func & 0xff, ...dataBytes.map((b) => b & 0xff), 0xff] } const BYTE_TO_RADAR = { 0: 'status_off', 1: 'status_2M', 2: 'status_4M', 3: 'status_7M', 4: 'status_10M' } const BYTE_TO_LED = { 0: 'led_off', 1: 'led_flash', 2: 'led_low_flash', 3: 'led_steady', 4: 'led_alarm' } const RADAR_KEY_TO_BYTE = { status_off: 0, status_2M: 1, status_4M: 2, status_7M: 3, status_10M: 4, status_on: 1 } const LED_KEY_TO_BYTE = { led_off: 0, led_flash: 1, led_low_flash: 2, led_steady: 3, led_alarm: 4 } export function encodeChannelSet(channelDecimal1to80) { const ch = Math.max(1, Math.min(80, Number(channelDecimal1to80) || 1)) return buildFaFrame(0x21, [ch]) } export function encodeOnline(on) { return buildFaFrame(0x22, [on ? 1 : 0]) } export function encodeRadarFromUiKey(sta_RadarType) { const b = RADAR_KEY_TO_BYTE[sta_RadarType] !== undefined ? RADAR_KEY_TO_BYTE[sta_RadarType] : 0 return buildFaFrame(0x23, [b]) } export function encodeWarningLightFromLedKey(ledKey) { const m = LED_KEY_TO_BYTE[ledKey] !== undefined ? LED_KEY_TO_BYTE[ledKey] : 0 return buildFaFrame(0x24, [m]) } export function encodeQueryOnlineCount() { return buildFaFrame(0x25, [0]) } export function encodeQueryPower() { return buildFaFrame(0x26, []) } function formatMacFromBytes(u8, start, len) { const hex = [] for (let i = 0; i < len; i++) { hex.push(u8[start + i].toString(16).padStart(2, '0')) } return hex.join(':').toUpperCase() } /** * 解析设备上行一帧(Notify),输出与 HBY102 页 formData 对齐的字段 */ export function parseHby102jUplink(u8) { const out = {} if (!u8 || u8.length < 3) return out const h = u8[0] const func = u8[1] const tail = u8[u8.length - 1] if (tail !== 0xff && u8.length >= 4) { // 非标准结尾时仍尽量解析 } if (h === 0xfc && u8.length >= 8 && u8[7] === 0xff) { out.sta_address = formatMacFromBytes(u8, 1, 6) return out } if (h !== 0xfb) return out switch (func) { case 0x21: if (u8.length >= 4) { out.sta_Channel = u8[2] } break case 0x22: out.sta_Online = u8[2] === 1 ? 'E49_on' : 'E49_off' break case 0x23: out.sta_RadarType = BYTE_TO_RADAR[u8[2]] !== undefined ? BYTE_TO_RADAR[u8[2]] : 'status_off' break case 0x24: out.sta_LedType = BYTE_TO_LED[u8[2]] !== undefined ? BYTE_TO_LED[u8[2]] : 'led_off' break case 0x25: if (u8.length >= 4) { out.sta_onlineQuantity = u8[2] } break case 0x26: if (u8.length >= 5) { out.sta_PowerPercent = u8[2] out.sta_charge = u8[3] } break case 0x27: if (u8.length >= 9) { out.sta_PowerPercent = u8[2] out.sta_charge = u8[3] out.sta_Channel = u8[4] out.sta_LedType = BYTE_TO_LED[u8[5]] !== undefined ? BYTE_TO_LED[u8[5]] : 'led_off' out.sta_RadarType = BYTE_TO_RADAR[u8[6]] !== undefined ? BYTE_TO_RADAR[u8[6]] : 'status_off' out.sta_Online = u8[7] === 1 ? 'E49_on' : 'E49_off' } break case 0x28: if (u8.length >= 10) { const peerMac = formatMacFromBytes(u8, 2, 6) const ev = u8[8] if (ev === 0x01) { out.sta_sosadd = peerMac out.sta_Intrusion = 1 } else if (ev === 0x02) { out.sta_sosadd_off = peerMac } else if (ev === 0x03) { out.sta_tomac = peerMac } } break default: break } return out }