feat(sacn): zweite Slot-Belegung "rgb" fuer pixelorientierte Werkzeuge
LedFx und Verwandte denken in RGB-Pixeln: Ihr e131-Geraet schreibt je Pixel genau drei Kanaele. Unsere Belegung beginnt mit einem Master-Dimmer, also landet deren erstes Pixel auf Dimmer, R, G - ein rotes Effektbild (255,0,0) hiesse bei uns "Dimmer 255, Rot 0, Gruen 0" und damit Dunkelheit. Von Hand eintragen liess sich eine Lampe schon, sie zeigte aber Unsinn. Der Dimmer bleibt fuer DMX-Pulte richtig, deshalb eine zweite Belegung statt einer anderen: layout = "dimmer" (Vorgabe, unveraendert) oder "rgb" (R,G,B, drei Slots auf jedem Board). Im rgb-Layout ist eine Lampe genau ein Pixel. Ein Board ohne Farbkanaele bekommt trotzdem ein Farbtripel. Genommen wird der groesste der drei Werte auf beide Weisskanaele: die Lampe folgt sichtbar der Helligkeit des Effekts, ohne dass ein Farbmodell in den DMX-Pfad kommt - genau das wird dort sonst bewusst vermieden. Die Obergrenze fuer start_slot haengt jetzt am Layout (512 - slots + 1). Ein Wechsel, der einen bisher zulaessigen start_slot unzulaessig macht, wird abgelehnt statt still verschoben: eine Lampe, die nach einer Einstellung woanders im Universe liegt als angegeben, findet niemand wieder. Das neue Settings-Feld beginnt bei Offset 216, dem sizeof davor, und ist durch eine eigene Pruefung zur Uebersetzungszeit und einen Migrationstest abgesichert - derselbe Fallstrick, der beim Schlussreview fuer sacn_enabled gefunden wurde.
This commit is contained in:
@@ -50,7 +50,9 @@ const nl_board_t *nl_board_get(void)
|
||||
}
|
||||
#endif
|
||||
|
||||
uint8_t nl_board_dmx_slots(const nl_board_t *b)
|
||||
uint8_t nl_board_dmx_slots(const nl_board_t *b, bool rgb_layout)
|
||||
{
|
||||
if (rgb_layout)
|
||||
return 3u; /* R,G,B - unabhaengig vom Board, damit eine Lampe ein Pixel ist */
|
||||
return (b != NULL && b->has_rgb) ? 6u : 3u;
|
||||
}
|
||||
|
||||
@@ -41,8 +41,11 @@ const nl_board_t *nl_board_find(const char *id);
|
||||
/* Board selected at build time via NINLIGHT_DEVICE_ID. */
|
||||
const nl_board_t *nl_board_get(void);
|
||||
|
||||
/* Slots, die eine Lampe im DMX-Universe belegt: Dimmer + R,G,B,CW,WW bzw. Dimmer + CW,WW.
|
||||
* Steht hier, damit API, Uebernahme-Schicht und Doku dieselbe Zahl benutzen. */
|
||||
uint8_t nl_board_dmx_slots(const nl_board_t *b);
|
||||
/* Slots, die eine Lampe im DMX-Universe belegt. Steht hier, damit API, Uebernahme-Schicht
|
||||
* und Doku dieselbe Zahl benutzen.
|
||||
* rgb_layout = false: Dimmer + R,G,B,CW,WW (6) bzw. Dimmer + CW,WW (3)
|
||||
* rgb_layout = true: R,G,B (3) auf JEDEM Board - so ist eine Lampe genau ein Pixel
|
||||
* fuer pixelorientierte Werkzeuge wie LedFx. */
|
||||
uint8_t nl_board_dmx_slots(const nl_board_t *b, bool rgb_layout);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -56,6 +56,12 @@ typedef char nl_settings_size_check[(sizeof(nl_settings_t) % 4u == 0u) ? 1 : -1]
|
||||
* Bei sacn_enabled hiesse das: sACN nach dem Update auf jeder Lampe aus. */
|
||||
typedef char nl_settings_legacy_check[offsetof(nl_settings_t, sacn_enabled) == 208 ? 1 : -1];
|
||||
|
||||
/* Dasselbe fuer das naechste angehaengte Feld: sacn_layout muss bei 216 beginnen, dem
|
||||
* sizeof(nl_settings_t) der Firmware davor. Wer hier anstoesst, hat ein Feld zu frueh
|
||||
* eingefuegt - dann kaeme es aus einem aelteren Satz als Fuellbyte zurueck, nicht als
|
||||
* Vorgabe. */
|
||||
typedef char nl_settings_layout_offset_check[offsetof(nl_settings_t, sacn_layout) == 216 ? 1 : -1];
|
||||
|
||||
static uint32_t s_addr[2];
|
||||
|
||||
void nl_settings_defaults(nl_settings_t *s)
|
||||
@@ -71,6 +77,7 @@ void nl_settings_defaults(nl_settings_t *s)
|
||||
s->sacn_universe = 1;
|
||||
s->sacn_start_slot = 1;
|
||||
s->sacn_hold = 0;
|
||||
s->sacn_layout = 0;
|
||||
}
|
||||
|
||||
void nl_settings_init(uint32_t addr_a, uint32_t addr_b)
|
||||
|
||||
@@ -35,7 +35,12 @@ typedef struct {
|
||||
uint16_t sacn_universe; /* 1..63999 (Vorgabe 1) */
|
||||
uint16_t sacn_start_slot; /* 1..512 (Vorgabe 1) */
|
||||
uint8_t sacn_hold; /* 0 = nach dem Timeout zurueck zum HA-Zustand, 1 = halten */
|
||||
uint8_t _padding[1]; /* padding for 4-byte alignment of sizeof(nl_settings_t) */
|
||||
uint8_t _padding[1]; /* haelt sizeof auf einem Vielfachen von 4 */
|
||||
uint8_t sacn_layout; /* Offset 216 - 0 = Dimmer voran, 1 = nur R,G,B (nl_dmx_layout_t).
|
||||
* Dieselbe Regel wie oben: neue Felder beginnen bei oder hinter
|
||||
* dem vorherigen sizeof (216), sonst ueberschreibt das Fuellbyte
|
||||
* eines aelteren Satzes sie. */
|
||||
uint8_t _padding2[3]; /* haelt sizeof auf einem Vielfachen von 4 */
|
||||
} nl_settings_t;
|
||||
|
||||
void nl_settings_defaults(nl_settings_t *s);
|
||||
|
||||
+34
-10
@@ -16,10 +16,11 @@ 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_cfg.rgb_layout = false;
|
||||
s_slots = nl_board_dmx_slots(board, false);
|
||||
s_streaming = false;
|
||||
s_override = false;
|
||||
s_prio = 0;
|
||||
@@ -35,6 +36,9 @@ void nl_dmx_configure(const nl_dmx_cfg_t *cfg)
|
||||
* 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; }
|
||||
@@ -106,17 +110,37 @@ void nl_dmx_feed(const nl_e131_packet_t *p, uint32_t now_ms)
|
||||
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);
|
||||
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 {
|
||||
ch[NL_CH_CW] = scale(s[1], dim);
|
||||
ch[NL_CH_WW] = scale(s[2], dim);
|
||||
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;
|
||||
|
||||
@@ -14,6 +14,9 @@ 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) */
|
||||
/* false: Dimmer voran (DMX-Pulte erwarten das). true: nur R,G,B - dann ist eine Lampe
|
||||
* genau ein Pixel fuer Werkzeuge wie LedFx, die je Pixel drei Kanaele schreiben. */
|
||||
bool rgb_layout;
|
||||
} nl_dmx_cfg_t;
|
||||
|
||||
/* Die Engine muss initialisiert sein. Nicht threadsicher: Aufrufer serialisieren. */
|
||||
|
||||
+42
-13
@@ -305,7 +305,8 @@ static void handle_get_settings(const nl_api_ctx_t *ctx, nl_api_response_t *resp
|
||||
cJSON_AddStringToObject(sa, "on_timeout", s.sacn_hold ? "hold" : "release");
|
||||
/* Informativ und bewusst aus dem Board, nicht aus dem Datensatz: so steht beim
|
||||
* Adressieren direkt da, wieviel Platz diese Lampe im Universe belegt. */
|
||||
cJSON_AddNumberToObject(sa, "slots", nl_board_dmx_slots(ctx->board));
|
||||
cJSON_AddStringToObject(sa, "layout", s.sacn_layout ? "rgb" : "dimmer");
|
||||
cJSON_AddNumberToObject(sa, "slots", nl_board_dmx_slots(ctx->board, s.sacn_layout != 0));
|
||||
}
|
||||
reply_json(resp, 200, o);
|
||||
}
|
||||
@@ -356,12 +357,29 @@ static void handle_put_settings(const nl_api_ctx_t *ctx, const nl_http_request_t
|
||||
s.auth_all = cJSON_IsTrue(all) ? 1 : 0;
|
||||
} else if (strcmp(type, "sacn") == 0) {
|
||||
const cJSON *en = cJSON_GetObjectItemCaseSensitive(j, "enabled");
|
||||
const char *ot = NULL;
|
||||
const char *ot = NULL, *lo = NULL;
|
||||
long un = 0, st = 0;
|
||||
int unp, stp, otp;
|
||||
uint8_t slots = nl_board_dmx_slots(ctx->board);
|
||||
int unp, stp, otp, lop;
|
||||
bool rgb;
|
||||
uint8_t slots;
|
||||
uint16_t want_start;
|
||||
|
||||
if (en && !cJSON_IsBool(en)) {
|
||||
/* Das Layout wird ZUERST ausgewertet: es bestimmt die Slotzahl und damit die
|
||||
* Obergrenze fuer start_slot. Wer in derselben Anfrage beides setzt, soll gegen das
|
||||
* neue Layout geprueft werden, nicht gegen das alte. */
|
||||
lop = get_string(j, "layout", 1, 16, &lo);
|
||||
if (lop < 0 || (lop == 1 && strcmp(lo, "dimmer") != 0 && strcmp(lo, "rgb") != 0)) {
|
||||
err = "invalid layout";
|
||||
lop = 0; /* die Zweige unten duerfen den Wert dann nicht mehr benutzen */
|
||||
rgb = s.sacn_layout != 0;
|
||||
} else {
|
||||
rgb = (lop == 1) ? (strcmp(lo, "rgb") == 0) : (s.sacn_layout != 0);
|
||||
}
|
||||
slots = nl_board_dmx_slots(ctx->board, rgb);
|
||||
|
||||
if (err) {
|
||||
/* invalid layout steht schon fest */
|
||||
} else if (en && !cJSON_IsBool(en)) {
|
||||
err = "invalid enabled";
|
||||
} else if ((unp = get_int(j, "universe", 1, 63999, &un)) < 0) {
|
||||
err = "invalid universe";
|
||||
@@ -373,14 +391,25 @@ static void handle_put_settings(const nl_api_ctx_t *ctx, const nl_http_request_t
|
||||
(otp == 1 && strcmp(ot, "release") != 0 && strcmp(ot, "hold") != 0)) {
|
||||
err = "invalid on_timeout";
|
||||
} else {
|
||||
if (en)
|
||||
s.sacn_enabled = cJSON_IsTrue(en) ? 1 : 0;
|
||||
if (unp == 1)
|
||||
s.sacn_universe = (uint16_t)un;
|
||||
if (stp == 1)
|
||||
s.sacn_start_slot = (uint16_t)st;
|
||||
if (otp == 1)
|
||||
s.sacn_hold = strcmp(ot, "hold") == 0 ? 1 : 0;
|
||||
/* Ein Wechsel auf ein breiteres Layout kann einen bisher zulaessigen start_slot
|
||||
* unzulaessig machen. Das wird abgelehnt statt still verschoben: eine Lampe, die
|
||||
* nach einer Einstellung woanders im Universe liegt als angegeben, findet niemand
|
||||
* wieder. */
|
||||
want_start = (stp == 1) ? (uint16_t)st : s.sacn_start_slot;
|
||||
if ((uint32_t)want_start + slots - 1u > 512u) {
|
||||
err = "invalid start_slot";
|
||||
} else {
|
||||
if (en)
|
||||
s.sacn_enabled = cJSON_IsTrue(en) ? 1 : 0;
|
||||
if (unp == 1)
|
||||
s.sacn_universe = (uint16_t)un;
|
||||
if (stp == 1)
|
||||
s.sacn_start_slot = (uint16_t)st;
|
||||
if (otp == 1)
|
||||
s.sacn_hold = strcmp(ot, "hold") == 0 ? 1 : 0;
|
||||
if (lop == 1)
|
||||
s.sacn_layout = rgb ? 1 : 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
err = "invalid type";
|
||||
|
||||
@@ -40,6 +40,7 @@ void nl_sacn_apply_settings(const nl_settings_t *s)
|
||||
c.start_slot = s->sacn_start_slot;
|
||||
c.hold = s->sacn_hold != 0;
|
||||
c.release_fade_ms = s->fade_in_ms;
|
||||
c.rgb_layout = s->sacn_layout != 0;
|
||||
nl_app_lock();
|
||||
nl_dmx_configure(&c);
|
||||
nl_app_unlock();
|
||||
|
||||
Reference in New Issue
Block a user