文件
phonerail/firmware/main/cmd.c
T
zhou yang和Claude Opus 5.5 9fb5bba367 固件 0.1.5~0.1.13-c5:刷机模式、多组 WiFi、热插拔初始化、总线锁误判、硬重启断 VBUS
- 0.1.5/0.1.6:刷机模式(flash_mode,最多 2 路,60 分钟可加钟)——窗口内顶高按 4450 mV 绝对值判、不断 VBUS
- 0.1.7/0.1.8:反灌断 VBUS 阈值放宽到 -300 mA(adb reboot bootloader 过渡实测 -208 mA)
- 0.1.9:WiFi 最多存 4 组,连不上轮换;NVS v1 自动迁移并双写(OTA 回滚仍能上网)
- 0.1.10:热插拔进来的刀片重新初始化 PCA9555 + INA226(否则 VBUS 常开、电流恒读 0)
- 0.1.11:i2cbus 嵌套检测改看本任务持锁(原全局标志让 key 随机回 busy、vbus 随机回 io)
- 0.1.12:硬重启先断 VBUS,开机校验通过 60 s 后再接回(否则假重启 / 误判没开机 / 进关机充电)
- 0.1.13:这一路被安全判据断电时一并退出刷机模式(事件 21 arg 254)

26 ctest 全绿;新增用例均先在旧代码上确认变红。板 B 已 OTA 到 0.1.13-c5 并实测。

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-29 08:16:21 +08:00

