diff --git a/src/net/nl_api.c b/src/net/nl_api.c index 79e3bd4..b68cddc 100644 --- a/src/net/nl_api.c +++ b/src/net/nl_api.c @@ -275,6 +275,12 @@ static void handle_info(const nl_api_ctx_t *ctx, nl_api_response_t *resp) * 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); + /* Ob der Empfangs-Task ueberhaupt laeuft. Ohne dieses Feld war ein toter Task von + * "es sendet gerade niemand" nicht zu unterscheiden - beides meldete nur + * active=false, und "multicast" bleibt vom letzten gelungenen Beitritt true, auch + * wenn der Socket laengst zu ist. Das hat beim Montana-Black-Shield eine halbe + * Stunde Suche gekostet. */ + cJSON_AddBoolToObject(sa, "running", in.sacn_running); } reply_json(resp, 200, o); } diff --git a/src/net/nl_api.h b/src/net/nl_api.h index afa2c01..3d37c4e 100644 --- a/src/net/nl_api.h +++ b/src/net/nl_api.h @@ -26,6 +26,7 @@ typedef struct { 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 */ + bool sacn_running; /* Empfangs-Task lebt und haelt einen Socket */ } nl_sys_info_t; /* Everything the API needs from the application; implemented by src/sys/nl_app_api.c. */ diff --git a/src/sys/nl_app_api.c b/src/sys/nl_app_api.c index 01a108b..be04c4e 100644 --- a/src/sys/nl_app_api.c +++ b/src/sys/nl_app_api.c @@ -97,6 +97,7 @@ static void get_info(nl_sys_info_t *out) nl_app_unlock(); /* Ausserhalb der Sperre: nl_sacn haelt den Wert selbst, nl_dmx ist nicht beteiligt. */ out->sacn_joined = nl_sacn_joined(); + out->sacn_running = nl_sacn_running(); 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 0acf523..6abb14d 100644 --- a/test/test_api.c +++ b/test/test_api.c @@ -466,12 +466,27 @@ static void test_info_exposes_sacn_state(void) { fake_info.sacn_active = true; fake_info.sacn_joined = true; + fake_info.sacn_running = 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"))); + TEST_ASSERT_TRUE(cJSON_IsTrue(sacn_field("running"))); +} + +/* Ein toter Empfangs-Task sah von aussen genauso aus wie "es sendet gerade niemand": + * beides meldete nur active=false. Beim Montana-Black-Shield (2026-09-26) hat das eine + * halbe Stunde Suche gekostet - "multicast" half nicht weiter, weil es den gemerkten + * Erfolg des LETZTEN Beitritts zeigt und auch dann true bleibt, wenn der Socket laengst + * zu ist. */ +static void test_info_reports_a_dead_sacn_task(void) +{ + fake_info.sacn_running = false; + call(NL_HTTP_GET, "/api/v1/system/info", NULL, NULL); + TEST_ASSERT_EQUAL_INT(200, resp.status); + TEST_ASSERT_FALSE(cJSON_IsTrue(sacn_field("running"))); } /* --- system/command --- */ @@ -610,6 +625,7 @@ int main(void) RUN_TEST(test_put_sacn_start_slot_must_leave_room_for_all_slots); RUN_TEST(test_put_sacn_start_slot_limit_follows_the_board); RUN_TEST(test_info_exposes_sacn_state); + RUN_TEST(test_info_reports_a_dead_sacn_task); RUN_TEST(test_commands); return UNITY_END(); }