/* ================================================================ * jb_protocol.c — 见宝 IOT 协议实现(自动生成) * 设备: 侠客猫激光 | 生成时间: 2026-07-28 15:42:40 * ================================================================ */ #include "jb_protocol.h" #include "jb_common.h" #include "jb_product.h" #include "jb_ringbuffer.h" #include "stdio.h" /* ════════════════════════════════════════════════════════════════ * 全局实例 * ════════════════════════════════════════════════════════════════ */ jb_protocol_t jbProtocol; static jb_ringbuffer_t rb; /* UART 接收缓冲区 */ /* ════════════════════════════════════════════════════════════════ * jbInit — 协议层初始化 * ════════════════════════════════════════════════════════════════ */ void jbInit(void) { memset(&jbProtocol, 0, sizeof(jb_protocol_t)); jbRbInit(&rb); } /* ════════════════════════════════════════════════════════════════ * jbPutData — UART 中断将数据压入环形缓冲区 * ════════════════════════════════════════════════════════════════ */ int32_t jbPutData(uint8_t *buf, uint32_t len) { if (!buf) { return -1; } for (uint32_t i = 0; i < len; i++) { jbRbPush(&rb, buf[i]); } return (int32_t)len; } /* ════════════════════════════════════════════════════════════════ * jbSendFrame — 组装协议帧并发送到 UART * 返回帧总长度,< 0 表示出错 * ════════════════════════════════════════════════════════════════ */ static int32_t jbSendFrame(uint8_t cmd, uint8_t sn, const uint8_t *payload, uint16_t pLen) { uint8_t buf[MAX_PACKAGE_LEN]; uint16_t lenField = pLen + 3; /* CMD + SN + payload + CKSUM */ uint16_t total = lenField + 4; if (total > MAX_PACKAGE_LEN) { return -1; } /* 55 AA | LEN_H LEN_L | CMD | SN | payload | CKSUM */ buf[0] = PKT_HEAD_0; buf[1] = PKT_HEAD_1; jbWriteU16BE(&buf[2], lenField); buf[4] = cmd; buf[5] = sn; if (payload && pLen > 0) { memcpy(&buf[6], payload, pLen); } buf[total - 1] = jbChecksum(buf, total); return uartWrite(buf, total); } /* 发送单字节结果应答 */ static void jbSendAck(uint8_t cmd, uint8_t sn, uint8_t result) { jbSendFrame(cmd, sn, &result, 1); } /* ════════════════════════════════════════════════════════════════ * jbGetOnePacket — 从环形缓冲区提取完整帧 * 返回: 0 = 成功, 1 = 需要更多数据, -2 = 校验和错误 * ════════════════════════════════════════════════════════════════ */ static int8_t jbGetOnePacket(uint8_t *out, uint16_t *outLen) { static uint8_t state = 0; static uint16_t expect = 0; static uint16_t idx = 0; static uint8_t prev = 0; while (jbRbAvailable(&rb)) { uint8_t b = jbRbPop(&rb); if (state == 0) { /* 搜索同步头 55 AA */ if (prev == PKT_HEAD_0 && b == PKT_HEAD_1) { out[0] = PKT_HEAD_0; out[1] = PKT_HEAD_1; idx = 2; state = 1; } } else if (state == 1) { /* 接收 LEN(2B) + CMD + SN */ out[idx++] = b; if (idx == 6) { expect = 4 + jbReadU16BE(&out[2]); if (expect > MAX_PACKAGE_LEN || expect < 7) { state = 0; } else { state = 2; } } } else { /* 接收剩余数据 */ out[idx++] = b; if (idx >= expect) { state = 0; if (out[expect - 1] == jbChecksum(out, expect)) { *outLen = expect; return 0; } return -2; } } prev = b; } return 1; } /* ════════════════════════════════════════════════════════════════ * jbPackDps — dataPoint → outBuf[bitmap | values] * mask 指定要打包的 DP(bit 序号 = DP ID) * 返回 outBuf 总字节数(bitmap + values) * 仅打包 RO / RW 方向的 DP * ════════════════════════════════════════════════════════════════ */ static uint16_t jbPackDps(const jb_data_point_t *dp, const uint8_t *mask, uint8_t *outBuf) { uint8_t *bitmap = outBuf; uint8_t *out = outBuf + DP_BITMAP_SIZE; uint16_t valIdx = 0; memset(bitmap, 0, DP_BITMAP_SIZE); /* DP0 开关 (bool, 1B, RW) */ if (mask[DP_ID_POWER / 8] & (1 << (DP_ID_POWER % 8))) { bitmap[DP_ID_POWER / 8] |= (1 << (DP_ID_POWER % 8)); out[valIdx++] = dp->power ? 1 : 0; } /* DP1 自动模式 (enum, 1B, RW) */ if (mask[DP_ID_PLAY_MODE / 8] & (1 << (DP_ID_PLAY_MODE % 8))) { bitmap[DP_ID_PLAY_MODE / 8] |= (1 << (DP_ID_PLAY_MODE % 8)); out[valIdx++] = (uint8_t)dp->play_mode; } /* DP3 低电量报警 (bool, 1B, RO) */ if (mask[DP_ID_ALM_LOW_BATTERY / 8] & (1 << (DP_ID_ALM_LOW_BATTERY % 8))) { bitmap[DP_ID_ALM_LOW_BATTERY / 8] |= (1 << (DP_ID_ALM_LOW_BATTERY % 8)); out[valIdx++] = dp->alm_low_battery ? 1 : 0; } /* DP4 声音开关 (bool, 1B, RW) */ if (mask[DP_ID_BEEP_TRIG / 8] & (1 << (DP_ID_BEEP_TRIG % 8))) { bitmap[DP_ID_BEEP_TRIG / 8] |= (1 << (DP_ID_BEEP_TRIG % 8)); out[valIdx++] = dp->beep_trig ? 1 : 0; } /* DP5 电量上报 (uint8, 1B, RO) */ if (mask[DP_ID_BATTERY / 8] & (1 << (DP_ID_BATTERY % 8))) { bitmap[DP_ID_BATTERY / 8] |= (1 << (DP_ID_BATTERY % 8)); out[valIdx++] = (uint8_t)dp->battery; } /* DP6 震动触发开机 (bool, 1B, RW) */ if (mask[DP_ID_SHAKE_WAKE / 8] & (1 << (DP_ID_SHAKE_WAKE % 8))) { bitmap[DP_ID_SHAKE_WAKE / 8] |= (1 << (DP_ID_SHAKE_WAKE % 8)); out[valIdx++] = dp->shake_wake ? 1 : 0; } /* DP7 勿扰模式开关 (raw, 13B, RW) */ if (mask[DP_ID_NO_DISTURB_ENABLE / 8] & (1 << (DP_ID_NO_DISTURB_ENABLE % 8))) { bitmap[DP_ID_NO_DISTURB_ENABLE / 8] |= (1 << (DP_ID_NO_DISTURB_ENABLE % 8)); memcpy(&out[valIdx], dp->no_disturb_enable, 13); valIdx += 13; } /* DP8 预留DP1 (uint32, 4B, RW) */ if (mask[DP_ID_RESERVA_DP1 / 8] & (1 << (DP_ID_RESERVA_DP1 % 8))) { bitmap[DP_ID_RESERVA_DP1 / 8] |= (1 << (DP_ID_RESERVA_DP1 % 8)); jbWriteU32BE(&out[valIdx], (uint32_t)dp->reserva_dp1); valIdx += 4; } /* DP9 预留DP2 (uint32, 4B, RW) */ if (mask[DP_ID_RESERVA_DP2 / 8] & (1 << (DP_ID_RESERVA_DP2 % 8))) { bitmap[DP_ID_RESERVA_DP2 / 8] |= (1 << (DP_ID_RESERVA_DP2 % 8)); jbWriteU32BE(&out[valIdx], (uint32_t)dp->reserva_dp2); valIdx += 4; } /* DP10 预留DP3 (uint32, 4B, RW) */ if (mask[DP_ID_RESERVA_DP3 / 8] & (1 << (DP_ID_RESERVA_DP3 % 8))) { bitmap[DP_ID_RESERVA_DP3 / 8] |= (1 << (DP_ID_RESERVA_DP3 % 8)); jbWriteU32BE(&out[valIdx], (uint32_t)dp->reserva_dp3); valIdx += 4; } return DP_BITMAP_SIZE + valIdx; } /* ════════════════════════════════════════════════════════════════ * jbUnpackDps — bitmap+values → dataPoint + event_info * 仅解包 WO / RW 方向的 DP * ════════════════════════════════════════════════════════════════ */ static void jbUnpackDps(const uint8_t *bitmap, const uint8_t *values, uint16_t valLen, jb_data_point_t *dp, jb_event_info_t *info) { uint16_t valIdx = 0; if (info) { info->count = 0; } /* DP0 开关 (bool, 1B, RW) */ if (bitmap[DP_ID_POWER / 8] & (1 << (DP_ID_POWER % 8))) { if (valIdx < valLen) { dp->power = (values[valIdx++] != 0); } if (info && info->count < DP_ID_MAX) { info->dp_ids[info->count++] = DP_ID_POWER; } } /* DP1 自动模式 (enum, 1B, RW) */ if (bitmap[DP_ID_PLAY_MODE / 8] & (1 << (DP_ID_PLAY_MODE % 8))) { if (valIdx < valLen) { dp->play_mode = values[valIdx++]; } if (info && info->count < DP_ID_MAX) { info->dp_ids[info->count++] = DP_ID_PLAY_MODE; } } /* DP2 手动模式 (enum, 1B, WO) */ if (bitmap[DP_ID_HAND_MODE / 8] & (1 << (DP_ID_HAND_MODE % 8))) { if (valIdx < valLen) { dp->hand_mode = values[valIdx++]; } if (info && info->count < DP_ID_MAX) { info->dp_ids[info->count++] = DP_ID_HAND_MODE; } } /* DP4 声音开关 (bool, 1B, RW) */ if (bitmap[DP_ID_BEEP_TRIG / 8] & (1 << (DP_ID_BEEP_TRIG % 8))) { if (valIdx < valLen) { dp->beep_trig = (values[valIdx++] != 0); } if (info && info->count < DP_ID_MAX) { info->dp_ids[info->count++] = DP_ID_BEEP_TRIG; } } /* DP6 震动触发开机 (bool, 1B, RW) */ if (bitmap[DP_ID_SHAKE_WAKE / 8] & (1 << (DP_ID_SHAKE_WAKE % 8))) { if (valIdx < valLen) { dp->shake_wake = (values[valIdx++] != 0); } if (info && info->count < DP_ID_MAX) { info->dp_ids[info->count++] = DP_ID_SHAKE_WAKE; } } /* DP7 勿扰模式开关 (raw, 13B, RW) */ if (bitmap[DP_ID_NO_DISTURB_ENABLE / 8] & (1 << (DP_ID_NO_DISTURB_ENABLE % 8))) { if (valIdx + 13 <= valLen) { memcpy(dp->no_disturb_enable, &values[valIdx], 13); valIdx += 13; } if (info && info->count < DP_ID_MAX) { info->dp_ids[info->count++] = DP_ID_NO_DISTURB_ENABLE; } } /* DP8 预留DP1 (uint32, 4B, RW) */ if (bitmap[DP_ID_RESERVA_DP1 / 8] & (1 << (DP_ID_RESERVA_DP1 % 8))) { if (valIdx + 4 <= valLen) { dp->reserva_dp1 = (uint32_t)jbReadU32BE(&values[valIdx]); valIdx += 4; } if (info && info->count < DP_ID_MAX) { info->dp_ids[info->count++] = DP_ID_RESERVA_DP1; } } /* DP9 预留DP2 (uint32, 4B, RW) */ if (bitmap[DP_ID_RESERVA_DP2 / 8] & (1 << (DP_ID_RESERVA_DP2 % 8))) { if (valIdx + 4 <= valLen) { dp->reserva_dp2 = (uint32_t)jbReadU32BE(&values[valIdx]); valIdx += 4; } if (info && info->count < DP_ID_MAX) { info->dp_ids[info->count++] = DP_ID_RESERVA_DP2; } } /* DP10 预留DP3 (uint32, 4B, RW) */ if (bitmap[DP_ID_RESERVA_DP3 / 8] & (1 << (DP_ID_RESERVA_DP3 % 8))) { if (valIdx + 4 <= valLen) { dp->reserva_dp3 = (uint32_t)jbReadU32BE(&values[valIdx]); valIdx += 4; } if (info && info->count < DP_ID_MAX) { info->dp_ids[info->count++] = DP_ID_RESERVA_DP3; } } } /* ════════════════════════════════════════════════════════════════ * jbBuildDiffMask — 比较上次与当前值,为变化的可上报 DP 置 mask 位 * 返回: 1 = 有 DP 发生变化, 0 = 全部未变化 * ════════════════════════════════════════════════════════════════ */ static uint8_t jbBuildDiffMask(const jb_data_point_t *last, const jb_data_point_t *cur, uint8_t *mask) { uint8_t hasChange = 0; memset(mask, 0, DP_BITMAP_SIZE); /* DP0 开关 */ if (last->power != cur->power) { mask[DP_ID_POWER / 8] |= (1 << (DP_ID_POWER % 8)); hasChange = 1; } /* DP1 自动模式 */ if (last->play_mode != cur->play_mode) { mask[DP_ID_PLAY_MODE / 8] |= (1 << (DP_ID_PLAY_MODE % 8)); hasChange = 1; } /* DP3 低电量报警 */ if (last->alm_low_battery != cur->alm_low_battery) { mask[DP_ID_ALM_LOW_BATTERY / 8] |= (1 << (DP_ID_ALM_LOW_BATTERY % 8)); hasChange = 1; } /* DP4 声音开关 */ if (last->beep_trig != cur->beep_trig) { mask[DP_ID_BEEP_TRIG / 8] |= (1 << (DP_ID_BEEP_TRIG % 8)); hasChange = 1; } /* DP5 电量上报 */ if (last->battery != cur->battery) { mask[DP_ID_BATTERY / 8] |= (1 << (DP_ID_BATTERY % 8)); hasChange = 1; } /* DP6 震动触发开机 */ if (last->shake_wake != cur->shake_wake) { mask[DP_ID_SHAKE_WAKE / 8] |= (1 << (DP_ID_SHAKE_WAKE % 8)); hasChange = 1; } /* DP7 勿扰模式开关 */ if (memcmp(last->no_disturb_enable, cur->no_disturb_enable, 13)) { mask[DP_ID_NO_DISTURB_ENABLE / 8] |= (1 << (DP_ID_NO_DISTURB_ENABLE % 8)); hasChange = 1; } /* DP8 预留DP1 */ if (last->reserva_dp1 != cur->reserva_dp1) { mask[DP_ID_RESERVA_DP1 / 8] |= (1 << (DP_ID_RESERVA_DP1 % 8)); hasChange = 1; } /* DP9 预留DP2 */ if (last->reserva_dp2 != cur->reserva_dp2) { mask[DP_ID_RESERVA_DP2 / 8] |= (1 << (DP_ID_RESERVA_DP2 % 8)); hasChange = 1; } /* DP10 预留DP3 */ if (last->reserva_dp3 != cur->reserva_dp3) { mask[DP_ID_RESERVA_DP3 / 8] |= (1 << (DP_ID_RESERVA_DP3 % 8)); hasChange = 1; } return hasChange; } /* 置全量掩码(所有可上报 DP)*/ static void jbSetFullMask(uint8_t *mask) { memset(mask, 0xFF, DP_BITMAP_SIZE); if (DP_COUNT % 8) { mask[DP_BITMAP_SIZE - 1] &= (1 << (DP_COUNT % 8)) - 1; } } /* ════════════════════════════════════════════════════════════════ * jbSendReqWithAck — 发送请求帧并登记 ACK 重传 * * 与 jbSendFrame 不同,本函数把完整帧缓存到 waitAck.buf, * 并启动 ACK 等待。本协议约定 ACK 命令码 = 请求命令码 + 1 * (如 0x10→0x11),收到对应 ACK 时由 jbWaitAckCheck() 清除 * 等待;若 SEND_MAX_TIME 内未收到,jbAckRetry() 自动重传, * 最多 SEND_MAX_NUM 次。 * 返回 uartWrite 结果(<0 出错)。 * ════════════════════════════════════════════════════════════════ */ static int32_t jbSendReqWithAck(uint8_t cmd, uint8_t sn, const uint8_t *payload, uint16_t pLen) { uint16_t lenField = pLen + 3; /* CMD + SN + payload + CKSUM */ uint16_t total = lenField + 4; /* + 55 AA + LEN(2) */ if (total > MAX_PACKAGE_LEN) { return -1; } uint8_t buf[MAX_PACKAGE_LEN]; buf[0] = PKT_HEAD_0; buf[1] = PKT_HEAD_1; jbWriteU16BE(&buf[2], lenField); buf[4] = cmd; buf[5] = sn; if (payload && pLen > 0) { memcpy(&buf[6], payload, pLen); } buf[total - 1] = jbChecksum(buf, total); /* 缓存整帧,供超时重传 */ memcpy(jbProtocol.waitAck.buf, buf, total); jbProtocol.waitAck.len = total; jbProtocol.waitAck.active = 1; jbProtocol.waitAck.sn = sn; jbProtocol.waitAck.retry = 0; jbProtocol.waitAck.sendTime = jbGetTimerCount(); return uartWrite(buf, total); } /* ════════════════════════════════════════════════════════════════ * jbAckRetry — ACK 超时重传 * ════════════════════════════════════════════════════════════════ */ static void jbAckRetry(void) { if (!jbProtocol.waitAck.active) { return; } if (jbProtocol.waitAck.retry >= SEND_MAX_NUM) { memset(&jbProtocol.waitAck, 0, sizeof(jb_wait_ack_t)); return; } if (jbGetTimerCount() - jbProtocol.waitAck.sendTime > SEND_MAX_TIME) { uartWrite(jbProtocol.waitAck.buf, jbProtocol.waitAck.len); jbProtocol.waitAck.retry++; jbProtocol.waitAck.sendTime = jbGetTimerCount(); } } /* ════════════════════════════════════════════════════════════════ * jbWaitAckCheck — ACK 等待处理 * * 收到一个 BLE→MCU 的 ACK 帧后,在每个 ACK 的 case 中调用本函数。 * 若当前有等待中的请求,且其命令码 +1 等于本帧命令码 * (本协议约定:ACK 命令码 = 请求命令码 + 1,如 0x10→0x11), * 则清除 waitAck、停止重传。 * * head: 该 ACK 帧首地址(即 jbProtocol.rxBuf)。 * 返回: -1=参数错误; 1=命中并清除等待; 0=未命中(无等待或非对应 ACK)。 * ════════════════════════════════════════════════════════════════ */ static int8_t jbWaitAckCheck(const uint8_t *head) { if (NULL == head) { return -1; } if (jbProtocol.waitAck.active && (jbProtocol.waitAck.buf[4] + 1 == head[4])) { uint8_t cmd = head[4]; uint8_t result = head[6]; /* 应答结果字节固定位于偏移 6 */ memset(&jbProtocol.waitAck, 0, sizeof(jb_wait_ack_t)); jbOnAck(cmd, result); return 1; } return 0; } /* ════════════════════════════════════════════════════════════════ * jbSendReport — 发送主动上报 (0x07) * * mask 指定本次上报包含哪些 DP: * - 差分掩码(来自 jbBuildDiffMask) → 仅上报变化的 DP * - 全量掩码(来自 jbSetFullMask) → 定时全量同步 * ════════════════════════════════════════════════════════════════ */ static void jbSendReport(jb_data_point_t *data, const uint8_t *mask) { uint8_t payload[MAX_PACKAGE_LEN]; uint16_t plen = jbPackDps(data, mask, payload); uint8_t sn = jbProtocol.sn++; jbSendReqWithAck(CMD_REPORT, sn, payload, plen); } /* ════════════════════════════════════════════════════════════════ * jbReportPolicy — 变化上报(差分) + 定时上报(全量) * * 策略: * 1. DP 发生变化 → 仅上报变化的 DP(差分掩码) * 2. 每隔 REPORT_PERIOD ms → 上报全部可上报 DP(全量同步) * ════════════════════════════════════════════════════════════════ */ static void jbReportPolicy(jb_data_point_t *data) { uint32_t now = jbGetTimerCount(); uint8_t diffMask[DP_BITMAP_SIZE]; /* ── 1. 变化上报(差分) ── */ uint8_t hasChange = jbBuildDiffMask(&jbProtocol.lastData, data, diffMask); if (hasChange && !jbProtocol.waitAck.active && (now - jbProtocol.lastChangeTime > REPORT_DEBOUNCE)) { jbSendReport(data, diffMask); memcpy(&jbProtocol.lastData, data, sizeof(jb_data_point_t)); jbProtocol.lastChangeTime = now; jbProtocol.lastReportTime = now; return; } /* ── 2. 定时全量上报 ── */ if (!jbProtocol.waitAck.active && (now - jbProtocol.lastReportTime > REPORT_PERIOD)) { uint8_t fullMask[DP_BITMAP_SIZE]; jbSetFullMask(fullMask); jbSendReport(data, fullMask); memcpy(&jbProtocol.lastData, data, sizeof(jb_data_point_t)); jbProtocol.lastReportTime = now; } } /* ════════════════════════════════════════════════════════════════ * 命令处理函数(BLE → MCU 下行帧) * ════════════════════════════════════════════════════════════════ */ /* ── 0x03 APP 下发控制 ── */ static void jbHandleControl(uint8_t *frame, uint16_t len) { uint8_t sn = frame[5]; uint8_t *bitmap = &frame[6]; uint8_t *values = &frame[6 + DP_BITMAP_SIZE]; uint16_t valLen = len - 7 - DP_BITMAP_SIZE; jb_event_info_t info; jb_data_point_t ctrlData; memset(&ctrlData, 0, sizeof(ctrlData)); jbUnpackDps(bitmap, values, valLen, &ctrlData, &info); /* 分发给用户层;用户在回调中更新 gDevData */ jbEventProcess(&info, &ctrlData); jbSendAck(CMD_CONTROL_ACK, sn, JB_OK); } /* ── 0x05 APP 读取请求 ── */ static void jbHandleRead(uint8_t *frame, uint16_t len, jb_data_point_t *data) { uint8_t sn = frame[5]; uint8_t *reqBitmap = &frame[6]; uint8_t mask[DP_BITMAP_SIZE]; memcpy(mask, reqBitmap, DP_BITMAP_SIZE); /* bitmap 全 1(=0xFF...) 表示读取全部 DP */ uint8_t allOnes = 1; for (int i = 0; i < DP_BITMAP_SIZE; i++) { if (mask[i] != 0xFF) { allOnes = 0; break; } } if (allOnes) { jbSetFullMask(mask); } uint8_t payload[MAX_PACKAGE_LEN]; uint16_t plen = jbPackDps(data, mask, payload); jbSendFrame(CMD_READ_ACK, sn, payload, plen); } /* ── 0x01 BLE 请求设备身份信息 ── */ static void jbHandleIdentityReq(uint8_t *frame, uint16_t len) { uint8_t sn = frame[5]; uint8_t payload[64]; uint16_t p = 0; (void)len; payload[p++] = JB_OK; jbWriteU16BE(&payload[p], JB_PROTOCOL_VERSION); p += 2; memset(&payload[p], 0, 8); memcpy(&payload[p], JB_CATEGORY_CODE, sizeof(JB_CATEGORY_CODE) - 1); p += 8; memset(&payload[p], 0, 8); memcpy(&payload[p], JB_PRODUCT_CODE, sizeof(JB_PRODUCT_CODE) - 1); p += 8; memset(&payload[p], JB_DEVICE_SN, 16); p += 16; jbWriteU32BE(&payload[p], (uint32_t)(JB_DEVICE_CAPABILITY >> 32)); p += 4; jbWriteU32BE(&payload[p], (uint32_t)(JB_DEVICE_CAPABILITY & 0xFFFFFFFFULL)); p += 4; jbSendFrame(CMD_IDENTITY_ACK, sn, payload, p); } /* ── 0x40 BLE 请求 MCU 信息 ── */ static void jbHandleMcuInfoReq(uint8_t *frame, uint16_t len) { uint8_t sn = frame[5]; uint8_t payload[8]; uint16_t p = 0; (void)len; payload[p++] = JB_OK; payload[p++] = JB_MCU_SW_VERSION_MAJOR; payload[p++] = JB_MCU_SW_VERSION_MINOR; payload[p++] = JB_MCU_SW_VERSION_PATCH; payload[p++] = JB_MCU_HW_VERSION_MAJOR; payload[p++] = JB_MCU_HW_VERSION_MINOR; payload[p++] = JB_MCU_HW_VERSION_PATCH; jbSendFrame(CMD_MCU_INFO_ACK, sn, payload, p); } /* ── 0x12 心跳(BLE → MCU)── */ static void jbHandleHeartbeat(uint8_t *frame, uint16_t len) { uint8_t sn = frame[5]; jbSendAck(CMD_HEARTBEAT_ACK, sn, JB_OK); } /* ── 0x14 BLE 工作状态同步(含 RSSI)── */ static void jbHandleWorkState(uint8_t *frame, uint16_t len) { uint8_t sn = frame[5]; uint8_t state = frame[6]; int8_t rssi = (int8_t)frame[7]; /* uint8 补码 → int8 dBm */ jbOnWorkState(state, rssi); jbSendAck(CMD_WORK_STATE_ACK, sn, JB_OK); } /* ── 0x1A BLE 通知 MCU 重启 ── */ static void jbHandleBleRestart(uint8_t *frame, uint16_t len) { uint8_t sn = frame[5]; jbSendAck(CMD_RESTART_MCU_ACK, sn, JB_OK); jbOnBleRestartRequest(); } /* ── 0x30 OTA 升级请求 ── */ static void jbHandleOtaNotify(uint8_t *frame, uint16_t len) { uint8_t sn = frame[5]; uint32_t fwVersion = ((uint32_t)frame[6] << 16) | ((uint32_t)frame[7] << 8) | frame[8]; uint32_t fwLength = jbReadU32BE(&frame[9]); uint32_t fwCrc32 = jbReadU32BE(&frame[13]); /* 应答: 结果码 + 最大单包长度(2B)(协议原格式,3 字节) */ int8_t otaRet = jbOnOtaNotify(fwVersion, fwLength, fwCrc32); uint8_t ack[3]; ack[0] = (otaRet == 0) ? JB_OK : (uint8_t)otaRet; ack[1] = (uint8_t)(JB_OTA_MAX_PAYLOAD >> 8); ack[2] = (uint8_t)(JB_OTA_MAX_PAYLOAD & 0xFF); jbSendFrame(CMD_OTA_NOTIFY_ACK, sn, ack, 3); } /* ── 0x32 OTA 数据(含分片序号)── */ static void jbHandleOtaData(uint8_t *frame, uint16_t len) { uint8_t sn = frame[5]; uint16_t shardIdx = jbReadU16BE(&frame[6]); uint16_t totalShards = jbReadU16BE(&frame[8]); const uint8_t *pData = &frame[10]; uint16_t dataLen = (len > 11) ? (len - 11) : 0; /* 先发本包 0x33 应答,再调用户回调满足「先回复、再重启」 */ jbSendAck(CMD_OTA_DATA_ACK, sn, JB_OK); jbOnOtaData(pData, dataLen, shardIdx, totalShards); } /* ── 0x36 BLE 通知停止 OTA ── */ static void jbHandleOtaStop(uint8_t *frame, uint16_t len) { uint8_t sn = frame[5]; jbOnOtaStop(); jbSendAck(CMD_OTA_STOP_BLE_ACK, sn, JB_OK); } /* ════════════════════════════════════════════════════════════════ * MCU → BLE 请求 API * ════════════════════════════════════════════════════════════════ */ /* 0x10 请求配对(cfg_mode: 1=纯蓝牙 2=WIFI双模; param 仅 WIFI 有效)*/ int32_t jbRequestPairing(uint8_t cfg_mode, uint16_t timeout_sec, const uint8_t *param, uint8_t param_len) { uint8_t payload[64]; uint16_t p = 0; payload[p++] = cfg_mode; jbWriteU16BE(&payload[p], timeout_sec); p += 2; if (param && param_len > 0 && (p + 1 + param_len) <= sizeof(payload)) { payload[p++] = param_len; memcpy(&payload[p], param, param_len); p += param_len; } uint8_t sn = jbProtocol.sn++; return jbSendReqWithAck(CMD_PAIRING_REQ, sn, payload, p); } /* 0x1C 获取网络时间(等待 ACK,超时重传)*/ int32_t jbRequestTime(void) { uint8_t sn = jbProtocol.sn++; return jbSendReqWithAck(CMD_TIME_REQ, sn, NULL, 0); } /* 0x16 重置 BLE 模组(恢复出厂,等待 ACK 重传)*/ int32_t jbRequestBleReset(void) { uint8_t sn = jbProtocol.sn++; return jbSendReqWithAck(CMD_RESET_BLE, sn, NULL, 0); } /* 0x18 重启 BLE 模组(等待 ACK 重传)*/ int32_t jbRequestBleRestart(void) { uint8_t sn = jbProtocol.sn++; return jbSendReqWithAck(CMD_RESTART_BLE, sn, NULL, 0); } /* 0x20 产测模式(0=退出 1=进入,等待 ACK 重传)*/ int32_t jbRequestProduction(uint8_t mode) { uint8_t sn = jbProtocol.sn++; return jbSendReqWithAck(CMD_PRODUCTION, sn, &mode, 1); } /* 0x22 开启拍照(预留 2 字节,等待 ACK 重传)*/ int32_t jbRequestPhoto(void) { uint8_t sn = jbProtocol.sn++; uint8_t payload[2] = {0, 0}; return jbSendReqWithAck(CMD_PHOTO_REQ, sn, payload, 2); } /* 0x24 开启录像(duration_sec: 0=关闭; 预留 2 字节,等待 ACK 重传)*/ int32_t jbRequestRecord(uint16_t duration_sec) { uint8_t sn = jbProtocol.sn++; uint8_t payload[4]; jbWriteU16BE(&payload[0], duration_sec); payload[2] = 0; payload[3] = 0; return jbSendReqWithAck(CMD_RECORD_REQ, sn, payload, 4); } /* 0x26 启动音频(audio_id: 音频编号; 预留 2 字节,等待 ACK 重传)*/ int32_t jbRequestAudio(uint8_t audio_id) { uint8_t sn = jbProtocol.sn++; uint8_t payload[3]; payload[0] = audio_id; payload[1] = 0; payload[2] = 0; return jbSendReqWithAck(CMD_AUDIO_REQ, sn, payload, 3); } /* 0x38 通知 BLE 停止 OTA(MCU 主动,等待 ACK 重传)*/ int32_t jbNotifyOtaStop(void) { uint8_t sn = jbProtocol.sn++; return jbSendReqWithAck(CMD_OTA_STOP_MCU, sn, NULL, 0); } /* 0x34 OTA 升级完成(result + MCU 版本 3B,等待 ACK 重传)*/ int32_t jbSendOtaResult(uint8_t result, const uint8_t *mcuVersion) { uint8_t sn = jbProtocol.sn++; uint8_t payload[4]; payload[0] = result; if (mcuVersion) { memcpy(&payload[1], mcuVersion, 3); } else { memset(&payload[1], 0, 3); } return jbSendReqWithAck(CMD_OTA_COMPLETE, sn, payload, 4); } /* ════════════════════════════════════════════════════════════════ * jbProtocolHandle — 协议主循环(在 while(1) 中调用) * ════════════════════════════════════════════════════════════════ */ int32_t jbProtocolHandle(jb_data_point_t *data) { if (!data) { return -1; } /* ACK 超时重传 */ jbAckRetry(); /* 提取一帧 */ uint16_t pktLen = 0; int8_t ret = jbGetOnePacket(jbProtocol.rxBuf, &pktLen); uint8_t cmd; if (ret == 0) { cmd = jbProtocol.rxBuf[4]; printf("cmd: %02X\r\n", cmd); /* 按命令码统一分发 BLE → MCU 的所有帧(含 MCU 主动请求的应答)*/ switch (cmd) { /* ── 身份信息 / MCU 信息 ── */ case CMD_IDENTITY_REQ: jbHandleIdentityReq(jbProtocol.rxBuf, pktLen); break; case CMD_MCU_INFO_REQ: jbHandleMcuInfoReq(jbProtocol.rxBuf, pktLen); break; /* ── APP-BLE 数据 ── */ case CMD_CONTROL: jbHandleControl(jbProtocol.rxBuf, pktLen); break; case CMD_READ: jbHandleRead(jbProtocol.rxBuf, pktLen, data); break; /* ── 心跳 / 状态同步 ── */ case CMD_HEARTBEAT: jbHandleHeartbeat(jbProtocol.rxBuf, pktLen); break; case CMD_WORK_STATE: jbHandleWorkState(jbProtocol.rxBuf, pktLen); break; /* ── BLE 通知 MCU 重启 ── */ case CMD_RESTART_MCU: jbHandleBleRestart(jbProtocol.rxBuf, pktLen); break; /* ── BLE-MCU OTA ── */ case CMD_OTA_NOTIFY: jbHandleOtaNotify(jbProtocol.rxBuf, pktLen); break; case CMD_OTA_DATA: jbHandleOtaData(jbProtocol.rxBuf, pktLen); break; case CMD_OTA_STOP_BLE: jbHandleOtaStop(jbProtocol.rxBuf, pktLen); break; /* ── BLE → MCU 的 ACK(MCU 主动请求后的应答)── 各 ACK 分支统一调用 jbWaitAckCheck:命中等待则清除等待、 停止重传,并回调 jbOnAck 通知应答结果;其余帧由 default 忽略。 */ case CMD_TIME_ACK: jbWaitAckCheck(jbProtocol.rxBuf); if (pktLen >= 18) { uint32_t ts = jbReadU32BE(&jbProtocol.rxBuf[14]); jbOnTimeSync(ts); } break; case CMD_REPORT_ACK: case CMD_PAIRING_ACK: case CMD_RESET_BLE_ACK: case CMD_RESTART_BLE_ACK: case CMD_PRODUCTION_ACK: case CMD_PHOTO_ACK: case CMD_RECORD_ACK: case CMD_AUDIO_ACK: case CMD_OTA_COMPLETE_ACK: case CMD_OTA_STOP_MCU_ACK: jbWaitAckCheck(jbProtocol.rxBuf); break; default: break; } } jbReportPolicy(data); return 0; }