#include #include "unity.h" #include "board/nl_board.h" #include "light/nl_dmx.h" #include "light/nl_engine.h" static uint8_t last_ch[NL_CH_COUNT]; static int out_calls; static void sink(const uint8_t ch[NL_CH_COUNT], void *ctx) { (void)ctx; memcpy(last_ch, ch, sizeof last_ch); out_calls++; } static uint8_t slot_data[16]; /* Baut ein Paket aus slot_data und speist es ein - als Funktion, damit kein Aufrufer * die Adresse eines Rueckgabewerts nehmen muss. */ static void feed(uint8_t prio, uint8_t seq, bool terminated, uint16_t count, uint32_t now_ms) { nl_e131_packet_t p; p.universe = 1; p.priority = prio; p.sequence = seq; p.terminated = terminated; p.slot_count = count; p.slots = slot_data; nl_dmx_feed(&p, now_ms); } static void start(const char *board_id, uint16_t start_slot, bool hold) { const nl_board_t *b = nl_board_find(board_id); nl_engine_cfg_t cfg; nl_dmx_cfg_t dc; nl_light_state_t off = { false, 100, NL_MODE_WHITE, 0, 0, 0, 3000 }; cfg.ct_min = b->ct_min; cfg.ct_max = b->ct_max; cfg.output = sink; cfg.ctx = NULL; nl_engine_init(&cfg); nl_engine_set(&off, 0); nl_dmx_init(b); dc.start_slot = start_slot; dc.hold = hold; dc.release_fade_ms = 0; nl_dmx_configure(&dc); out_calls = 0; } void setUp(void) { memset(slot_data, 0, sizeof slot_data); memset(last_ch, 0, sizeof last_ch); } void tearDown(void) {} static void test_first_packet_takes_over(void) { /* Dimmer voll, R=10 G=20 B=30 CW=40 WW=50 */ uint8_t d[6] = { 255, 10, 20, 30, 40, 50 }; start("gu10_rgb", 1, false); memcpy(slot_data, d, sizeof d); feed(100, 1, false, 6, 1000); nl_engine_tick(10); TEST_ASSERT_TRUE(nl_dmx_active()); TEST_ASSERT_EQUAL_UINT8(10, last_ch[NL_CH_R]); TEST_ASSERT_EQUAL_UINT8(20, last_ch[NL_CH_G]); TEST_ASSERT_EQUAL_UINT8(30, last_ch[NL_CH_B]); TEST_ASSERT_EQUAL_UINT8(40, last_ch[NL_CH_CW]); TEST_ASSERT_EQUAL_UINT8(50, last_ch[NL_CH_WW]); } static void test_master_dimmer_scales(void) { uint8_t d[6] = { 128, 200, 200, 200, 200, 200 }; start("gu10_rgb", 1, false); memcpy(slot_data, d, sizeof d); feed(100, 1, false, 6, 1000); nl_engine_tick(10); TEST_ASSERT_UINT8_WITHIN(1, 100, last_ch[NL_CH_R]); } static void test_dimmer_zero_is_dark(void) { uint8_t d[6] = { 0, 255, 255, 255, 255, 255 }; start("gu10_rgb", 1, false); memcpy(slot_data, d, sizeof d); feed(100, 1, false, 6, 1000); nl_engine_tick(10); TEST_ASSERT_TRUE(nl_dmx_active()); /* dunkel, aber sehr wohl ferngesteuert */ TEST_ASSERT_EQUAL_UINT8(0, last_ch[NL_CH_R]); TEST_ASSERT_EQUAL_UINT8(0, last_ch[NL_CH_WW]); } static void test_board_without_rgb_uses_three_slots(void) { uint8_t d[3] = { 255, 77, 88 }; start("g95_cw", 1, false); memcpy(slot_data, d, sizeof d); feed(100, 1, false, 3, 1000); nl_engine_tick(10); TEST_ASSERT_EQUAL_UINT8(77, last_ch[NL_CH_CW]); TEST_ASSERT_EQUAL_UINT8(88, last_ch[NL_CH_WW]); TEST_ASSERT_EQUAL_UINT8(0, last_ch[NL_CH_R]); } static void test_start_slot_offset(void) { uint8_t d[9] = { 9, 9, 9, 255, 11, 22, 33, 44, 55 }; start("gu10_rgb", 4, false); memcpy(slot_data, d, sizeof d); feed(100, 1, false, 9, 1000); nl_engine_tick(10); TEST_ASSERT_EQUAL_UINT8(11, last_ch[NL_CH_R]); TEST_ASSERT_EQUAL_UINT8(55, last_ch[NL_CH_WW]); } static void test_packet_too_short_for_our_slots_is_dropped(void) { uint8_t d[4] = { 255, 1, 2, 3 }; start("gu10_rgb", 1, false); memcpy(slot_data, d, sizeof d); feed(100, 1, false, 4, 1000); /* braucht 6, bekommt 4 */ nl_engine_tick(10); TEST_ASSERT_FALSE(nl_dmx_active()); } static void test_stale_sequence_is_dropped(void) { uint8_t d[6] = { 255, 10, 0, 0, 0, 0 }; uint8_t older[6] = { 255, 99, 0, 0, 0, 0 }; start("gu10_rgb", 1, false); memcpy(slot_data, d, sizeof d); feed(100, 50, false, 6, 1000); nl_engine_tick(10); TEST_ASSERT_EQUAL_UINT8(10, last_ch[NL_CH_R]); memcpy(slot_data, older, sizeof older); feed(100, 40, false, 6, 1010); /* 10 zurueck: im Fenster, verwerfen */ nl_engine_tick(10); TEST_ASSERT_EQUAL_UINT8(10, last_ch[NL_CH_R]); feed(100, 20, false, 6, 1020); /* 30 zurueck: ausserhalb, annehmen */ nl_engine_tick(10); TEST_ASSERT_EQUAL_UINT8(99, last_ch[NL_CH_R]); } static void test_lower_priority_is_ignored_while_streaming(void) { uint8_t high[6] = { 255, 10, 0, 0, 0, 0 }; uint8_t low[6] = { 255, 200, 0, 0, 0, 0 }; start("gu10_rgb", 1, false); memcpy(slot_data, high, sizeof high); feed(150, 1, false, 6, 1000); nl_engine_tick(10); memcpy(slot_data, low, sizeof low); feed(100, 2, false, 6, 1010); nl_engine_tick(10); TEST_ASSERT_EQUAL_UINT8(10, last_ch[NL_CH_R]); /* Nach dem Timeout ist wieder jede Prioritaet willkommen. */ nl_dmx_tick(1000 + NL_DMX_TIMEOUT_MS + 1); feed(100, 3, false, 6, 5000); nl_engine_tick(10); TEST_ASSERT_EQUAL_UINT8(200, last_ch[NL_CH_R]); } static void test_timeout_release_returns_to_engine(void) { uint8_t d[6] = { 255, 255, 255, 255, 255, 255 }; start("gu10_rgb", 1, false); /* die Lampe steht auf "aus" */ memcpy(slot_data, d, sizeof d); feed(100, 1, false, 6, 1000); nl_engine_tick(10); TEST_ASSERT_EQUAL_UINT8(255, last_ch[NL_CH_R]); nl_dmx_tick(1000 + NL_DMX_TIMEOUT_MS - 1); TEST_ASSERT_TRUE(nl_dmx_active()); nl_dmx_tick(1000 + NL_DMX_TIMEOUT_MS); nl_engine_tick(10); TEST_ASSERT_FALSE(nl_dmx_active()); TEST_ASSERT_EQUAL_UINT8(0, last_ch[NL_CH_R]); /* wieder aus, wie von nl_ctrl gesetzt */ } static void test_timeout_hold_keeps_last_values(void) { uint8_t d[6] = { 255, 123, 0, 0, 0, 0 }; start("gu10_rgb", 1, true); memcpy(slot_data, d, sizeof d); feed(100, 1, false, 6, 1000); nl_engine_tick(10); nl_dmx_tick(1000 + NL_DMX_TIMEOUT_MS + 500); nl_engine_tick(10); TEST_ASSERT_TRUE(nl_dmx_active()); TEST_ASSERT_EQUAL_UINT8(123, last_ch[NL_CH_R]); } static void test_hold_keeps_darkness(void) { uint8_t dark[6] = { 0, 0, 0, 0, 0, 0 }; nl_light_state_t on = { true, 100, NL_MODE_WHITE, 0, 0, 0, 3000 }; start("gu10_rgb", 1, true); nl_engine_set(&on, 0); /* HA haette die Lampe gern an */ memcpy(slot_data, dark, sizeof dark); feed(100, 1, false, 6, 1000); nl_dmx_tick(1000 + NL_DMX_TIMEOUT_MS + 1); nl_engine_tick(10); /* Genau der Fall, fuer den "hold" existiert: es bleibt dunkel. */ TEST_ASSERT_EQUAL_UINT8(0, last_ch[NL_CH_WW]); } static void test_terminated_bit_ends_immediately(void) { uint8_t d[6] = { 255, 255, 0, 0, 0, 0 }; start("gu10_rgb", 1, false); memcpy(slot_data, d, sizeof d); feed(100, 1, false, 6, 1000); nl_engine_tick(10); feed(100, 2, true, 6, 1010); nl_engine_tick(10); TEST_ASSERT_FALSE(nl_dmx_active()); TEST_ASSERT_EQUAL_UINT8(0, last_ch[NL_CH_R]); } static void test_release_hands_control_back_in_both_modes(void) { uint8_t d[6] = { 255, 255, 0, 0, 0, 0 }; int mode; for (mode = 0; mode < 2; mode++) { start("gu10_rgb", 1, mode == 1); memcpy(slot_data, d, sizeof d); feed(100, 1, false, 6, 1000); nl_engine_tick(10); TEST_ASSERT_TRUE(nl_dmx_active()); nl_dmx_release(); nl_engine_tick(10); TEST_ASSERT_FALSE(nl_dmx_active()); TEST_ASSERT_EQUAL_UINT8(0, last_ch[NL_CH_R]); } } static void test_configure_during_stream_takes_effect(void) { uint8_t d[9] = { 255, 1, 0, 0, 0, 0, 0, 0, 0 }; nl_dmx_cfg_t dc; start("gu10_rgb", 1, false); memcpy(slot_data, d, sizeof d); feed(100, 1, false, 9, 1000); nl_engine_tick(10); TEST_ASSERT_EQUAL_UINT8(1, last_ch[NL_CH_R]); slot_data[3] = 255; /* Slot 4 = Dimmer, wenn start_slot 4 ist */ slot_data[4] = 222; /* Slot 5 = R, wenn start_slot 4 ist */ dc.start_slot = 4; dc.hold = false; dc.release_fade_ms = 0; nl_dmx_configure(&dc); feed(100, 2, false, 9, 1010); nl_engine_tick(10); TEST_ASSERT_EQUAL_UINT8(222, last_ch[NL_CH_R]); } int main(void) { UNITY_BEGIN(); RUN_TEST(test_first_packet_takes_over); RUN_TEST(test_master_dimmer_scales); RUN_TEST(test_dimmer_zero_is_dark); RUN_TEST(test_board_without_rgb_uses_three_slots); RUN_TEST(test_start_slot_offset); RUN_TEST(test_packet_too_short_for_our_slots_is_dropped); RUN_TEST(test_stale_sequence_is_dropped); RUN_TEST(test_lower_priority_is_ignored_while_streaming); RUN_TEST(test_timeout_release_returns_to_engine); RUN_TEST(test_timeout_hold_keeps_last_values); RUN_TEST(test_hold_keeps_darkness); RUN_TEST(test_terminated_bit_ends_immediately); RUN_TEST(test_release_hands_control_back_in_both_modes); RUN_TEST(test_configure_during_stream_takes_effect); return UNITY_END(); }