diff --git a/src/net/nl_api.c b/src/net/nl_api.c index a6ef00c..d85608b 100644 --- a/src/net/nl_api.c +++ b/src/net/nl_api.c @@ -265,6 +265,13 @@ static void handle_info(const nl_api_ctx_t *ctx, nl_api_response_t *resp) cJSON_AddStringToObject(o, "last_ota_error", in.last_ota_error); else cJSON_AddNullToObject(o, "last_ota_error"); + { + nl_settings_t s; + cJSON *sa = cJSON_AddObjectToObject(o, "sacn"); + ctx->get_settings(&s); + cJSON_AddBoolToObject(sa, "active", in.sacn_active); + cJSON_AddNumberToObject(sa, "universe", s.sacn_universe); + } reply_json(resp, 200, o); } @@ -286,6 +293,16 @@ static void handle_get_settings(const nl_api_ctx_t *ctx, nl_api_response_t *resp * kommen, ohne die Bedeutung von "ssid" zu aendern. */ cJSON_AddStringToObject(w, "source", s.wifi_ssid[0] ? "stored" : "compiled"); cJSON_AddBoolToObject(o, "auth_all", s.auth_all != 0); + { + cJSON *sa = cJSON_AddObjectToObject(o, "sacn"); + cJSON_AddBoolToObject(sa, "enabled", s.sacn_enabled != 0); + cJSON_AddNumberToObject(sa, "universe", s.sacn_universe); + cJSON_AddNumberToObject(sa, "start_slot", s.sacn_start_slot); + 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)); + } reply_json(resp, 200, o); } @@ -333,6 +350,34 @@ static void handle_put_settings(const nl_api_ctx_t *ctx, const nl_http_request_t err = "invalid all"; else s.auth_all = cJSON_IsTrue(all) ? 1 : 0; + } else if (strcmp(type, "sacn") == 0) { + const cJSON *en = cJSON_GetObjectItemCaseSensitive(j, "enabled"); + const cJSON *un = cJSON_GetObjectItemCaseSensitive(j, "universe"); + const cJSON *st = cJSON_GetObjectItemCaseSensitive(j, "start_slot"); + const char *ot = NULL; + int otp; + uint8_t slots = nl_board_dmx_slots(ctx->board); + + if (en && !cJSON_IsBool(en)) { + err = "invalid enabled"; + } else if (un && (!cJSON_IsNumber(un) || un->valuedouble < 1 || un->valuedouble > 63999)) { + err = "invalid universe"; + } else if (st && (!cJSON_IsNumber(st) || st->valuedouble < 1 || + st->valuedouble + slots - 1 > 512)) { + err = "invalid start_slot"; + } else if ((otp = get_string(j, "on_timeout", 1, 16, &ot)) < 0 || + (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 (un) + s.sacn_universe = (uint16_t)un->valuedouble; + if (st) + s.sacn_start_slot = (uint16_t)st->valuedouble; + if (otp == 1) + s.sacn_hold = strcmp(ot, "hold") == 0 ? 1 : 0; + } } else { err = "invalid type"; } diff --git a/src/net/nl_api.h b/src/net/nl_api.h index 83980ee..21e977f 100644 --- a/src/net/nl_api.h +++ b/src/net/nl_api.h @@ -24,6 +24,7 @@ typedef struct { uint32_t uptime_s; uint32_t free_heap; const char *last_ota_error; /* NULL until OTA exists */ + bool sacn_active; /* true, solange die Lampe ferngesteuert wird */ } nl_sys_info_t; /* Everything the API needs from the application; implemented by src/sys/nl_app_api.c. */ diff --git a/test/test_api.c b/test/test_api.c index ca25feb..553c5be 100644 --- a/test/test_api.c +++ b/test/test_api.c @@ -346,6 +346,113 @@ static void test_settings_name_token_auth(void) TEST_ASSERT_EQUAL_INT(500, resp.status); } +/* --- system/settings: sacn --- */ +static const cJSON *sacn_field(const char *name) +{ + const cJSON *s = cJSON_GetObjectItemCaseSensitive(doc, "sacn"); + return cJSON_GetObjectItemCaseSensitive(s, name); +} + +static void test_settings_exposes_sacn_block(void) +{ + call(NL_HTTP_GET, "/api/v1/system/settings", TOKEN, NULL); + TEST_ASSERT_EQUAL_INT(200, resp.status); + TEST_ASSERT_TRUE(cJSON_IsTrue(sacn_field("enabled"))); + TEST_ASSERT_EQUAL_INT(1, (int)cJSON_GetNumberValue(sacn_field("universe"))); + TEST_ASSERT_EQUAL_INT(1, (int)cJSON_GetNumberValue(sacn_field("start_slot"))); + TEST_ASSERT_EQUAL_STRING("release", cJSON_GetStringValue(sacn_field("on_timeout"))); + TEST_ASSERT_EQUAL_INT(6, (int)cJSON_GetNumberValue(sacn_field("slots"))); +} + +static void test_settings_sacn_block_reflects_stored_values(void) +{ + fake_settings.sacn_enabled = 0; + fake_settings.sacn_universe = 12; + fake_settings.sacn_start_slot = 7; + fake_settings.sacn_hold = 1; + call(NL_HTTP_GET, "/api/v1/system/settings", TOKEN, NULL); + TEST_ASSERT_FALSE(cJSON_IsTrue(sacn_field("enabled"))); + TEST_ASSERT_EQUAL_INT(12, (int)cJSON_GetNumberValue(sacn_field("universe"))); + TEST_ASSERT_EQUAL_INT(7, (int)cJSON_GetNumberValue(sacn_field("start_slot"))); + TEST_ASSERT_EQUAL_STRING("hold", cJSON_GetStringValue(sacn_field("on_timeout"))); +} + +static void test_put_sacn_all_fields(void) +{ + call(NL_HTTP_PUT, "/api/v1/system/settings", TOKEN, + "{\"type\":\"sacn\",\"enabled\":false,\"universe\":300," + "\"start_slot\":100,\"on_timeout\":\"hold\"}"); + TEST_ASSERT_EQUAL_INT(200, resp.status); + TEST_ASSERT_EQUAL_INT(1, fake_save_calls); + TEST_ASSERT_EQUAL_UINT8(0, fake_settings.sacn_enabled); + TEST_ASSERT_EQUAL_UINT16(300, fake_settings.sacn_universe); + TEST_ASSERT_EQUAL_UINT16(100, fake_settings.sacn_start_slot); + TEST_ASSERT_EQUAL_UINT8(1, fake_settings.sacn_hold); +} + +static void test_put_sacn_single_field_leaves_rest(void) +{ + fake_settings.sacn_universe = 9; + fake_settings.sacn_hold = 1; + call(NL_HTTP_PUT, "/api/v1/system/settings", TOKEN, + "{\"type\":\"sacn\",\"start_slot\":50}"); + TEST_ASSERT_EQUAL_INT(200, resp.status); + TEST_ASSERT_EQUAL_UINT16(50, fake_settings.sacn_start_slot); + TEST_ASSERT_EQUAL_UINT16(9, fake_settings.sacn_universe); + TEST_ASSERT_EQUAL_UINT8(1, fake_settings.sacn_hold); +} + +static void test_put_sacn_rejects_bad_values(void) +{ + call(NL_HTTP_PUT, "/api/v1/system/settings", TOKEN, "{\"type\":\"sacn\",\"enabled\":\"ja\"}"); + TEST_ASSERT_EQUAL_INT(400, resp.status); + TEST_ASSERT_EQUAL_STRING("invalid enabled", error_text()); + + call(NL_HTTP_PUT, "/api/v1/system/settings", TOKEN, "{\"type\":\"sacn\",\"universe\":0}"); + TEST_ASSERT_EQUAL_INT(400, resp.status); + TEST_ASSERT_EQUAL_STRING("invalid universe", error_text()); + + call(NL_HTTP_PUT, "/api/v1/system/settings", TOKEN, "{\"type\":\"sacn\",\"universe\":64000}"); + TEST_ASSERT_EQUAL_STRING("invalid universe", error_text()); + + call(NL_HTTP_PUT, "/api/v1/system/settings", TOKEN, "{\"type\":\"sacn\",\"start_slot\":0}"); + TEST_ASSERT_EQUAL_STRING("invalid start_slot", error_text()); + + call(NL_HTTP_PUT, "/api/v1/system/settings", TOKEN, "{\"type\":\"sacn\",\"on_timeout\":\"halten\"}"); + TEST_ASSERT_EQUAL_STRING("invalid on_timeout", error_text()); + + TEST_ASSERT_EQUAL_INT(0, fake_save_calls); /* nichts davon darf gespeichert werden */ +} + +/* Ein Board mit RGB braucht 6 Slots: ab 508 passt der Satz nicht mehr ins Universe. */ +static void test_put_sacn_start_slot_must_leave_room_for_all_slots(void) +{ + call(NL_HTTP_PUT, "/api/v1/system/settings", TOKEN, "{\"type\":\"sacn\",\"start_slot\":507}"); + TEST_ASSERT_EQUAL_INT(200, resp.status); + call(NL_HTTP_PUT, "/api/v1/system/settings", TOKEN, "{\"type\":\"sacn\",\"start_slot\":508}"); + TEST_ASSERT_EQUAL_INT(400, resp.status); + TEST_ASSERT_EQUAL_STRING("invalid start_slot", error_text()); +} + +static void test_put_sacn_start_slot_limit_follows_the_board(void) +{ + fake_api_reset("g95_cw"); /* drei Slots: 510 passt noch */ + call(NL_HTTP_PUT, "/api/v1/system/settings", TOKEN, "{\"type\":\"sacn\",\"start_slot\":510}"); + TEST_ASSERT_EQUAL_INT(200, resp.status); + call(NL_HTTP_PUT, "/api/v1/system/settings", TOKEN, "{\"type\":\"sacn\",\"start_slot\":511}"); + TEST_ASSERT_EQUAL_INT(400, resp.status); +} + +static void test_info_exposes_sacn_state(void) +{ + fake_info.sacn_active = 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"))); +} + /* --- system/command --- */ static void test_commands(void) { @@ -406,6 +513,14 @@ int main(void) RUN_TEST(test_settings_wifi_source_reflects_compiled_fallback); RUN_TEST(test_settings_wifi); RUN_TEST(test_settings_name_token_auth); + RUN_TEST(test_settings_exposes_sacn_block); + RUN_TEST(test_settings_sacn_block_reflects_stored_values); + RUN_TEST(test_put_sacn_all_fields); + RUN_TEST(test_put_sacn_single_field_leaves_rest); + RUN_TEST(test_put_sacn_rejects_bad_values); + 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_commands); return UNITY_END(); }