#include #include "unity.h" #include "light/nl_boot_state.h" #include "light/nl_control.h" #include "light/nl_engine.h" static void out(const uint8_t ch[NL_CH_COUNT], void *ctx) { (void)ch; (void)ctx; } static void start(const char *board_id, bool on) { const nl_board_t *b = nl_board_find(board_id); nl_engine_cfg_t cfg = {b->ct_min, b->ct_max, out, NULL}; nl_engine_init(&cfg); nl_ctrl_init(b, 500, 800); nl_light_state_t s = nl_boot_default_state(b); s.on = on; nl_ctrl_boot(&s); nl_engine_tick(10000); /* finish a possible boot fade */ } static uint16_t bri(void) { nl_visual_t v; nl_engine_current(&v); return v.bri; } static nl_light_change_t chg(void) { nl_light_change_t c; memset(&c, 0, sizeof c); return c; } void setUp(void) { start("evk", false); } void tearDown(void) {} static void test_turn_on_uses_fade_in(void) { nl_light_change_t c = chg(); c.has_on = true; c.on = true; TEST_ASSERT_EQUAL(NL_CTRL_OK, nl_ctrl_apply(&c, 0)); nl_engine_tick(250); TEST_ASSERT_EQUAL_UINT16(5000, bri()); nl_engine_tick(250); TEST_ASSERT_EQUAL_UINT16(10000, bri()); } static void test_turn_off_uses_fade_out(void) { start("evk", true); nl_light_change_t c = chg(); c.has_on = true; c.on = false; nl_ctrl_apply(&c, 0); nl_engine_tick(400); TEST_ASSERT_EQUAL_UINT16(5000, bri()); } static void test_explicit_transition_wins(void) { nl_light_change_t c = chg(); c.has_on = true; c.on = true; c.has_transition = true; c.transition_ms = 100; nl_ctrl_apply(&c, 0); nl_engine_tick(100); TEST_ASSERT_EQUAL_UINT16(10000, bri()); } static void test_brightness_change_without_transition_is_instant(void) { start("evk", true); nl_light_change_t c = chg(); c.has_brightness = true; c.brightness = 50; nl_ctrl_apply(&c, 0); TEST_ASSERT_EQUAL_UINT16(5000, bri()); } static void test_brightness_while_off_stays_off(void) { nl_light_change_t c = chg(); c.has_brightness = true; c.brightness = 30; TEST_ASSERT_EQUAL(NL_CTRL_OK, nl_ctrl_apply(&c, 0)); nl_light_state_t s; nl_ctrl_get(&s); TEST_ASSERT_FALSE(s.on); TEST_ASSERT_EQUAL_UINT8(30, s.brightness); TEST_ASSERT_EQUAL_UINT16(0, bri()); } static void test_validation(void) { nl_light_change_t c = chg(); c.has_brightness = true; c.brightness = 0; TEST_ASSERT_EQUAL(NL_CTRL_ERR_BRIGHTNESS, nl_ctrl_apply(&c, 0)); c.brightness = 101; TEST_ASSERT_EQUAL(NL_CTRL_ERR_BRIGHTNESS, nl_ctrl_apply(&c, 0)); c = chg(); c.has_transition = true; c.transition_ms = 60001; TEST_ASSERT_EQUAL(NL_CTRL_ERR_TRANSITION, nl_ctrl_apply(&c, 0)); c = chg(); c.has_color = true; c.has_ct = true; TEST_ASSERT_EQUAL(NL_CTRL_ERR_COLOR_AND_CT, nl_ctrl_apply(&c, 0)); } static void test_color_rejected_on_white_board(void) { start("e27_cw", true); nl_light_change_t c = chg(); c.has_color = true; c.r = 255; TEST_ASSERT_EQUAL(NL_CTRL_ERR_NO_RGB, nl_ctrl_apply(&c, 0)); nl_light_state_t s; nl_ctrl_get(&s); TEST_ASSERT_EQUAL(NL_MODE_WHITE, s.mode); } static void test_mode_follows_color_or_ct(void) { nl_light_change_t c = chg(); nl_light_state_t s; c.has_color = true; c.r = 10; c.g = 20; c.b = 30; nl_ctrl_apply(&c, 0); nl_ctrl_get(&s); TEST_ASSERT_EQUAL(NL_MODE_COLOR, s.mode); TEST_ASSERT_EQUAL_UINT8(20, s.g); c = chg(); c.has_ct = true; c.ct = 1000; nl_ctrl_apply(&c, 0); nl_ctrl_get(&s); TEST_ASSERT_EQUAL(NL_MODE_WHITE, s.mode); TEST_ASSERT_EQUAL_UINT16(2700, s.ct); /* clamped to ct_min */ } static void test_persist_debounce(void) { nl_light_state_t s; nl_light_change_t c = chg(); c.has_brightness = true; c.brightness = 70; TEST_ASSERT_FALSE(nl_ctrl_persist_due(100000, &s)); /* boot is not dirty */ nl_ctrl_apply(&c, 1000); TEST_ASSERT_FALSE(nl_ctrl_persist_due(3999, &s)); TEST_ASSERT_TRUE(nl_ctrl_persist_due(4000, &s)); TEST_ASSERT_EQUAL_UINT8(70, s.brightness); nl_ctrl_persisted(&s); TEST_ASSERT_FALSE(nl_ctrl_persist_due(9000, &s)); } static void test_change_during_write_stays_dirty(void) { nl_light_state_t written; nl_light_change_t c = chg(); c.has_brightness = true; c.brightness = 70; nl_ctrl_apply(&c, 0); TEST_ASSERT_TRUE(nl_ctrl_persist_due(3000, &written)); c.brightness = 20; /* arrives while the flash write is running */ nl_ctrl_apply(&c, 3001); nl_ctrl_persisted(&written); /* old state written */ TEST_ASSERT_FALSE(nl_ctrl_persist_due(3002, &written)); TEST_ASSERT_TRUE(nl_ctrl_persist_due(6001, &written)); TEST_ASSERT_EQUAL_UINT8(20, written.brightness); } static void test_boot_fades_in_when_on(void) { const nl_board_t *b = nl_board_find("evk"); nl_engine_cfg_t cfg = {b->ct_min, b->ct_max, out, NULL}; nl_engine_init(&cfg); nl_ctrl_init(b, 500, 800); nl_light_state_t s = nl_boot_default_state(b); nl_ctrl_boot(&s); nl_engine_tick(250); TEST_ASSERT_EQUAL_UINT16(5000, bri()); } int main(void) { UNITY_BEGIN(); RUN_TEST(test_turn_on_uses_fade_in); RUN_TEST(test_turn_off_uses_fade_out); RUN_TEST(test_explicit_transition_wins); RUN_TEST(test_brightness_change_without_transition_is_instant); RUN_TEST(test_brightness_while_off_stays_off); RUN_TEST(test_validation); RUN_TEST(test_color_rejected_on_white_board); RUN_TEST(test_mode_follows_color_or_ct); RUN_TEST(test_persist_debounce); RUN_TEST(test_change_during_write_stays_dirty); RUN_TEST(test_boot_fades_in_when_on); return UNITY_END(); }