/****************************************************************************** * @file usr_dengle_sched.c * @brief Dengle 参数定时下发实现(含 Flash 断电保存) * @author cyWu <1917507415@qq.com> * @date 2026.07.30 * @version V1.2.0 * @history * - V1.0.0, 2026.07.30, cyWu, 首次发布 * - V1.1.0, 2026.07.30, cyWu, 增加 Flash 持久化 * - V1.1.1, 2026.07.30, cyWu, 修复设置后立即发送导致栈溢出复位 * - V1.2.0, 2026.07.30, cyWu, USB 连接时暂停定时发送 * - V1.2.1, 2026.07.30, cyWu, 改为 COM(DTR) 开闭控制暂停/恢复 * * USB 设置帧 payload(CMD=0x70)格式: * [0] slot 槽位 0~9 * [1] enable 0=关闭该槽 1=使能 * [2..5] period_ms 发送周期(大端 ms,建议 >=100) * [6..7] cmd_id CBCT/CBFD→ycmd.cmd_id;其它→ble_proto.cmd(大端) * [8] data_n int 模式= int32 个数;bytes 模式= 字节数 * [9..] data int 模式= data_n 个大端 int32; * bytes 模式= data_n 字节原始数据 * * 发送策略(按设备当前 prodcode): * - CBCT / CBFD → REPORTCMD + ycmd.arg_int32[](参考 usr_test_bleproto_runcmd) * - 其它 → REPORTCMD_V2 + bytes_data(参考 usr_test_protocol) ******************************************************************************/ #include "system/includes.h" #include "app_config.h" #include "usr_dengle_sched.h" #include "usr_usb_jb.h" #include "bleproto_api.h" #include "syscfg_id.h" #if TCFG_USB_SLAVE_CDC_ENABLE #include "usb/device/cdc.h" #endif #define LOG_TAG_CONST APP #define LOG_TAG "[DENGLE]" #define LOG_ERROR_ENABLE #define LOG_DEBUG_ENABLE #define LOG_INFO_ENABLE #define LOG_CLI_ENABLE #include "debug.h" /*============================================================================*/ /* 私有宏 */ /*============================================================================*/ #define DENGLE_TX_BUF_SZ (384) #define DENGLE_PROD_CBCT "CBCT" #define DENGLE_PROD_CBFD "CBFD" #define DENGLE_FLASH_MAGIC (0xD6) #define DENGLE_FLASH_VER (0x01) #define DENGLE_FLASH_HALF (5) /* 前半/后半各 5 个槽 */ extern void usr_ble_send_data(u8 *data, u16 len); extern u16 usr_get_conn_service(void); /*============================================================================*/ /* 私有类型 */ /*============================================================================*/ /** 发送模式:由设备 prodcode 决定 */ typedef enum { DENGLE_MODE_INT32 = 0, /**< CBCT/CBFD:int32 数组 → REPORTCMD */ DENGLE_MODE_BYTES /**< 其它:bytes → REPORTCMD_V2 */ } DengleMode_t; /** 运行时单条定时配置(含 elapsed,不落盘) */ typedef struct { uint8_t enable; uint8_t mode; uint16_t cmd_id; uint32_t period_ms; uint32_t elapsed_ms; uint8_t data_n; union { int32_t i32[DENGLE_INT_MAX]; uint8_t bytes[DENGLE_BYTES_MAX]; } data; } DengleSlot_t; /** Flash 单槽结构(不含 elapsed) */ typedef struct { uint8_t enable; uint8_t mode; uint16_t cmd_id; uint32_t period_ms; uint8_t data_n; uint8_t data[DENGLE_BYTES_MAX]; } DengleSlotFlash_t; /** 半区 Flash 块:5 个槽 */ typedef struct { uint8_t magic; uint8_t ver; uint8_t reserved[2]; DengleSlotFlash_t slots[DENGLE_FLASH_HALF]; } DengleFlashHalf_t; /*============================================================================*/ /* 静态变量 */ /*============================================================================*/ static DengleSlot_t s_slots[DENGLE_SLOT_MAX]; static uint8_t s_inited; static int s_tick_timer; static int s_save_timer; static int s_imm_send_timer; static uint8_t s_imm_send_slot; static uint8_t s_txbuf[DENGLE_TX_BUF_SZ]; /* 大结构体放静态区,避免 USB/定时器回调栈溢出 */ static DengleFlashHalf_t s_flash_half; static bleproto_appdata_desc_t s_encode_desc; static uint8_t s_usb_pause_logged; /*============================================================================*/ /* 工具函数 */ /*============================================================================*/ static uint16_t dengle_read_u16_be(const uint8_t *p) { return ((uint16_t)p[0] << 8) | p[1]; } static uint32_t dengle_read_u32_be(const uint8_t *p) { return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) | ((uint32_t)p[2] << 8) | p[3]; } /** * @brief 根据当前设备 prodcode 选择发送模式 */ static DengleMode_t dengle_get_mode_by_prodcode(void) { uint8_t prod[USR_PRODCODE_LEN]; usr_prodcode_get(prod, NULL); if (memcmp(prod, DENGLE_PROD_CBCT, USR_PRODCODE_LEN) == 0 || memcmp(prod, DENGLE_PROD_CBFD, USR_PRODCODE_LEN) == 0) { return DENGLE_MODE_INT32; } return DENGLE_MODE_BYTES; } /** * @brief 上位机已打开 COM 时暂停 Dengle 定时发送 * @note 设备可一直插着 USB;仅 COM 打开=配置态,COM 关闭后恢复自动发送 * @return 1=应暂停发送 0=允许发送 */ static uint8_t dengle_is_usb_pause(void) { #if TCFG_USB_SLAVE_CDC_ENABLE return cdc_is_com_open() ? 1 : 0; #else return 0; #endif } /*============================================================================*/ /* Flash 读写 */ /*============================================================================*/ /** * @brief 运行时槽 → Flash 槽 */ static void dengle_slot_to_flash(const DengleSlot_t *src, DengleSlotFlash_t *dst) { memset(dst, 0, sizeof(*dst)); dst->enable = src->enable; dst->mode = src->mode; dst->cmd_id = src->cmd_id; dst->period_ms = src->period_ms; dst->data_n = src->data_n; memcpy(dst->data, src->data.bytes, DENGLE_BYTES_MAX); } /** * @brief Flash 槽 → 运行时槽(elapsed 清零) */ static void dengle_slot_from_flash(const DengleSlotFlash_t *src, DengleSlot_t *dst) { memset(dst, 0, sizeof(*dst)); dst->enable = src->enable; dst->mode = src->mode; dst->cmd_id = src->cmd_id; dst->period_ms = src->period_ms; dst->data_n = src->data_n; dst->elapsed_ms = 0; memcpy(dst->data.bytes, src->data, DENGLE_BYTES_MAX); } /** * @brief 保存全部 Dengle 配置到 Flash(分两半写入,控制单条 VM 长度) * @note 须在任务/定时器上下文调用,禁止在中断里调用 */ static void dengle_flash_save(void) { int ret; uint8_t i; /* 前半:slot 0~4 */ memset(&s_flash_half, 0, sizeof(s_flash_half)); s_flash_half.magic = DENGLE_FLASH_MAGIC; s_flash_half.ver = DENGLE_FLASH_VER; for (i = 0; i < DENGLE_FLASH_HALF; i++) { dengle_slot_to_flash(&s_slots[i], &s_flash_half.slots[i]); } ret = syscfg_write(CFG_USER_DENGLE_L, (u8 *)&s_flash_half, sizeof(s_flash_half)); if (ret <= 0) { printf("dengle flash L write fail ret=%d\n", ret); } /* 后半:slot 5~9 */ memset(&s_flash_half, 0, sizeof(s_flash_half)); s_flash_half.magic = DENGLE_FLASH_MAGIC; s_flash_half.ver = DENGLE_FLASH_VER; for (i = 0; i < DENGLE_FLASH_HALF; i++) { dengle_slot_to_flash(&s_slots[DENGLE_FLASH_HALF + i], &s_flash_half.slots[i]); } ret = syscfg_write(CFG_USER_DENGLE_H, (u8 *)&s_flash_half, sizeof(s_flash_half)); if (ret <= 0) { printf("dengle flash H write fail ret=%d\n", ret); } else { printf("dengle flash saved, active=%u\n", dengle_sched_get_active_count()); } } /** * @brief 延时落盘:先回 ACK,避免与组包发送叠在同一深调用栈 */ static void dengle_flash_save_timeout(void *priv) { (void)priv; s_save_timer = 0; dengle_flash_save(); } static void dengle_flash_save_defer(void) { if (s_save_timer) { sys_timeout_del(s_save_timer); s_save_timer = 0; } s_save_timer = sys_timeout_add(NULL, dengle_flash_save_timeout, 50); } /** * @brief 从上电 Flash 恢复 Dengle 配置 */ static void dengle_flash_load(void) { int ret; uint8_t i; /* 前半 */ memset(&s_flash_half, 0, sizeof(s_flash_half)); ret = syscfg_read(CFG_USER_DENGLE_L, (u8 *)&s_flash_half, sizeof(s_flash_half)); if (ret > 0 && s_flash_half.magic == DENGLE_FLASH_MAGIC && s_flash_half.ver == DENGLE_FLASH_VER) { for (i = 0; i < DENGLE_FLASH_HALF; i++) { dengle_slot_from_flash(&s_flash_half.slots[i], &s_slots[i]); } } else { printf("dengle flash L empty/invalid ret=%d\n", ret); } /* 后半 */ memset(&s_flash_half, 0, sizeof(s_flash_half)); ret = syscfg_read(CFG_USER_DENGLE_H, (u8 *)&s_flash_half, sizeof(s_flash_half)); if (ret > 0 && s_flash_half.magic == DENGLE_FLASH_MAGIC && s_flash_half.ver == DENGLE_FLASH_VER) { for (i = 0; i < DENGLE_FLASH_HALF; i++) { dengle_slot_from_flash(&s_flash_half.slots[i], &s_slots[DENGLE_FLASH_HALF + i]); } } else { printf("dengle flash H empty/invalid ret=%d\n", ret); } printf("dengle flash loaded, active=%u\n", dengle_sched_get_active_count()); } /*============================================================================*/ /* BLE 发送(两条路径) */ /*============================================================================*/ /** * @brief CBCT/CBFD:REPORTCMD + ycmd.arg_int32[] * @note 对齐 usr_test_bleproto_runcmd 组包方式 */ static void dengle_send_int32(const DengleSlot_t *slot) { bleproto_reportcmd_req_t *req; int elen; uint8_t i; if (!slot || usr_get_conn_service() == 0) { return; } memset(&s_encode_desc, 0, sizeof(s_encode_desc)); s_encode_desc.header.version = 0; s_encode_desc.header.datafmt = BLEPROTO_APPDATA_DATAFMT_JSON; s_encode_desc.header.msgtype = BLEPROTO_APPDATA_MSGTYPE_REQ; s_encode_desc.header.msgid = usr_ble_rx_msgid_get(); s_encode_desc.header.encrypt = 0; s_encode_desc.header.serviceid = E_BLEPROTO_SERVICE_ID_REPORTCMD; req = &s_encode_desc.datast.reportcmd_req; req->ycmd.cmd_id = slot->cmd_id; req->ycmd.arg_int32_count = slot->data_n; req->ycmd.arg_string_count = 0; req->ycmd.arg_bytes_count = 0; for (i = 0; i < slot->data_n && i < DENGLE_INT_MAX; i++) { req->ycmd.arg_int32[i] = slot->data.i32[i]; } elen = bleproto_appdata_encode(s_txbuf, sizeof(s_txbuf), sizeof(s_txbuf), &s_encode_desc); printf("dengle INT32 cmd_id=%u n=%u elen=%d\n", slot->cmd_id, slot->data_n, elen); if (elen > 0) { usr_ble_send_data(s_txbuf, (u16)elen); } } /** * @brief 其它产品:REPORTCMD_V2 + bytes_data * @note 对齐 usr_test_protocol 组包方式 */ static void dengle_send_bytes(const DengleSlot_t *slot) { ble_proto_packet_t *pkt; int elen; if (!slot || usr_get_conn_service() == 0) { return; } memset(&s_encode_desc, 0, sizeof(s_encode_desc)); s_encode_desc.header.version = 0; s_encode_desc.header.datafmt = BLEPROTO_APPDATA_DATAFMT_JSON; s_encode_desc.header.msgtype = BLEPROTO_APPDATA_MSGTYPE_REQ; s_encode_desc.header.msgid = usr_ble_rx_msgid_get(); s_encode_desc.header.encrypt = 0; s_encode_desc.header.serviceid = E_BLEPROTO_SERVICE_ID_REPORTCMD_V2; pkt = &s_encode_desc.datast.reportcmd_v2_req; pkt->cmd = slot->cmd_id; pkt->reserved = 0; pkt->gateway_int_cnt = 0; pkt->device_int_len = 0; pkt->device_str_len = 0; pkt->device_bytes_len = slot->data_n; pkt->bytes_data = slot->data.bytes; elen = bleproto_appdata_encode(s_txbuf, sizeof(s_txbuf), sizeof(s_txbuf), &s_encode_desc); printf("dengle BYTES cmd=%u n=%u elen=%d\n", slot->cmd_id, slot->data_n, elen); if (elen > 0) { usr_ble_send_data(s_txbuf, (u16)elen); } } static void dengle_send_slot(const DengleSlot_t *slot) { if (!slot || !slot->enable || slot->data_n == 0) { return; } /* USB 连着电脑时不发,避免配置阶段干扰 */ if (dengle_is_usb_pause()) { return; } if (slot->mode == DENGLE_MODE_INT32) { dengle_send_int32(slot); } else { dengle_send_bytes(slot); } } /** * @brief 延时立即发送:与 USB 解析/ACK 错开,降低栈峰值 */ static void dengle_imm_send_timeout(void *priv) { uint8_t slot_id = (uint8_t)((u32)priv); s_imm_send_timer = 0; if (slot_id < DENGLE_SLOT_MAX) { dengle_send_slot(&s_slots[slot_id]); } } static void dengle_imm_send_defer(uint8_t slot_id) { s_imm_send_slot = slot_id; if (s_imm_send_timer) { sys_timeout_del(s_imm_send_timer); s_imm_send_timer = 0; } s_imm_send_timer = sys_timeout_add((void *)((u32)slot_id), dengle_imm_send_timeout, 30); } /*============================================================================*/ /* 定时节拍 */ /*============================================================================*/ static void dengle_tick(void *priv) { uint8_t i; DengleSlot_t *slot; (void)priv; /* USB 在线:冻结计时,断开后从完整周期重新开始,避免拔线瞬间连发 */ if (dengle_is_usb_pause()) { if (!s_usb_pause_logged) { s_usb_pause_logged = 1; printf("dengle pause: COM open\n"); } for (i = 0; i < DENGLE_SLOT_MAX; i++) { s_slots[i].elapsed_ms = 0; } return; } if (s_usb_pause_logged) { s_usb_pause_logged = 0; printf("dengle resume: COM closed\n"); } for (i = 0; i < DENGLE_SLOT_MAX; i++) { slot = &s_slots[i]; if (!slot->enable || slot->period_ms == 0) { continue; } slot->elapsed_ms += DENGLE_TICK_MS; if (slot->elapsed_ms >= slot->period_ms) { slot->elapsed_ms = 0; dengle_send_slot(slot); } } } /*============================================================================*/ /* 对外接口 */ /*============================================================================*/ Dengle_Ret_t dengle_sched_init(void) { memset(s_slots, 0, sizeof(s_slots)); dengle_flash_load(); s_inited = 1; if (s_tick_timer == 0) { s_tick_timer = sys_timer_add(NULL, dengle_tick, DENGLE_TICK_MS); } printf("dengle_sched_init ok\n"); return DENGLE_OK; } Dengle_Ret_t dengle_sched_clear_all(void) { if (!s_inited) { return DENGLE_ERR_NOT_INIT; } memset(s_slots, 0, sizeof(s_slots)); dengle_flash_save_defer(); printf("dengle clear all\n"); return DENGLE_OK; } uint8_t dengle_sched_get_active_count(void) { uint8_t i; uint8_t cnt = 0; for (i = 0; i < DENGLE_SLOT_MAX; i++) { if (s_slots[i].enable) { cnt++; } } return cnt; } Dengle_Ret_t dengle_sched_usb_set(const uint8_t *payload, uint16_t payload_len) { DengleSlot_t *slot; DengleMode_t mode; uint8_t slot_id; uint8_t enable; uint8_t data_n; uint8_t i; uint16_t need; if (!s_inited) { return DENGLE_ERR_NOT_INIT; } if (!payload || payload_len < 9) { return DENGLE_ERR_PARAM; } slot_id = payload[0]; enable = payload[1]; if (slot_id >= DENGLE_SLOT_MAX) { return DENGLE_ERR_SLOT; } slot = &s_slots[slot_id]; memset(slot, 0, sizeof(*slot)); /* 仅关闭该槽 */ if (enable == 0) { dengle_flash_save_defer(); printf("dengle slot%u disabled\n", slot_id); return DENGLE_OK; } mode = dengle_get_mode_by_prodcode(); data_n = payload[8]; if (mode == DENGLE_MODE_INT32) { if (data_n == 0 || data_n > DENGLE_INT_MAX) { return DENGLE_ERR_PARAM; } need = 9 + (uint16_t)data_n * 4; if (payload_len < need) { return DENGLE_ERR_PARAM; } for (i = 0; i < data_n; i++) { slot->data.i32[i] = (int32_t)dengle_read_u32_be(&payload[9 + i * 4]); } } else { if (data_n == 0 || data_n > DENGLE_BYTES_MAX) { return DENGLE_ERR_PARAM; } need = 9 + data_n; if (payload_len < need) { return DENGLE_ERR_PARAM; } memcpy(slot->data.bytes, &payload[9], data_n); } slot->enable = 1; slot->mode = (uint8_t)mode; slot->period_ms = dengle_read_u32_be(&payload[2]); slot->cmd_id = dengle_read_u16_be(&payload[6]); slot->data_n = data_n; slot->elapsed_ms = 0; if (slot->period_ms < DENGLE_TICK_MS) { slot->period_ms = DENGLE_TICK_MS; } printf("dengle set slot=%u mode=%u period=%ums cmd=%u n=%u\n", slot_id, slot->mode, slot->period_ms, slot->cmd_id, slot->data_n); /* 先 ACK,再延时落盘/发送,避免与组包叠栈导致复位 */ dengle_flash_save_defer(); dengle_imm_send_defer(slot_id); return DENGLE_OK; } /*============================================================================*/ /* 查询导出(供 USB 0x74 / 0x75) */ /*============================================================================*/ static void dengle_write_u16_be(uint8_t *p, uint16_t v) { p[0] = (uint8_t)(v >> 8); p[1] = (uint8_t)(v & 0xFF); } static void dengle_write_u32_be(uint8_t *p, uint32_t v) { p[0] = (uint8_t)(v >> 24); p[1] = (uint8_t)(v >> 16); p[2] = (uint8_t)(v >> 8); p[3] = (uint8_t)(v & 0xFF); } /** * @brief 打包单个槽位(与 0x70 设置 payload 同格式,便于上位机复用解析) * enable=0 时仅输出 [slot][0] * enable=1 时输出 [slot][1][period][cmd][data_n][data...] * INT32 模式 data 仍按大端 int32 导出 */ int dengle_sched_pack_slot(uint8_t slot_id, uint8_t *buf, uint16_t max_len) { const DengleSlot_t *slot; uint16_t need; uint8_t i; if (!s_inited || !buf) { return -1; } if (slot_id >= DENGLE_SLOT_MAX) { return -1; } slot = &s_slots[slot_id]; if (!slot->enable) { if (max_len < 2) { return -1; } buf[0] = slot_id; buf[1] = 0; return 2; } if (slot->mode == DENGLE_MODE_INT32) { need = 9 + (uint16_t)slot->data_n * 4; } else { need = 9 + slot->data_n; } if (max_len < need) { return -1; } buf[0] = slot_id; buf[1] = 1; dengle_write_u32_be(&buf[2], slot->period_ms); dengle_write_u16_be(&buf[6], slot->cmd_id); buf[8] = slot->data_n; if (slot->mode == DENGLE_MODE_INT32) { for (i = 0; i < slot->data_n; i++) { dengle_write_u32_be(&buf[9 + i * 4], (uint32_t)slot->data.i32[i]); } } else { memcpy(&buf[9], slot->data.bytes, slot->data_n); } return (int)need; } int dengle_sched_pack_all(uint8_t *buf, uint16_t max_len) { uint16_t offset = 0; uint8_t i; int n; if (!s_inited || !buf) { return -1; } for (i = 0; i < DENGLE_SLOT_MAX; i++) { n = dengle_sched_pack_slot(i, &buf[offset], (uint16_t)(max_len - offset)); if (n <= 0) { return -1; } offset = (uint16_t)(offset + n); } return (int)offset; }