- 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]>
218 行
7.8 KiB
C
218 行
7.8 KiB
C
/* 单一命令处理器:解析 → 序号判重 → 限幅 / 空槽 → 执行 → 回应。契约在 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;
|
||
}
|