218 行
7.8 KiB
C
原始文件 Blame 文件历史

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
/* 单一命令处理器:解析 → 序号判重 → 限幅 / 空槽 → 执行 → 回应。契约在 cmd.h。 */
#include "cmd.h"
#include <string.h>
#include "cmd_priv.h"
#include "evlog.h"
/* ---- 名字表(线上用的,全 ASCII) ---------------------------------------- */
static const char *const k_names[CMD_COUNT] = {
[CMD_NONE] = "", [CMD_STATUS] = "status", [CMD_SET_VOLTAGE] = "set_voltage",
[CMD_POWER_CYCLE] = "power_cycle", [CMD_KEY] = "key", [CMD_EDL] = "edl",
[CMD_VBUS] = "vbus", [CMD_FAN] = "fan", [CMD_EVENTS] = "events",
[CMD_POWER] = "power", [CMD_WIFI] = "wifi", [CMD_MQTT] = "mqtt",
[CMD_NET] = "net", [CMD_OTA] = "ota", [CMD_FLASH_MODE] = "flash_mode",
};
static const char *const k_errs[CMD_ERR_COUNT] = {
[CMD_OK] = "ok", [CMD_ERR_PARSE] = "parse", [CMD_ERR_UNKNOWN] = "unknown_cmd",
[CMD_ERR_NO_SEQ] = "no_seq", [CMD_ERR_ARG] = "arg", [CMD_ERR_RANGE] = "range",
[CMD_ERR_EMPTY] = "empty", [CMD_ERR_BUSY] = "busy", [CMD_ERR_STATE] = "state",
[CMD_ERR_IO] = "io", [CMD_ERR_OVERFLOW] = "overflow", [CMD_ERR_LINK] = "link",
};
const char *cmd_name(cmd_id_t id) { return (id > CMD_NONE && id < CMD_COUNT) ? k_names[id] : "?"; }
const char *cmd_err_str(cmd_err_t e) { return (e < CMD_ERR_COUNT) ? k_errs[e] : "?"; }
/* 会动东西的命令:必须带 seq、进判重窗口。status / events 幂等,不进。 */
static bool mutates(cmd_id_t id)
{
return id != CMD_STATUS && id != CMD_EVENTS && id != CMD_NET;
}
/* ---- 状态 ---------------------------------------------------------------- */
typedef struct {
uint32_t seq;
uint8_t cmd;
uint8_t err;
} seen_t;
static cmd_env_t s_env;
const cmd_env_t *cmd_env(void) { return &s_env; }
static seen_t s_seen[CMD_SEQ_WINDOW];
static uint32_t s_seen_n; /* 已填几条(≤ 窗口) */
static uint32_t s_seen_head; /* 下一条写哪 */
static cmd_stats_t s_stats;
void cmd_init(const cmd_env_t *env)
{
memset(&s_env, 0, sizeof(s_env));
if (env != NULL) {
s_env = *env;
}
memset(s_seen, 0, sizeof(s_seen));
s_seen_n = 0;
s_seen_head = 0;
memset(&s_stats, 0, sizeof(s_stats));
}
void cmd_stats(cmd_stats_t *st) { *st = s_stats; }
static const seen_t *seen_find(uint32_t seq)
{
for (uint32_t i = 0; i < s_seen_n; i++) {
if (s_seen[i].seq == seq) {
return &s_seen[i];
}
}
return NULL;
}
static void seen_put(uint32_t seq, cmd_id_t id, cmd_err_t err)
{
s_seen[s_seen_head] = (seen_t){ .seq = seq, .cmd = (uint8_t)id, .err = (uint8_t)err };
s_seen_head = (s_seen_head + 1u) % CMD_SEQ_WINDOW;
if (s_seen_n < CMD_SEQ_WINDOW) {
s_seen_n++;
}
}
static cmd_err_t do_status(ctx_t *c)
{
cmd_report_status(c->w, &s_env);
return CMD_OK;
}
static cmd_err_t do_events(ctx_t *c)
{
int64_t from = 0;
const int g = cj_get_int(c->o, "from", &from);
if (g < 0 || (g == 1 && (from < 0 || from > (int64_t)UINT32_MAX))) {
return CMD_ERR_ARG;
}
cmd_report_events(c->w, (uint32_t)from);
return CMD_OK;
}
typedef cmd_err_t (*handler_t)(ctx_t *c);
static const handler_t k_handlers[CMD_COUNT] = {
[CMD_STATUS] = do_status, [CMD_SET_VOLTAGE] = do_set_voltage,
[CMD_POWER_CYCLE] = do_power_cycle, [CMD_KEY] = do_key, [CMD_EDL] = do_edl,
[CMD_VBUS] = do_vbus, [CMD_FAN] = do_fan, [CMD_EVENTS] = do_events,
[CMD_POWER] = do_power, [CMD_WIFI] = do_wifi, [CMD_MQTT] = do_mqtt,
[CMD_NET] = do_net, [CMD_OTA] = do_ota, [CMD_FLASH_MODE] = do_flash_mode,
};
static cmd_id_t lookup(const char *name)
{
for (int i = CMD_NONE + 1; i < CMD_COUNT; i++) {
if (strcmp(k_names[i], name) == 0) {
return (cmd_id_t)i;
}
}
return CMD_NONE;
}
/* ---- 入口 ---------------------------------------------------------------- */
/* 拒绝记事件的只有「越界 / 空槽 / 状态不许 / 链路不许」四类:它们是操作者(或上位机逻辑)
真的想做一件不该做的事。解析错、缺参数、忙、判重不记 —— USB 线上一串乱码
不该把 256 条的 RAM 缓冲刷掉(evlog.h)。
事件字段:arg = 命令号,bus_mv = 越界的那个值,current_ua = 拒绝原因(cmd_err_t)。 */
static void log_reject(const ctx_t *c, cmd_id_t id, cmd_err_t e)
{
if (e != CMD_ERR_RANGE && e != CMD_ERR_EMPTY && e != CMD_ERR_STATE && e != CMD_ERR_LINK) {
return;
}
const uint8_t rail = (c->rail >= 1 && c->rail <= PHONERAIL_RAIL_COUNT) ? (uint8_t)c->rail : 0;
evlog_put(rail, LOG_EV_CMD_REJECT, (uint8_t)id, (int32_t)c->bad, (int32_t)e);
}
static size_t reply(char *out, size_t len, bool has_seq, uint32_t seq, cmd_err_t e, bool dup,
const char *extra)
{
cj_writer_t w;
cj_w_init(&w, out, len);
cj_w(&w, "{");
if (has_seq) {
cj_w(&w, "\"seq\":%lu,", (unsigned long)seq);
}
if (e == CMD_OK) {
cj_w(&w, "\"ok\":true%s", extra != NULL ? extra : "");
} else {
cj_w(&w, "\"ok\":false,\"err\":\"%s\"", cmd_err_str(e));
}
cj_w(&w, "%s}", dup ? ",\"dup\":true" : "");
return w.overflow ? 0 : w.pos;
}
size_t cmd_handle_line(cmd_link_t link, const char *line, char *out, size_t out_len)
{
s_stats.lines++;
/* 两块大缓冲放静态区:处理器只在执行器任务里跑(cmd.h),不可重入,
而 1.5 KB 放在那个任务的栈上就是把栈顶到头。 */
static cj_obj_t o;
static char extra[CMD_OUT_MAX];
if (!cj_parse(line, &o)) {
s_stats.rejects++;
return reply(out, out_len, false, 0, CMD_ERR_PARSE, false, NULL);
}
int64_t seq64 = 0;
const int gs = cj_get_int(&o, "seq", &seq64);
const bool has_seq = (gs == 1 && seq64 >= 1 && seq64 <= (int64_t)UINT32_MAX);
const uint32_t seq = has_seq ? (uint32_t)seq64 : 0u;
const char *name = NULL;
const cmd_id_t id = (cj_get_str(&o, "cmd", &name) == 1) ? lookup(name) : CMD_NONE;
if (id == CMD_NONE) {
s_stats.rejects++;
return reply(out, out_len, has_seq, seq, CMD_ERR_UNKNOWN, false, NULL);
}
if ((gs != 0 && !has_seq) || (mutates(id) && !has_seq)) {
s_stats.rejects++; /* 带了一个坏 seq,或者该带没带 */
return reply(out, out_len, false, 0, CMD_ERR_NO_SEQ, false, NULL);
}
if (mutates(id)) {
const seen_t *hit = seen_find(seq);
if (hit != NULL) {
/* 重传:回第一次的结论,**不再执行**(§10.8 规矩 3)。
命令号对不上说明上位机把同一个 seq 用在了两条命令上 —— 那是它的 bug,
照样不执行(执行了就是「一个 seq 两个动作」),但回的是 arg 让它看得见。 */
s_stats.dups++;
const cmd_err_t e = (hit->cmd == (uint8_t)id) ? (cmd_err_t)hit->err : CMD_ERR_ARG;
return reply(out, out_len, true, seq, e, true, NULL);
}
}
cj_writer_t w;
cj_w_init(&w, extra, sizeof(extra));
ctx_t c = { .o = &o, .link = link, .w = &w, .rail = 0, .bad = 0 };
cmd_err_t e = k_handlers[id](&c);
if (e == CMD_OK && w.overflow && !mutates(id)) {
e = CMD_ERR_OVERFLOW; /* 只读命令:什么都没做,如实报 */
}
if (e != CMD_OK) {
s_stats.rejects++;
log_reject(&c, id, e);
}
if (mutates(id)) {
seen_put(seq, id, e);
}
size_t n = reply(out, out_len, has_seq, seq, e, false, w.overflow ? NULL : extra);
if (n == 0) {
/* 附加字段装不进调用方的缓冲。会动东西的命令此时**已经执行了**:
回一条不带附加字段的 ok,别让上位机以为没执行而换个 seq 再发一遍。 */
n = reply(out, out_len, has_seq, seq, mutates(id) ? e : CMD_ERR_OVERFLOW, false, NULL);
}
return n;
}