327 lines
11 KiB
C
327 lines
11 KiB
C
#include <string.h>
|
|
#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 void sink(const uint8_t ch[NL_CH_COUNT], void *ctx)
|
|
{
|
|
(void)ctx;
|
|
memcpy(last_ch, ch, sizeof last_ch);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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_a_packet_exactly_twenty_behind_is_accepted(void)
|
|
{
|
|
/* E1.31-2018 6.7.2 verwirft "<= 0 und > -20" - bei d == -20 ist das Fenster bereits
|
|
* verlassen, das Paket gilt als neu. Ein Reviewer hat das schon einmal fuer einen
|
|
* Off-by-one gehalten; dieser Test legt fest, auf welcher Seite die Grenze liegt. */
|
|
uint8_t d[6] = { 255, 10, 0, 0, 0, 0 };
|
|
uint8_t twenty_behind[6] = { 255, 77, 0, 0, 0, 0 };
|
|
start("gu10_rgb", 1, false);
|
|
memcpy(slot_data, d, sizeof d);
|
|
feed(100, 20, false, 6, 1000);
|
|
nl_engine_tick(10);
|
|
TEST_ASSERT_EQUAL_UINT8(10, last_ch[NL_CH_R]);
|
|
|
|
memcpy(slot_data, twenty_behind, sizeof twenty_behind);
|
|
feed(100, 0, false, 6, 1010); /* d = 0 - 20 = -20: ausserhalb des Fensters, annehmen */
|
|
nl_engine_tick(10);
|
|
TEST_ASSERT_EQUAL_UINT8(77, last_ch[NL_CH_R]);
|
|
}
|
|
|
|
static void test_higher_priority_source_takes_over_despite_stale_looking_sequence(void)
|
|
{
|
|
/* Quelle A laeuft mit hoher Sequenznummer. Quelle B mit hoeherer Prioritaet startet mit
|
|
* einer Sequenz, die - gegen A's Zaehler geprueft - wie ein alter Wiederholer aussaehe.
|
|
* B fuehrt aber ihre eigene Zaehlung und muss trotzdem sofort uebernehmen. */
|
|
uint8_t a[6] = { 255, 10, 0, 0, 0, 0 };
|
|
uint8_t b[6] = { 255, 222, 0, 0, 0, 0 };
|
|
start("gu10_rgb", 1, false);
|
|
memcpy(slot_data, a, sizeof a);
|
|
feed(100, 50, false, 6, 1000); /* Quelle A, Prioritaet 100, Sequenz 50 */
|
|
nl_engine_tick(10);
|
|
TEST_ASSERT_EQUAL_UINT8(10, last_ch[NL_CH_R]);
|
|
|
|
memcpy(slot_data, b, sizeof b);
|
|
/* Quelle B, Prioritaet 150, Sequenz 45: gegen A's Zaehler (50) waere d = -5, also
|
|
* innerhalb des Verwerfungsfensters - trotzdem muss B sofort uebernehmen. */
|
|
feed(150, 45, false, 6, 1010);
|
|
nl_engine_tick(10);
|
|
TEST_ASSERT_EQUAL_UINT8(222, 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_a_packet_exactly_twenty_behind_is_accepted);
|
|
RUN_TEST(test_higher_priority_source_takes_over_despite_stale_looking_sequence);
|
|
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();
|
|
}
|