From fd61f151d72efd5052ce39018186c113c2850096 Mon Sep 17 00:00:00 2001 From: Michael Sauer Date: Sun, 20 Sep 2026 02:23:33 +0200 Subject: [PATCH] feat(sacn): Ergebnis des Multicast-Beitritts sichtbar machen Bei der Abnahme kam Multicast nicht an, waehrend Unicast lief. Der Rueckgabewert von IP_ADD_MEMBERSHIP wurde bis jetzt verworfen - absichtlich, weil ein gescheiterter Beitritt den Unicast-Empfang nicht verhindern soll. Die Folge war aber, dass sich die entscheidende Frage nicht beantworten liess: tritt die Lampe nicht bei, oder reicht das WLAN die Gruppe nicht weiter? system/info traegt deshalb jetzt "sacn.multicast", und der Beitritt schreibt beim Socket-Aufbau eine Zeile auf die Konsole. Am Verhalten aendert sich nichts: ein Fehlschlag bleibt folgenlos fuer Unicast. --- src/net/nl_api.c | 4 ++++ src/net/nl_api.h | 1 + src/net/nl_sacn.c | 16 ++++++++++++++-- src/net/nl_sacn.h | 3 +++ src/sys/nl_app_api.c | 2 ++ test/test_api.c | 2 ++ 6 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/net/nl_api.c b/src/net/nl_api.c index 80d4fd6..d649c5d 100644 --- a/src/net/nl_api.c +++ b/src/net/nl_api.c @@ -271,6 +271,10 @@ static void handle_info(const nl_api_ctx_t *ctx, nl_api_response_t *resp) ctx->get_settings(&s); cJSON_AddBoolToObject(sa, "active", in.sacn_active); cJSON_AddNumberToObject(sa, "universe", s.sacn_universe); + /* Sagt, ob der Beitritt zur Multicast-Gruppe gelungen ist. Ohne dieses Feld war bei + * der Abnahme nicht zu unterscheiden, ob die Lampe nicht beitritt oder das WLAN die + * Gruppe nicht weiterreicht - Unicast laeuft in beiden Faellen. */ + cJSON_AddBoolToObject(sa, "multicast", in.sacn_joined); } reply_json(resp, 200, o); } diff --git a/src/net/nl_api.h b/src/net/nl_api.h index 21e977f..afa2c01 100644 --- a/src/net/nl_api.h +++ b/src/net/nl_api.h @@ -25,6 +25,7 @@ typedef struct { uint32_t free_heap; const char *last_ota_error; /* NULL until OTA exists */ bool sacn_active; /* true, solange die Lampe ferngesteuert wird */ + bool sacn_joined; /* Multicast-Gruppe des Universe beigetreten */ } nl_sys_info_t; /* Everything the API needs from the application; implemented by src/sys/nl_app_api.c. */ diff --git a/src/net/nl_sacn.c b/src/net/nl_sacn.c index ef20fa8..431bc69 100644 --- a/src/net/nl_sacn.c +++ b/src/net/nl_sacn.c @@ -30,6 +30,8 @@ volatile uint32_t nl_sacn_beat; * wuerde die Lampe im 20-s-Takt endlos zuruecksetzen - ein Zusatzdienst darf sie nicht * unbrauchbar machen. */ static volatile bool s_running = true; +/* Ergebnis des letzten Multicast-Beitritts; ueber system/info sichtbar. */ +static volatile bool s_joined; void nl_sacn_apply_settings(const nl_settings_t *s) { @@ -79,12 +81,20 @@ static int open_socket(uint16_t universe, char ip_seen[16]) ip_seen[15] = '\0'; /* 239.255.. gemaess E1.31 Anhang A. Ein fehlgeschlagener Beitritt ist kein - * Abbruchgrund: Unicast an die Lampen-IP kommt auf demselben Socket weiterhin an. */ + * Abbruchgrund: Unicast an die Lampen-IP kommt auf demselben Socket weiterhin an. + * + * Das Ergebnis wird aber festgehalten und ueber system/info sichtbar gemacht. Bei der + * Abnahme am 2026-09-20 kam Multicast nicht an, waehrend Unicast lief - und weil der + * Rueckgabewert damals verworfen wurde, liess sich nicht unterscheiden, ob die Lampe + * nicht beitritt oder das WLAN die Gruppe nicht weiterreicht. Genau diese Frage + * beantwortet das Feld. */ memset(&mreq, 0, sizeof mreq); mreq.imr_multiaddr.s_addr = htonl(0xefff0000u | ((uint32_t)(universe >> 8) << 8) | (universe & 0xffu)); mreq.imr_interface.s_addr = ip_seen[0] ? inet_addr(ip_seen) : htonl(INADDR_ANY); - setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof mreq); + s_joined = setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof mreq) == 0; + printf("sacn: universe %u, multicast join %s\r\n", (unsigned)universe, + s_joined ? "ok" : "FEHLGESCHLAGEN"); return fd; } @@ -171,3 +181,5 @@ bool nl_sacn_running(void) { return s_running; } + +bool nl_sacn_joined(void) { return s_joined; } diff --git a/src/net/nl_sacn.h b/src/net/nl_sacn.h index ec1a26a..3068ce9 100644 --- a/src/net/nl_sacn.h +++ b/src/net/nl_sacn.h @@ -18,5 +18,8 @@ void nl_sacn_start(void); /* false, wenn der Empfangs-Task nicht angelegt werden konnte. Dann bewegt sich * nl_sacn_beat nie und der Wachhund darf ihn nicht mehr verlangen. */ bool nl_sacn_running(void); +/* true, wenn der Beitritt zur Multicast-Gruppe des eingestellten Universe gelungen ist. + * Unicast an die Lampen-IP funktioniert unabhaengig davon. */ +bool nl_sacn_joined(void); #endif diff --git a/src/sys/nl_app_api.c b/src/sys/nl_app_api.c index c9859f2..01a108b 100644 --- a/src/sys/nl_app_api.c +++ b/src/sys/nl_app_api.c @@ -95,6 +95,8 @@ static void get_info(nl_sys_info_t *out) * zu sperren. */ out->sacn_active = nl_dmx_active(); nl_app_unlock(); + /* Ausserhalb der Sperre: nl_sacn haelt den Wert selbst, nl_dmx ist nicht beteiligt. */ + out->sacn_joined = nl_sacn_joined(); nl_wifi_status(&w); out->wifi_mode = w.mode; memcpy(out->ssid, w.ssid, sizeof out->ssid); diff --git a/test/test_api.c b/test/test_api.c index 0cb8867..dc39b67 100644 --- a/test/test_api.c +++ b/test/test_api.c @@ -461,11 +461,13 @@ static void test_put_sacn_start_slot_limit_follows_the_board(void) static void test_info_exposes_sacn_state(void) { fake_info.sacn_active = true; + fake_info.sacn_joined = true; fake_settings.sacn_universe = 5; call(NL_HTTP_GET, "/api/v1/system/info", NULL, NULL); TEST_ASSERT_EQUAL_INT(200, resp.status); TEST_ASSERT_TRUE(cJSON_IsTrue(sacn_field("active"))); TEST_ASSERT_EQUAL_INT(5, (int)cJSON_GetNumberValue(sacn_field("universe"))); + TEST_ASSERT_TRUE(cJSON_IsTrue(sacn_field("multicast"))); } /* --- system/command --- */