固件 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]>
这个提交包含在:
zhou yang
2026-09-29 08:16:21 +08:00
共同撰写人 Claude Opus 5.5
父节点 385d2f9c47
当前提交 9fb5bba367
共修改 35 个文件,包含 5120 行新增和 3675 行删除
+104 -6
查看文件
@@ -97,8 +97,8 @@ static void test_config_over_usb_is_saved_and_applied(void)
"\"ok\":true");
TEST_ASSERT_EQUAL_INT(2, s_saves);
TEST_ASSERT_EQUAL_INT(2, s_changed);
TEST_ASSERT_EQUAL_STRING("farm-2g", s_saved.ssid);
TEST_ASSERT_EQUAL_STRING("s3cret-pass", s_saved.pass); /* 第二次存的是整份:WiFi 那部分还在 */
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);
@@ -172,7 +172,7 @@ static void test_config_validation(void)
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.pass);
TEST_ASSERT_EQUAL_STRING(pass, s_saved.wifi[1].pass);
TEST_ASSERT_EQUAL_INT(3, s_saves);
}
@@ -186,7 +186,7 @@ static void test_unsaved_config_does_not_take_effect(void)
"\"err\":\"io\"");
netcfg_t c;
netcfg_get(&c);
TEST_ASSERT_EQUAL_STRING("old", c.ssid);
TEST_ASSERT_EQUAL_STRING("old", c.wifi[0].ssid);
TEST_ASSERT_EQUAL_INT(1, s_changed);
}
@@ -197,11 +197,11 @@ static void test_loaded_config_is_sanitised(void)
netcfg_t junk;
memset(&junk, 'A', sizeof(junk));
junk.tele_ms = 5;
memcpy(junk.ssid, "a\"b\\c\n", 6);
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.ssid));
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);
@@ -210,6 +210,101 @@ static void test_loaded_config_is_sanitised(void)
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)
@@ -287,6 +382,9 @@ int main(void)
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);