1、新增Dengle定时参数下发功能,支持最多10条配置,断电保存到Flash,COM打开时暂停发送、关闭后恢复

2、新增USB查询产品码(0x66)和查询Dengle配置(0x74)命令
3、修复bleproto组包栈溢出导致设置Dengle后复位的问题,并完善CDC的DTR状态判断
This commit is contained in:
2026-07-31 14:54:26 +08:00
parent 79e15a9ccc
commit 2260768167
38 changed files with 1742 additions and 717 deletions
+28 -26
View File
@@ -222,91 +222,93 @@ int bleproto_appdata_encode(uint8_t * txbuf,
{
int rc;
uint16_t txbuf_size = size;
/* 原栈上 appdata 约 2KB+,在 USB/定时器回调里极易栈溢出复位,改为静态 */
static bleproto_appdata_t s_enc_appdata;
bleproto_appdata_t *appdata = &s_enc_appdata;
bleproto_appdata_t appdata;
/* header */
{
appdata.header = desc->header;
appdata->header = desc->header;
}
/* data */
if (appdata.header.msgtype == BLEPROTO_APPDATA_MSGTYPE_REQ) {
switch (appdata.header.serviceid) {
if (appdata->header.msgtype == BLEPROTO_APPDATA_MSGTYPE_REQ) {
switch (appdata->header.serviceid) {
case E_BLEPROTO_SERVICE_ID_DEVICEINFO:
rc = bleproto_encjson_deviceinfo_req(&desc->datast.deviceinfo_req,
appdata.data);
appdata->data);
break;
case E_BLEPROTO_SERVICE_ID_AUTHSETUP:
rc = bleproto_encjson_authsetup_req(&desc->datast.authsetup_req,
appdata.data);
appdata->data);
break;
case E_BLEPROTO_SERVICE_ID_AUTHDELETE:
rc = bleproto_encjson_authdelete_req(&desc->datast.authdelete_req,
appdata.data);
appdata->data);
break;
case E_BLEPROTO_SERVICE_ID_RUNCMD:
rc = bleproto_encjson_runcmd_req(&desc->datast.runcmd_req,
appdata.data);
appdata->data);
break;
case E_BLEPROTO_SERVICE_ID_REPORTCMD:
rc = bleproto_encjson_reportcmd_req(&desc->datast.reportcmd_req,
appdata.data);
appdata->data);
break;
case E_BLEPROTO_SERVICE_ID_DOACTION:
rc = bleproto_encjson_doaction_req(&desc->datast.doaction_req,
appdata.data);
appdata->data);
break;
case E_BLEPROTO_SERVICE_ID_OTANOTIFY:
rc = bleproto_encjson_otanotify_req(&desc->datast.otanotify_req,
appdata.data);
appdata->data);
break;
case E_BLEPROTO_SERVICE_ID_OTADATA:
rc = bleproto_encjson_otadata_req(&desc->datast.otadata_req,
appdata.data);
appdata->data);
break;
case E_BLEPROTO_SERVICE_ID_REPORTCMD_V2:
rc = ble_proto_pack(&desc->datast.runcmd_v2_req, appdata.data, sizeof(appdata.data));
rc = ble_proto_pack(&desc->datast.runcmd_v2_req, appdata->data, sizeof(appdata->data));
break;
default:
/* unkown serviceid */
return (-1);
}
} else if (appdata.header.msgtype == BLEPROTO_APPDATA_MSGTYPE_RSP) {
switch (appdata.header.serviceid) {
} else if (appdata->header.msgtype == BLEPROTO_APPDATA_MSGTYPE_RSP) {
switch (appdata->header.serviceid) {
case E_BLEPROTO_SERVICE_ID_DEVICEINFO:
rc = bleproto_encjson_deviceinfo_rsp(&desc->datast.deviceinfo_rsp,
appdata.data);
appdata->data);
break;
case E_BLEPROTO_SERVICE_ID_AUTHSETUP:
rc = bleproto_encjson_authsetup_rsp(&desc->datast.authsetup_rsp,
appdata.data);
appdata->data);
break;
case E_BLEPROTO_SERVICE_ID_AUTHDELETE:
rc = bleproto_encjson_authdelete_rsp(&desc->datast.authdelete_rsp,
appdata.data);
appdata->data);
break;
case E_BLEPROTO_SERVICE_ID_RUNCMD:
rc = bleproto_encjson_runcmd_rsp(&desc->datast.runcmd_rsp,
appdata.data);
appdata->data);
break;
case E_BLEPROTO_SERVICE_ID_REPORTCMD:
rc = bleproto_encjson_reportcmd_rsp(&desc->datast.reportcmd_rsp,
appdata.data);
appdata->data);
break;
case E_BLEPROTO_SERVICE_ID_DOACTION:
rc = bleproto_encjson_doaction_rsp(&desc->datast.doaction_rsp,
appdata.data);
appdata->data);
break;
case E_BLEPROTO_SERVICE_ID_OTANOTIFY:
rc = bleproto_encjson_otanotify_rsp(&desc->datast.otanotify_rsp,
appdata.data);
appdata->data);
break;
case E_BLEPROTO_SERVICE_ID_OTADATA:
rc = bleproto_encbin_otadata_rsp(&desc->datast.otadata_rsp,
appdata.data);
appdata->data);
break;
case E_BLEPROTO_SERVICE_ID_REPORTCMD_V2:
case E_BLEPROTO_SERVICE_ID_RUNCMD_V2:
rc = ble_proto_pack(&desc->datast.runcmd_v2_rsp, appdata.data, sizeof(appdata.data));
rc = ble_proto_pack(&desc->datast.runcmd_v2_rsp, appdata->data, sizeof(appdata->data));
break;
default:
/* unkown serviceid */
@@ -322,10 +324,10 @@ int bleproto_appdata_encode(uint8_t * txbuf,
}
/* datalen */
appdata.header.datalen = (uint16_t)rc;
appdata->header.datalen = (uint16_t)rc;
/* appdata_serialize */
return bleproto_appdata_serialize(&appdata, packetlen, txbuf, txbuf_size);
return bleproto_appdata_serialize(appdata, packetlen, txbuf, txbuf_size);
}
int bleproto_appdata_decode(uint8_t* rxbuf,
+662
View File
@@ -0,0 +1,662 @@
/******************************************************************************
* @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 设置帧 payloadCMD=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/CBFDint32 数组 → 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/CBFDREPORTCMD + 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;
}
+88
View File
@@ -0,0 +1,88 @@
/******************************************************************************
* @file usr_dengle_sched.h
* @brief Dengle 参数定时下发:USB 配置最多 10 条,按周期经 BLE 发出
* @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.2.0, 2026.07.30, cyWu, USB 连接时暂停定时发送
* - V1.2.1, 2026.07.30, cyWu, 改为 COM(DTR) 开闭控制暂停/恢复
******************************************************************************/
#ifndef __USR_DENGLE_SCHED_H__
#define __USR_DENGLE_SCHED_H__
#include <stdint.h>
/*============================================================================*/
/* 宏定义 */
/*============================================================================*/
#define DENGLE_SLOT_MAX (10) /**< 最多定时条目数 */
#define DENGLE_INT_MAX (16) /**< CBCT/CBFD:最多 int32 个数 */
#define DENGLE_BYTES_MAX (64) /**< 其它产品:最多 bytes 长度 */
#define DENGLE_TICK_MS (100) /**< 调度节拍 */
/*============================================================================*/
/* 错误码 */
/*============================================================================*/
typedef enum {
DENGLE_OK = 0,
DENGLE_ERR_PARAM,
DENGLE_ERR_SLOT,
DENGLE_ERR_MODE,
DENGLE_ERR_NOT_INIT
} Dengle_Ret_t;
/*============================================================================*/
/* 外部接口 */
/*============================================================================*/
/**
* @brief 初始化 Dengle 定时模块(启动 100ms 节拍)
* @return Dengle_Ret_t
*/
Dengle_Ret_t dengle_sched_init(void);
/**
* @brief USB 设置一条定时任务
* @note payload 格式见 .c 文件头注释
* @param payload USB 帧 payload(不含 55AA/LEN/CMD/SN/CKSUM
* @param payload_len 长度
* @return Dengle_Ret_t
*/
Dengle_Ret_t dengle_sched_usb_set(const uint8_t *payload, uint16_t payload_len);
/**
* @brief 清除全部定时任务
* @return Dengle_Ret_t
*/
Dengle_Ret_t dengle_sched_clear_all(void);
/**
* @brief 打包单个槽位配置(格式与 0x70 设置 payload 一致)
* @param slot_id 槽位 0~9
* @param buf 输出缓冲
* @param max_len 缓冲最大长度
* @return >0 写入字节数;<=0 失败
*/
int dengle_sched_pack_slot(uint8_t slot_id, uint8_t *buf, uint16_t max_len);
/**
* @brief 打包全部槽位配置(连续拼接多个 0x70 风格记录)
* @param buf 输出缓冲
* @param max_len 缓冲最大长度
* @return >0 写入字节数;<=0 失败
*/
int dengle_sched_pack_all(uint8_t *buf, uint16_t max_len);
/**
* @brief 获取有效条目数(调试用)
* @return 已使能槽位数
*/
uint8_t dengle_sched_get_active_count(void);
#endif /* __USR_DENGLE_SCHED_H__ */
+39
View File
@@ -714,6 +714,45 @@ void usr_app_ota_init()
}
}
// void usr_test_bleproto_runcmd(void)
// {
// bleproto_appdata_desc_t encode_desc = {0};
// encode_desc.header.version = 0;
// encode_desc.header.datafmt = BLEPROTO_APPDATA_DATAFMT_JSON;
// encode_desc.header.msgtype = BLEPROTO_APPDATA_MSGTYPE_REQ;
// encode_desc.header.msgid = 1;
// encode_desc.header.encrypt = 0;
// encode_desc.header.serviceid = E_BLEPROTO_SERVICE_ID_REPORTCMD;
// bleproto_reportcmd_req_t *pkt = &encode_desc.datast.reportcmd_req;
// /*
// > 告警上报 (*设备主动上报*)
// * 命令ID 120
// * 参数说明
// | 索引 | 类型 | 描述 | 取值 |
// | ---- | ----- | ---------- | ----------------------------------------------------- |
// | 0 | int32 | 告警事件id | 5000:硬件故障;5001:低电量报警; 5010:震动开机报警 |
// | 1 | int32 | UTC时间戳 | 事件触发UTCS时间戳单位秒 |
// */
// req->ycmd.cmd_id = 100;
// req->ycmd.arg_int32[0] = 5001; // 电量上报
// req->ycmd.arg_int32[1] = usr_var.usr_cureent_utc; // UTC时间戳
// req->ycmd.arg_int32_count = 2;
// req->ycmd.t = usr_var.usr_cureent_utc;
// int elen = bleproto_appdata_encode(txbuf, sizeof(txbuf), sizeof(txbuf), &encode_desc);
// printf("encode_len = %d\n", elen);
// if (elen > 0)
// {
// usr_ble_send_data(txbuf, elen);
// printf_buf(txbuf, elen);
// }
// }
void usr_test_protocol(void)
{
bleproto_appdata_desc_t encode_desc = {0};
+164 -6
View File
@@ -15,6 +15,7 @@
#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]"
@@ -36,6 +37,7 @@
#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 → 应答类 */
@@ -86,6 +88,7 @@ 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];
void usr_ble_rx_msgid_save(uint8_t msgid)
{
@@ -426,6 +429,117 @@ static void usr_jb_send_ack(uint8_t cmd, uint8_t sn, uint8_t 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
/**
@@ -458,25 +572,59 @@ static void usr_jb_process_timeout(void *priv)
printf("usr_jb cmd=0x%02x sn=%u plen=%u\n", cmd, sn, payload_len);
printf_buf(pkt, pkt_len);
if (cmd == USR_JB_CMD_SET_PRODCODE) {
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
continue;
break;
}
/* 缓存后统一落盘,避免同一周期多次写 flash */
s_pending_cmd = cmd;
s_pending_sn = sn;
s_pending_save = 1;
continue;
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;
}
/* 其它命令:整帧透传到 BLE(按 JB cmd 映射 ble_proto cmd */
usr_jb_forward_to_ble(pkt, pkt_len);
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:
/* 其它命令:整帧透传到 BLE(按 JB cmd 映射 ble_proto cmd */
usr_jb_forward_to_ble(pkt, pkt_len);
break;
}
} while (1);
if (!s_pending_save) {
@@ -484,6 +632,16 @@ static void usr_jb_process_timeout(void *priv)
}
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);
+5 -1
View File
@@ -19,7 +19,11 @@
#define USR_PRODCODE_LEN (4)
#define USR_PCODE_LEN (6)
#define USR_JB_CMD_SET_PRODCODE (0x64) /**< USB 下发产品码命令 */
#define USR_JB_CMD_SET_PRODCODE (0x64) /**< USB 设置产品码ACK=0x65 */
#define USR_JB_CMD_GET_PRODCODE (0x66) /**< USB 查询产品码;RSP=0x67 */
#define USR_JB_CMD_DENGLE_SET (0x70) /**< USB 设置 Dengle 定时;ACK=0x71 */
#define USR_JB_CMD_DENGLE_CLR (0x72) /**< USB 清除全部 DengleACK=0x73 */
#define USR_JB_CMD_DENGLE_QRY (0x74) /**< USB 查询 Dengle 配置;RSP=0x75 */
/*============================================================================*/
/* 外部接口 */