feat(sacn): Override-Zweig in der Licht-Engine

This commit is contained in:
Michael Sauer
2026-09-20 00:02:16 +02:00
parent 33d085cbed
commit 6be0473f18
3 changed files with 142 additions and 0 deletions
+48
View File
@@ -1,5 +1,7 @@
#include "light/nl_engine.h" #include "light/nl_engine.h"
#include <string.h>
#include "light/nl_color.h" #include "light/nl_color.h"
typedef struct { typedef struct {
@@ -19,6 +21,11 @@ static uint32_t s_blink_t;
static bool s_pulse; static bool s_pulse;
static uint32_t s_pulse_t; 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) static nl_visual_t to_visual(const nl_light_state_t *s)
{ {
nl_visual_t v; nl_visual_t v;
@@ -69,6 +76,18 @@ static void emit(void)
v = s_cur; v = s_cur;
} }
nl_visual_to_channels(&v, s_cfg.ct_min, s_cfg.ct_max, ch); 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); 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_blink_t = 0;
s_pulse = false; s_pulse = false;
s_pulse_t = 0; s_pulse_t = 0;
s_ov = false;
s_ov_fade_dur = s_ov_fade_el = 0;
emit(); emit();
} }
@@ -123,6 +144,12 @@ void nl_engine_tick(uint32_t elapsed_ms)
{ {
uint32_t left = 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) { while (s_iseg < s_nseg) {
seg_t *s = &s_seg[s_iseg]; seg_t *s = &s_seg[s_iseg];
if (s->elapsed + left >= s->dur) { if (s->elapsed + left >= s->dur) {
@@ -180,3 +207,24 @@ void nl_engine_pulse(bool enable)
s_pulse_t = 0; s_pulse_t = 0;
emit(); 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;
}
+9
View File
@@ -29,4 +29,13 @@ void nl_engine_current(nl_visual_t *out);
void nl_engine_blink(uint8_t count); void nl_engine_blink(uint8_t count);
void nl_engine_pulse(bool enable); 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 #endif
+85
View File
@@ -183,6 +183,86 @@ static void test_blink_max_clamped(void)
TEST_ASSERT_TRUE(nl_engine_idle()); 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) int main(void)
{ {
UNITY_BEGIN(); UNITY_BEGIN();
@@ -197,5 +277,10 @@ int main(void)
RUN_TEST(test_blink_overrides_and_restores); RUN_TEST(test_blink_overrides_and_restores);
RUN_TEST(test_pulse); RUN_TEST(test_pulse);
RUN_TEST(test_blink_max_clamped); 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(); return UNITY_END();
} }