diff --git a/README.md b/README.md index 9f8a014..28e57fe 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,16 @@ Die Lampe empfängt sACN (E1.31) auf UDP-Port 5568 — per Multicast auf `239.255..` und per Unicast direkt an ihre IP. Nach einem Update ist der Empfang aktiv: Universe 1, Startslot 1. -Slots ab `start_slot`: **Dimmer, R, G, B, CW, WW** (Boards ohne RGB: **Dimmer, CW, WW**). +Es gibt zwei Slot-Belegungen, umschaltbar über `layout`: + +| `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 | +| `rgb` | 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, +und so weiter. Ein Board ohne Farbkanäle nimmt den größten der drei Werte auf beide +Weißkanäle — es folgt damit der Helligkeit des Effekts. Wieviele es sind, steht als `slots` in `GET system/settings`. Während Pakete fließen, hat sACN Vorrang; die Werte gehen ohne Fade direkt an die diff --git a/src/board/nl_board.c b/src/board/nl_board.c index 70d4dee..0a49838 100644 --- a/src/board/nl_board.c +++ b/src/board/nl_board.c @@ -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; } diff --git a/src/board/nl_board.h b/src/board/nl_board.h index 7c02950..7cda347 100644 --- a/src/board/nl_board.h +++ b/src/board/nl_board.h @@ -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 diff --git a/src/config/nl_settings.c b/src/config/nl_settings.c index 3a95fcd..970ed4f 100644 --- a/src/config/nl_settings.c +++ b/src/config/nl_settings.c @@ -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) diff --git a/src/config/nl_settings.h b/src/config/nl_settings.h index bef8e92..476efc1 100644 --- a/src/config/nl_settings.h +++ b/src/config/nl_settings.h @@ -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); diff --git a/src/light/nl_dmx.c b/src/light/nl_dmx.c index b5bae2e..c8d1223 100644 --- a/src/light/nl_dmx.c +++ b/src/light/nl_dmx.c @@ -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; diff --git a/src/light/nl_dmx.h b/src/light/nl_dmx.h index af905c5..ac519c0 100644 --- a/src/light/nl_dmx.h +++ b/src/light/nl_dmx.h @@ -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. */ diff --git a/src/net/nl_api.c b/src/net/nl_api.c index d649c5d..79e3bd4 100644 --- a/src/net/nl_api.c +++ b/src/net/nl_api.c @@ -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"; diff --git a/src/net/nl_sacn.c b/src/net/nl_sacn.c index 431bc69..0a756d0 100644 --- a/src/net/nl_sacn.c +++ b/src/net/nl_sacn.c @@ -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(); diff --git a/test/test_api.c b/test/test_api.c index dc39b67..b721e15 100644 --- a/test/test_api.c +++ b/test/test_api.c @@ -508,9 +508,76 @@ static void test_commands(void) TEST_ASSERT_EQUAL_STRING("invalid command", error_text()); } +/* --- sacn: Belegung --- */ + +static void test_settings_reports_the_layout_and_matching_slots(void) +{ + call(NL_HTTP_GET, "/api/v1/system/settings", TOKEN, NULL); + TEST_ASSERT_EQUAL_STRING("dimmer", cJSON_GetStringValue(sacn_field("layout"))); + 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) +{ + call(NL_HTTP_PUT, "/api/v1/system/settings", TOKEN, "{\"type\":\"sacn\",\"layout\":\"rgb\"}"); + TEST_ASSERT_EQUAL_INT(200, resp.status); + TEST_ASSERT_EQUAL_UINT8(1, fake_settings.sacn_layout); + + call(NL_HTTP_PUT, "/api/v1/system/settings", TOKEN, "{\"type\":\"sacn\",\"layout\":\"dimmer\"}"); + TEST_ASSERT_EQUAL_INT(200, resp.status); + TEST_ASSERT_EQUAL_UINT8(0, fake_settings.sacn_layout); +} + +static void test_put_sacn_rejects_an_unknown_layout(void) +{ + call(NL_HTTP_PUT, "/api/v1/system/settings", TOKEN, "{\"type\":\"sacn\",\"layout\":\"pixel\"}"); + TEST_ASSERT_EQUAL_INT(400, resp.status); + TEST_ASSERT_EQUAL_STRING("invalid layout", error_text()); + TEST_ASSERT_EQUAL_INT(0, fake_save_calls); +} + +/* Die Obergrenze fuer start_slot haengt am Layout. Im rgb-Layout (3 Slots) ist 510 erlaubt, + * mit Dimmer (6 Slots) nicht mehr - ein Wechsel darf einen unzulaessigen Zustand also nicht + * still herstellen. */ +static void test_put_sacn_start_slot_limit_follows_the_layout(void) +{ + fake_settings.sacn_layout = 1; + call(NL_HTTP_PUT, "/api/v1/system/settings", TOKEN, "{\"type\":\"sacn\",\"start_slot\":510}"); + TEST_ASSERT_EQUAL_INT(200, resp.status); + TEST_ASSERT_EQUAL_UINT16(510, fake_settings.sacn_start_slot); + + /* Zurueck auf dimmer wuerde 510 + 6 - 1 = 515 bedeuten: abgelehnt. */ + call(NL_HTTP_PUT, "/api/v1/system/settings", TOKEN, "{\"type\":\"sacn\",\"layout\":\"dimmer\"}"); + TEST_ASSERT_EQUAL_INT(400, resp.status); + TEST_ASSERT_EQUAL_STRING("invalid start_slot", error_text()); + TEST_ASSERT_EQUAL_UINT8(1, fake_settings.sacn_layout); /* nichts uebernommen */ +} + +static void test_put_sacn_layout_and_start_slot_together(void) +{ + /* Beides in einer Anfrage: geprueft wird gegen das NEUE Layout. */ + fake_settings.sacn_layout = 0; + call(NL_HTTP_PUT, "/api/v1/system/settings", TOKEN, + "{\"type\":\"sacn\",\"layout\":\"rgb\",\"start_slot\":510}"); + TEST_ASSERT_EQUAL_INT(200, resp.status); + TEST_ASSERT_EQUAL_UINT8(1, fake_settings.sacn_layout); + TEST_ASSERT_EQUAL_UINT16(510, fake_settings.sacn_start_slot); +} + int main(void) { UNITY_BEGIN(); + RUN_TEST(test_settings_reports_the_layout_and_matching_slots); + RUN_TEST(test_put_sacn_layout); + RUN_TEST(test_put_sacn_rejects_an_unknown_layout); + RUN_TEST(test_put_sacn_start_slot_limit_follows_the_layout); + RUN_TEST(test_put_sacn_layout_and_start_slot_together); RUN_TEST(test_unknown_path_and_method); RUN_TEST(test_get_state); RUN_TEST(test_white_board_capabilities); diff --git a/test/test_board.c b/test/test_board.c index 45e8d89..bb3655d 100644 --- a/test/test_board.c +++ b/test/test_board.c @@ -108,9 +108,12 @@ static void test_known_values_from_old_firmware(void) static void test_dmx_slots(void) { - TEST_ASSERT_EQUAL_UINT8(6, nl_board_dmx_slots(nl_board_find("gu10_rgb"))); - TEST_ASSERT_EQUAL_UINT8(3, nl_board_dmx_slots(nl_board_find("g95_cw"))); - TEST_ASSERT_EQUAL_UINT8(6, nl_board_dmx_slots(nl_board_find("evk"))); + TEST_ASSERT_EQUAL_UINT8(6, nl_board_dmx_slots(nl_board_find("gu10_rgb"), false)); + TEST_ASSERT_EQUAL_UINT8(3, nl_board_dmx_slots(nl_board_find("g95_cw"), false)); + TEST_ASSERT_EQUAL_UINT8(6, nl_board_dmx_slots(nl_board_find("evk"), false)); + /* Im rgb-Layout belegt JEDES Board drei Slots - eine Lampe ist ein Pixel. */ + TEST_ASSERT_EQUAL_UINT8(3, nl_board_dmx_slots(nl_board_find("gu10_rgb"), true)); + TEST_ASSERT_EQUAL_UINT8(3, nl_board_dmx_slots(nl_board_find("g95_cw"), true)); } int main(void) diff --git a/test/test_dmx.c b/test/test_dmx.c index 7d58375..d695787 100644 --- a/test/test_dmx.c +++ b/test/test_dmx.c @@ -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); diff --git a/test/test_settings.c b/test/test_settings.c index 790c0ff..4e9fc74 100644 --- a/test/test_settings.c +++ b/test/test_settings.c @@ -238,6 +238,43 @@ static void test_record_without_sacn_gets_defaults(void) /* Die Satzlaenge muss durch 4 teilbar bleiben, sonst laufen Schreiber und Leser beim CRC * auseinander (siehe NL_SETTINGS_CRC_OFF in nl_settings.c). */ +static void test_sacn_layout_defaults_to_dimmer(void) +{ + nl_settings_t s; + TEST_ASSERT_FALSE(nl_settings_load(&s)); + TEST_ASSERT_EQUAL_UINT8(0, s.sacn_layout); +} + +static void test_sacn_layout_roundtrip(void) +{ + nl_settings_t in = sample("layout"), out; + in.sacn_layout = 1; + TEST_ASSERT_EQUAL_INT(0, nl_settings_save(&in)); + TEST_ASSERT_TRUE(nl_settings_load(&out)); + TEST_ASSERT_EQUAL_UINT8(1, out.sacn_layout); +} + +/* Derselbe Fallstrick wie bei sacn_enabled, eine Feldgeneration weiter: ein Satz, der von + * einer Firmware MIT sACN, aber OHNE layout geschrieben wurde, ist 216 Byte lang. Laege + * sacn_layout darunter, kaeme es als Fuellbyte dieses Satzes zurueck statt als Vorgabe. */ +static void test_record_without_layout_gets_the_default(void) +{ + nl_settings_t in = sample("vor-layout"), out; + uint32_t crc; + in.sacn_layout = 1; + nl_settings_save(&in); + { + uint16_t shorter = 216u; /* sizeof(nl_settings_t) vor dem Layout-Feld */ + memcpy(&fake_flash_mem[NL_FL_SETTINGS_A + 6], &shorter, 2); + crc = nl_crc32(0, &fake_flash_mem[NL_FL_SETTINGS_A], 12 + shorter); + memcpy(&fake_flash_mem[NL_FL_SETTINGS_A + 12 + shorter], &crc, 4); + } + TEST_ASSERT_TRUE(nl_settings_load(&out)); + TEST_ASSERT_EQUAL_STRING("vor-layout", out.name); + TEST_ASSERT_EQUAL_UINT8(1, out.sacn_enabled); + TEST_ASSERT_EQUAL_UINT8(0, out.sacn_layout); +} + static void test_record_size_stays_aligned(void) { TEST_ASSERT_EQUAL_UINT(0, sizeof(nl_settings_t) % 4u); @@ -260,6 +297,9 @@ int main(void) RUN_TEST(test_sacn_defaults); RUN_TEST(test_sacn_roundtrip); RUN_TEST(test_record_without_sacn_gets_defaults); + RUN_TEST(test_sacn_layout_defaults_to_dimmer); + RUN_TEST(test_sacn_layout_roundtrip); + RUN_TEST(test_record_without_layout_gets_the_default); RUN_TEST(test_record_size_stays_aligned); return UNITY_END(); }