固件 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]>
这个提交包含在:
+147
-1
@@ -11,6 +11,7 @@
|
||||
#include <string.h>
|
||||
|
||||
#include "board_pins.h"
|
||||
#include "chgmode.h"
|
||||
#include "cmd.h"
|
||||
#include "dac63204.h"
|
||||
#include "evlog.h"
|
||||
@@ -112,6 +113,7 @@ void setUp(void)
|
||||
t_thermal_set_floor(0);
|
||||
TEST_ASSERT_EQUAL_INT(HAL_OK, evlog_init());
|
||||
t_actuator_init(NULL, NULL);
|
||||
chgmode_reset();
|
||||
cmd_init(NULL);
|
||||
|
||||
/* 启动期独占,之后任何一次 I²C 事务没持锁就记违规 —— `set_voltage` 那一笔
|
||||
@@ -186,7 +188,8 @@ static void test_cmd_numbers_are_frozen(void)
|
||||
TEST_ASSERT_EQUAL_INT(11, CMD_MQTT);
|
||||
TEST_ASSERT_EQUAL_INT(12, CMD_NET);
|
||||
TEST_ASSERT_EQUAL_INT(13, CMD_OTA); /* C2 */
|
||||
TEST_ASSERT_EQUAL_INT(14, CMD_COUNT);
|
||||
TEST_ASSERT_EQUAL_INT(14, CMD_FLASH_MODE); /* 0.1.5 刷机模式 */
|
||||
TEST_ASSERT_EQUAL_INT(15, CMD_COUNT);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(0, CMD_OK);
|
||||
TEST_ASSERT_EQUAL_INT(1, CMD_ERR_PARSE);
|
||||
@@ -690,6 +693,144 @@ static void test_small_output_buffer(void)
|
||||
TEST_ASSERT_EQUAL_UINT16(300, t_thermal_floor());
|
||||
}
|
||||
|
||||
/* ---- 0.1.5 刷机模式(flash_mode) ----------------------------------------- */
|
||||
|
||||
static void test_flash_mode_command(void)
|
||||
{
|
||||
/* 关着电的路不许开(与 vbus 同一条规矩),记拒绝事件。 */
|
||||
ASSERT_HAS(send("{\"seq\":1,\"cmd\":\"flash_mode\",\"rail\":1,\"on\":true}"), "\"err\":\"state\"");
|
||||
TEST_ASSERT_EQUAL_INT(1, count_events(LOG_EV_CMD_REJECT));
|
||||
|
||||
ASSERT_HAS(send("{\"seq\":2,\"cmd\":\"power\",\"rail\":1,\"on\":true}"), "\"ok\":true");
|
||||
ASSERT_HAS(send("{\"seq\":3,\"cmd\":\"flash_mode\",\"rail\":1,\"on\":true}"), "\"left\":3600"); /* 默认 60 分钟 */
|
||||
TEST_ASSERT_TRUE(chgmode_active(1, hal_time_now_ms()));
|
||||
evlog_rec_t r;
|
||||
TEST_ASSERT_TRUE(find_event(LOG_EV_CHG_MODE, 0, &r));
|
||||
TEST_ASSERT_EQUAL_UINT8(1, r.rail);
|
||||
TEST_ASSERT_EQUAL_UINT8(CHG_MODE_DEFAULT_MIN, r.arg);
|
||||
|
||||
/* status 每一路报剩余秒数 */
|
||||
ASSERT_HAS(send("{\"cmd\":\"status\"}"), "\"chg\":3600");
|
||||
|
||||
/* 越界 / 参数错 */
|
||||
ASSERT_HAS(send("{\"seq\":4,\"cmd\":\"flash_mode\",\"rail\":1,\"on\":true,\"min\":0}"), "\"err\":\"range\"");
|
||||
ASSERT_HAS(send("{\"seq\":5,\"cmd\":\"flash_mode\",\"rail\":1,\"on\":true,\"min\":121}"), "\"err\":\"range\"");
|
||||
ASSERT_HAS(send("{\"seq\":6,\"cmd\":\"flash_mode\",\"rail\":1,\"on\":false,\"min\":5}"), "\"err\":\"arg\"");
|
||||
ASSERT_HAS(send("{\"seq\":7,\"cmd\":\"flash_mode\",\"rail\":1}"), "\"err\":\"arg\"");
|
||||
|
||||
/* 续期:本路再开一次不占新名额 */
|
||||
ASSERT_HAS(send("{\"seq\":8,\"cmd\":\"flash_mode\",\"rail\":1,\"on\":true,\"min\":120}"), "\"left\":7200");
|
||||
|
||||
/* 第三路被拒 */
|
||||
ASSERT_HAS(send("{\"seq\":9,\"cmd\":\"power\",\"rail\":2,\"on\":true}"), "\"ok\":true");
|
||||
ASSERT_HAS(send("{\"seq\":10,\"cmd\":\"power\",\"rail\":3,\"on\":true}"), "\"ok\":true");
|
||||
ASSERT_HAS(send("{\"seq\":11,\"cmd\":\"flash_mode\",\"rail\":2,\"on\":true,\"min\":10}"), "\"ok\":true");
|
||||
ASSERT_HAS(send("{\"seq\":12,\"cmd\":\"flash_mode\",\"rail\":3,\"on\":true}"), "\"err\":\"state\"");
|
||||
TEST_ASSERT_FALSE(chgmode_active(3, hal_time_now_ms()));
|
||||
|
||||
/* 关:回 left 0、记一条 arg=0;腾出的名额第三路就能用了 */
|
||||
ASSERT_HAS(send("{\"seq\":13,\"cmd\":\"flash_mode\",\"rail\":1,\"on\":false}"), "\"left\":0");
|
||||
TEST_ASSERT_TRUE(find_event(LOG_EV_CHG_MODE, 3, &r)); /* 开1、续期、开2、关1 */
|
||||
TEST_ASSERT_EQUAL_UINT8(CHG_EV_OFF, r.arg);
|
||||
ASSERT_HAS(send("{\"seq\":14,\"cmd\":\"flash_mode\",\"rail\":3,\"on\":true}"), "\"ok\":true");
|
||||
}
|
||||
|
||||
/* 关这一路电 = 刷机模式一并退出(不白占名额),记一条 arg=0。 */
|
||||
static void test_power_off_ends_flash_mode(void)
|
||||
{
|
||||
ASSERT_HAS(send("{\"seq\":1,\"cmd\":\"power\",\"rail\":4,\"on\":true}"), "\"ok\":true");
|
||||
ASSERT_HAS(send("{\"seq\":2,\"cmd\":\"flash_mode\",\"rail\":4,\"on\":true}"), "\"ok\":true");
|
||||
ASSERT_HAS(send("{\"seq\":3,\"cmd\":\"power\",\"rail\":4,\"on\":false}"), "\"ok\":true");
|
||||
TEST_ASSERT_FALSE(chgmode_active(4, hal_time_now_ms()));
|
||||
TEST_ASSERT_EQUAL_INT(2, count_events(LOG_EV_CHG_MODE));
|
||||
evlog_rec_t r;
|
||||
TEST_ASSERT_TRUE(find_event(LOG_EV_CHG_MODE, 1, &r));
|
||||
TEST_ASSERT_EQUAL_UINT8(CHG_EV_OFF, r.arg);
|
||||
}
|
||||
|
||||
/* 自然到期:执行器循环里的 cmd_chgmode_tick 记**一条** arg=255,之后不再重复记。 */
|
||||
static void test_flash_mode_expiry_is_logged_once(void)
|
||||
{
|
||||
ASSERT_HAS(send("{\"seq\":1,\"cmd\":\"power\",\"rail\":5,\"on\":true}"), "\"ok\":true");
|
||||
ASSERT_HAS(send("{\"seq\":2,\"cmd\":\"flash_mode\",\"rail\":5,\"on\":true,\"min\":1}"), "\"left\":60");
|
||||
cmd_chgmode_tick(hal_time_now_ms());
|
||||
TEST_ASSERT_EQUAL_INT(1, count_events(LOG_EV_CHG_MODE));
|
||||
|
||||
sim_time_advance_ms(60000 + CHG_MODE_GRACE_MS);
|
||||
cmd_chgmode_tick(hal_time_now_ms());
|
||||
cmd_chgmode_tick(hal_time_now_ms() + 100);
|
||||
TEST_ASSERT_EQUAL_INT(2, count_events(LOG_EV_CHG_MODE));
|
||||
evlog_rec_t r;
|
||||
TEST_ASSERT_TRUE(find_event(LOG_EV_CHG_MODE, 1, &r));
|
||||
TEST_ASSERT_EQUAL_UINT8(5, r.rail);
|
||||
TEST_ASSERT_EQUAL_UINT8(CHG_EV_EXPIRED, r.arg);
|
||||
ASSERT_HAS(send("{\"cmd\":\"status\"}"), "\"chg\":0");
|
||||
}
|
||||
|
||||
/* 0.1.13:安全判据 / 拔刀片把这一路关了(不经过 power 命令)⇒ 下一次 tick 退出刷机模式、记 arg=254、
|
||||
让出名额;硬重启中间 eFuse 关着的那几秒不算。 */
|
||||
static void test_flash_mode_ends_when_the_rail_loses_power(void)
|
||||
{
|
||||
ASSERT_HAS(send("{\"seq\":1,\"cmd\":\"power\",\"rail\":1,\"on\":true}"), "\"ok\":true");
|
||||
ASSERT_HAS(send("{\"seq\":2,\"cmd\":\"power\",\"rail\":2,\"on\":true}"), "\"ok\":true");
|
||||
ASSERT_HAS(send("{\"seq\":3,\"cmd\":\"power\",\"rail\":3,\"on\":true}"), "\"ok\":true");
|
||||
ASSERT_HAS(send("{\"seq\":4,\"cmd\":\"flash_mode\",\"rail\":1,\"on\":true}"), "\"ok\":true");
|
||||
ASSERT_HAS(send("{\"seq\":5,\"cmd\":\"flash_mode\",\"rail\":2,\"on\":true}"), "\"ok\":true");
|
||||
ASSERT_HAS(send("{\"seq\":6,\"cmd\":\"flash_mode\",\"rail\":3,\"on\":true}"), "\"err\":\"state\"");
|
||||
|
||||
/* 硬重启:HR_OFF 期间 eFuse 关着,但这是序列的一部分 —— 不退。 */
|
||||
ASSERT_HAS(send("{\"seq\":7,\"cmd\":\"power_cycle\",\"rail\":1}"), "\"ok\":true");
|
||||
TEST_ASSERT_FALSE(rail_efuse_is_on(1));
|
||||
cmd_chgmode_tick(hal_time_now_ms());
|
||||
TEST_ASSERT_TRUE(chgmode_active(1, hal_time_now_ms()));
|
||||
|
||||
/* 第 2 路被 t_safety 关了(这里直接拉低 EN 扮演):下一次 tick 退出。 */
|
||||
TEST_ASSERT_EQUAL_INT(HAL_OK, rail_efuse_enable(2, false));
|
||||
const int before = count_events(LOG_EV_CHG_MODE);
|
||||
cmd_chgmode_tick(hal_time_now_ms());
|
||||
TEST_ASSERT_FALSE(chgmode_active(2, hal_time_now_ms()));
|
||||
TEST_ASSERT_EQUAL_INT(before + 1, count_events(LOG_EV_CHG_MODE));
|
||||
evlog_rec_t r;
|
||||
TEST_ASSERT_TRUE(find_event(LOG_EV_CHG_MODE, before, &r));
|
||||
TEST_ASSERT_EQUAL_UINT8(2, r.rail);
|
||||
TEST_ASSERT_EQUAL_UINT8(CHG_EV_DROPPED, r.arg);
|
||||
cmd_chgmode_tick(hal_time_now_ms() + 100);
|
||||
TEST_ASSERT_EQUAL_INT(before + 1, count_events(LOG_EV_CHG_MODE)); /* 只记一次 */
|
||||
|
||||
/* 名额让出来了。 */
|
||||
ASSERT_HAS(send("{\"seq\":8,\"cmd\":\"flash_mode\",\"rail\":3,\"on\":true}"), "\"ok\":true");
|
||||
}
|
||||
|
||||
/* 0.1.6 加钟:只给开着的窗口加;剩余最多 120 分钟(超出截断);与 on / min 互斥;事件 arg = 0x80 | 分钟。 */
|
||||
static void test_flash_mode_extend(void)
|
||||
{
|
||||
ASSERT_HAS(send("{\"seq\":1,\"cmd\":\"power\",\"rail\":2,\"on\":true}"), "\"ok\":true");
|
||||
/* 没开着 ⇒ state(要重新开,不是加钟) */
|
||||
ASSERT_HAS(send("{\"seq\":2,\"cmd\":\"flash_mode\",\"rail\":2,\"add\":30}"), "\"err\":\"state\"");
|
||||
|
||||
ASSERT_HAS(send("{\"seq\":3,\"cmd\":\"flash_mode\",\"rail\":2,\"on\":true,\"min\":20}"), "\"left\":1200");
|
||||
sim_time_advance_ms(5 * 60000); /* 过了 5 分钟,还剩 15 */
|
||||
ASSERT_HAS(send("{\"seq\":4,\"cmd\":\"flash_mode\",\"rail\":2,\"add\":30}"), "\"left\":2700"); /* 15 + 30 */
|
||||
evlog_rec_t r;
|
||||
TEST_ASSERT_TRUE(find_event(LOG_EV_CHG_MODE, 1, &r));
|
||||
TEST_ASSERT_EQUAL_UINT8(CHG_EV_EXTEND | 30u, r.arg);
|
||||
|
||||
/* 截断到 120 分钟 */
|
||||
ASSERT_HAS(send("{\"seq\":5,\"cmd\":\"flash_mode\",\"rail\":2,\"add\":120}"), "\"left\":7200");
|
||||
|
||||
/* 越界 / 混用 */
|
||||
ASSERT_HAS(send("{\"seq\":6,\"cmd\":\"flash_mode\",\"rail\":2,\"add\":0}"), "\"err\":\"range\"");
|
||||
ASSERT_HAS(send("{\"seq\":7,\"cmd\":\"flash_mode\",\"rail\":2,\"add\":121}"), "\"err\":\"range\"");
|
||||
ASSERT_HAS(send("{\"seq\":8,\"cmd\":\"flash_mode\",\"rail\":2,\"add\":10,\"on\":true}"), "\"err\":\"arg\"");
|
||||
ASSERT_HAS(send("{\"seq\":9,\"cmd\":\"flash_mode\",\"rail\":2,\"add\":10,\"min\":10}"), "\"err\":\"arg\"");
|
||||
ASSERT_HAS(send("{\"seq\":10,\"cmd\":\"flash_mode\",\"rail\":2,\"add\":true}"), "\"err\":\"arg\"");
|
||||
|
||||
/* 加钟不占新名额:两路都开着时照样能加 */
|
||||
ASSERT_HAS(send("{\"seq\":11,\"cmd\":\"power\",\"rail\":3,\"on\":true}"), "\"ok\":true");
|
||||
ASSERT_HAS(send("{\"seq\":12,\"cmd\":\"flash_mode\",\"rail\":3,\"on\":true}"), "\"ok\":true");
|
||||
ASSERT_HAS(send("{\"seq\":13,\"cmd\":\"flash_mode\",\"rail\":3,\"add\":10}"), "\"left\":4200");
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
UNITY_BEGIN();
|
||||
@@ -709,6 +850,11 @@ int main(void)
|
||||
RUN_TEST(test_busy_rail_refuses_a_second_sequence);
|
||||
RUN_TEST(test_reboots_are_logged_with_kind_and_link);
|
||||
RUN_TEST(test_vbus_needs_the_rail_powered);
|
||||
RUN_TEST(test_flash_mode_command);
|
||||
RUN_TEST(test_power_off_ends_flash_mode);
|
||||
RUN_TEST(test_flash_mode_expiry_is_logged_once);
|
||||
RUN_TEST(test_flash_mode_ends_when_the_rail_loses_power);
|
||||
RUN_TEST(test_flash_mode_extend);
|
||||
RUN_TEST(test_power_off_drops_vbus_and_always_drops_the_efuse);
|
||||
RUN_TEST(test_power_records_intent_only_when_it_took_effect);
|
||||
RUN_TEST(test_fan_floor);
|
||||
|
||||
在新工单中引用
屏蔽一个用户