- 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]>
394 行
17 KiB
C
394 行
17 KiB
C
/* A14 / C1:网络配置、MQTT 链路的平台无关部分、三条网络命令。
|
||
|
||
计划里 A14 的验收原话是「协议层用 sim 传输测通;真连网留到 B 段」。
|
||
这里测的是会写错而且上板查不出来的那几件事:
|
||
· 密码**永远**不出现在任何回应里;
|
||
· 配置命令**只收 USB**,经 WiFi 来的一律拒并记事件;
|
||
· 没存进 flash 的配置不生效(重启前后一致);
|
||
· MQTT 入站消息与 USB 同一套坏字节规矩;topic / 退避 / 遥测节拍的数。 */
|
||
#include <stdio.h>
|
||
#include <string.h>
|
||
|
||
#include "cmd.h"
|
||
#include "evlog.h"
|
||
#include "mlink.h"
|
||
#include "netcfg.h"
|
||
#include "sim_hal.h"
|
||
#include "unity.h"
|
||
|
||
static char s_out[CMD_OUT_MAX];
|
||
|
||
/* 假存储后端:记住存了什么、可以注入失败。 */
|
||
static netcfg_t s_saved;
|
||
static int s_saves;
|
||
static hal_err_t s_save_err;
|
||
static int s_changed;
|
||
|
||
static hal_err_t fake_save(void *ctx, const netcfg_t *cfg)
|
||
{
|
||
(void)ctx;
|
||
if (s_save_err != HAL_OK) {
|
||
return s_save_err;
|
||
}
|
||
s_saved = *cfg;
|
||
s_saves++;
|
||
return HAL_OK;
|
||
}
|
||
|
||
static void fake_changed(void *ctx)
|
||
{
|
||
(void)ctx;
|
||
s_changed++;
|
||
}
|
||
|
||
void setUp(void)
|
||
{
|
||
sim_hal_reset();
|
||
memset(&s_saved, 0, sizeof(s_saved));
|
||
s_saves = 0;
|
||
s_save_err = HAL_OK;
|
||
s_changed = 0;
|
||
netcfg_init(NULL, fake_save, fake_changed, NULL);
|
||
TEST_ASSERT_EQUAL_INT(HAL_OK, evlog_init());
|
||
cmd_init(NULL);
|
||
}
|
||
|
||
void tearDown(void) { TEST_ASSERT_EQUAL_INT(0, sim_crit_depth()); }
|
||
|
||
static const char *send_on(cmd_link_t link, const char *line)
|
||
{
|
||
cmd_handle_line(link, line, s_out, sizeof(s_out));
|
||
return s_out;
|
||
}
|
||
|
||
static void assert_has_at(const char *resp, const char *needle, int line)
|
||
{
|
||
UNITY_TEST_ASSERT_NOT_NULL(strstr(resp, needle), line, resp);
|
||
}
|
||
static void assert_lacks_at(const char *resp, const char *needle, int line)
|
||
{
|
||
UNITY_TEST_ASSERT_NULL(strstr(resp, needle), line, resp);
|
||
}
|
||
#define ASSERT_HAS(r, n) assert_has_at((r), (n), __LINE__)
|
||
#define ASSERT_LACKS(r, n) assert_lacks_at((r), (n), __LINE__)
|
||
|
||
static int count_rejects(void)
|
||
{
|
||
evlog_rec_t rec[64];
|
||
(void)evlog_flush(0);
|
||
const uint32_t n = evlog_read(0, rec, 64);
|
||
int k = 0;
|
||
for (uint32_t i = 0; i < n; i++) {
|
||
k += rec[i].type == LOG_EV_CMD_REJECT;
|
||
}
|
||
return k;
|
||
}
|
||
|
||
/* ---- 配置命令 ------------------------------------------------------------ */
|
||
|
||
/* ⭐ USB 来的 wifi / mqtt:存进后端、内存生效、通知网络那一侧重连。 */
|
||
static void test_config_over_usb_is_saved_and_applied(void)
|
||
{
|
||
ASSERT_HAS(send_on(CMD_LINK_USB,
|
||
"{\"seq\":1,\"cmd\":\"wifi\",\"ssid\":\"farm-2g\",\"pass\":\"s3cret-pass\"}"),
|
||
"\"ok\":true");
|
||
ASSERT_HAS(send_on(CMD_LINK_USB, "{\"seq\":2,\"cmd\":\"mqtt\",\"uri\":\"mqtt://10.5.5.13:1883\","
|
||
"\"user\":\"rack\",\"pass\":\"m-pass-1\",\"tele_ms\":2000}"),
|
||
"\"ok\":true");
|
||
TEST_ASSERT_EQUAL_INT(2, s_saves);
|
||
TEST_ASSERT_EQUAL_INT(2, s_changed);
|
||
TEST_ASSERT_EQUAL_STRING("farm-2g", s_saved.wifi[0].ssid);
|
||
TEST_ASSERT_EQUAL_STRING("s3cret-pass", s_saved.wifi[0].pass); /* 第二次存的是整份:WiFi 那部分还在 */
|
||
TEST_ASSERT_EQUAL_STRING("mqtt://10.5.5.13:1883", s_saved.uri);
|
||
TEST_ASSERT_EQUAL_UINT32(2000, s_saved.tele_ms);
|
||
|
||
netcfg_t c;
|
||
netcfg_get(&c);
|
||
TEST_ASSERT_EQUAL_MEMORY(&s_saved, &c, sizeof(c));
|
||
}
|
||
|
||
/* ⭐ 经 WiFi 改 WiFi / MQTT 配置:一律 `link`、记事件、后端一次都不碰。 */
|
||
static void test_config_over_wifi_is_refused_and_logged(void)
|
||
{
|
||
ASSERT_HAS(send_on(CMD_LINK_WIFI,
|
||
"{\"seq\":1,\"cmd\":\"wifi\",\"ssid\":\"evil\",\"pass\":\"12345678\"}"),
|
||
"\"err\":\"link\"");
|
||
ASSERT_HAS(send_on(CMD_LINK_WIFI, "{\"seq\":2,\"cmd\":\"mqtt\",\"uri\":\"mqtt://1.2.3.4\"}"),
|
||
"\"err\":\"link\"");
|
||
TEST_ASSERT_EQUAL_INT(0, s_saves);
|
||
TEST_ASSERT_EQUAL_INT(0, s_changed);
|
||
TEST_ASSERT_EQUAL_INT(2, count_rejects());
|
||
/* 只读的 net 经 WiFi 照常可用。 */
|
||
ASSERT_HAS(send_on(CMD_LINK_WIFI, "{\"cmd\":\"net\"}"), "\"ok\":true");
|
||
}
|
||
|
||
/* ⭐ 密码永不外泄:写进去之后 net 回应里只有 *_set,没有明文;换哪条链路问都一样。 */
|
||
static void test_net_never_echoes_a_password(void)
|
||
{
|
||
send_on(CMD_LINK_USB, "{\"seq\":1,\"cmd\":\"wifi\",\"ssid\":\"farm\",\"pass\":\"TopSecret99\"}");
|
||
send_on(CMD_LINK_USB, "{\"seq\":2,\"cmd\":\"mqtt\",\"uri\":\"mqtt://h:1883\",\"user\":\"u1\","
|
||
"\"pass\":\"BrokerPw77\"}");
|
||
for (int link = CMD_LINK_TEST; link <= CMD_LINK_WIFI; link++) {
|
||
const char *r = send_on((cmd_link_t)link, "{\"cmd\":\"net\"}");
|
||
ASSERT_LACKS(r, "TopSecret99");
|
||
ASSERT_LACKS(r, "BrokerPw77");
|
||
ASSERT_HAS(r, "\"ssid\":\"farm\",\"pass_set\":true");
|
||
ASSERT_HAS(r, "\"user\":\"u1\",\"mpass_set\":true");
|
||
ASSERT_HAS(r, "\"uri\":\"mqtt://h:1883\"");
|
||
ASSERT_HAS(r, "\"tele_ms\":1000"); /* 没给 → 默认 */
|
||
}
|
||
/* status 里也不许有。 */
|
||
ASSERT_LACKS(send_on(CMD_LINK_USB, "{\"cmd\":\"status\"}"), "TopSecret99");
|
||
}
|
||
|
||
static void test_config_validation(void)
|
||
{
|
||
static const char *const bad[] = {
|
||
"{\"seq\":1,\"cmd\":\"wifi\",\"ssid\":\"\",\"pass\":\"12345678\"}", /* 空 SSID */
|
||
"{\"seq\":2,\"cmd\":\"wifi\",\"ssid\":\"x\",\"pass\":\"1234567\"}", /* 7 位口令 */
|
||
"{\"seq\":3,\"cmd\":\"mqtt\",\"uri\":\"http://10.5.5.13\"}", /* 不是 mqtt:// */
|
||
"{\"seq\":4,\"cmd\":\"mqtt\",\"uri\":\"ws://10.5.5.13\"}", /* 不认的 scheme */
|
||
"{\"seq\":5,\"cmd\":\"mqtt\",\"uri\":\"mqtt://h\",\"tele_ms\":100}", /* 太密 */
|
||
"{\"seq\":6,\"cmd\":\"mqtt\",\"uri\":\"mqtt://h\",\"tele_ms\":60001}",
|
||
"{\"seq\":7,\"cmd\":\"mqtt\",\"uri\":\"mqtt://h\",\"tele_ms\":-1}",
|
||
};
|
||
for (size_t i = 0; i < sizeof(bad) / sizeof(bad[0]); i++) {
|
||
ASSERT_HAS(send_on(CMD_LINK_USB, bad[i]), "\"err\":\"range\"");
|
||
}
|
||
ASSERT_HAS(send_on(CMD_LINK_USB, "{\"seq\":8,\"cmd\":\"wifi\",\"pass\":\"12345678\"}"),
|
||
"\"err\":\"arg\"");
|
||
ASSERT_HAS(send_on(CMD_LINK_USB, "{\"seq\":9,\"cmd\":\"wifi\",\"ssid\":\"x\",\"pass\":1}"),
|
||
"\"err\":\"arg\"");
|
||
ASSERT_HAS(send_on(CMD_LINK_USB, "{\"seq\":12,\"cmd\":\"mqtt\",\"uri\":\"mqtts://\"}"),
|
||
"\"err\":\"range\""); /* 只有 scheme 没有主机 */
|
||
ASSERT_HAS(send_on(CMD_LINK_USB, "{\"seq\":13,\"cmd\":\"mqtt\",\"uri\":\"mqtts://home.example:8883\"}"),
|
||
"\"ok\":true");
|
||
/* 开放网络(空口令)是合法的;63 位口令顶到上限也合法。 */
|
||
ASSERT_HAS(send_on(CMD_LINK_USB, "{\"seq\":10,\"cmd\":\"wifi\",\"ssid\":\"open\"}"),
|
||
"\"ok\":true");
|
||
char line[200];
|
||
char pass[64];
|
||
memset(pass, 'p', 63);
|
||
pass[63] = '\0';
|
||
snprintf(line, sizeof(line), "{\"seq\":11,\"cmd\":\"wifi\",\"ssid\":\"x\",\"pass\":\"%s\"}", pass);
|
||
ASSERT_HAS(send_on(CMD_LINK_USB, line), "\"ok\":true");
|
||
TEST_ASSERT_EQUAL_STRING(pass, s_saved.wifi[1].pass);
|
||
TEST_ASSERT_EQUAL_INT(3, s_saves);
|
||
}
|
||
|
||
/* 存不下:回 io,内存里的配置**不变**,也不通知重连(重启前后一致)。 */
|
||
static void test_unsaved_config_does_not_take_effect(void)
|
||
{
|
||
send_on(CMD_LINK_USB, "{\"seq\":1,\"cmd\":\"wifi\",\"ssid\":\"old\",\"pass\":\"12345678\"}");
|
||
s_save_err = HAL_ERR_BUS;
|
||
ASSERT_HAS(send_on(CMD_LINK_USB,
|
||
"{\"seq\":2,\"cmd\":\"wifi\",\"ssid\":\"new\",\"pass\":\"12345678\"}"),
|
||
"\"err\":\"io\"");
|
||
netcfg_t c;
|
||
netcfg_get(&c);
|
||
TEST_ASSERT_EQUAL_STRING("old", c.wifi[0].ssid);
|
||
TEST_ASSERT_EQUAL_INT(1, s_changed);
|
||
}
|
||
|
||
/* NVS 里读回来的东西不可信:没带结尾 0 的字段被强制收尾;遥测周期越界回默认;
|
||
引号 / 反斜杠 / 控制字符在 net 回应里换成 `?`,回应仍是一行合法 JSON。 */
|
||
static void test_loaded_config_is_sanitised(void)
|
||
{
|
||
netcfg_t junk;
|
||
memset(&junk, 'A', sizeof(junk));
|
||
junk.tele_ms = 5;
|
||
memcpy(junk.wifi[0].ssid, "a\"b\\c\n", 6);
|
||
netcfg_init(&junk, NULL, NULL, NULL);
|
||
netcfg_t c;
|
||
netcfg_get(&c);
|
||
TEST_ASSERT_EQUAL_size_t(NETCFG_SSID_MAX, strlen(c.wifi[0].ssid));
|
||
TEST_ASSERT_EQUAL_size_t(NETCFG_URI_MAX, strlen(c.uri));
|
||
TEST_ASSERT_EQUAL_UINT32(NETCFG_TELE_DEFAULT_MS, c.tele_ms);
|
||
|
||
const char *r = send_on(CMD_LINK_USB, "{\"cmd\":\"net\"}");
|
||
ASSERT_HAS(r, "\"ssid\":\"a?b?c?AAAA");
|
||
TEST_ASSERT_EQUAL_CHAR('}', r[strlen(r) - 1]);
|
||
}
|
||
|
||
/* ---- 多组 WiFi(0.1.9)--------------------------------------------------- */
|
||
|
||
/* ⭐ 加第二组不挤掉第一组;同名再发 = 改口令、不多占一格;回应带组数。 */
|
||
static void test_wifi_adds_and_updates_by_name(void)
|
||
{
|
||
ASSERT_HAS(send_on(CMD_LINK_USB, "{\"seq\":1,\"cmd\":\"wifi\",\"ssid\":\"home\",\"pass\":\"pass-home-1\"}"),
|
||
"\"n\":1");
|
||
ASSERT_HAS(send_on(CMD_LINK_USB, "{\"seq\":2,\"cmd\":\"wifi\",\"ssid\":\"hotspot\",\"pass\":\"pass-hot-1\"}"),
|
||
"\"n\":2");
|
||
ASSERT_HAS(send_on(CMD_LINK_USB, "{\"seq\":3,\"cmd\":\"wifi\",\"ssid\":\"home\",\"pass\":\"pass-home-2\"}"),
|
||
"\"n\":2");
|
||
netcfg_t c;
|
||
netcfg_get(&c);
|
||
TEST_ASSERT_EQUAL_INT(2, netcfg_wifi_count(&c));
|
||
TEST_ASSERT_EQUAL_STRING("home", c.wifi[0].ssid);
|
||
TEST_ASSERT_EQUAL_STRING("pass-home-2", c.wifi[0].pass);
|
||
TEST_ASSERT_EQUAL_STRING("hotspot", c.wifi[1].ssid);
|
||
TEST_ASSERT_EQUAL_STRING("pass-hot-1", c.wifi[1].pass);
|
||
TEST_ASSERT_EQUAL_MEMORY(&s_saved, &c, sizeof(c)); /* 存的是整张表 */
|
||
|
||
/* net:ssid = 正在连的那组(状态里 wifi_idx),ssids = 整张表;任何一组的口令都不出现。 */
|
||
net_status_t st;
|
||
memset(&st, 0, sizeof(st));
|
||
st.wifi_idx = 1;
|
||
net_status_set(&st);
|
||
const char *r = send_on(CMD_LINK_WIFI, "{\"cmd\":\"net\"}");
|
||
ASSERT_HAS(r, "\"ssid\":\"hotspot\",\"pass_set\":true,\"ssids\":[\"home\",\"hotspot\"]");
|
||
ASSERT_LACKS(r, "pass-home");
|
||
ASSERT_LACKS(r, "pass-hot");
|
||
}
|
||
|
||
/* ⭐ 删一组后面的往前补;删没有的 = range;满了再加新名字 = state、表不变;满了改已有的照样行。 */
|
||
static void test_wifi_delete_and_full_table(void)
|
||
{
|
||
char line[128];
|
||
for (int i = 0; i < NETCFG_WIFI_N; i++) {
|
||
snprintf(line, sizeof(line), "{\"seq\":%d,\"cmd\":\"wifi\",\"ssid\":\"n%d\",\"pass\":\"12345678\"}",
|
||
i + 1, i);
|
||
ASSERT_HAS(send_on(CMD_LINK_USB, line), "\"ok\":true");
|
||
}
|
||
ASSERT_HAS(send_on(CMD_LINK_USB, "{\"seq\":20,\"cmd\":\"wifi\",\"ssid\":\"extra\",\"pass\":\"12345678\"}"),
|
||
"\"err\":\"state\"");
|
||
ASSERT_HAS(send_on(CMD_LINK_USB, "{\"seq\":21,\"cmd\":\"wifi\",\"ssid\":\"n3\",\"pass\":\"87654321\"}"),
|
||
"\"ok\":true");
|
||
TEST_ASSERT_EQUAL_INT(NETCFG_WIFI_N + 1, s_saves);
|
||
|
||
ASSERT_HAS(send_on(CMD_LINK_USB, "{\"seq\":22,\"cmd\":\"wifi\",\"ssid\":\"n1\",\"del\":true}"),
|
||
"\"n\":3");
|
||
netcfg_t c;
|
||
netcfg_get(&c);
|
||
TEST_ASSERT_EQUAL_INT(NETCFG_WIFI_N - 1, netcfg_wifi_count(&c));
|
||
TEST_ASSERT_EQUAL_STRING("n0", c.wifi[0].ssid);
|
||
TEST_ASSERT_EQUAL_STRING("n2", c.wifi[1].ssid);
|
||
TEST_ASSERT_EQUAL_STRING("n3", c.wifi[2].ssid);
|
||
TEST_ASSERT_EQUAL_STRING("87654321", c.wifi[2].pass);
|
||
TEST_ASSERT_EQUAL_STRING("", c.wifi[3].ssid);
|
||
TEST_ASSERT_EQUAL_STRING("", c.wifi[3].pass); /* 挪走之后末尾清干净,不留口令 */
|
||
|
||
ASSERT_HAS(send_on(CMD_LINK_USB, "{\"seq\":23,\"cmd\":\"wifi\",\"ssid\":\"n1\",\"del\":true}"),
|
||
"\"err\":\"range\"");
|
||
ASSERT_HAS(send_on(CMD_LINK_USB, "{\"seq\":24,\"cmd\":\"wifi\",\"ssid\":\"n1\",\"del\":1}"),
|
||
"\"err\":\"arg\"");
|
||
/* 删也只收 USB。 */
|
||
ASSERT_HAS(send_on(CMD_LINK_WIFI, "{\"seq\":25,\"cmd\":\"wifi\",\"ssid\":\"n0\",\"del\":true}"),
|
||
"\"err\":\"link\"");
|
||
/* 删到空:net 回应仍是合法 JSON,ssids 是空数组。 */
|
||
for (int i = 0; i < 3; i++) {
|
||
snprintf(line, sizeof(line), "{\"seq\":%d,\"cmd\":\"wifi\",\"ssid\":\"%s\",\"del\":true}", 30 + i,
|
||
i == 0 ? "n0" : i == 1 ? "n2" : "n3");
|
||
ASSERT_HAS(send_on(CMD_LINK_USB, line), "\"ok\":true");
|
||
}
|
||
ASSERT_HAS(send_on(CMD_LINK_USB, "{\"cmd\":\"net\"}"), "\"ssid\":\"\",\"pass_set\":false,\"ssids\":[]");
|
||
}
|
||
|
||
/* NVS 读回来的表中间有空位:挤到前面,count 才算得对、轮换才不会停在空位上。 */
|
||
static void test_loaded_table_is_compacted(void)
|
||
{
|
||
netcfg_t in;
|
||
memset(&in, 0, sizeof(in));
|
||
strcpy(in.wifi[1].ssid, "b");
|
||
strcpy(in.wifi[1].pass, "12345678");
|
||
strcpy(in.wifi[3].ssid, "d");
|
||
strcpy(in.wifi[2].pass, "orphan-pass"); /* 空 SSID 带着口令:整格丢掉 */
|
||
netcfg_init(&in, NULL, NULL, NULL);
|
||
netcfg_t c;
|
||
netcfg_get(&c);
|
||
TEST_ASSERT_EQUAL_INT(2, netcfg_wifi_count(&c));
|
||
TEST_ASSERT_EQUAL_STRING("b", c.wifi[0].ssid);
|
||
TEST_ASSERT_EQUAL_STRING("12345678", c.wifi[0].pass);
|
||
TEST_ASSERT_EQUAL_STRING("d", c.wifi[1].ssid);
|
||
TEST_ASSERT_EQUAL_INT(1, netcfg_wifi_find(&c, "d"));
|
||
TEST_ASSERT_EQUAL_INT(-1, netcfg_wifi_find(&c, ""));
|
||
TEST_ASSERT_EQUAL_STRING("", c.wifi[2].pass);
|
||
}
|
||
|
||
/* ---- MQTT 链路的平台无关部分 --------------------------------------------- */
|
||
|
||
static void test_topics_follow_the_mac(void)
|
||
{
|
||
const uint8_t mac[6] = { 0xB8, 0x1F, 0x3F, 0xC9, 0xC8, 0xBC };
|
||
mlink_init(mac);
|
||
TEST_ASSERT_EQUAL_STRING("b81f3fc9c8bc", mlink_id());
|
||
TEST_ASSERT_EQUAL_STRING("phonerail/b81f3fc9c8bc/cmd", mlink_topic(MLINK_T_CMD));
|
||
TEST_ASSERT_EQUAL_STRING("phonerail/b81f3fc9c8bc/rsp", mlink_topic(MLINK_T_RSP));
|
||
TEST_ASSERT_EQUAL_STRING("phonerail/b81f3fc9c8bc/tele", mlink_topic(MLINK_T_TELE));
|
||
TEST_ASSERT_EQUAL_STRING("phonerail/b81f3fc9c8bc/state", mlink_topic(MLINK_T_STATE));
|
||
TEST_ASSERT_TRUE(strlen(mlink_topic(MLINK_T_STATE)) < MLINK_TOPIC_MAX);
|
||
|
||
char buf[96];
|
||
TEST_ASSERT_TRUE(mlink_state_online("0.1.0-a14", "10.5.5.77", buf, sizeof(buf)) > 0);
|
||
TEST_ASSERT_EQUAL_STRING("{\"online\":true,\"fw\":\"0.1.0-a14\",\"ip\":\"10.5.5.77\"}", buf);
|
||
}
|
||
|
||
/* 入站消息:与 USB 同一套坏字节规矩;切片的 / 超长的直接拒。 */
|
||
static void test_inbound_messages(void)
|
||
{
|
||
char line[CMD_LINE_MAX];
|
||
const char *ok = "{\"cmd\":\"status\"}";
|
||
TEST_ASSERT_NULL(mlink_inbound(ok, (int)strlen(ok), (int)strlen(ok), line, sizeof(line)));
|
||
TEST_ASSERT_EQUAL_STRING(ok, line); /* 不带结尾 0 的负载也被正确收尾 */
|
||
|
||
const char nul[] = "{\"cmd\":\"status\"}\0junk";
|
||
TEST_ASSERT_EQUAL_STRING("bad_frame",
|
||
mlink_inbound(nul, sizeof(nul) - 1, sizeof(nul) - 1, line, sizeof(line)));
|
||
TEST_ASSERT_EQUAL_STRING("bad_frame", mlink_inbound("{\n}", 3, 3, line, sizeof(line)));
|
||
TEST_ASSERT_EQUAL_STRING("too_long", mlink_inbound(ok, 5, 100, line, sizeof(line))); /* 切片 */
|
||
TEST_ASSERT_EQUAL_STRING("too_long", mlink_inbound(ok, 0, 0, line, sizeof(line)));
|
||
static char big[CMD_LINE_MAX + 8];
|
||
memset(big, 'x', sizeof(big));
|
||
TEST_ASSERT_EQUAL_STRING("too_long", mlink_inbound(big, CMD_LINE_MAX, CMD_LINE_MAX, line,
|
||
sizeof(line)));
|
||
TEST_ASSERT_NULL(mlink_inbound(big, CMD_LINE_MAX - 1, CMD_LINE_MAX - 1, line, sizeof(line)));
|
||
}
|
||
|
||
static void test_backoff_doubles_and_caps(void)
|
||
{
|
||
static const uint32_t want[] = { 1000, 2000, 4000, 8000, 16000, 32000, 60000, 60000 };
|
||
for (uint32_t i = 0; i < sizeof(want) / sizeof(want[0]); i++) {
|
||
TEST_ASSERT_EQUAL_UINT32(want[i], mlink_backoff_ms(i));
|
||
}
|
||
TEST_ASSERT_EQUAL_UINT32(MLINK_BACKOFF_MAX_MS, mlink_backoff_ms(0xFFFFFFFFu));
|
||
}
|
||
|
||
/* 遥测节拍:一连上先报一条;之后按整周期推进(被挤晚了不漂);落后一整周期以上
|
||
就对齐到现在、不补发;32 位毫秒回绕照样对。 */
|
||
static void test_telemetry_cadence(void)
|
||
{
|
||
mlink_tele_t t = { 0 };
|
||
TEST_ASSERT_TRUE(mlink_tele_due(&t, 5000, 1000));
|
||
TEST_ASSERT_FALSE(mlink_tele_due(&t, 5999, 1000));
|
||
TEST_ASSERT_TRUE(mlink_tele_due(&t, 6030, 1000)); /* 晚 30 ms */
|
||
TEST_ASSERT_EQUAL_UINT32(6000, t.last_ms); /* 相位没漂 */
|
||
TEST_ASSERT_FALSE(mlink_tele_due(&t, 6999, 1000));
|
||
TEST_ASSERT_TRUE(mlink_tele_due(&t, 12000, 1000)); /* 卡了 5 s */
|
||
TEST_ASSERT_EQUAL_UINT32(12000, t.last_ms); /* 对齐到现在,不补发 */
|
||
TEST_ASSERT_FALSE(mlink_tele_due(&t, 12500, 1000));
|
||
|
||
mlink_tele_t w = { 0 };
|
||
TEST_ASSERT_TRUE(mlink_tele_due(&w, 0xFFFFFF00u, 1000));
|
||
TEST_ASSERT_FALSE(mlink_tele_due(&w, 0x00000100u, 1000)); /* 回绕后才过了 512 ms */
|
||
TEST_ASSERT_TRUE(mlink_tele_due(&w, 0x00000300u, 1000));
|
||
}
|
||
|
||
int main(void)
|
||
{
|
||
UNITY_BEGIN();
|
||
RUN_TEST(test_config_over_usb_is_saved_and_applied);
|
||
RUN_TEST(test_config_over_wifi_is_refused_and_logged);
|
||
RUN_TEST(test_net_never_echoes_a_password);
|
||
RUN_TEST(test_config_validation);
|
||
RUN_TEST(test_unsaved_config_does_not_take_effect);
|
||
RUN_TEST(test_loaded_config_is_sanitised);
|
||
RUN_TEST(test_wifi_adds_and_updates_by_name);
|
||
RUN_TEST(test_wifi_delete_and_full_table);
|
||
RUN_TEST(test_loaded_table_is_compacted);
|
||
RUN_TEST(test_topics_follow_the_mac);
|
||
RUN_TEST(test_inbound_messages);
|
||
RUN_TEST(test_backoff_doubles_and_caps);
|
||
RUN_TEST(test_telemetry_cadence);
|
||
return UNITY_END();
|
||
}
|