#include "light/nl_dmx.h" #include #include "light/nl_engine.h" static const nl_board_t *s_board; static uint8_t s_slots; /* 3 oder 6 */ static nl_dmx_cfg_t s_cfg; static bool s_streaming; /* es fliessen Pakete */ static bool s_override; /* die Engine haelt unsere Kanaele */ static uint8_t s_prio; static uint8_t s_seq; static uint32_t s_last_ms; void nl_dmx_init(const nl_board_t *board) { s_board = board; s_cfg.start_slot = 1; s_cfg.hold = false; s_cfg.release_fade_ms = 0; s_cfg.rgb_layout = false; s_slots = nl_board_dmx_slots(board, false); s_streaming = false; s_override = false; s_prio = 0; s_seq = 0; s_last_ms = 0; } void nl_dmx_configure(const nl_dmx_cfg_t *cfg) { s_cfg = *cfg; /* Sicherheitsnetz: start_slot=0 wuerde unten vor den Puffer lesen. Die REST-Schicht * validiert 1..512, aber dieses Modul soll dafuer nicht auf einen Aufrufer angewiesen * sein, den es nicht sehen kann. */ if (s_cfg.start_slot < 1u) s_cfg.start_slot = 1u; /* Die Slotzahl haengt am Layout, nicht nur am Board - hier nachziehen, sonst liest * nl_dmx_feed() beim Umschalten die falsche Anzahl. */ s_slots = nl_board_dmx_slots(s_board, s_cfg.rgb_layout); } bool nl_dmx_active(void) { return s_override; } void nl_dmx_release(void) { if (s_override) nl_engine_override_clear(s_cfg.release_fade_ms); s_override = false; s_streaming = false; s_prio = 0; } /* Der Strom ist zu Ende - entweder durch das terminated-Bit oder durch Timeout. Im * "release"-Modus geben wir den Override frei und blenden zurueck zum Zustand von * nl_ctrl; im "hold"-Modus bleiben die zuletzt gesetzten Kanalwerte einfach stehen. */ static void stream_ended(void) { s_streaming = false; s_prio = 0; if (!s_cfg.hold && s_override) { nl_engine_override_clear(s_cfg.release_fade_ms); s_override = false; } } static uint8_t scale(uint8_t v, uint8_t dim) { return (uint8_t)(((uint16_t)v * (uint16_t)dim + 127u) / 255u); } void nl_dmx_feed(const nl_e131_packet_t *p, uint32_t now_ms) { const uint8_t *s; uint8_t ch[NL_CH_COUNT]; uint8_t dim; if (s_board == NULL) return; /* Reicht das Paket bis zu unserem letzten Slot? Ein halb belegter Satz ergaebe auf den * restlichen Kanaelen willkuerliche Werte - also gar nicht erst anwenden. */ if ((uint32_t)s_cfg.start_slot + s_slots - 1u > (uint32_t)p->slot_count) return; if (s_streaming) { if (p->priority < s_prio) return; if (p->priority == s_prio) { /* E1.31-2018 Section 6.7.2: verwerfen, wenn die Differenz <= 0 und > -20 ist. * Das Fenster ist bei -20 exklusiv - eine Sequenznummer genau 20 hinter der * zuletzt angenommenen gilt bereits wieder als neu und wird angenommen. Nicht * "korrigieren": ein Reviewer hat genau das schon einmal versucht. */ int8_t d = (int8_t)(p->sequence - s_seq); if (d <= 0 && d > -NL_DMX_SEQ_WINDOW) return; /* veraltet oder Wiederholung */ } /* p->priority > s_prio: eine neue, hoeherpriore Quelle loest die laufende ab. Sie * fuehrt ihre eigene Sequenzzaehlung - ein Abgleich gegen s_seq der abgeloesten * Quelle waere Zufall und koennte die neue Quelle bis zu NL_DMX_SEQ_WINDOW Pakete * lang verwerfen. Also: wie das erste Paket eines frischen Streams behandeln. */ } s_prio = p->priority; s_seq = p->sequence; s_last_ms = now_ms; if (p->terminated) { /* die Quelle sagt selbst, dass sie fertig ist */ stream_ended(); return; } s_streaming = true; s = p->slots + (s_cfg.start_slot - 1u); memset(ch, 0, sizeof ch); if (s_cfg.rgb_layout) { /* Drei Kanaele, kein Dimmer: so schreiben pixelorientierte Werkzeuge (LedFx und * Verwandte) je Pixel genau R,G,B. Eine Lampe ist damit ein Pixel. */ if (s_board->has_rgb) { ch[NL_CH_R] = s[0]; ch[NL_CH_G] = s[1]; ch[NL_CH_B] = s[2]; } else { /* Ein Board ohne Farbkanaele bekommt trotzdem ein Farbtripel. Genommen wird der * groesste der drei Werte auf beide Weisskanaele: die Lampe folgt damit sichtbar * der Helligkeit des Effekts, ohne dass ein Farbmodell in den DMX-Pfad kommt - * genau das wird hier sonst bewusst vermieden. */ uint8_t v = s[0] > s[1] ? s[0] : s[1]; if (s[2] > v) v = s[2]; ch[NL_CH_CW] = v; ch[NL_CH_WW] = v; } } else { dim = s[0]; if (s_board->has_rgb) { ch[NL_CH_R] = scale(s[1], dim); ch[NL_CH_G] = scale(s[2], dim); ch[NL_CH_B] = scale(s[3], dim); ch[NL_CH_CW] = scale(s[4], dim); ch[NL_CH_WW] = scale(s[5], dim); } else { ch[NL_CH_CW] = scale(s[1], dim); ch[NL_CH_WW] = scale(s[2], dim); } } nl_engine_override(ch); s_override = true; } void nl_dmx_tick(uint32_t now_ms) { if (!s_streaming) return; if (now_ms - s_last_ms >= NL_DMX_TIMEOUT_MS) stream_ended(); }