From 6be0473f18536971c748ef269d63794e9ba76c8c Mon Sep 17 00:00:00 2001 From: Michael Sauer Date: Sun, 20 Sep 2026 00:02:16 +0200 Subject: [PATCH] feat(sacn): Override-Zweig in der Licht-Engine --- src/light/nl_engine.c | 48 ++++++++++++++++++++++++ src/light/nl_engine.h | 9 +++++ test/test_engine.c | 85 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 142 insertions(+) diff --git a/src/light/nl_engine.c b/src/light/nl_engine.c index 039f89a..af1be8e 100644 --- a/src/light/nl_engine.c +++ b/src/light/nl_engine.c @@ -1,5 +1,7 @@ #include "light/nl_engine.h" +#include + #include "light/nl_color.h" typedef struct { @@ -19,6 +21,11 @@ static uint32_t s_blink_t; static bool s_pulse; static uint32_t s_pulse_t; +static bool s_ov; /* Override haelt die Ausgabe */ +static uint8_t s_ov_ch[NL_CH_COUNT]; /* zuletzt gesetzte Override-Kanaele */ +static uint32_t s_ov_fade_dur; /* > 0: Rueckblende laeuft */ +static uint32_t s_ov_fade_el; + static nl_visual_t to_visual(const nl_light_state_t *s) { nl_visual_t v; @@ -69,6 +76,18 @@ static void emit(void) v = s_cur; } nl_visual_to_channels(&v, s_cfg.ct_min, s_cfg.ct_max, ch); + + /* Blink und Pulse sind Rueckmeldungen des Geraets an den Menschen (Werksreset, + * Onboarding) - die muessen auch waehrend einer laufenden Show sichtbar bleiben. */ + if (!s_blink_left && !s_pulse) { + if (s_ov) { + memcpy(ch, s_ov_ch, sizeof ch); + } else if (s_ov_fade_dur) { + int i; + for (i = 0; i < NL_CH_COUNT; i++) + ch[i] = (uint8_t)lerp(s_ov_ch[i], ch[i], s_ov_fade_el, s_ov_fade_dur); + } + } s_cfg.output(ch, s_cfg.ctx); } @@ -84,6 +103,8 @@ void nl_engine_init(const nl_engine_cfg_t *cfg) s_blink_t = 0; s_pulse = false; s_pulse_t = 0; + s_ov = false; + s_ov_fade_dur = s_ov_fade_el = 0; emit(); } @@ -123,6 +144,12 @@ void nl_engine_tick(uint32_t elapsed_ms) { uint32_t left = elapsed_ms; + if (s_ov_fade_dur) { + s_ov_fade_el += elapsed_ms; + if (s_ov_fade_el >= s_ov_fade_dur) + s_ov_fade_dur = s_ov_fade_el = 0; + } + while (s_iseg < s_nseg) { seg_t *s = &s_seg[s_iseg]; if (s->elapsed + left >= s->dur) { @@ -180,3 +207,24 @@ void nl_engine_pulse(bool enable) s_pulse_t = 0; emit(); } + +void nl_engine_override(const uint8_t ch[NL_CH_COUNT]) +{ + memcpy(s_ov_ch, ch, sizeof s_ov_ch); + s_ov = true; + s_ov_fade_dur = s_ov_fade_el = 0; +} + +void nl_engine_override_clear(uint32_t fade_ms) +{ + if (!s_ov) + return; + s_ov = false; + s_ov_fade_dur = fade_ms; + s_ov_fade_el = 0; +} + +bool nl_engine_override_active(void) +{ + return s_ov; +} diff --git a/src/light/nl_engine.h b/src/light/nl_engine.h index 6a62cc4..331acf4 100644 --- a/src/light/nl_engine.h +++ b/src/light/nl_engine.h @@ -29,4 +29,13 @@ void nl_engine_current(nl_visual_t *out); void nl_engine_blink(uint8_t count); void nl_engine_pulse(bool enable); +/* DMX-Override: die Kanalwerte gehen unveraendert an die Ausgabe, ohne Fade und ohne + * Farbmischung. Rangfolge in emit(): Blink > Pulse > Override > Fade-Engine. Der + * darunterliegende Zustand laeuft unberuehrt weiter. */ +void nl_engine_override(const uint8_t ch[NL_CH_COUNT]); +/* Beendet den Override. fade_ms > 0 blendet im Kanalraum auf das zurueck, was die + * Fade-Engine gerade ausgeben wuerde; 0 schaltet sofort um. */ +void nl_engine_override_clear(uint32_t fade_ms); +bool nl_engine_override_active(void); + #endif diff --git a/test/test_engine.c b/test/test_engine.c index 84963a0..bc74084 100644 --- a/test/test_engine.c +++ b/test/test_engine.c @@ -183,6 +183,86 @@ static void test_blink_max_clamped(void) TEST_ASSERT_TRUE(nl_engine_idle()); } +static void test_override_takes_over_output(void) +{ + nl_light_state_t on = st(true, 100, NL_MODE_WHITE, 0, 0, 0, 3000); + uint8_t ov[NL_CH_COUNT] = { 10, 20, 30, 40, 50 }; + + nl_engine_set(&on, 0); + nl_engine_tick(10); + TEST_ASSERT_FALSE(nl_engine_override_active()); + + nl_engine_override(ov); + nl_engine_tick(10); + TEST_ASSERT_TRUE(nl_engine_override_active()); + TEST_ASSERT_EQUAL_UINT8(10, last[NL_CH_R]); + TEST_ASSERT_EQUAL_UINT8(50, last[NL_CH_WW]); +} + +static void test_override_clear_without_fade_is_immediate(void) +{ + nl_light_state_t off = st(false, 100, NL_MODE_WHITE, 0, 0, 0, 3000); + uint8_t ov[NL_CH_COUNT] = { 255, 255, 255, 255, 255 }; + + nl_engine_set(&off, 0); + nl_engine_tick(10); + nl_engine_override(ov); + nl_engine_tick(10); + TEST_ASSERT_EQUAL_UINT8(255, last[NL_CH_WW]); + + nl_engine_override_clear(0); + nl_engine_tick(10); + TEST_ASSERT_FALSE(nl_engine_override_active()); + TEST_ASSERT_EQUAL_UINT8(0, last[NL_CH_WW]); /* die Lampe war aus, also sofort dunkel */ +} + +static void test_override_clear_fades_back(void) +{ + nl_light_state_t off = st(false, 100, NL_MODE_WHITE, 0, 0, 0, 3000); + uint8_t ov[NL_CH_COUNT] = { 0, 0, 0, 0, 200 }; + + nl_engine_set(&off, 0); + nl_engine_tick(10); + nl_engine_override(ov); + nl_engine_tick(10); + + nl_engine_override_clear(1000); + nl_engine_tick(500); + /* auf halber Strecke zwischen 200 und 0, mit Spielraum fuer die Rundung */ + TEST_ASSERT_UINT8_WITHIN(12, 100, last[NL_CH_WW]); + nl_engine_tick(500); + TEST_ASSERT_EQUAL_UINT8(0, last[NL_CH_WW]); + TEST_ASSERT_FALSE(nl_engine_override_active()); +} + +static void test_blink_wins_over_override(void) +{ + uint8_t ov[NL_CH_COUNT] = { 0, 0, 0, 0, 0 }; + + nl_engine_override(ov); + nl_engine_blink(3); + nl_engine_tick(10); + /* Blink leuchtet hell weiss, obwohl der Override alles auf 0 haelt */ + TEST_ASSERT_NOT_EQUAL_UINT8(0, last[NL_CH_WW]); +} + +static void test_underlying_state_survives_the_override(void) +{ + nl_light_state_t on = st(true, 100, NL_MODE_WHITE, 0, 0, 0, 3000); + uint8_t ov[NL_CH_COUNT] = { 0, 0, 0, 0, 0 }; + uint8_t before; + + nl_engine_set(&on, 0); + nl_engine_tick(10); + before = last[NL_CH_WW]; + + nl_engine_override(ov); + nl_engine_tick(2000); + nl_engine_override_clear(0); + nl_engine_tick(10); + TEST_ASSERT_EQUAL_UINT8(before, last[NL_CH_WW]); +} + int main(void) { UNITY_BEGIN(); @@ -197,5 +277,10 @@ int main(void) RUN_TEST(test_blink_overrides_and_restores); RUN_TEST(test_pulse); RUN_TEST(test_blink_max_clamped); + RUN_TEST(test_override_takes_over_output); + RUN_TEST(test_override_clear_without_fade_is_immediate); + RUN_TEST(test_override_clear_fades_back); + RUN_TEST(test_blink_wins_over_override); + RUN_TEST(test_underlying_state_survives_the_override); return UNITY_END(); }