/* 单一命令处理器:解析 → 序号判重 → 限幅 / 空槽 → 执行 → 回应。契约在 cmd.h。 */ #include "cmd.h" #include #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", }; 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, }; 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; }