feat(motor): 支持多通道同节点号电机状态管理

支持同一节点号在多个CAN通道上的电机状态管理,采用node×channel双键存储机制。

- 修改motor_state.cpp中的槽位索引逻辑,从单节点索引改为(node-1)*MOTOR_CHANNELS+channel索引
- 增加MOTOR_CHANNELS和MOTOR_MAX_SLOTS宏定义,支持9节点×3通道共27个槽位
- 更新http_handlers.cpp中的二进制响应缓冲区为static类型,避免大缓冲区占用栈空间
- 调整循环遍历范围从MOTOR_MAX_NODES改为MOTOR_MAX_SLOTS
- 添加HTML页面的空favicon配置,避免浏览器404请求
- 新增CSS样式用于显示节点在其他通道在线的状态提示
This commit is contained in:
小龙虾
2026-07-30 11:57:21 +08:00
parent 74eb68b140
commit e9cb7342f6
9 changed files with 190 additions and 245 deletions
+6 -5
View File
@@ -63,19 +63,20 @@ bool onMotorStateEvent(const uint8_t *payload, size_t len)
s_table.boot_active = (active_cnt & 0x80U) ? 1U : 0U;
}
/* 逐条解析 entry */
/* 逐条解析 entry:node×channel 双键槽位,同节点号多通道互不覆盖 */
size_t offset = MOTOR_PAGE_HEADER;
for (uint8_t i = 0; i < entry_count; ++i) {
const uint8_t *e = &payload[offset];
const uint8_t node = e[0];
if (node < 1 || node > MOTOR_MAX_NODES) {
const uint8_t ch = e[1];
if (node < 1 || node > MOTOR_MAX_NODES || ch >= MOTOR_CHANNELS) {
offset += MOTOR_ENTRY_SIZE;
continue;
}
motor_slot_t &slot = s_table.slots[node - 1];
motor_slot_t &slot = s_table.slots[(node - 1) * MOTOR_CHANNELS + ch];
slot.node_id = node;
slot.channel = e[1];
slot.channel = ch;
slot.online = e[2];
slot.dlc = e[3];
slot.rx_count = rdU32Le(&e[4]);
@@ -87,7 +88,7 @@ bool onMotorStateEvent(const uint8_t *payload, size_t len)
/* 统计有效槽位 */
uint8_t valid = 0;
for (uint8_t i = 0; i < MOTOR_MAX_NODES; ++i) {
for (uint8_t i = 0; i < MOTOR_MAX_SLOTS; ++i) {
if (s_table.slots[i].node_id != 0) valid++;
}
s_table.valid_slots = valid;
+7 -2
View File
@@ -10,6 +10,9 @@
* [0] node_id, [1] channel, [2] online, [3] dlc,
* [4..7] rx_count (LE), [8..11] seq (LE),
* [12..43] data[32] (原始 STATUS payload)
*
* 同一节点号可能同时挂在多个 CAN 通道(两台电机同号异线),
* 缓存表与 GD32 端一致按 node×channel 双键存储(27 槽)。
*/
#ifndef MOTOR_STATE_H
#define MOTOR_STATE_H
@@ -23,10 +26,12 @@ extern "C" {
#endif
#define MOTOR_MAX_NODES 9
#define MOTOR_CHANNELS 3
#define MOTOR_MAX_SLOTS (MOTOR_MAX_NODES * MOTOR_CHANNELS)
#define MOTOR_STATUS_DLC 32
#define MOTOR_ENTRY_SIZE 44
#define MOTOR_PAGE_HEADER 4
#define MOTOR_TOTAL_PAGES 2
#define MOTOR_TOTAL_PAGES 6
/** @brief 单电机状态快照。 */
typedef struct {
@@ -41,7 +46,7 @@ typedef struct {
/** @brief 全局电机状态表。 */
typedef struct {
motor_slot_t slots[MOTOR_MAX_NODES]; ///< 按 node_id-1 索引
motor_slot_t slots[MOTOR_MAX_SLOTS]; ///< 按 (node_id-1)*MOTOR_CHANNELS+channel 索引
uint8_t active_count; ///< 在线电机数
uint8_t boot_active; ///< 1=总线上有电机在 BOOT(0x103 心跳,来自 0xC5 头 bit7)
uint8_t valid_slots; ///< 有效槽位数(node_id!=0)
+6 -5
View File
@@ -1868,7 +1868,7 @@ esp_err_t motor_state_handler(httpd_req_t *req)
* rsvd u16 = 0
* Entry: node u8, ch u8, online u8, dlc u8,
* rx_count u32, seq u32, data[32]
* 9 节点最大 12 + 9*44 = 408B。
* 27 槽(9 node × 3 ch,node×channel 双键)最大 12 + 27*44 = 1200B。
* 无 have_data 时仍发 header(count=0),让前端区分「无数据」与「空总线」。 */
bool want_bin = false;
{
@@ -1882,8 +1882,9 @@ esp_err_t motor_state_handler(httpd_req_t *req)
}
if (want_bin) {
httpd_resp_set_type(req, "application/octet-stream");
/* 静态栈缓冲:12 + 9*44 = 408B,远小于常见任务栈 */
uint8_t buf[12 + MOTOR_MAX_NODES * MOTOR_ENTRY_SIZE];
/* 静态缓冲:27 槽扩容后 1200B 不再上栈(httpd 栈 7KB 已紧);
* httpd 单任务串行处理请求,静态缓冲无竞态。 */
static uint8_t buf[12 + MOTOR_MAX_SLOTS * MOTOR_ENTRY_SIZE];
size_t off = 0;
/* magic 'MS' (LE: 0x53, 0x4D) */
buf[off++] = 0x53U; buf[off++] = 0x4DU;
@@ -1904,7 +1905,7 @@ esp_err_t motor_state_handler(httpd_req_t *req)
uint8_t count = 0;
if (have) {
for (uint8_t i = 0; i < MOTOR_MAX_NODES; ++i) {
for (uint8_t i = 0; i < MOTOR_MAX_SLOTS; ++i) {
const motor_slot_t &s = tbl.slots[i];
if (s.node_id == 0) continue;
buf[off++] = s.node_id;
@@ -1953,7 +1954,7 @@ esp_err_t motor_state_handler(httpd_req_t *req)
(unsigned)tbl.active_count, tbl.boot_active ? "true" : "false", (unsigned long)age);
bool first = true;
for (uint8_t i = 0; i < MOTOR_MAX_NODES && off > 0 && (size_t)off < HTTP_JSON_WORK_CAP - 128; ++i) {
for (uint8_t i = 0; i < MOTOR_MAX_SLOTS && off > 0 && (size_t)off < HTTP_JSON_WORK_CAP - 128; ++i) {
const motor_slot_t &s = tbl.slots[i];
if (s.node_id == 0) continue;