- 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]>
85 行
2.8 KiB
C
85 行
2.8 KiB
C
/* MQTT 链路的平台无关部分。topic 表与各条规矩在 mlink.h。 */
|
|
#include "mlink.h"
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include "cmd.h"
|
|
#include "cmd_json.h"
|
|
|
|
static char s_id[13];
|
|
static char s_topic[MLINK_T_COUNT][MLINK_TOPIC_MAX];
|
|
|
|
static const char *const k_suffix[MLINK_T_COUNT] = {
|
|
[MLINK_T_CMD] = "cmd", [MLINK_T_RSP] = "rsp", [MLINK_T_TELE] = "tele", [MLINK_T_STATE] = "state",
|
|
};
|
|
|
|
void mlink_init(const uint8_t mac[6])
|
|
{
|
|
snprintf(s_id, sizeof(s_id), "%02x%02x%02x%02x%02x%02x", mac[0], mac[1], mac[2], mac[3],
|
|
mac[4], mac[5]);
|
|
for (int t = 0; t < MLINK_T_COUNT; t++) {
|
|
snprintf(s_topic[t], sizeof(s_topic[t]), MLINK_TOPIC_ROOT "/%s/%s", s_id, k_suffix[t]);
|
|
}
|
|
}
|
|
|
|
const char *mlink_id(void) { return s_id; }
|
|
|
|
const char *mlink_topic(mlink_topic_t t) { return (t < MLINK_T_COUNT) ? s_topic[t] : ""; }
|
|
|
|
size_t mlink_state_online(const char *fw, const char *ip, char *out, size_t cap)
|
|
{
|
|
cj_writer_t w;
|
|
cj_w_init(&w, out, cap);
|
|
cj_w(&w, "{\"online\":true,\"fw\":\"%s\",\"ip\":\"%s\"}", fw, ip);
|
|
return w.overflow ? 0 : w.pos;
|
|
}
|
|
|
|
const char *mlink_inbound(const char *data, int len, int total, char *line, size_t cap)
|
|
{
|
|
/* 被切片的消息:命令最长 CMD_LINE_MAX,esp-mqtt 的缓冲 1 KB,切片只会出现在
|
|
超长消息上 —— 直接拒,不做重组(重组就是一块要管生命周期的缓冲)。 */
|
|
if (data == NULL || len <= 0 || len != total || cap == 0 || (size_t)len >= cap ||
|
|
len >= CMD_LINE_MAX) {
|
|
return "too_long";
|
|
}
|
|
for (int i = 0; i < len; i++) {
|
|
const unsigned char c = (unsigned char)data[i];
|
|
/* 同 USB 的理由:NUL 会让 JSON 解析提前结束,后面的内容就被放过了。 */
|
|
if ((c < 0x20 && c != '\t') || c == 0x7F) {
|
|
return "bad_frame";
|
|
}
|
|
}
|
|
memcpy(line, data, (size_t)len);
|
|
line[len] = '\0';
|
|
return NULL;
|
|
}
|
|
|
|
uint32_t mlink_backoff_ms(uint32_t attempt)
|
|
{
|
|
if (attempt >= 6) {
|
|
return MLINK_BACKOFF_MAX_MS; /* 1 s << 6 = 64 s,已经过顶 */
|
|
}
|
|
const uint32_t ms = 1000u << attempt;
|
|
return ms > MLINK_BACKOFF_MAX_MS ? MLINK_BACKOFF_MAX_MS : ms;
|
|
}
|
|
|
|
bool mlink_tele_due(mlink_tele_t *t, uint32_t now_ms, uint32_t period_ms)
|
|
{
|
|
if (!t->started) {
|
|
t->started = true;
|
|
t->last_ms = now_ms;
|
|
return true; /* 一连上就先报一条:服务器立刻有数据 */
|
|
}
|
|
if ((uint32_t)(now_ms - t->last_ms) < period_ms) {
|
|
return false;
|
|
}
|
|
/* 推进一个整周期而不是「= now」:被挤晚了不累积漂移;落后超过一个周期就对齐到现在,
|
|
不补发(补发的现象是网络一恢复连着冒出一串旧遥测)。 */
|
|
t->last_ms += period_ms;
|
|
if ((uint32_t)(now_ms - t->last_ms) >= period_ms) {
|
|
t->last_ms = now_ms;
|
|
}
|
|
return true;
|
|
}
|