feat: sACN-Belegung rgb als Vorgabe

Pixelorientierte Werkzeuge wie LedFx schreiben drei Kanaele je Pixel; mit
dem Dimmer voran landet deren erstes Pixel auf Dimmer, R, G. rgb ist damit
die Belegung, die out of the box das Richtige tut - der Dimmer bleibt fuer
DMX-Pulte eine Umschaltung entfernt.
This commit is contained in:
Michael Sauer
2026-09-22 23:20:12 +02:00
parent 363aee12ef
commit 9ee1983dff
5 changed files with 24 additions and 20 deletions
+3 -3
View File
@@ -75,14 +75,14 @@ Verfügbare Befehle in der Konsole: `help`.
Die Lampe empfängt sACN (E1.31) auf UDP-Port 5568 — per Multicast auf Die Lampe empfängt sACN (E1.31) auf UDP-Port 5568 — per Multicast auf
`239.255.<universe_hi>.<universe_lo>` und per Unicast direkt an ihre IP. Nach einem `239.255.<universe_hi>.<universe_lo>` und per Unicast direkt an ihre IP. Nach einem
Update ist der Empfang aktiv: Universe 1, Startslot 1. Update ist der Empfang aktiv: Universe 1, Startslot 1, Belegung `rgb`.
Es gibt zwei Slot-Belegungen, umschaltbar über `layout`: Es gibt zwei Slot-Belegungen, umschaltbar über `layout`:
| `layout` | Slots ab `start_slot` | wofür | | `layout` | Slots ab `start_slot` | wofür |
|---|---|---| |---|---|---|
| `dimmer` (Vorgabe) | Dimmer, R, G, B, CW, WW — ohne RGB: Dimmer, CW, WW | DMX-Pulte, die einen Master-Dimmer erwarten | | `dimmer` | Dimmer, R, G, B, CW, WW — ohne RGB: Dimmer, CW, WW | DMX-Pulte, die einen Master-Dimmer erwarten |
| `rgb` | R, G, B (3 Slots auf jedem Board) | pixelorientierte Werkzeuge wie LedFx, die je Pixel drei Kanäle schreiben | | `rgb` (Vorgabe) | R, G, B (3 Slots auf jedem Board) | pixelorientierte Werkzeuge wie LedFx, die je Pixel drei Kanäle schreiben |
Im `rgb`-Layout ist jede Lampe genau ein Pixel: Lampe 1 auf Offset 0, Lampe 2 auf Offset 3, Im `rgb`-Layout ist jede Lampe genau ein Pixel: Lampe 1 auf Offset 0, Lampe 2 auf Offset 3,
und so weiter. Ein Board ohne Farbkanäle nimmt den größten der drei Werte auf beide und so weiter. Ein Board ohne Farbkanäle nimmt den größten der drei Werte auf beide
+1 -1
View File
@@ -77,7 +77,7 @@ void nl_settings_defaults(nl_settings_t *s)
s->sacn_universe = 1; s->sacn_universe = 1;
s->sacn_start_slot = 1; s->sacn_start_slot = 1;
s->sacn_hold = 0; s->sacn_hold = 0;
s->sacn_layout = 0; s->sacn_layout = 1;
} }
void nl_settings_init(uint32_t addr_a, uint32_t addr_b) void nl_settings_init(uint32_t addr_a, uint32_t addr_b)
+1 -1
View File
@@ -36,7 +36,7 @@ typedef struct {
uint16_t sacn_start_slot; /* 1..512 (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 sacn_hold; /* 0 = nach dem Timeout zurueck zum HA-Zustand, 1 = halten */
uint8_t _padding[1]; /* haelt sizeof auf einem Vielfachen von 4 */ 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). uint8_t sacn_layout; /* Offset 216 - 0 = Dimmer voran, 1 = nur R,G,B (Vorgabe 1).
* Dieselbe Regel wie oben: neue Felder beginnen bei oder hinter * Dieselbe Regel wie oben: neue Felder beginnen bei oder hinter
* dem vorherigen sizeof (216), sonst ueberschreibt das Fuellbyte * dem vorherigen sizeof (216), sonst ueberschreibt das Fuellbyte
* eines aelteren Satzes sie. */ * eines aelteren Satzes sie. */
+12 -8
View File
@@ -361,7 +361,8 @@ static void test_settings_exposes_sacn_block(void)
TEST_ASSERT_EQUAL_INT(1, (int)cJSON_GetNumberValue(sacn_field("universe"))); TEST_ASSERT_EQUAL_INT(1, (int)cJSON_GetNumberValue(sacn_field("universe")));
TEST_ASSERT_EQUAL_INT(1, (int)cJSON_GetNumberValue(sacn_field("start_slot"))); TEST_ASSERT_EQUAL_INT(1, (int)cJSON_GetNumberValue(sacn_field("start_slot")));
TEST_ASSERT_EQUAL_STRING("release", cJSON_GetStringValue(sacn_field("on_timeout"))); TEST_ASSERT_EQUAL_STRING("release", cJSON_GetStringValue(sacn_field("on_timeout")));
TEST_ASSERT_EQUAL_INT(6, (int)cJSON_GetNumberValue(sacn_field("slots"))); TEST_ASSERT_EQUAL_STRING("rgb", cJSON_GetStringValue(sacn_field("layout")));
TEST_ASSERT_EQUAL_INT(3, (int)cJSON_GetNumberValue(sacn_field("slots")));
} }
static void test_settings_sacn_block_reflects_stored_values(void) static void test_settings_sacn_block_reflects_stored_values(void)
@@ -442,6 +443,7 @@ static void test_put_sacn_rejects_fractional_numbers(void)
/* Ein Board mit RGB braucht 6 Slots: ab 508 passt der Satz nicht mehr ins Universe. */ /* Ein Board mit RGB braucht 6 Slots: ab 508 passt der Satz nicht mehr ins Universe. */
static void test_put_sacn_start_slot_must_leave_room_for_all_slots(void) static void test_put_sacn_start_slot_must_leave_room_for_all_slots(void)
{ {
fake_settings.sacn_layout = 0; /* die Vorgabe ist rgb mit 3 Slots - hier geht es um 6 */
call(NL_HTTP_PUT, "/api/v1/system/settings", TOKEN, "{\"type\":\"sacn\",\"start_slot\":507}"); call(NL_HTTP_PUT, "/api/v1/system/settings", TOKEN, "{\"type\":\"sacn\",\"start_slot\":507}");
TEST_ASSERT_EQUAL_INT(200, resp.status); TEST_ASSERT_EQUAL_INT(200, resp.status);
call(NL_HTTP_PUT, "/api/v1/system/settings", TOKEN, "{\"type\":\"sacn\",\"start_slot\":508}"); call(NL_HTTP_PUT, "/api/v1/system/settings", TOKEN, "{\"type\":\"sacn\",\"start_slot\":508}");
@@ -451,7 +453,9 @@ static void test_put_sacn_start_slot_must_leave_room_for_all_slots(void)
static void test_put_sacn_start_slot_limit_follows_the_board(void) static void test_put_sacn_start_slot_limit_follows_the_board(void)
{ {
fake_api_reset("g95_cw"); /* drei Slots: 510 passt noch */ fake_api_reset("g95_cw");
fake_settings.sacn_layout = 0; /* im dimmer-Layout haengt die Slotzahl am Board */
/* Ein Board ohne Farbe hat auch mit Dimmer nur drei Slots: 510 passt noch. */
call(NL_HTTP_PUT, "/api/v1/system/settings", TOKEN, "{\"type\":\"sacn\",\"start_slot\":510}"); call(NL_HTTP_PUT, "/api/v1/system/settings", TOKEN, "{\"type\":\"sacn\",\"start_slot\":510}");
TEST_ASSERT_EQUAL_INT(200, resp.status); TEST_ASSERT_EQUAL_INT(200, resp.status);
call(NL_HTTP_PUT, "/api/v1/system/settings", TOKEN, "{\"type\":\"sacn\",\"start_slot\":511}"); call(NL_HTTP_PUT, "/api/v1/system/settings", TOKEN, "{\"type\":\"sacn\",\"start_slot\":511}");
@@ -512,15 +516,15 @@ static void test_commands(void)
static void test_settings_reports_the_layout_and_matching_slots(void) static void test_settings_reports_the_layout_and_matching_slots(void)
{ {
call(NL_HTTP_GET, "/api/v1/system/settings", TOKEN, NULL);
/* Vorgabe: rgb - und darin belegt auch ein Board mit Farbe nur drei Slots. */
TEST_ASSERT_EQUAL_STRING("rgb", cJSON_GetStringValue(sacn_field("layout")));
TEST_ASSERT_EQUAL_INT(3, (int)cJSON_GetNumberValue(sacn_field("slots")));
fake_settings.sacn_layout = 0;
call(NL_HTTP_GET, "/api/v1/system/settings", TOKEN, NULL); call(NL_HTTP_GET, "/api/v1/system/settings", TOKEN, NULL);
TEST_ASSERT_EQUAL_STRING("dimmer", cJSON_GetStringValue(sacn_field("layout"))); TEST_ASSERT_EQUAL_STRING("dimmer", cJSON_GetStringValue(sacn_field("layout")));
TEST_ASSERT_EQUAL_INT(6, (int)cJSON_GetNumberValue(sacn_field("slots"))); TEST_ASSERT_EQUAL_INT(6, (int)cJSON_GetNumberValue(sacn_field("slots")));
fake_settings.sacn_layout = 1;
call(NL_HTTP_GET, "/api/v1/system/settings", TOKEN, NULL);
TEST_ASSERT_EQUAL_STRING("rgb", cJSON_GetStringValue(sacn_field("layout")));
/* Im rgb-Layout belegt auch ein Board mit Farbe nur drei Slots. */
TEST_ASSERT_EQUAL_INT(3, (int)cJSON_GetNumberValue(sacn_field("slots")));
} }
static void test_put_sacn_layout(void) static void test_put_sacn_layout(void)
+7 -7
View File
@@ -238,20 +238,20 @@ static void test_record_without_sacn_gets_defaults(void)
/* Die Satzlaenge muss durch 4 teilbar bleiben, sonst laufen Schreiber und Leser beim CRC /* Die Satzlaenge muss durch 4 teilbar bleiben, sonst laufen Schreiber und Leser beim CRC
* auseinander (siehe NL_SETTINGS_CRC_OFF in nl_settings.c). */ * auseinander (siehe NL_SETTINGS_CRC_OFF in nl_settings.c). */
static void test_sacn_layout_defaults_to_dimmer(void) static void test_sacn_layout_defaults_to_rgb(void)
{ {
nl_settings_t s; nl_settings_t s;
TEST_ASSERT_FALSE(nl_settings_load(&s)); TEST_ASSERT_FALSE(nl_settings_load(&s));
TEST_ASSERT_EQUAL_UINT8(0, s.sacn_layout); TEST_ASSERT_EQUAL_UINT8(1, s.sacn_layout);
} }
static void test_sacn_layout_roundtrip(void) static void test_sacn_layout_roundtrip(void)
{ {
nl_settings_t in = sample("layout"), out; nl_settings_t in = sample("layout"), out;
in.sacn_layout = 1; in.sacn_layout = 0; /* weg von der Vorgabe, sonst prueft der Test nur diese */
TEST_ASSERT_EQUAL_INT(0, nl_settings_save(&in)); TEST_ASSERT_EQUAL_INT(0, nl_settings_save(&in));
TEST_ASSERT_TRUE(nl_settings_load(&out)); TEST_ASSERT_TRUE(nl_settings_load(&out));
TEST_ASSERT_EQUAL_UINT8(1, out.sacn_layout); TEST_ASSERT_EQUAL_UINT8(0, out.sacn_layout);
} }
/* Derselbe Fallstrick wie bei sacn_enabled, eine Feldgeneration weiter: ein Satz, der von /* Derselbe Fallstrick wie bei sacn_enabled, eine Feldgeneration weiter: ein Satz, der von
@@ -261,7 +261,7 @@ static void test_record_without_layout_gets_the_default(void)
{ {
nl_settings_t in = sample("vor-layout"), out; nl_settings_t in = sample("vor-layout"), out;
uint32_t crc; uint32_t crc;
in.sacn_layout = 1; in.sacn_layout = 0; /* der abgeschnittene Satz darf diese 0 nicht zurueckliefern */
nl_settings_save(&in); nl_settings_save(&in);
{ {
uint16_t shorter = 216u; /* sizeof(nl_settings_t) vor dem Layout-Feld */ uint16_t shorter = 216u; /* sizeof(nl_settings_t) vor dem Layout-Feld */
@@ -272,7 +272,7 @@ static void test_record_without_layout_gets_the_default(void)
TEST_ASSERT_TRUE(nl_settings_load(&out)); TEST_ASSERT_TRUE(nl_settings_load(&out));
TEST_ASSERT_EQUAL_STRING("vor-layout", out.name); TEST_ASSERT_EQUAL_STRING("vor-layout", out.name);
TEST_ASSERT_EQUAL_UINT8(1, out.sacn_enabled); TEST_ASSERT_EQUAL_UINT8(1, out.sacn_enabled);
TEST_ASSERT_EQUAL_UINT8(0, out.sacn_layout); TEST_ASSERT_EQUAL_UINT8(1, out.sacn_layout);
} }
static void test_record_size_stays_aligned(void) static void test_record_size_stays_aligned(void)
@@ -297,7 +297,7 @@ int main(void)
RUN_TEST(test_sacn_defaults); RUN_TEST(test_sacn_defaults);
RUN_TEST(test_sacn_roundtrip); RUN_TEST(test_sacn_roundtrip);
RUN_TEST(test_record_without_sacn_gets_defaults); RUN_TEST(test_record_without_sacn_gets_defaults);
RUN_TEST(test_sacn_layout_defaults_to_dimmer); RUN_TEST(test_sacn_layout_defaults_to_rgb);
RUN_TEST(test_sacn_layout_roundtrip); RUN_TEST(test_sacn_layout_roundtrip);
RUN_TEST(test_record_without_layout_gets_the_default); RUN_TEST(test_record_without_layout_gets_the_default);
RUN_TEST(test_record_size_stays_aligned); RUN_TEST(test_record_size_stays_aligned);