feat(sacn): Uebernahme-Schicht mit Timeout, Prioritaet und Sequenz
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
#include "light/nl_dmx.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#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_slots = nl_board_dmx_slots(board);
|
||||
s_cfg.start_slot = 1;
|
||||
s_cfg.hold = false;
|
||||
s_cfg.release_fade_ms = 0;
|
||||
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; }
|
||||
|
||||
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;
|
||||
{
|
||||
int8_t d = (int8_t)(p->sequence - s_seq);
|
||||
if (d <= 0 && d > -NL_DMX_SEQ_WINDOW)
|
||||
return; /* veraltet oder Wiederholung */
|
||||
}
|
||||
}
|
||||
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);
|
||||
dim = s[0];
|
||||
memset(ch, 0, sizeof ch);
|
||||
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();
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
#ifndef NL_DMX_H
|
||||
#define NL_DMX_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "board/nl_board.h"
|
||||
#include "net/nl_e131.h"
|
||||
|
||||
#define NL_DMX_TIMEOUT_MS 2500u /* E1.31, Abschnitt 6.7.1 */
|
||||
#define NL_DMX_SEQ_WINDOW 20 /* E1.31, Abschnitt 6.7.2 */
|
||||
|
||||
typedef struct {
|
||||
uint16_t start_slot; /* 1..512 */
|
||||
bool hold; /* false = nach dem Timeout zurueck zum Zustand von nl_ctrl */
|
||||
uint16_t release_fade_ms; /* Dauer der Rueckblende (aus settings.fade_in_ms) */
|
||||
} nl_dmx_cfg_t;
|
||||
|
||||
/* Die Engine muss initialisiert sein. Nicht threadsicher: Aufrufer serialisieren. */
|
||||
void nl_dmx_init(const nl_board_t *board);
|
||||
void nl_dmx_configure(const nl_dmx_cfg_t *cfg);
|
||||
/* Ein vom Parser angenommenes Paket. Prueft Sequenz, Prioritaet und Slotbereich und setzt
|
||||
* bei Annahme den Override. */
|
||||
void nl_dmx_feed(const nl_e131_packet_t *p, uint32_t now_ms);
|
||||
/* Aus dem Licht-Task (alle ~10 ms): prueft den Timeout. */
|
||||
void nl_dmx_tick(uint32_t now_ms);
|
||||
/* Ein HA-/REST-Befehl holt die Kontrolle zurueck - in beiden Timeout-Modi. */
|
||||
void nl_dmx_release(void);
|
||||
/* true, solange die Lampe ferngesteuert ist, einschliesslich der Haltephase. */
|
||||
bool nl_dmx_active(void);
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user