固件:A12 命令处理器 + A13 USB 分帧 + A14 WiFi/MQTT(含 C1 配网)
- A12 单一命令处理器(cmd*.c):一行 JSON 进出、seq 全局判重(32 条按值)、
限幅 / 空槽一律拒 / 拒绝记事件;命令号与错误码冻结(进 flash)。新增 power 命令,
fan 改为只抬不压的下限(t_thermal_set_floor)。rail_set_mv 注明不拿锁,调用方包 xact。
- A13 USB-Serial-JTAG 分帧(ulink*.c):@C / @R 前缀 + CRC-16,日志行首 @ 垫空格,
任意切块可切;关掉 USB 副控制台(它绕开驱动写 FIFO)。PC 端客户端 tools/prlink.py。
- A14 + C1(netcfg / mlink / cmd_net):topic phonerail/<mac>/{cmd,rsp,tele,state},
mqtts(evse-ca 编进固件)、1 s 遥测、遗嘱离线;wifi / mqtt 配置只收 USB、存 NVS,
net 永不回显密码。
- 协议定稿写进需求文档 §10.8(A12 / A13 / A14 三节);N7 撤销改 D7(机架管理前后端)。
宿主机 23 个 ctest / 400 例全绿;板 B 已烧 0.1.0-a14,经 WiFi → 公网 → 8883 TLS 真连网验过。
固件 0xE2220 / 1 MB 分区只剩 12%,C2 前要重排分区表。
Co-Authored-By: Claude Opus 5.5 <[email protected]>
这个提交包含在:
+73
-8
@@ -1,11 +1,15 @@
|
||||
/* ESP32-S3 入口。只在 ESP-IDF 目标里编译,宿主机构建不包含本文件。 */
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "board_pins.h"
|
||||
#include "cmd.h"
|
||||
#include "cmd_queue.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_system.h"
|
||||
#include "evlog.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "freertos/task.h"
|
||||
#include "hal_gpio.h"
|
||||
#include "hal_i2c.h"
|
||||
@@ -21,6 +25,8 @@
|
||||
#include "t_actuator.h"
|
||||
#include "t_safety.h"
|
||||
#include "tasks.h"
|
||||
#include "mlink_esp.h"
|
||||
#include "ulink_esp.h"
|
||||
|
||||
static const char *TAG = "phonerail";
|
||||
|
||||
@@ -141,28 +147,66 @@ static void task_thermal(void *arg)
|
||||
}
|
||||
}
|
||||
|
||||
/* A9 的三个状态机。任务体还是三行:推一步 / 拿它算出来的下一个截止时刻 / 睡到那时。
|
||||
判据与时序全在 `t_actuator_step()` 里,那份在宿主机上跑得到。 */
|
||||
/* ---- 命令队列(A12)--------------------------------------------------------
|
||||
适配器(A13 USB / A14 WiFi)投递一行,执行器任务取出来调 `cmd_handle_line()`。
|
||||
处理器放在执行器任务里而不是链路任务里,是因为 `t_actuator_*` 的状态机没有锁:
|
||||
请求函数与 `t_actuator_step()` 必须同一个任务调(cmd.h「在哪个任务里跑」)。
|
||||
深度 4:人点按钮 + 上位机轮询 status,四条排队已经是异常情形了。 */
|
||||
typedef struct {
|
||||
cmd_link_t link;
|
||||
cmd_reply_fn reply;
|
||||
void *ctx;
|
||||
char line[CMD_LINE_MAX];
|
||||
} cmd_msg_t;
|
||||
|
||||
#define CMD_QUEUE_DEPTH 4
|
||||
static QueueHandle_t s_cmd_q;
|
||||
|
||||
bool cmd_queue_submit(cmd_link_t link, const char *line, cmd_reply_fn reply, void *ctx)
|
||||
{
|
||||
if (s_cmd_q == NULL || line == NULL) {
|
||||
return false;
|
||||
}
|
||||
cmd_msg_t m = { .link = link, .reply = reply, .ctx = ctx };
|
||||
const size_t n = strlen(line);
|
||||
if (n >= sizeof(m.line)) {
|
||||
return false; /* 超长行整行拒收,不截断(截断的 JSON 可能碰巧仍然合法) */
|
||||
}
|
||||
memcpy(m.line, line, n + 1);
|
||||
return xQueueSend(s_cmd_q, &m, 0) == pdTRUE; /* 不等:满了就是满了,让适配器去报 */
|
||||
}
|
||||
|
||||
/* A9 的三个状态机 + A12 的命令。任务体:推一步 → 带着「下一个截止时刻」去等命令 →
|
||||
有命令就处理、回应,然后回到开头再推一步。判据与时序全在宿主机上跑得到的那两个函数里。 */
|
||||
static void task_actuator(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
static char reply[CMD_OUT_MAX]; /* 静态:1.5 KB 放栈上会把 4 KB 的栈吃掉一大半 */
|
||||
cmd_msg_t m;
|
||||
for (;;) {
|
||||
/* A12 会把这里换成「带超时地等命令队列」:超时值就是这个返回值。 */
|
||||
const TickType_t t = pdMS_TO_TICKS(t_actuator_step());
|
||||
/* ⚠️ `CONFIG_FREERTOS_HZ=100` ⇒ 一个 tick 是 10 ms,`pdMS_TO_TICKS(5)`
|
||||
是 **0**,而 `vTaskDelay(0)` 只是让一次 CPU —— 那会让这个中优先级任务
|
||||
在收尾重试时空转,把 t_link / t_log 饿死。至少睡一个 tick。
|
||||
是 **0**,而「等 0 tick」只是查一眼就回来 —— 那会让这个中优先级任务
|
||||
在收尾重试时空转,把 t_link / t_log 饿死。至少等一个 tick。
|
||||
早醒没关系:每个状态都是拿绝对时刻算的,早来就再返回一个剩余时间。 */
|
||||
vTaskDelay(t == 0 ? 1 : t);
|
||||
if (xQueueReceive(s_cmd_q, &m, t == 0 ? 1 : t) == pdTRUE) {
|
||||
const size_t n = cmd_handle_line(m.link, m.line, reply, sizeof(reply));
|
||||
if (m.reply != NULL) {
|
||||
m.reply(m.ctx, reply, n);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 两条传输链路(§10.8)。本任务只收字节、切行、投进命令队列 ——
|
||||
**不执行命令**(那在执行器任务里,cmd.h)。USB(A13)在这里收包;MQTT(A14)的收包在
|
||||
esp-mqtt 自己的任务里,这里只管它的生命周期与遥测节拍。 */
|
||||
static void task_link(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
for (;;) {
|
||||
/* A12~A14:命令处理器 + 两条传输链路。 */
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
ulink_esp_poll(100); /* 最多等 100 ms 一包;没有 USB 驱动时退化成睡 100 ms */
|
||||
mlink_esp_poll();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -351,6 +395,27 @@ void app_main(void)
|
||||
而没 init 的执行器只会直接返回,那时序列还在跑,电关掉几秒又会被抬回来。 */
|
||||
t_actuator_init(act_pg_from_safety, NULL);
|
||||
|
||||
/* 命令处理器(A12)。status 读巡检快照;热管理快照等 A16 把任务体接上再给。
|
||||
队列要在执行器任务起来之前建好 —— 那个任务第一件事就是等它。 */
|
||||
const cmd_env_t cmd_env = { .safety = &s_snap, .thermal = NULL };
|
||||
cmd_init(&cmd_env);
|
||||
s_cmd_q = xQueueCreate(CMD_QUEUE_DEPTH, sizeof(cmd_msg_t));
|
||||
if (s_cmd_q == NULL) {
|
||||
ESP_LOGE(TAG, "命令队列建不起来");
|
||||
abort();
|
||||
}
|
||||
/* A13:USB 命令链路。装驱动 + 把日志复制一份到 USB(UART0 照旧)。
|
||||
失败不 abort:USB 命令链路不可用,供电与别的链路照常(§10.2)。 */
|
||||
err = ulink_esp_init();
|
||||
if (err != HAL_OK) {
|
||||
ESP_LOGE(TAG, "USB 命令链路不可用:%s", hal_err_str(err));
|
||||
}
|
||||
/* A14:WiFi / MQTT 命令链路(配置在 NVS,经 USB 下发)。失败同样不 abort。 */
|
||||
err = mlink_esp_init();
|
||||
if (err != HAL_OK) {
|
||||
ESP_LOGE(TAG, "WiFi 命令链路不可用:%s", hal_err_str(err));
|
||||
}
|
||||
|
||||
/* 事件日志(§10.7)。**要在接事件出口之前 init**,否则第一条事件进的是
|
||||
一个还没扫过 flash 的缓冲。
|
||||
⚠️ 失败**不 abort**:没有 `evlog` 分区时日志落不了盘,但五路照供电
|
||||
|
||||
在新工单中引用
屏蔽一个用户