固件 0.1.3-c5:硬重启开机校验 + 重按(0.1.2)、status 上报上游电脑在不在(host)
- 0.1.2:power_cycle 按完电源键后看电流,没开就重按,最多 3 次;新事件 19 BOOT_CHECK - 0.1.3:UP_VBUS_DET(IO6)启动时配成输入,status 加 "host":true|false|null - rack:单板页显示「上游:已连电脑 / 未连电脑」 - HANDOFF:损耗两档的来历(Hub 工作 ≈1.1 W)、板 B 第 2 槽 PRSNT2# 卡低 Co-Authored-By: Claude Opus 5.5 <[email protected]>
这个提交包含在:
@@ -645,7 +645,15 @@ static void test_status_fits_and_reports_unknowns_as_null(void)
|
||||
const cmd_env_t env = { .safety = &s, .thermal = &t };
|
||||
cmd_init(&env);
|
||||
|
||||
/* 上游 VBUS:没配过的脚读不到 ⇒ null;配成输入后如实报高低。 */
|
||||
ASSERT_HAS(send("{\"cmd\":\"status\"}"), "\"host\":null");
|
||||
TEST_ASSERT_EQUAL(HAL_OK, hal_gpio_config(BOARD_PIN_UP_VBUS_DET, HAL_GPIO_INPUT));
|
||||
sim_gpio_drive(BOARD_PIN_UP_VBUS_DET, 0);
|
||||
ASSERT_HAS(send("{\"cmd\":\"status\"}"), "\"host\":false");
|
||||
sim_gpio_drive(BOARD_PIN_UP_VBUS_DET, 1); /* 留着高电平:下面量满配长度 */
|
||||
|
||||
const char *r = send("{\"seq\":4000000000,\"cmd\":\"status\"}");
|
||||
ASSERT_HAS(r, "\"host\":true");
|
||||
ASSERT_HAS(r, "{\"seq\":4000000000,\"ok\":true,\"fw\":\"" PHONERAIL_FW_VERSION "\"");
|
||||
ASSERT_HAS(r, "\"in\":{\"mv\":12030,\"ua\":1234567}");
|
||||
ASSERT_HAS(r, "{\"r\":2,\"pr\":true,\"en\":false,\"vb\":false,\"set\":3900,\"mv\":null,\"ua\":null");
|
||||
|
||||
@@ -86,7 +86,8 @@ static void test_flash_format_is_frozen(void)
|
||||
TEST_ASSERT_EQUAL_INT(16, LOG_EV_PD_LIMIT);
|
||||
TEST_ASSERT_EQUAL_INT(17, LOG_EV_PG_TIMEOUT); /* A15 */
|
||||
TEST_ASSERT_EQUAL_INT(18, LOG_EV_OTA); /* C2 */
|
||||
TEST_ASSERT_EQUAL_INT(19, LOG_EV_COUNT);
|
||||
TEST_ASSERT_EQUAL_INT(19, LOG_EV_BOOT_CHECK); /* 0.1.2 开机校验 */
|
||||
TEST_ASSERT_EQUAL_INT(20, LOG_EV_COUNT);
|
||||
}
|
||||
|
||||
/* §10.7 的「必记事件」有八类,A10 只产生其中两类。剩下的号先占住,
|
||||
|
||||
@@ -1096,6 +1096,250 @@ static void test_format_prints_every_rail(void)
|
||||
TEST_ASSERT_EQUAL_CHAR('\0', small[sizeof(small) - 1]);
|
||||
}
|
||||
|
||||
/* ==== 0.1.2:硬重启的开机校验与重按 =======================================
|
||||
2026-09-28 板 B 第 1 路:电源键弹片压刀片铜盘接触不良,7 次硬重启只开起来 1 次,
|
||||
失败时电流一直 1~2 mA(屏都不亮)。假手机照实测建模:
|
||||
· 第 `boots_on` 次按满 5 s 松手后开机 ⇒ 电流 ~210 mA(logo 段);
|
||||
· 其余情况 1.5 mA(关机态);
|
||||
· `half_on`:第一次按下去 PMIC 醒了、SoC 没接住 —— 松手后 415 mA 撑 2.5 s 再掉回去
|
||||
(照「按 2 s」那次的实测形态),校验不能被它骗过去。 */
|
||||
static bool s_ua_readable;
|
||||
static int s_phone_boots_on; /* 第几次按能开;0 = 永远开不了(弹片没压上) */
|
||||
static bool s_phone_half_on;
|
||||
static bool s_phone_on;
|
||||
static uint32_t s_phone_half_until;
|
||||
static int s_edges; /* 看到的「按下」次数 */
|
||||
static int s_last_pin;
|
||||
static uint32_t s_press_t, s_release_t, s_min_gap;
|
||||
|
||||
static int s_boot_calls;
|
||||
static bool s_boot_booted;
|
||||
static uint8_t s_boot_presses;
|
||||
|
||||
static bool fake_ua(void *ctx, int rail, int32_t *ua)
|
||||
{
|
||||
(void)ctx;
|
||||
(void)rail;
|
||||
if (!s_ua_readable) {
|
||||
return false;
|
||||
}
|
||||
if (s_phone_on) {
|
||||
*ua = 210000;
|
||||
} else if ((int32_t)(s_phone_half_until - hal_time_now_ms()) > 0) {
|
||||
*ua = 415000;
|
||||
} else {
|
||||
*ua = 1500;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void fake_boot_done(void *ctx, int rail, bool booted, uint8_t presses, int32_t ua)
|
||||
{
|
||||
(void)ctx;
|
||||
(void)rail;
|
||||
(void)ua;
|
||||
s_boot_calls++;
|
||||
s_boot_booted = booted;
|
||||
s_boot_presses = presses;
|
||||
}
|
||||
|
||||
static void phone_up(int boots_on, bool half_on)
|
||||
{
|
||||
s_ua_readable = true;
|
||||
s_phone_boots_on = boots_on;
|
||||
s_phone_half_on = half_on;
|
||||
s_phone_on = false;
|
||||
s_phone_half_until = hal_time_now_ms();
|
||||
s_edges = 0;
|
||||
s_last_pin = 0;
|
||||
s_press_t = s_release_t = 0;
|
||||
s_min_gap = UINT32_MAX;
|
||||
s_boot_calls = 0;
|
||||
t_actuator_set_boot_check(fake_ua, fake_boot_done, NULL);
|
||||
}
|
||||
|
||||
/* 看电源键那根栅极的边沿,推假手机的状态。 */
|
||||
static void phone_watch(int rail)
|
||||
{
|
||||
const int p = key_pin(rail, PCA9555_KEY_PWR);
|
||||
const uint32_t now = hal_time_now_ms();
|
||||
if (p && !s_last_pin) {
|
||||
s_edges++;
|
||||
if (s_release_t != 0 && now - s_release_t < s_min_gap) {
|
||||
s_min_gap = now - s_release_t;
|
||||
}
|
||||
s_press_t = now;
|
||||
}
|
||||
if (!p && s_last_pin) {
|
||||
s_release_t = now;
|
||||
if (now - s_press_t >= ACT_KEY_MS_ON) {
|
||||
if (s_edges == s_phone_boots_on) {
|
||||
s_phone_on = true;
|
||||
} else if (s_phone_half_on && s_edges == 1) {
|
||||
s_phone_half_until = now + 2500;
|
||||
}
|
||||
}
|
||||
}
|
||||
s_last_pin = p;
|
||||
}
|
||||
|
||||
/* `run_ms()` 加上每一步之后看一眼按键。 */
|
||||
static void run_phone_ms(int rail, uint32_t ms)
|
||||
{
|
||||
const uint32_t target = hal_time_now_ms() + ms;
|
||||
for (;;) {
|
||||
uint32_t w = t_actuator_step();
|
||||
phone_watch(rail);
|
||||
const uint32_t now = hal_time_now_ms();
|
||||
if ((int32_t)(now - target) >= 0) {
|
||||
return;
|
||||
}
|
||||
const uint32_t left = target - now;
|
||||
if (w == 0 || w > left) {
|
||||
w = left;
|
||||
}
|
||||
sim_time_advance_ms(w);
|
||||
}
|
||||
}
|
||||
|
||||
/* 一轮按键 + 校验最长多久:按 5 s + 松键后判到窗口末尾。 */
|
||||
#define ONE_TRY_MS (ACT_HR_BOOT_KEY_MS + ACT_HR_VERIFY_DELAY_MS + ACT_HR_VERIFY_WINDOW_MS + 50)
|
||||
#define HR_TO_PRESS_MS (ACT_HR_OFF_MS + ACT_HR_RAMP_MS + ACT_HR_PG_POLL_MS)
|
||||
|
||||
static void test_boot_check_first_press_boots(void)
|
||||
{
|
||||
const int rail = 1;
|
||||
s_pg_val[rail - 1] = true;
|
||||
phone_up(1, false);
|
||||
|
||||
const uint32_t t0 = hal_time_now_ms();
|
||||
TEST_ASSERT_EQUAL_INT(HAL_OK, t_actuator_hard_reset(rail));
|
||||
run_phone_ms(rail, HR_TO_PRESS_MS + ACT_HR_BOOT_KEY_MS + 10);
|
||||
TEST_ASSERT_EQUAL_INT(ACT_ST_HR_VERIFY, t_actuator_state(rail));
|
||||
TEST_ASSERT_EQUAL_INT(0, key_pin(rail, PCA9555_KEY_PWR)); /* 校验期间键是松开的 */
|
||||
|
||||
run_phone_ms(rail, ONE_TRY_MS);
|
||||
TEST_ASSERT_FALSE(t_actuator_busy(rail));
|
||||
TEST_ASSERT_EQUAL_INT(ACT_RES_OK, t_actuator_last_result(rail));
|
||||
TEST_ASSERT_EQUAL_INT(1, s_edges);
|
||||
TEST_ASSERT_EQUAL_INT(1, s_boot_calls);
|
||||
TEST_ASSERT_TRUE(s_boot_booted);
|
||||
TEST_ASSERT_EQUAL_UINT8(1, s_boot_presses);
|
||||
/* 开起来就收工:不把整个窗口等满。 */
|
||||
TEST_ASSERT_TRUE(hal_time_now_ms() - t0 >= HR_TO_PRESS_MS + ACT_HR_BOOT_KEY_MS + ACT_HR_VERIFY_DELAY_MS);
|
||||
TEST_ASSERT_EQUAL_UINT32(1, t_actuator_done(rail));
|
||||
TEST_ASSERT_EQUAL_UINT32(0, sim_lock_violations(HAL_LOCK_I2C));
|
||||
}
|
||||
|
||||
/* ⭐ 这次的主症状:第一下没压上,第二下开了。 */
|
||||
static void test_boot_check_presses_again_when_the_phone_stays_off(void)
|
||||
{
|
||||
const int rail = 3;
|
||||
s_pg_val[rail - 1] = true;
|
||||
phone_up(2, false);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(HAL_OK, t_actuator_hard_reset(rail));
|
||||
run_phone_ms(rail, HR_TO_PRESS_MS + 2 * ONE_TRY_MS + 1000);
|
||||
|
||||
TEST_ASSERT_FALSE(t_actuator_busy(rail));
|
||||
TEST_ASSERT_EQUAL_INT(ACT_RES_OK, t_actuator_last_result(rail));
|
||||
TEST_ASSERT_EQUAL_INT(2, s_edges);
|
||||
TEST_ASSERT_TRUE(s_boot_booted);
|
||||
TEST_ASSERT_EQUAL_UINT8(2, s_boot_presses);
|
||||
TEST_ASSERT_EQUAL_INT(1, s_boot_calls); /* 只在有结论时报一次 */
|
||||
/* 两次按之间至少隔开整段判定延迟 —— 连着按等于一次 10 s 长按(强制重启档)。 */
|
||||
TEST_ASSERT_TRUE(s_min_gap >= ACT_HR_VERIFY_DELAY_MS);
|
||||
TEST_ASSERT_EQUAL_INT(0, key_pin(rail, PCA9555_KEY_PWR));
|
||||
}
|
||||
|
||||
/* 按满三次都不开:记 NO_BOOT,**电继续开着**,键松开,不再按第四下。 */
|
||||
static void test_boot_check_gives_up_after_three_presses_and_keeps_power(void)
|
||||
{
|
||||
const int rail = 2;
|
||||
s_pg_val[rail - 1] = true;
|
||||
phone_up(0, false);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(HAL_OK, t_actuator_hard_reset(rail));
|
||||
run_phone_ms(rail, HR_TO_PRESS_MS + ACT_HR_BOOT_TRIES * ONE_TRY_MS + 1000);
|
||||
|
||||
TEST_ASSERT_FALSE(t_actuator_busy(rail));
|
||||
TEST_ASSERT_EQUAL_INT(ACT_RES_NO_BOOT, t_actuator_last_result(rail));
|
||||
TEST_ASSERT_EQUAL_INT(ACT_HR_BOOT_TRIES, s_edges);
|
||||
TEST_ASSERT_EQUAL_INT(1, s_boot_calls);
|
||||
TEST_ASSERT_FALSE(s_boot_booted);
|
||||
TEST_ASSERT_EQUAL_UINT8(ACT_HR_BOOT_TRIES, s_boot_presses);
|
||||
TEST_ASSERT_EQUAL_INT(1, sim_gpio_level(rail_cfg(rail)->efuse_en));
|
||||
TEST_ASSERT_EQUAL_INT(0, key_pin(rail, PCA9555_KEY_PWR));
|
||||
TEST_ASSERT_EQUAL_STRING("no-boot", t_actuator_result_str(ACT_RES_NO_BOOT));
|
||||
|
||||
run_phone_ms(rail, 30000); /* 收工之后再也不按 */
|
||||
TEST_ASSERT_EQUAL_INT(ACT_HR_BOOT_TRIES, s_edges);
|
||||
}
|
||||
|
||||
/* 读不到电流 ⇒ 判不了 ⇒ **不重按**,也不报结论。 */
|
||||
static void test_boot_check_does_not_press_again_when_current_is_unreadable(void)
|
||||
{
|
||||
const int rail = 4;
|
||||
s_pg_val[rail - 1] = true;
|
||||
phone_up(0, false);
|
||||
s_ua_readable = false;
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(HAL_OK, t_actuator_hard_reset(rail));
|
||||
run_phone_ms(rail, HR_TO_PRESS_MS + ACT_HR_BOOT_TRIES * ONE_TRY_MS + 1000);
|
||||
|
||||
TEST_ASSERT_FALSE(t_actuator_busy(rail));
|
||||
TEST_ASSERT_EQUAL_INT(ACT_RES_OK, t_actuator_last_result(rail));
|
||||
TEST_ASSERT_EQUAL_INT(1, s_edges);
|
||||
TEST_ASSERT_EQUAL_INT(0, s_boot_calls);
|
||||
}
|
||||
|
||||
/* 「PMIC 醒了、SoC 没接住」:松手后先 415 mA 再掉回去。校验必须等它掉完再看,
|
||||
判成「开了」就不会有第二下。 */
|
||||
static void test_boot_check_is_not_fooled_by_a_pmic_that_wakes_and_drops(void)
|
||||
{
|
||||
const int rail = 5;
|
||||
s_pg_val[rail - 1] = true;
|
||||
phone_up(2, true);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(HAL_OK, t_actuator_hard_reset(rail));
|
||||
run_phone_ms(rail, HR_TO_PRESS_MS + 2 * ONE_TRY_MS + 1000);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(ACT_RES_OK, t_actuator_last_result(rail));
|
||||
TEST_ASSERT_EQUAL_INT(2, s_edges);
|
||||
TEST_ASSERT_EQUAL_UINT8(2, s_boot_presses);
|
||||
}
|
||||
|
||||
/* 校验期间被打断(比如判据要关这一路):收工、不再按。 */
|
||||
static void test_abort_during_boot_check_stops_pressing(void)
|
||||
{
|
||||
const int rail = 1;
|
||||
s_pg_val[rail - 1] = true;
|
||||
phone_up(0, false);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(HAL_OK, t_actuator_hard_reset(rail));
|
||||
run_phone_ms(rail, HR_TO_PRESS_MS + ACT_HR_BOOT_KEY_MS + 500);
|
||||
TEST_ASSERT_EQUAL_INT(ACT_ST_HR_VERIFY, t_actuator_state(rail));
|
||||
|
||||
t_actuator_abort(rail);
|
||||
TEST_ASSERT_FALSE(t_actuator_busy(rail));
|
||||
TEST_ASSERT_EQUAL_INT(ACT_RES_ABORTED, t_actuator_last_result(rail));
|
||||
run_phone_ms(rail, 3 * ONE_TRY_MS);
|
||||
TEST_ASSERT_EQUAL_INT(1, s_edges);
|
||||
TEST_ASSERT_EQUAL_INT(0, s_boot_calls);
|
||||
}
|
||||
|
||||
/* 单独的按键命令不校验:那是人按的,按什么、按多久是人的意图。 */
|
||||
static void test_plain_key_command_is_never_repeated(void)
|
||||
{
|
||||
const int rail = 2;
|
||||
phone_up(0, false);
|
||||
TEST_ASSERT_EQUAL_INT(HAL_OK, t_actuator_key(rail, ACT_KEYS_PWR, ACT_KEY_MS_ON));
|
||||
run_phone_ms(rail, ACT_KEY_MS_ON + 3 * ONE_TRY_MS);
|
||||
TEST_ASSERT_EQUAL_INT(1, s_edges);
|
||||
TEST_ASSERT_EQUAL_INT(ACT_RES_OK, t_actuator_last_result(rail));
|
||||
TEST_ASSERT_EQUAL_INT(0, s_boot_calls);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
UNITY_BEGIN();
|
||||
@@ -1130,6 +1374,14 @@ int main(void)
|
||||
|
||||
RUN_TEST(test_actuator_and_safety_share_one_lock_without_starving);
|
||||
|
||||
RUN_TEST(test_boot_check_first_press_boots);
|
||||
RUN_TEST(test_boot_check_presses_again_when_the_phone_stays_off);
|
||||
RUN_TEST(test_boot_check_gives_up_after_three_presses_and_keeps_power);
|
||||
RUN_TEST(test_boot_check_does_not_press_again_when_current_is_unreadable);
|
||||
RUN_TEST(test_boot_check_is_not_fooled_by_a_pmic_that_wakes_and_drops);
|
||||
RUN_TEST(test_abort_during_boot_check_stops_pressing);
|
||||
RUN_TEST(test_plain_key_command_is_never_repeated);
|
||||
|
||||
RUN_TEST(test_bad_arguments_are_rejected);
|
||||
RUN_TEST(test_timings_match_the_documents);
|
||||
RUN_TEST(test_the_lock_discipline_check_actually_fires);
|
||||
|
||||
在新工单中引用
屏蔽一个用户