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:
Michael Sauer
2026-09-20 03:35:19 +02:00
parent 2c7ebac6d1
commit fbdf0c40ef
13 changed files with 311 additions and 32 deletions
+86
View File
@@ -45,6 +45,20 @@ static void start(const char *board_id, uint16_t start_slot, bool hold)
dc.start_slot = start_slot;
dc.hold = hold;
dc.release_fade_ms = 0;
dc.rgb_layout = false;
nl_dmx_configure(&dc);
}
/* Wie start(), aber mit der Belegung ohne Dimmer (drei Kanaele je Lampe). */
static void start_rgb(const char *board_id, uint16_t start_slot)
{
nl_dmx_cfg_t dc;
start(board_id, start_slot, false);
dc.start_slot = start_slot;
dc.hold = false;
dc.release_fade_ms = 0;
dc.rgb_layout = true;
nl_dmx_configure(&dc);
}
@@ -303,9 +317,81 @@ static void test_configure_during_stream_takes_effect(void)
TEST_ASSERT_EQUAL_UINT8(222, last_ch[NL_CH_R]);
}
/* --- Belegung "rgb": drei Kanaele, kein Dimmer --- */
static void test_rgb_layout_maps_three_slots_to_colour(void)
{
uint8_t d[3] = { 10, 20, 30 };
start_rgb("gu10_rgb", 1);
memcpy(slot_data, d, sizeof d);
feed(100, 1, false, 3, 1000);
nl_engine_tick(10);
TEST_ASSERT_TRUE(nl_dmx_active());
TEST_ASSERT_EQUAL_UINT8(10, last_ch[NL_CH_R]);
TEST_ASSERT_EQUAL_UINT8(20, last_ch[NL_CH_G]);
TEST_ASSERT_EQUAL_UINT8(30, last_ch[NL_CH_B]);
/* Kein Dimmer heisst auch: die Weisskanaele bleiben aus, sie haben keinen Slot. */
TEST_ASSERT_EQUAL_UINT8(0, last_ch[NL_CH_CW]);
TEST_ASSERT_EQUAL_UINT8(0, last_ch[NL_CH_WW]);
}
static void test_rgb_layout_passes_full_value_through(void)
{
/* Der Fall, der mit der Dimmer-Belegung schiefging: ein rotes Effektbild eines
* pixelorientierten Werkzeugs (255,0,0) muss rot leuchten, nicht dunkel bleiben. */
uint8_t d[3] = { 255, 0, 0 };
start_rgb("gu10_rgb", 1);
memcpy(slot_data, d, sizeof d);
feed(100, 1, false, 3, 1000);
nl_engine_tick(10);
TEST_ASSERT_EQUAL_UINT8(255, last_ch[NL_CH_R]);
TEST_ASSERT_EQUAL_UINT8(0, last_ch[NL_CH_G]);
}
static void test_rgb_layout_on_a_board_without_colour_uses_the_largest_value(void)
{
uint8_t d[3] = { 40, 200, 90 };
start_rgb("g95_cw", 1);
memcpy(slot_data, d, sizeof d);
feed(100, 1, false, 3, 1000);
nl_engine_tick(10);
TEST_ASSERT_EQUAL_UINT8(200, last_ch[NL_CH_CW]);
TEST_ASSERT_EQUAL_UINT8(200, last_ch[NL_CH_WW]);
TEST_ASSERT_EQUAL_UINT8(0, last_ch[NL_CH_R]);
}
static void test_rgb_layout_uses_start_slot(void)
{
/* Zweite Lampe im Universe: LedFx-Offset 3, also Slots 4..6. */
uint8_t d[6] = { 9, 9, 9, 11, 22, 33 };
start_rgb("gu10_rgb", 4);
memcpy(slot_data, d, sizeof d);
feed(100, 1, false, 6, 1000);
nl_engine_tick(10);
TEST_ASSERT_EQUAL_UINT8(11, last_ch[NL_CH_R]);
TEST_ASSERT_EQUAL_UINT8(33, last_ch[NL_CH_B]);
}
static void test_rgb_layout_needs_only_three_slots(void)
{
/* Mit Dimmer braeuchte das gu10_rgb sechs Slots und wuerde ein Paket mit dreien
* verwerfen - im rgb-Layout reichen drei. */
uint8_t d[3] = { 1, 2, 3 };
start_rgb("gu10_rgb", 1);
memcpy(slot_data, d, sizeof d);
feed(100, 1, false, 3, 1000);
nl_engine_tick(10);
TEST_ASSERT_TRUE(nl_dmx_active());
}
int main(void)
{
UNITY_BEGIN();
RUN_TEST(test_rgb_layout_maps_three_slots_to_colour);
RUN_TEST(test_rgb_layout_passes_full_value_through);
RUN_TEST(test_rgb_layout_on_a_board_without_colour_uses_the_largest_value);
RUN_TEST(test_rgb_layout_uses_start_slot);
RUN_TEST(test_rgb_layout_needs_only_three_slots);
RUN_TEST(test_first_packet_takes_over);
RUN_TEST(test_master_dimmer_scales);
RUN_TEST(test_dimmer_zero_is_dark);