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.
This commit is contained in:
Michael Sauer
2026-09-20 02:23:33 +02:00
parent aa0ed770eb
commit fd61f151d7
6 changed files with 26 additions and 2 deletions
+4
View File
@@ -271,6 +271,10 @@ static void handle_info(const nl_api_ctx_t *ctx, nl_api_response_t *resp)
ctx->get_settings(&s); ctx->get_settings(&s);
cJSON_AddBoolToObject(sa, "active", in.sacn_active); cJSON_AddBoolToObject(sa, "active", in.sacn_active);
cJSON_AddNumberToObject(sa, "universe", s.sacn_universe); 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); reply_json(resp, 200, o);
} }
+1
View File
@@ -25,6 +25,7 @@ typedef struct {
uint32_t free_heap; uint32_t free_heap;
const char *last_ota_error; /* NULL until OTA exists */ const char *last_ota_error; /* NULL until OTA exists */
bool sacn_active; /* true, solange die Lampe ferngesteuert wird */ bool sacn_active; /* true, solange die Lampe ferngesteuert wird */
bool sacn_joined; /* Multicast-Gruppe des Universe beigetreten */
} nl_sys_info_t; } nl_sys_info_t;
/* Everything the API needs from the application; implemented by src/sys/nl_app_api.c. */ /* Everything the API needs from the application; implemented by src/sys/nl_app_api.c. */
+14 -2
View File
@@ -30,6 +30,8 @@ volatile uint32_t nl_sacn_beat;
* wuerde die Lampe im 20-s-Takt endlos zuruecksetzen - ein Zusatzdienst darf sie nicht * wuerde die Lampe im 20-s-Takt endlos zuruecksetzen - ein Zusatzdienst darf sie nicht
* unbrauchbar machen. */ * unbrauchbar machen. */
static volatile bool s_running = true; 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) 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'; ip_seen[15] = '\0';
/* 239.255.<hi>.<lo> gemaess E1.31 Anhang A. Ein fehlgeschlagener Beitritt ist kein /* 239.255.<hi>.<lo> 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); memset(&mreq, 0, sizeof mreq);
mreq.imr_multiaddr.s_addr = mreq.imr_multiaddr.s_addr =
htonl(0xefff0000u | ((uint32_t)(universe >> 8) << 8) | (universe & 0xffu)); htonl(0xefff0000u | ((uint32_t)(universe >> 8) << 8) | (universe & 0xffu));
mreq.imr_interface.s_addr = ip_seen[0] ? inet_addr(ip_seen) : htonl(INADDR_ANY); 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; return fd;
} }
@@ -171,3 +181,5 @@ bool nl_sacn_running(void)
{ {
return s_running; return s_running;
} }
bool nl_sacn_joined(void) { return s_joined; }
+3
View File
@@ -18,5 +18,8 @@ void nl_sacn_start(void);
/* false, wenn der Empfangs-Task nicht angelegt werden konnte. Dann bewegt sich /* false, wenn der Empfangs-Task nicht angelegt werden konnte. Dann bewegt sich
* nl_sacn_beat nie und der Wachhund darf ihn nicht mehr verlangen. */ * nl_sacn_beat nie und der Wachhund darf ihn nicht mehr verlangen. */
bool nl_sacn_running(void); 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 #endif
+2
View File
@@ -95,6 +95,8 @@ static void get_info(nl_sys_info_t *out)
* zu sperren. */ * zu sperren. */
out->sacn_active = nl_dmx_active(); out->sacn_active = nl_dmx_active();
nl_app_unlock(); 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); nl_wifi_status(&w);
out->wifi_mode = w.mode; out->wifi_mode = w.mode;
memcpy(out->ssid, w.ssid, sizeof out->ssid); memcpy(out->ssid, w.ssid, sizeof out->ssid);
+2
View File
@@ -461,11 +461,13 @@ static void test_put_sacn_start_slot_limit_follows_the_board(void)
static void test_info_exposes_sacn_state(void) static void test_info_exposes_sacn_state(void)
{ {
fake_info.sacn_active = true; fake_info.sacn_active = true;
fake_info.sacn_joined = true;
fake_settings.sacn_universe = 5; fake_settings.sacn_universe = 5;
call(NL_HTTP_GET, "/api/v1/system/info", NULL, NULL); call(NL_HTTP_GET, "/api/v1/system/info", NULL, NULL);
TEST_ASSERT_EQUAL_INT(200, resp.status); TEST_ASSERT_EQUAL_INT(200, resp.status);
TEST_ASSERT_TRUE(cJSON_IsTrue(sacn_field("active"))); TEST_ASSERT_TRUE(cJSON_IsTrue(sacn_field("active")));
TEST_ASSERT_EQUAL_INT(5, (int)cJSON_GetNumberValue(sacn_field("universe"))); TEST_ASSERT_EQUAL_INT(5, (int)cJSON_GetNumberValue(sacn_field("universe")));
TEST_ASSERT_TRUE(cJSON_IsTrue(sacn_field("multicast")));
} }
/* --- system/command --- */ /* --- system/command --- */