/* 0.1.14:Hub 按需启停(hubctl.h)。 口径同其它测试:判据用 `hub_decide()` 直接喂输入;引脚与事件用 `hubctl_tick()` 打在假 GPIO / 事件缓冲上。R716 的上拉用 `sim_gpio_drive(HUB_RST#, 1)` 模拟 —— 放成输入时读回 1 = Hub 在工作,配成输出拉低时读回 0 = 按住复位。 */ #include #include "board_pins.h" #include "evlog.h" #include "hal_time.h" #include "hubctl.h" #include "netcfg.h" #include "sim_hal.h" #include "unity.h" #define PIN BOARD_PIN_HUB_RST_N static hub_mode_t s_saved; static int s_saves; static void save(void *ctx, hub_mode_t m) { (void)ctx; s_saved = m; s_saves++; } static void link(bool up) { net_status_t st; memset(&st, 0, sizeof(st)); st.mqtt = up ? NET_UP : NET_CONNECTING; net_status_set(&st); } static uint32_t now(void) { return hal_time_now_ms(); } /* 每 100 ms 一拍(执行器空闲时的节奏),推 ms 毫秒。 */ static void run_ms(uint32_t ms) { for (uint32_t t = 0; t < ms; t += 100) { sim_time_advance_ms(100); hubctl_tick(now()); } } static int hub_events(uint8_t arg) { (void)evlog_flush(0); evlog_rec_t r[16]; const uint32_t n = evlog_read(0, r, 16); int k = 0; for (uint32_t i = 0; i < n; i++) { k += (r[i].type == LOG_EV_HUB && r[i].arg == arg) ? 1 : 0; } return k; } void setUp(void) { sim_hal_reset(); TEST_ASSERT_EQUAL_INT(HAL_OK, evlog_init()); sim_gpio_drive(PIN, 1); /* R716 */ s_saves = 0; link(true); hubctl_init(HUB_MODE_AUTO, save, NULL, now()); } void tearDown(void) {} static const hub_in_t k_idle = { .link_up = true }; /* ---- 判据 ---------------------------------------------------------------- */ /* 启动算一次需要:HUB_IDLE_MS 之内开着(idle),到点就关。 */ static void test_boot_window_then_off(void) { TEST_ASSERT_TRUE(hubctl_is_on()); TEST_ASSERT_EQUAL_INT(1, sim_gpio_level(PIN)); TEST_ASSERT_EQUAL_INT(HUB_WHY_IDLE, hub_decide(&k_idle, now() + HUB_IDLE_MS - 1)); TEST_ASSERT_EQUAL_INT(HUB_WHY_OFF, hub_decide(&k_idle, now() + HUB_IDLE_MS)); } /* 优先级:mode > vbus > flash > act > hold > link。任一条都刷新「最后一次需要」。 */ static void test_reasons_and_their_order(void) { hub_in_t in = k_idle; in.vbus_any = in.flash_any = in.act_any = true; TEST_ASSERT_EQUAL_INT(HUB_WHY_VBUS, hub_decide(&in, now())); in.vbus_any = false; TEST_ASSERT_EQUAL_INT(HUB_WHY_FLASH, hub_decide(&in, now())); in.flash_any = false; TEST_ASSERT_EQUAL_INT(HUB_WHY_ACT, hub_decide(&in, now())); in.act_any = false; TEST_ASSERT_EQUAL_INT(HUB_WHY_IDLE, hub_decide(&in, now())); /* VBUS 一直开着 30 分钟,关掉之后还要再开 HUB_IDLE_MS。 */ const uint32_t t0 = now() + 30u * 60000u; in.vbus_any = true; TEST_ASSERT_EQUAL_INT(HUB_WHY_VBUS, hub_decide(&in, t0)); in.vbus_any = false; TEST_ASSERT_EQUAL_INT(HUB_WHY_IDLE, hub_decide(&in, t0 + HUB_IDLE_MS - 1)); TEST_ASSERT_EQUAL_INT(HUB_WHY_OFF, hub_decide(&in, t0 + HUB_IDLE_MS)); TEST_ASSERT_EQUAL_INT(HAL_OK, hubctl_set_mode(HUB_MODE_ON)); TEST_ASSERT_EQUAL_INT(HUB_WHY_MODE, hub_decide(&in, t0 + 10u * HUB_IDLE_MS)); } /* MQTT 断满 HUB_LINK_LOST_MS 才算(WiFi 抖一下不开);恢复后再给一个 idle 窗口。 */ static void test_link_lost_forces_on(void) { const uint32_t t0 = now() + HUB_IDLE_MS; /* 启动窗口已过 */ hub_in_t in = k_idle; TEST_ASSERT_EQUAL_INT(HUB_WHY_OFF, hub_decide(&in, t0)); in.link_up = false; TEST_ASSERT_EQUAL_INT(HUB_WHY_OFF, hub_decide(&in, t0)); TEST_ASSERT_EQUAL_INT(HUB_WHY_OFF, hub_decide(&in, t0 + HUB_LINK_LOST_MS - 1)); TEST_ASSERT_EQUAL_INT(HUB_WHY_LINK, hub_decide(&in, t0 + HUB_LINK_LOST_MS)); TEST_ASSERT_EQUAL_INT(HUB_WHY_LINK, hub_decide(&in, t0 + 5u * HUB_IDLE_MS)); in.link_up = true; const uint32_t t1 = t0 + 5u * HUB_IDLE_MS + 1; TEST_ASSERT_EQUAL_INT(HUB_WHY_IDLE, hub_decide(&in, t1)); TEST_ASSERT_EQUAL_INT(HUB_WHY_OFF, hub_decide(&in, t1 + HUB_IDLE_MS)); /* 断了不到一分钟又连上:计时重来,不累计。 */ in.link_up = false; TEST_ASSERT_EQUAL_INT(HUB_WHY_OFF, hub_decide(&in, t1 + HUB_IDLE_MS + 1)); in.link_up = true; TEST_ASSERT_EQUAL_INT(HUB_WHY_OFF, hub_decide(&in, t1 + HUB_IDLE_MS + 30000u)); in.link_up = false; TEST_ASSERT_EQUAL_INT(HUB_WHY_OFF, hub_decide(&in, t1 + HUB_IDLE_MS + 30001u)); TEST_ASSERT_EQUAL_INT(HUB_WHY_OFF, hub_decide(&in, t1 + HUB_IDLE_MS + 30001u + HUB_LINK_LOST_MS - 1)); } /* 电脑插上(UP_VBUS_DET 上升沿)与 USB 命令都算一次需要;电平一直高不算。 */ static void test_host_edge_and_usb_touch(void) { const uint32_t t0 = now() + HUB_IDLE_MS; hub_in_t in = k_idle; in.host = true; TEST_ASSERT_EQUAL_INT(HUB_WHY_IDLE, hub_decide(&in, t0)); /* 上升沿 */ TEST_ASSERT_EQUAL_INT(HUB_WHY_OFF, hub_decide(&in, t0 + HUB_IDLE_MS)); /* 电平不算 */ hubctl_touch(t0 + 2u * HUB_IDLE_MS); TEST_ASSERT_EQUAL_INT(HUB_WHY_IDLE, hub_decide(&in, t0 + 3u * HUB_IDLE_MS - 1)); TEST_ASSERT_EQUAL_INT(HUB_WHY_OFF, hub_decide(&in, t0 + 3u * HUB_IDLE_MS)); } /* 保持窗口;min=0 取消保持并结束 idle 窗口(下一拍就关)。 */ static void test_hold(void) { const uint32_t t0 = now(); TEST_ASSERT_EQUAL_INT(HAL_ERR_ARG, hubctl_hold(HUB_HOLD_MAX_MIN + 1, t0)); TEST_ASSERT_EQUAL_INT(HAL_OK, hubctl_hold(30, t0)); TEST_ASSERT_EQUAL_UINT32(1800, hubctl_hold_left_s(t0)); TEST_ASSERT_EQUAL_INT(HUB_WHY_HOLD, hub_decide(&k_idle, t0 + 30u * 60000u - 1)); TEST_ASSERT_EQUAL_INT(HUB_WHY_IDLE, hub_decide(&k_idle, t0 + 30u * 60000u)); TEST_ASSERT_EQUAL_UINT32(0, hubctl_hold_left_s(t0 + 30u * 60000u)); TEST_ASSERT_EQUAL_INT(HAL_OK, hubctl_hold(0, t0 + 30u * 60000u)); TEST_ASSERT_EQUAL_INT(HUB_WHY_OFF, hub_decide(&k_idle, t0 + 30u * 60000u + 1)); } /* 关着 49.7 天,32 位时钟回绕之后 idle 窗口不许「复活」。 */ static void test_idle_window_does_not_revive_after_wrap(void) { const uint32_t t0 = now() + HUB_IDLE_MS; TEST_ASSERT_EQUAL_INT(HUB_WHY_OFF, hub_decide(&k_idle, t0)); TEST_ASSERT_EQUAL_INT(HUB_WHY_OFF, hub_decide(&k_idle, t0 + 0x80000000u)); TEST_ASSERT_EQUAL_INT(HUB_WHY_OFF, hub_decide(&k_idle, t0 + 0xFFFFFFF0u)); } /* ---- 引脚与事件 ---------------------------------------------------------- */ /* 关 = 配成输出拉低;开 = 放成输入(R716 拉高),**从不主动写 1**。每次翻转一条事件。 */ static void test_tick_drives_the_pin_and_logs(void) { run_ms(HUB_IDLE_MS + 200); TEST_ASSERT_FALSE(hubctl_is_on()); TEST_ASSERT_EQUAL_INT(0, sim_gpio_level(PIN)); TEST_ASSERT_EQUAL_INT(1, hub_events(HUB_EV_OFF)); TEST_ASSERT_EQUAL_UINT64(0, sim_gpio_rise_us(PIN)); /* 固件没写过 1 */ /* 断网一分钟 ⇒ 自己回来。 */ link(false); run_ms(HUB_LINK_LOST_MS + 200); TEST_ASSERT_TRUE(hubctl_is_on()); TEST_ASSERT_EQUAL_INT(HUB_WHY_LINK, hubctl_why()); TEST_ASSERT_EQUAL_INT(1, sim_gpio_level(PIN)); TEST_ASSERT_EQUAL_INT(1, hub_events(HUB_EV_ON)); TEST_ASSERT_EQUAL_UINT64(0, sim_gpio_rise_us(PIN)); /* 一直开着不重复记。 */ run_ms(5000); TEST_ASSERT_EQUAL_INT(1, hub_events(HUB_EV_ON)); } static void test_mode_is_saved_and_logged(void) { TEST_ASSERT_EQUAL_INT(HAL_ERR_ARG, hubctl_set_mode(HUB_MODE_COUNT)); TEST_ASSERT_EQUAL_INT(HAL_OK, hubctl_set_mode(HUB_MODE_ON)); TEST_ASSERT_EQUAL_INT(1, s_saves); TEST_ASSERT_EQUAL_INT(HUB_MODE_ON, s_saved); TEST_ASSERT_EQUAL_INT(1, hub_events(HUB_EV_MODE | HUB_MODE_ON)); run_ms(HUB_IDLE_MS + 200); TEST_ASSERT_TRUE(hubctl_is_on()); /* 常开 */ TEST_ASSERT_EQUAL_INT(HAL_OK, hubctl_set_mode(HUB_MODE_ON)); TEST_ASSERT_EQUAL_INT(1, hub_events(HUB_EV_MODE | HUB_MODE_ON)); /* 没变不记 */ hub_mode_t m = HUB_MODE_ON; TEST_ASSERT_TRUE(hub_mode_parse("auto", &m)); TEST_ASSERT_EQUAL_INT(HUB_MODE_AUTO, m); TEST_ASSERT_FALSE(hub_mode_parse("off", &m)); } /* 从 NVS 读到的是垃圾 ⇒ 按 AUTO。 */ static void test_bad_mode_falls_back_to_auto(void) { hubctl_init((hub_mode_t)7, NULL, NULL, now()); TEST_ASSERT_EQUAL_INT(HUB_MODE_AUTO, hubctl_mode()); } int main(void) { UNITY_BEGIN(); RUN_TEST(test_boot_window_then_off); RUN_TEST(test_reasons_and_their_order); RUN_TEST(test_link_lost_forces_on); RUN_TEST(test_host_edge_and_usb_touch); RUN_TEST(test_hold); RUN_TEST(test_idle_window_does_not_revive_after_wrap); RUN_TEST(test_tick_drives_the_pin_and_logs); RUN_TEST(test_mode_is_saved_and_logged); RUN_TEST(test_bad_mode_falls_back_to_auto); return UNITY_END(); }