/****************************************************************************** * @file usr_usb_jb.c * @brief USB CDC 见宝帧解析:校验后写入产品码并重广播 * @author cyWu <1917507415@qq.com> * @date 2026.07.24 * @version V1.0.2 * @history * - V1.0.2, 2026.07.28, cyWu, 非产品码命令整帧透传 BLE REPORTCMD_V2 * - V1.0.1, 2026.07.24, cyWu, CDC 中断内禁止 syscfg_write,改延时到任务上下文 * - V1.0.0, 2026.07.24, cyWu, 首次发布 ******************************************************************************/ #include "system/includes.h" #include "app_config.h" #include "vm.h" #include "usr_usb_jb.h" #include "bleproto_api.h" #include "usr_dengle_sched.h" #define LOG_TAG_CONST APP #define LOG_TAG "[USB_JB]" #define LOG_ERROR_ENABLE #define LOG_DEBUG_ENABLE #define LOG_INFO_ENABLE #define LOG_CLI_ENABLE #include "debug.h" #if TCFG_USB_SLAVE_CDC_ENABLE #include "usb/device/cdc.h" #endif /*============================================================================*/ /* 协议常量(对齐 jb_protocol / jb_common) */ /*============================================================================*/ #define PKT_HEAD_0 0x55 #define PKT_HEAD_1 0xAA #define MAX_PACKAGE_LEN 128 #define JB_RB_CAPACITY 256 #define JB_RSP_BUF_SZ 800 /* Dengle 查询应答:result + 最多 10 槽 */ #define USR_PROD_MAGIC 0x5A #define USR_JB_BLE_CMD_REPORT 100 /* 0x07/0x34/0x38 → 主动上报类 */ #define USR_JB_BLE_CMD_ACK 110 /* 0x04/0x06/0x31/0x37 → 应答类 */ #define USR_JB_BLE_TX_SZ 384 #ifndef CFG_USER_PROD_INFO #define CFG_USER_PROD_INFO 28 #endif extern void usr_ble_adv_init(void); extern void usr_ble_send_data(u8 *data, u16 len); extern u16 usr_get_conn_service(void); /*============================================================================*/ /* 类型 */ /*============================================================================*/ typedef struct { uint8_t magic; uint8_t prodcode[USR_PRODCODE_LEN]; uint8_t pcode[USR_PCODE_LEN]; } usr_prod_info_t; typedef struct { uint8_t buf[JB_RB_CAPACITY]; uint16_t write; uint16_t read; uint16_t count; } usr_jb_rb_t; /*============================================================================*/ /* 静态变量 */ /*============================================================================*/ static const uint8_t s_default_prodcode[USR_PRODCODE_LEN] = {'C', 'B', 'C', '0'}; static const uint8_t s_default_pcode[USR_PCODE_LEN] = {'P', 'I', 'C', 'B', 'C', '0'}; static uint8_t s_prodcode[USR_PRODCODE_LEN]; static uint8_t s_pcode[USR_PCODE_LEN]; static usr_jb_rb_t s_rb; /* 待写 flash / 回 ACK 的缓存(ISR 只填,定时器里执行) */ static uint8_t s_pending_save; static uint8_t s_pending_cmd; static uint8_t s_pending_sn; static uint8_t s_pending_prod[USR_PRODCODE_LEN]; static uint8_t s_pending_pcode[USR_PCODE_LEN]; static int s_process_timer; static uint8_t s_ble_txbuf[USR_JB_BLE_TX_SZ]; static uint8_t s_last_ble_msgid; /* 最近一次 BLE RX 的 msgid */ static uint8_t s_rsp_buf[JB_RSP_BUF_SZ]; static uint8_t usr_jb_checksum(const uint8_t *frame, uint16_t total_len); static uint8_t usr_jb_need_com_offline_auto_ack(uint8_t cmd); void usr_ble_rx_msgid_save(uint8_t msgid) { s_last_ble_msgid = msgid & 0x0F; printf("usr_ble rx msgid save=%u\n", s_last_ble_msgid); } uint8_t usr_ble_rx_msgid_get(void) { return s_last_ble_msgid; } /** * @brief 按 USB JB 命令映射 ble_proto cmd / gateway_int * @param jb_cmd USB 帧 CMD(frame[4]) * @param pkt 待填充的 ble_proto 包 * @return 0 可透传, -1 不支持的命令 */ static int usr_jb_map_ble_pkt(uint8_t jb_cmd, ble_proto_packet_t *pkt) { if (!pkt) { return -1; } memset(pkt, 0, sizeof(*pkt)); switch (jb_cmd) { case 0x07: /* REPORT */ case 0x34: /* OTA_COMPLETE */ case 0x38: /* OTA_STOP_MCU */ pkt->cmd = USR_JB_BLE_CMD_REPORT; pkt->gateway_int_cnt = 0; break; case 0x04: /* CONTROL_ACK */ case 0x06: /* READ_ACK */ case 0x31: /* OTA_NOTIFY_ACK */ case 0x33: /* OTA_DATA_ACK */ case 0x37: /* OTA_STOP_BLE_ACK */ pkt->cmd = USR_JB_BLE_CMD_ACK; pkt->gateway_int_cnt = 2; pkt->gateway_int[0] = 0; pkt->gateway_int[1] = 0; break; default: return -1; } return 0; } /** * @brief 将完整 JB 帧封装为 REPORTCMD_V2 后经 BLE 发出 * @param frame 完整 55 AA 帧 * @param len 帧长度 */ static void usr_jb_forward_to_ble(const uint8_t *frame, uint16_t len) { bleproto_appdata_desc_t encode_desc = {0}; ble_proto_packet_t *pkt; uint8_t jb_cmd; int elen; if (!frame || len < 7 || len > MAX_PACKAGE_LEN) { return; } if (usr_get_conn_service() == 0) { printf("usr_jb ble forward skip: not connected\n"); return; } jb_cmd = frame[4]; pkt = &encode_desc.datast.reportcmd_v2_req; if (usr_jb_map_ble_pkt(jb_cmd, pkt) != 0) { printf("usr_jb ble forward ignore jb_cmd=0x%02x\n", jb_cmd); return; } encode_desc.header.version = 0; encode_desc.header.datafmt = BLEPROTO_APPDATA_DATAFMT_JSON; /* 100 上报用 REQ,110 应答用 RSP */ encode_desc.header.msgtype = (pkt->cmd == USR_JB_BLE_CMD_ACK) ? BLEPROTO_APPDATA_MSGTYPE_RSP : BLEPROTO_APPDATA_MSGTYPE_REQ; encode_desc.header.msgid = usr_ble_rx_msgid_get(); encode_desc.header.encrypt = 0; encode_desc.header.serviceid = (pkt->cmd == USR_JB_BLE_CMD_ACK) ? E_BLEPROTO_SERVICE_ID_RUNCMD_V2 : E_BLEPROTO_SERVICE_ID_REPORTCMD_V2; pkt->reserved = 0; pkt->device_int_len = 0; pkt->device_str_len = 0; pkt->device_bytes_len = len; pkt->bytes_data = frame; elen = bleproto_appdata_encode(s_ble_txbuf, sizeof(s_ble_txbuf), sizeof(s_ble_txbuf), &encode_desc); printf("usr_jb ble forward jb_cmd=0x%02x ble_cmd=%u msgtype=%u msgid=%u gw_cnt=%u elen=%d\n", jb_cmd, pkt->cmd, encode_desc.header.msgtype, encode_desc.header.msgid, pkt->gateway_int_cnt, elen); if (elen > 0) { usr_ble_send_data(s_ble_txbuf, (u16)elen); printf_buf(s_ble_txbuf, elen); } else { printf("usr_jb ble encode fail\n"); } } /** * @brief 是否属于 COM 离线时需本地自动应答的命令 */ static uint8_t usr_jb_need_com_offline_auto_ack(uint8_t cmd) { switch (cmd) { case USR_JB_CMD_CTRL: /* 0x03 → ACK 0x04,映射表已支持 */ case USR_JB_CMD_READ: /* 0x05 → ACK 0x06,映射表已支持 */ case USR_JB_CMD_OTA_NOTIFY: /* 0x30 → ACK 0x31 */ case USR_JB_CMD_OTA_DATA: /* 0x32 → ACK 0x33 */ case USR_JB_CMD_OTA_STOP_BLE: /* 0x36 → ACK 0x37 */ return 1; default: return 0; } } /** * @brief 组简单 JB 应答帧(cmd+1 / 同 SN / result),再走 forward 发 BLE */ static void usr_jb_send_simple_ack_frame(uint8_t req_cmd, uint8_t sn, uint8_t result) { uint8_t frame[8]; uint16_t len_field = 4; /* CMD+SN+result+CKSUM */ uint16_t total = len_field + 4; frame[0] = PKT_HEAD_0; frame[1] = PKT_HEAD_1; frame[2] = (uint8_t)(len_field >> 8); frame[3] = (uint8_t)(len_field & 0xFF); frame[4] = (uint8_t)(req_cmd + 1); frame[5] = sn; frame[6] = result; frame[7] = usr_jb_checksum(frame, total); printf("usr_jb auto-ack req=0x%02x rsp=0x%02x sn=%u result=0x%02x\n", req_cmd, frame[4], sn, result); usr_jb_forward_to_ble(frame, total); } /*============================================================================*/ /* 环形缓冲 / 校验 */ /*============================================================================*/ static void usr_jb_rb_init(usr_jb_rb_t *rb) { memset(rb, 0, sizeof(*rb)); } static void usr_jb_rb_push(usr_jb_rb_t *rb, uint8_t byte) { if (rb->count >= JB_RB_CAPACITY) { rb->read = (rb->read + 1) % JB_RB_CAPACITY; rb->count--; } rb->buf[rb->write] = byte; rb->write = (rb->write + 1) % JB_RB_CAPACITY; rb->count++; } static uint8_t usr_jb_rb_pop(usr_jb_rb_t *rb) { uint8_t b = rb->buf[rb->read]; rb->read = (rb->read + 1) % JB_RB_CAPACITY; rb->count--; return b; } static uint16_t usr_jb_rb_available(const usr_jb_rb_t *rb) { return rb->count; } static uint16_t usr_jb_read_u16_be(const uint8_t *buf) { return ((uint16_t)buf[0] << 8) | buf[1]; } /** * @brief 校验和:sum(LEN_H .. payload_last) & 0xFF */ static uint8_t usr_jb_checksum(const uint8_t *frame, uint16_t total_len) { uint16_t sum = 0; uint16_t i; if (!frame || total_len < 4) { return 0; } for (i = 2; i < total_len - 1; i++) { sum += frame[i]; } return (uint8_t)(sum & 0xFF); } /** * @brief 从环形缓冲提取完整帧(逻辑对齐 jbGetOnePacket) * @return 0 成功, 1 需要更多数据, -2 校验失败 */ static int8_t usr_jb_get_one_packet(uint8_t *out, uint16_t *out_len) { static uint8_t state = 0; static uint16_t expect = 0; static uint16_t idx = 0; static uint8_t prev = 0; while (usr_jb_rb_available(&s_rb)) { uint8_t b = usr_jb_rb_pop(&s_rb); if (state == 0) { 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) { out[idx++] = b; if (idx == 6) { expect = 4 + usr_jb_read_u16_be(&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] == usr_jb_checksum(out, expect)) { *out_len = expect; return 0; } printf("usr_jb checksum fail expect=0x%02x calc=0x%02x\n", out[expect - 1], usr_jb_checksum(out, expect)); return -2; } } prev = b; } return 1; } /*============================================================================*/ /* 产品码 flash */ /*============================================================================*/ static void usr_prodcode_set_default(void) { memcpy(s_prodcode, s_default_prodcode, USR_PRODCODE_LEN); memcpy(s_pcode, s_default_pcode, USR_PCODE_LEN); } void usr_prodcode_init(void) { usr_prod_info_t info; int ret; usr_jb_rb_init(&s_rb); s_pending_save = 0; s_process_timer = 0; usr_prodcode_set_default(); memset(&info, 0, sizeof(info)); ret = syscfg_read(CFG_USER_PROD_INFO, (u8 *)&info, sizeof(info)); if (ret > 0 && info.magic == USR_PROD_MAGIC) { memcpy(s_prodcode, info.prodcode, USR_PRODCODE_LEN); memcpy(s_pcode, info.pcode, USR_PCODE_LEN); printf("prodcode from flash: %c%c%c%c / %c%c%c%c%c%c\n", s_prodcode[0], s_prodcode[1], s_prodcode[2], s_prodcode[3], s_pcode[0], s_pcode[1], s_pcode[2], s_pcode[3], s_pcode[4], s_pcode[5]); } else { printf("prodcode use default: %c%c%c%c / %c%c%c%c%c%c\n", s_prodcode[0], s_prodcode[1], s_prodcode[2], s_prodcode[3], s_pcode[0], s_pcode[1], s_pcode[2], s_pcode[3], s_pcode[4], s_pcode[5]); } } void usr_prodcode_get(uint8_t *prodcode, uint8_t *pcode) { if (prodcode) { memcpy(prodcode, s_prodcode, USR_PRODCODE_LEN); } if (pcode) { memcpy(pcode, s_pcode, USR_PCODE_LEN); } } /** * @brief 写 VM(必须在任务/定时器上下文调用,禁止在 CDC 中断里调用) */ static int usr_prodcode_save(const uint8_t *prodcode, const uint8_t *pcode) { usr_prod_info_t info; int ret; if (!prodcode || !pcode) { return -1; } memset(&info, 0, sizeof(info)); info.magic = USR_PROD_MAGIC; memcpy(info.prodcode, prodcode, USR_PRODCODE_LEN); memcpy(info.pcode, pcode, USR_PCODE_LEN); memcpy(s_prodcode, prodcode, USR_PRODCODE_LEN); memcpy(s_pcode, pcode, USR_PCODE_LEN); ret = syscfg_write(CFG_USER_PROD_INFO, (u8 *)&info, sizeof(info)); if (ret <= 0) { printf("prodcode flash write fail ret=%d\n", ret); return -1; } printf("prodcode saved: %c%c%c%c / %c%c%c%c%c%c ret=%d\n", s_prodcode[0], s_prodcode[1], s_prodcode[2], s_prodcode[3], s_pcode[0], s_pcode[1], s_pcode[2], s_pcode[3], s_pcode[4], s_pcode[5], ret); return 0; } /** * @brief 解析产品码 payload(仅 TLV) * 格式: [len=4][ProdtCode 4B][len=6][productCode 6B] * @return 0 成功, -1 失败 */ static int usr_jb_parse_prod_payload(const uint8_t *payload, uint16_t payload_len, uint8_t *prodcode, uint8_t *pcode) { if (!payload || !prodcode || !pcode) { return -1; } /* TLV: 04 xx xx xx xx 06 yy yy yy yy yy yy */ if (payload_len < (1 + USR_PRODCODE_LEN + 1 + USR_PCODE_LEN)) { return -1; } if (payload[0] != USR_PRODCODE_LEN) { return -1; } if (payload[1 + USR_PRODCODE_LEN] != USR_PCODE_LEN) { return -1; } memcpy(prodcode, &payload[1], USR_PRODCODE_LEN); memcpy(pcode, &payload[1 + USR_PRODCODE_LEN + 1], USR_PCODE_LEN); return 0; } #if TCFG_USB_SLAVE_CDC_ENABLE /** * @brief 回 ACK:CMD = 请求 CMD+1,payload = result */ static void usr_jb_send_ack(uint8_t cmd, uint8_t sn, uint8_t result) { uint8_t buf[8]; uint16_t len_field = 4; /* CMD+SN+result+CKSUM */ uint16_t total = len_field + 4; buf[0] = PKT_HEAD_0; buf[1] = PKT_HEAD_1; buf[2] = (uint8_t)(len_field >> 8); buf[3] = (uint8_t)(len_field & 0xFF); buf[4] = (uint8_t)(cmd + 1); buf[5] = sn; buf[6] = result; buf[7] = usr_jb_checksum(buf, total); cdc_write_data(0, buf, total); } /** * @brief 回带 payload 的应答:CMD = 请求 CMD+1 * @param cmd 请求命令 * @param sn 流水号 * @param payload 应答负载(可为 NULL) * @param payload_len 负载长度 */ static void usr_jb_send_rsp(uint8_t cmd, uint8_t sn, const uint8_t *payload, uint16_t payload_len) { uint16_t len_field; uint16_t total; /* LEN = CMD + SN + payload + CKSUM */ len_field = (uint16_t)(2 + payload_len + 1); total = (uint16_t)(len_field + 4); if (total > JB_RSP_BUF_SZ) { printf("usr_jb rsp too long %u\n", total); usr_jb_send_ack(cmd, sn, 0x01); return; } s_rsp_buf[0] = PKT_HEAD_0; s_rsp_buf[1] = PKT_HEAD_1; s_rsp_buf[2] = (uint8_t)(len_field >> 8); s_rsp_buf[3] = (uint8_t)(len_field & 0xFF); s_rsp_buf[4] = (uint8_t)(cmd + 1); s_rsp_buf[5] = sn; if (payload && payload_len) { memcpy(&s_rsp_buf[6], payload, payload_len); } s_rsp_buf[total - 1] = usr_jb_checksum(s_rsp_buf, total); cdc_write_data(0, s_rsp_buf, total); printf("usr_jb rsp cmd=0x%02x plen=%u total=%u\n", cmd + 1, payload_len, total); } /** * @brief Dengle 查询:空/0xFF=全部;单字节槽位=查一条 * @note 应答 0x75: * 查全部 → [result][slot_rec x 10],slot_rec 与 0x70 payload 同格式 * 查单槽 → [result][slot_rec x 1] */ static void usr_jb_handle_dengle_query(uint8_t sn, const uint8_t *payload, uint16_t payload_len) { static uint8_t s_body[JB_RSP_BUF_SZ - 8]; int n; uint8_t slot_id; uint8_t i; /* 查单槽 */ if (payload_len >= 1 && payload[0] < DENGLE_SLOT_MAX) { slot_id = payload[0]; s_body[0] = 0x00; /* result ok */ n = dengle_sched_pack_slot(slot_id, &s_body[1], sizeof(s_body) - 1); if (n <= 0) { usr_jb_send_ack(USR_JB_CMD_DENGLE_QRY, sn, 0x01); return; } usr_jb_send_rsp(USR_JB_CMD_DENGLE_QRY, sn, s_body, (uint16_t)(1 + n)); return; } /* 查全部:payload 空 / 0xFF / 其它 */ s_body[0] = 0x00; n = dengle_sched_pack_all(&s_body[1], sizeof(s_body) - 1); if (n > 0) { usr_jb_send_rsp(USR_JB_CMD_DENGLE_QRY, sn, s_body, (uint16_t)(1 + n)); return; } /* 缓冲不够时退化为逐槽上报 */ printf("dengle qry pack_all fail, fallback per-slot\n"); for (i = 0; i < DENGLE_SLOT_MAX; i++) { s_body[0] = 0x00; n = dengle_sched_pack_slot(i, &s_body[1], sizeof(s_body) - 1); if (n <= 0) { usr_jb_send_ack(USR_JB_CMD_DENGLE_QRY, sn, 0x01); return; } usr_jb_send_rsp(USR_JB_CMD_DENGLE_QRY, sn, s_body, (uint16_t)(1 + n)); } } /** * @brief 查询产品码:应答格式与 0x64 设置 payload 对齐(带 result) * RSP 0x67 payload: * [result:1][04][ProdtCode 4B][06][productCode 6B] */ static void usr_jb_handle_prodcode_query(uint8_t sn) { uint8_t body[1 + 1 + USR_PRODCODE_LEN + 1 + USR_PCODE_LEN]; uint8_t prod[USR_PRODCODE_LEN]; uint8_t pcode[USR_PCODE_LEN]; usr_prodcode_get(prod, pcode); body[0] = 0x00; /* result ok */ body[1] = USR_PRODCODE_LEN; memcpy(&body[2], prod, USR_PRODCODE_LEN); body[2 + USR_PRODCODE_LEN] = USR_PCODE_LEN; memcpy(&body[2 + USR_PRODCODE_LEN + 1], pcode, USR_PCODE_LEN); printf("prodcode qry: %c%c%c%c / %c%c%c%c%c%c\n", prod[0], prod[1], prod[2], prod[3], pcode[0], pcode[1], pcode[2], pcode[3], pcode[4], pcode[5]); usr_jb_send_rsp(USR_JB_CMD_GET_PRODCODE, sn, body, sizeof(body)); } #endif /** * @brief 任务上下文:解析帧 / 写 flash / ACK / 重建广播 */ static void usr_jb_process_timeout(void *priv) { uint8_t pkt[MAX_PACKAGE_LEN]; uint16_t pkt_len; int8_t ret; uint8_t cmd; uint8_t sn; uint16_t payload_len; (void)priv; s_process_timer = 0; /* 先把环形缓冲里的完整帧抽完 */ do { pkt_len = 0; ret = usr_jb_get_one_packet(pkt, &pkt_len); if (ret != 0) { break; } cmd = pkt[4]; sn = pkt[5]; payload_len = (pkt_len >= 7) ? (pkt_len - 7) : 0; printf("usr_jb cmd=0x%02x sn=%u plen=%u\n", cmd, sn, payload_len); printf_buf(pkt, pkt_len); switch (cmd) { case USR_JB_CMD_SET_PRODCODE: if (usr_jb_parse_prod_payload(&pkt[6], payload_len, s_pending_prod, s_pending_pcode) != 0) { printf("usr_jb set prodcode payload invalid\n"); #if TCFG_USB_SLAVE_CDC_ENABLE usr_jb_send_ack(cmd, sn, 0x01); #endif break; } /* 缓存后统一落盘,避免同一周期多次写 flash */ s_pending_cmd = cmd; s_pending_sn = sn; s_pending_save = 1; break; case USR_JB_CMD_GET_PRODCODE: #if TCFG_USB_SLAVE_CDC_ENABLE usr_jb_handle_prodcode_query(sn); #endif break; case USR_JB_CMD_DENGLE_SET: { Dengle_Ret_t dr = dengle_sched_usb_set(&pkt[6], payload_len); printf("dengle sched usb set ret=%d\n", dr); #if TCFG_USB_SLAVE_CDC_ENABLE usr_jb_send_ack(cmd, sn, (dr == DENGLE_OK) ? 0x00 : 0x01); #endif break; } case USR_JB_CMD_DENGLE_CLR: { Dengle_Ret_t dr = dengle_sched_clear_all(); printf("dengle sched clear all ret=%d\n", dr); #if TCFG_USB_SLAVE_CDC_ENABLE usr_jb_send_ack(cmd, sn, (dr == DENGLE_OK) ? 0x00 : 0x01); #endif break; } case USR_JB_CMD_DENGLE_QRY: #if TCFG_USB_SLAVE_CDC_ENABLE usr_jb_handle_dengle_query(sn, &pkt[6], payload_len); #else (void)payload_len; #endif break; default: /* * COM 未打开:0x03/0x05 等需本地自动应答回 BLE; * COM 已打开或其它命令:按原逻辑透传(由映射表决定能否发出) */ #if TCFG_USB_SLAVE_CDC_ENABLE if (!cdc_is_com_open() && usr_jb_need_com_offline_auto_ack(cmd)) { usr_jb_send_simple_ack_frame(cmd, sn, 0x00); break; } #else if (usr_jb_need_com_offline_auto_ack(cmd)) { usr_jb_send_simple_ack_frame(cmd, sn, 0x00); break; } #endif usr_jb_forward_to_ble(pkt, pkt_len); break; } } while (1); if (!s_pending_save) { return; } s_pending_save = 0; /* code / prodcode 与当前完全一致:ACK 成功,不写 Flash、不重建广播 */ if (memcmp(s_prodcode, s_pending_prod, USR_PRODCODE_LEN) == 0 && memcmp(s_pcode, s_pending_pcode, USR_PCODE_LEN) == 0) { #if TCFG_USB_SLAVE_CDC_ENABLE usr_jb_send_ack(s_pending_cmd, s_pending_sn, 0x00); #endif printf("prodcode unchanged, skip adv rebuild\n"); return; } if (usr_prodcode_save(s_pending_prod, s_pending_pcode) == 0) { #if TCFG_USB_SLAVE_CDC_ENABLE usr_jb_send_ack(s_pending_cmd, s_pending_sn, 0x00); #endif extern void usr_goto_pair_mode(void); usr_goto_pair_mode(); printf("prodcode applied, adv rebuilt\n"); } else { #if TCFG_USB_SLAVE_CDC_ENABLE usr_jb_send_ack(s_pending_cmd, s_pending_sn, 0x05); #endif } } /** * @brief USB CDC 喂数:仅压入环形缓冲并挂定时器(可在中断调用) */ void usr_usb_jb_feed(uint8_t *buf, uint32_t len) { uint32_t i; if (!buf || len == 0) { return; } for (i = 0; i < len; i++) { usr_jb_rb_push(&s_rb, buf[i]); } /* 延时到系统定时器任务再解析/写 flash,避免中断里擦写导致复位 */ if (s_process_timer == 0) { s_process_timer = sys_timeout_add(NULL, usr_jb_process_timeout, 20); } } /** * @brief BLE RUNCMD_V2 bytes:COM 开则透传 CDC,关则走 feed 统一解析/自动应答 */ void usr_jb_on_runcmd_bytes(const uint8_t *data, uint16_t len) { if (!data || len == 0) { return; } #if TCFG_USB_SLAVE_CDC_ENABLE if (cdc_is_com_open()) { u32 wlen = cdc_write_data(0, (u8 *)data, len); printf("usr_jb runcmd -> CDC %u/%u\n", wlen, (u32)len); return; } #endif /* COM 未打开:与 USB 入环同一路径,由 process 里 get_one_packet + auto-ack 处理 */ printf("usr_jb runcmd COM closed, feed for local auto-ack len=%u\n", len); usr_usb_jb_feed((uint8_t *)data, len); }