From 8a6e1ad53cf7fc960a47802fa4cc93d36e703b8a Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Tue, 9 Jun 2026 20:11:56 +0300 Subject: [PATCH 01/55] config: color type --- components/config/cmd.c | 4 ++++ components/config/get.c | 12 ++++++++++++ components/config/include/config.h | 10 ++++++++++ components/config/print.c | 11 +++++++++++ components/config/reset.c | 4 ++++ components/config/set.c | 21 +++++++++++++++++++++ 6 files changed, 62 insertions(+) diff --git a/components/config/cmd.c b/components/config/cmd.c index aa286f03..b3375fc5 100644 --- a/components/config/cmd.c +++ b/components/config/cmd.c @@ -165,6 +165,10 @@ static void print_configtab(const struct config_path path) break; + case CONFIG_TYPE_COLOR: + printf(""); + break; + default: printf("???"); break; diff --git a/components/config/get.c b/components/config/get.c index 7a9c2e98..0ca1dc35 100644 --- a/components/config/get.c +++ b/components/config/get.c @@ -71,6 +71,18 @@ int config_get(const struct config_path path, unsigned index, char *buf, size_t break; } + case CONFIG_TYPE_COLOR: + if (snprintf(buf, size, "%02x%02x%02x%02x", + tab->color_type.value[index].r, + tab->color_type.value[index].g, + tab->color_type.value[index].b, + tab->color_type.value[index].a + ) >= size) { + return -1; + } else { + break; + } + default: LOG_ERROR("invalid type=%d", tab->type); return -1; diff --git a/components/config/include/config.h b/components/config/include/config.h index 082c99ce..2a2d5a3f 100644 --- a/components/config/include/config.h +++ b/components/config/include/config.h @@ -36,6 +36,7 @@ enum config_type { CONFIG_TYPE_BOOL, CONFIG_TYPE_ENUM, CONFIG_TYPE_FILE, + CONFIG_TYPE_COLOR, }; struct config_enum { @@ -52,6 +53,10 @@ struct config_file_path { const char *suffix; }; +struct config_color { + uint8_t r, g, b, a; +}; + struct configmod; struct configtab; @@ -119,6 +124,11 @@ struct configtab { size_t size; const struct config_file_path *paths; } file_type; + + struct { + struct config_color *value; + struct config_color default_value; + } color_type; }; }; diff --git a/components/config/print.c b/components/config/print.c index d92e5218..21f6c4a8 100644 --- a/components/config/print.c +++ b/components/config/print.c @@ -69,6 +69,17 @@ int config_print(const struct config_path path, unsigned index, FILE *file) } break; + case CONFIG_TYPE_COLOR: + if (fprintf(file, "%02x%02x%02x%02x", + tab->color_type.value[index].r, + tab->color_type.value[index].g, + tab->color_type.value[index].b, + tab->color_type.value[index].a + ) < 0) { + return -1; + } + break; + default: LOG_ERROR("invalid type=%d", tab->type); return -1; diff --git a/components/config/reset.c b/components/config/reset.c index bbceeec1..84606c19 100644 --- a/components/config/reset.c +++ b/components/config/reset.c @@ -41,6 +41,10 @@ int configtab_reset(const struct config_path path) memset(tab->file_type.value, 0, tab->file_type.size); break; + case CONFIG_TYPE_COLOR: + *tab->color_type.value = tab->color_type.default_value; + break; + default: LOG_ERROR("invalid type=%d", tab->type); return -1; diff --git a/components/config/set.c b/components/config/set.c index 02eaa08c..dd944d6a 100644 --- a/components/config/set.c +++ b/components/config/set.c @@ -43,6 +43,10 @@ int config_clear(const struct config_path path) memset(tab->file_type.value, 0, tab->file_type.size); break; + case CONFIG_TYPE_COLOR: + *tab->color_type.value = tab->color_type.default_value; + break; + default: LOG_ERROR("invalid type=%d", tab->type); return -1; @@ -160,6 +164,20 @@ static int config_set_file(const struct config_path path, unsigned index, const return 0; } +static int config_set_color(const struct config_path path, unsigned index, const char *str) +{ + const struct configtab *tab = path.tab; + int r, g, b, a; + + if (sscanf(str, "%2x%2x%2x%2x", &r, &g, &b, &a) < 4) { + return -1; + } + + tab->color_type.value[index] = (struct config_color) { r, g, b, a }; + + return 0; +} + int config_set(const struct config_path path, const char *value) { const struct configtab *tab = path.tab; @@ -201,6 +219,9 @@ int config_set(const struct config_path path, const char *value) case CONFIG_TYPE_FILE: return config_set_file(path, index, value); + case CONFIG_TYPE_COLOR: + return config_set_color(path, index, value); + default: LOG_ERROR("invalid type=%d", tab->type); return -1; From 792ca9968cf2ef0430c8c3ec0fcbfef45a038c98 Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Tue, 9 Jun 2026 21:09:52 +0300 Subject: [PATCH 02/55] leds: do not repeat artnet timeout, clear once when entering test mode --- main/leds_artnet.c | 12 ++++++++---- main/leds_artnet.h | 2 +- main/leds_test.c | 2 +- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/main/leds_artnet.c b/main/leds_artnet.c index 7df50c7c..2f7eecaf 100644 --- a/main/leds_artnet.c +++ b/main/leds_artnet.c @@ -152,7 +152,7 @@ static void leds_artnet_sync_reset(struct leds_state *state) } } -void leds_artnet_timeout_reset(struct leds_state *state) +static void leds_artnet_timeout_reset(struct leds_state *state) { const struct leds_config *config = state->config; @@ -163,6 +163,11 @@ void leds_artnet_timeout_reset(struct leds_state *state) } } +void leds_artnet_timeout_clear(struct leds_state *state) +{ + state->artnet->timeout_tick = 0; +} + TickType_t leds_artnet_wait(struct leds_state *state) { if (state->artnet->sync_missed) { @@ -174,7 +179,7 @@ TickType_t leds_artnet_wait(struct leds_state *state) return state->artnet->sync_tick; } - if (state->artnet->timeout_tick && !leds_test_active(state, 0)) { + if (state->artnet->timeout_tick) { // use loss-of-signal timeout return state->artnet->timeout_tick; } @@ -358,8 +363,7 @@ int leds_artnet_update(struct leds_state *state, EventBits_t event_bits) LOG_WARN("leds_artnet_timeout"); } - // repeat - leds_artnet_timeout_reset(state); + leds_artnet_timeout_clear(state); } } diff --git a/main/leds_artnet.h b/main/leds_artnet.h index c95e3660..e67b64d0 100644 --- a/main/leds_artnet.h +++ b/main/leds_artnet.h @@ -29,7 +29,7 @@ unsigned count_leds_artnet_outputs(); int init_leds_artnet(struct leds_state *state, int index, const struct leds_config *config); int start_leds_artnet(struct leds_state *state, const struct leds_config *config); -void leds_artnet_timeout_reset(struct leds_state *state); +void leds_artnet_timeout_clear(struct leds_state *state); /* Return next tick for possible artnet timeout */ TickType_t leds_artnet_wait(struct leds_state *state); diff --git a/main/leds_test.c b/main/leds_test.c index 6d8553b5..57b10732 100644 --- a/main/leds_test.c +++ b/main/leds_test.c @@ -176,7 +176,7 @@ int leds_test_update(struct leds_state *state, EventBits_t bits) } if (state->artnet) { - leds_artnet_timeout_reset(state); + leds_artnet_timeout_clear(state); } return 1; From 665b650d1628e38dc61007a2831b8a19dcd2a4cb Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Tue, 9 Jun 2026 21:15:17 +0300 Subject: [PATCH 03/55] fixup config_set_color --- components/config/set.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/config/set.c b/components/config/set.c index dd944d6a..99ad6830 100644 --- a/components/config/set.c +++ b/components/config/set.c @@ -167,7 +167,7 @@ static int config_set_file(const struct config_path path, unsigned index, const static int config_set_color(const struct config_path path, unsigned index, const char *str) { const struct configtab *tab = path.tab; - int r, g, b, a; + int r = 0, g = 0, b = 0, a = 0; if (sscanf(str, "%2x%2x%2x%2x", &r, &g, &b, &a) < 4) { return -1; From 982544e01370320b75c5b8afe3691f5179cd7aa0 Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Tue, 9 Jun 2026 21:16:44 +0300 Subject: [PATCH 04/55] leds static color, basic functionality to set at boot --- main/leds.c | 10 ++++- main/leds_cmd.c | 1 + main/leds_config.h | 3 ++ main/leds_configtab.i | 9 +++++ main/leds_state.h | 1 + main/leds_static.c | 88 +++++++++++++++++++++++++++++++++++++++++++ main/leds_static.h | 19 ++++++++++ main/leds_stats.c | 1 + main/leds_stats.h | 3 ++ main/leds_task.c | 23 ++++++++++- main/leds_task.h | 3 +- main/user.c | 1 + main/user.h | 1 + 13 files changed, 159 insertions(+), 4 deletions(-) create mode 100644 main/leds_static.c create mode 100644 main/leds_static.h diff --git a/main/leds.c b/main/leds.c index 5ab11e71..1450e34c 100644 --- a/main/leds.c +++ b/main/leds.c @@ -1,7 +1,8 @@ #include "leds.h" #include "leds_artnet.h" -#include "leds_state.h" #include "leds_config.h" +#include "leds_state.h" +#include "leds_static.h" #include "leds_stats.h" #include "leds_sequence.h" #include "leds_task.h" @@ -105,6 +106,13 @@ int init_leds() return err; } } + + if (config->static_enabled) { + if ((err = init_leds_static(state, config))) { + LOG_ERROR("leds%d: init_leds_static", i + 1); + return err; + } + } } return 0; diff --git a/main/leds_cmd.c b/main/leds_cmd.c index 51b75626..0f20fa11 100644 --- a/main/leds_cmd.c +++ b/main/leds_cmd.c @@ -406,6 +406,7 @@ int leds_cmd_stats(int argc, char **argv, void *ctx) print_stats_timer("task", "test", &stats->test); print_stats_timer("task", "artnet", &stats->artnet); print_stats_timer("task", "sequence", &stats->sequence); + print_stats_timer("task", "static", &stats->static_); print_stats_timer("task", "update", &stats->update); printf("\n"); print_stats_counter("artnet", "timeout", &stats->artnet_timeout); diff --git a/main/leds_config.h b/main/leds_config.h index 25733088..224b39df 100644 --- a/main/leds_config.h +++ b/main/leds_config.h @@ -178,6 +178,9 @@ struct leds_config { uint16_t sequence_leds_group; uint16_t sequence_leds_segment; uint16_t sequence_leds_offset; + + bool static_enabled; + struct config_color static_color; }; extern struct leds_config leds_configs[LEDS_COUNT]; diff --git a/main/leds_configtab.i b/main/leds_configtab.i index 66cae892..710173cc 100644 --- a/main/leds_configtab.i +++ b/main/leds_configtab.i @@ -261,5 +261,14 @@ const struct configtab LEDS_CONFIGTAB[] = { .uint16_type = { .value = &LEDS_CONFIG.sequence_leds_offset }, }, + { CONFIG_TYPE_BOOL, "static_enabled", + .description = "Output static color", + .bool_type = { .value = &LEDS_CONFIG.static_enabled }, + }, + { CONFIG_TYPE_COLOR, "static_color", + .description = "LED color format for sequence channels", + .color_type = { .value = &LEDS_CONFIG.static_color }, + }, + {} }; diff --git a/main/leds_state.h b/main/leds_state.h index 84c27494..357523f5 100644 --- a/main/leds_state.h +++ b/main/leds_state.h @@ -28,6 +28,7 @@ struct leds_state { struct leds_test_state *test; struct leds_artnet_state *artnet; struct leds_sequence_state *sequence; + struct leds_static_state *static_; struct leds_status_timers status_timers; struct leds_status_timer_metrics status_timer_metrics; diff --git a/main/leds_static.c b/main/leds_static.c new file mode 100644 index 00000000..a9c0961b --- /dev/null +++ b/main/leds_static.c @@ -0,0 +1,88 @@ +#include "leds_static.h" +#include "leds_task.h" +#include "leds_artnet.h" + +#include + +static struct leds_color config_leds_color(struct config_color config_color, enum leds_parameter_type parameter_type) +{ + switch (parameter_type) { + case LEDS_PARAMETER_NONE: + return (struct leds_color) { + .r = config_color.r * config_color.a / 255, + .g = config_color.g * config_color.a / 255, + .b = config_color.b * config_color.a / 255, + }; + + case LEDS_PARAMETER_DIMMER: + return (struct leds_color) { + .r = config_color.r, + .g = config_color.g, + .b = config_color.b, + + .dimmer = config_color.a, + }; + + case LEDS_PARAMETER_WHITE: + return (struct leds_color) { + .r = config_color.r, + .g = config_color.g, + .b = config_color.b, + + // XXX: totally wrong + .white = config_color.a, + }; + + default: + LOG_FATAL("parameter_type=%d", parameter_type); + } +} + +int init_leds_static(struct leds_state *state, const struct leds_config *config) +{ + if (!(state->static_ = calloc(1, sizeof(*state->static_)))) { + LOG_ERROR("calloc"); + return -1; + } + + state->static_->color = config_leds_color(config->static_color, leds_parameter_type_for_protocol(leds_protocol(state->leds))); + + LOG_INFO("color=%02x%02x%02x.%02x", state->static_->color.r, state->static_->color.g, state->static_->color.b, state->static_->color.parameter); + + // required to start initial tick + notify_leds_task(state, 1 << LEDS_EVENT_STATIC_BIT); + + return 0; +} + +TickType_t leds_static_wait(struct leds_state *state) +{ + return 0; // it's static, duh +} + +bool leds_static_active(struct leds_state *state, EventBits_t bits) +{ + if (bits & (1 << LEDS_EVENT_STATIC_BIT)) { + return true; + } + + // TODO: active if artnet, test inactive? + + return false; +} + +int leds_static_update(struct leds_state *state, EventBits_t bits) +{ + LOG_INFO("color=%02x%02x%02x.%02x", state->static_->color.r, state->static_->color.g, state->static_->color.b, state->static_->color.parameter); + + if (leds_set_all(state->leds, state->static_->color)) { + LOG_ERROR("leds_set_all"); + return -1; + } + + if (state->artnet) { + leds_artnet_timeout_clear(state); + } + + return 1; +} diff --git a/main/leds_static.h b/main/leds_static.h new file mode 100644 index 00000000..37edda10 --- /dev/null +++ b/main/leds_static.h @@ -0,0 +1,19 @@ +#pragma once + +#include "leds_state.h" + +struct leds_static_state { + struct leds_color color; +}; + +int init_leds_static(struct leds_state *state, const struct leds_config *config); + + +/* Return next tick */ +TickType_t leds_static_wait(struct leds_state *state); + +/* Need update? */ +bool leds_static_active(struct leds_state *state, EventBits_t bits); + +/* Update LEDs */ +int leds_static_update(struct leds_state *state, EventBits_t bits); diff --git a/main/leds_stats.c b/main/leds_stats.c index 610f3d70..2f285495 100644 --- a/main/leds_stats.c +++ b/main/leds_stats.c @@ -21,6 +21,7 @@ void init_leds_stats() stats_timer_init(&stats->test); stats_timer_init(&stats->artnet); stats_timer_init(&stats->sequence); + stats_timer_init(&stats->static_); stats_timer_init(&stats->update); stats_counter_init(&stats->artnet_timeout); diff --git a/main/leds_stats.h b/main/leds_stats.h index faf35118..046dc464 100644 --- a/main/leds_stats.h +++ b/main/leds_stats.h @@ -26,8 +26,11 @@ struct leds_stats { struct stats_timer sequence; + struct stats_timer static_; + struct stats_timer update; struct stats_counter update_timeout; + }; extern struct leds_sequence_stats leds_sequence_stats; diff --git a/main/leds_task.c b/main/leds_task.c index b4147368..0da80eb0 100644 --- a/main/leds_task.c +++ b/main/leds_task.c @@ -1,6 +1,7 @@ #include "leds_artnet.h" #include "leds_sequence.h" #include "leds_state.h" +#include "leds_static.h" #include "leds_stats.h" #include "leds_task.h" #include "leds_test.h" @@ -68,6 +69,12 @@ static EventBits_t leds_task_wait(struct leds_state *state) } } + if (state->static_ && (tick = leds_static_wait(state))) { + if (tick < wait_tick) { + wait_tick = tick; + } + } + // how long to wait for TickType_t wait_ticks = portMAX_DELAY; tick = xTaskGetTickCount(); @@ -87,11 +94,12 @@ static EventBits_t leds_task_wait(struct leds_state *state) const bool wait_for_all_bits = false; EventBits_t event_bits = xEventGroupWaitBits(state->event_group, LEDS_EVENT_BITS, clear_on_exit, wait_for_all_bits, wait_ticks); - LOG_DEBUG("leds%d: test=%d artnet_dmx=%d artnet_sync=%d sequence=%d", state->index + 1, + LOG_DEBUG("leds%d: test=%d artnet_dmx=%d artnet_sync=%d sequence=%d static=%d", state->index + 1, !!(event_bits & (1 << LEDS_EVENT_TEST_BIT)), !!(event_bits & (1 << LEDS_EVENT_ARTNET_DMX_BIT)), !!(event_bits & (1 << LEDS_EVENT_ARTNET_SYNC_BIT)), - !!(event_bits & (1 << LEDS_EVENT_SEQUENCE_BIT)) + !!(event_bits & (1 << LEDS_EVENT_SEQUENCE_BIT)), + !!(event_bits & (1 << LEDS_EVENT_STATIC_BIT)) ); return event_bits; @@ -114,6 +122,7 @@ static void leds_main(void *ctx) loop_start = stats_timer_start(&stats->loop); + if (state->sequence && leds_sequence_active(state, event_bits)) { LOG_DEBUG("sequence"); @@ -156,6 +165,16 @@ static void leds_main(void *ctx) } } + if (state->static_ && leds_static_active(state, event_bits)) { + LOG_DEBUG("static"); + + WITH_STATS_TIMER(&stats->static_) { + if (leds_static_update(state, event_bits)) { + update_activity = USER_ACTIVITY_LEDS_STATIC; + } + } + } + if (leds_update_active(state)) { LOG_DEBUG("update timeout"); diff --git a/main/leds_task.h b/main/leds_task.h index 681fd90d..af379f44 100644 --- a/main/leds_task.h +++ b/main/leds_task.h @@ -5,13 +5,14 @@ #include -#define LEDS_EVENT_BITS 0x0f +#define LEDS_EVENT_BITS 0x1f enum leds_event_bit { LEDS_EVENT_TEST_BIT = 0, LEDS_EVENT_ARTNET_DMX_BIT = 1, LEDS_EVENT_ARTNET_SYNC_BIT = 2, LEDS_EVENT_SEQUENCE_BIT = 3, + LEDS_EVENT_STATIC_BIT = 4, }; int init_leds_task(struct leds_state *state, const struct leds_config *config); diff --git a/main/user.c b/main/user.c index 232bfb97..1fbf6ea8 100644 --- a/main/user.c +++ b/main/user.c @@ -48,6 +48,7 @@ const char *user_activity_str(enum user_activity activity) case USER_ACTIVITY_LEDS_ARTNET: return "LEDS_ARTNET"; case USER_ACTIVITY_LEDS_ARTNET_TIMEOUT: return "LEDS_ARTNET_TIMEOUT"; case USER_ACTIVITY_LEDS_TEST: return "LEDS_TEST"; + case USER_ACTIVITY_LEDS_STATIC: return "LEDS_STATIC"; case USER_ACTIVITY_DMX_INPUT: return "DMX_INPUT"; case USER_ACTIVITY_DMX_OUTPUT: return "DMX_OUTPUT"; diff --git a/main/user.h b/main/user.h index d4d115f1..1a9fba3f 100644 --- a/main/user.h +++ b/main/user.h @@ -32,6 +32,7 @@ enum user_activity { USER_ACTIVITY_LEDS_ARTNET, USER_ACTIVITY_LEDS_ARTNET_TIMEOUT, USER_ACTIVITY_LEDS_TEST, + USER_ACTIVITY_LEDS_STATIC, USER_ACTIVITY_DMX_INPUT, USER_ACTIVITY_DMX_OUTPUT, From c8123d2ef83e404af627dec9115724abeb47a9a8 Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Tue, 9 Jun 2026 21:38:35 +0300 Subject: [PATCH 05/55] main: leds static is always initialized --- main/leds.c | 2 +- main/leds_state.h | 4 +++- main/leds_static.c | 15 +++++---------- main/leds_static.h | 6 +----- main/leds_task.c | 5 ++--- 5 files changed, 12 insertions(+), 20 deletions(-) diff --git a/main/leds.c b/main/leds.c index 1450e34c..eb4c8733 100644 --- a/main/leds.c +++ b/main/leds.c @@ -108,7 +108,7 @@ int init_leds() } if (config->static_enabled) { - if ((err = init_leds_static(state, config))) { + if ((err = config_leds_static(state, config))) { LOG_ERROR("leds%d: init_leds_static", i + 1); return err; } diff --git a/main/leds_state.h b/main/leds_state.h index 357523f5..f8c9d050 100644 --- a/main/leds_state.h +++ b/main/leds_state.h @@ -28,7 +28,9 @@ struct leds_state { struct leds_test_state *test; struct leds_artnet_state *artnet; struct leds_sequence_state *sequence; - struct leds_static_state *static_; + struct leds_static_state { + struct leds_color color; + } static_; struct leds_status_timers status_timers; struct leds_status_timer_metrics status_timer_metrics; diff --git a/main/leds_static.c b/main/leds_static.c index a9c0961b..924e6bd6 100644 --- a/main/leds_static.c +++ b/main/leds_static.c @@ -38,16 +38,11 @@ static struct leds_color config_leds_color(struct config_color config_color, enu } } -int init_leds_static(struct leds_state *state, const struct leds_config *config) +int config_leds_static(struct leds_state *state, const struct leds_config *config) { - if (!(state->static_ = calloc(1, sizeof(*state->static_)))) { - LOG_ERROR("calloc"); - return -1; - } - - state->static_->color = config_leds_color(config->static_color, leds_parameter_type_for_protocol(leds_protocol(state->leds))); + state->static_.color = config_leds_color(config->static_color, leds_parameter_type_for_protocol(leds_protocol(state->leds))); - LOG_INFO("color=%02x%02x%02x.%02x", state->static_->color.r, state->static_->color.g, state->static_->color.b, state->static_->color.parameter); + LOG_INFO("color=%02x%02x%02x.%02x", state->static_.color.r, state->static_.color.g, state->static_.color.b, state->static_.color.parameter); // required to start initial tick notify_leds_task(state, 1 << LEDS_EVENT_STATIC_BIT); @@ -73,9 +68,9 @@ bool leds_static_active(struct leds_state *state, EventBits_t bits) int leds_static_update(struct leds_state *state, EventBits_t bits) { - LOG_INFO("color=%02x%02x%02x.%02x", state->static_->color.r, state->static_->color.g, state->static_->color.b, state->static_->color.parameter); + LOG_INFO("color=%02x%02x%02x.%02x", state->static_.color.r, state->static_.color.g, state->static_.color.b, state->static_.color.parameter); - if (leds_set_all(state->leds, state->static_->color)) { + if (leds_set_all(state->leds, state->static_.color)) { LOG_ERROR("leds_set_all"); return -1; } diff --git a/main/leds_static.h b/main/leds_static.h index 37edda10..c519bc32 100644 --- a/main/leds_static.h +++ b/main/leds_static.h @@ -2,11 +2,7 @@ #include "leds_state.h" -struct leds_static_state { - struct leds_color color; -}; - -int init_leds_static(struct leds_state *state, const struct leds_config *config); +int config_leds_static(struct leds_state *state, const struct leds_config *config); /* Return next tick */ diff --git a/main/leds_task.c b/main/leds_task.c index 0da80eb0..310ffa5f 100644 --- a/main/leds_task.c +++ b/main/leds_task.c @@ -69,7 +69,7 @@ static EventBits_t leds_task_wait(struct leds_state *state) } } - if (state->static_ && (tick = leds_static_wait(state))) { + if ((tick = leds_static_wait(state))) { if (tick < wait_tick) { wait_tick = tick; } @@ -122,7 +122,6 @@ static void leds_main(void *ctx) loop_start = stats_timer_start(&stats->loop); - if (state->sequence && leds_sequence_active(state, event_bits)) { LOG_DEBUG("sequence"); @@ -165,7 +164,7 @@ static void leds_main(void *ctx) } } - if (state->static_ && leds_static_active(state, event_bits)) { + if (leds_static_active(state, event_bits)) { LOG_DEBUG("static"); WITH_STATS_TIMER(&stats->static_) { From 7e58d5106941307ef54d9e6b23842a9d6709f057 Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Tue, 9 Jun 2026 21:39:22 +0300 Subject: [PATCH 06/55] main: leds static overrides test mode --- main/leds_static.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/main/leds_static.c b/main/leds_static.c index 924e6bd6..cc5ad044 100644 --- a/main/leds_static.c +++ b/main/leds_static.c @@ -1,5 +1,6 @@ #include "leds_static.h" #include "leds_task.h" +#include "leds_test.h" #include "leds_artnet.h" #include @@ -70,12 +71,18 @@ int leds_static_update(struct leds_state *state, EventBits_t bits) { LOG_INFO("color=%02x%02x%02x.%02x", state->static_.color.r, state->static_.color.g, state->static_.color.b, state->static_.color.parameter); + if (state->test) { + // clear any test mode output + leds_test_clear(state); + } + if (leds_set_all(state->leds, state->static_.color)) { LOG_ERROR("leds_set_all"); return -1; } if (state->artnet) { + // inhibit artnet timeout leds_artnet_timeout_clear(state); } From c7279ec367418e3eedbf2f2859a791acdb13d406 Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Tue, 9 Jun 2026 21:39:47 +0300 Subject: [PATCH 07/55] leds: cmd static, replaces all --- main/leds_cmd.c | 14 +++++--------- main/leds_static.c | 11 +++++++++++ main/leds_static.h | 1 + 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/main/leds_cmd.c b/main/leds_cmd.c index 0f20fa11..4b5a2a78 100644 --- a/main/leds_cmd.c +++ b/main/leds_cmd.c @@ -1,5 +1,6 @@ #include "leds.h" #include "leds_state.h" +#include "leds_static.h" #include "leds_status.h" #include "leds_artnet.h" #include "leds_test.h" @@ -157,7 +158,7 @@ int leds_cmd_clear(int argc, char **argv, void *ctx) return 0; } -int leds_cmd_all(int argc, char **argv, void *ctx) +int leds_cmd_static(int argc, char **argv, void *ctx) { int rgb, a = 0xff, w = 0; int err; @@ -196,13 +197,8 @@ int leds_cmd_all(int argc, char **argv, void *ctx) break; } - if ((err = leds_set_all(state->leds, leds_color))) { - LOG_ERROR("leds_set_all"); - return err; - } - - if ((err = update_leds(state, USER_ACTIVITY_LEDS_CMD))) { - LOG_ERROR("update_leds"); + if ((err = set_leds_static(state, leds_color))) { + LOG_ERROR("set_leds_static"); return err; } } @@ -434,7 +430,7 @@ const struct cmd leds_commands[] = { { "info", leds_cmd_info, .describe = "Show LED info" }, { "status", leds_cmd_status, .describe = "Show LED status" }, { "clear", leds_cmd_clear, .describe = "Clear all output values" }, - { "all", leds_cmd_all, .usage = "RGB [A]", .describe = "Set all output pixels to value" }, + { "static", leds_cmd_static, .usage = "RGB [A]", .describe = "Set static LEDs color" }, { "set", leds_cmd_set, .usage = "LEDS-ID LED-INDEX RGB [A]", .describe = "Set one output pixel to value" }, { "update", leds_cmd_update, .usage = "[LEDS-ID]", .describe = "Refresh one or all LED outputs" }, { "test", leds_cmd_test, .usage = "LEDS-ID [MODE]", .describe = "Output test patterns" }, diff --git a/main/leds_static.c b/main/leds_static.c index cc5ad044..cc156ef6 100644 --- a/main/leds_static.c +++ b/main/leds_static.c @@ -51,6 +51,17 @@ int config_leds_static(struct leds_state *state, const struct leds_config *confi return 0; } +int set_leds_static(struct leds_state *state, struct leds_color color) +{ + LOG_INFO("color=%02x%02x%02x.%02x", color.r, color.g, color.b, color.parameter); + + state->static_.color = color; + + notify_leds_task(state, 1 << LEDS_EVENT_STATIC_BIT); + + return 0; +} + TickType_t leds_static_wait(struct leds_state *state) { return 0; // it's static, duh diff --git a/main/leds_static.h b/main/leds_static.h index c519bc32..7d7d5421 100644 --- a/main/leds_static.h +++ b/main/leds_static.h @@ -4,6 +4,7 @@ int config_leds_static(struct leds_state *state, const struct leds_config *config); +int set_leds_static(struct leds_state *state, struct leds_color color); /* Return next tick */ TickType_t leds_static_wait(struct leds_state *state); From a11b7abab4a43b69af814d6083084384e34472f3 Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Tue, 9 Jun 2026 22:27:37 +0300 Subject: [PATCH 08/55] main: replace unsafe direct leds test cmd/api with task-based test mode --- main/leds.c | 55 ------------------------------------ main/leds_cmd.c | 65 ++++++++++++++++++++++++------------------- main/leds_http_post.c | 39 ++++++++++++++++---------- main/leds_state.h | 3 -- main/leds_test.c | 58 ++++++++++++++++++++++++++++++++++++++ main/leds_test.h | 5 ++++ 6 files changed, 123 insertions(+), 102 deletions(-) diff --git a/main/leds.c b/main/leds.c index eb4c8733..fd4b81a1 100644 --- a/main/leds.c +++ b/main/leds.c @@ -295,58 +295,3 @@ int clear_leds(struct leds_state *state) return 0; } - -int test_leds_mode(struct leds_state *state, enum leds_test_mode mode) -{ - int err = 0; - - LOG_INFO("mode=%d", mode); - - if ((err = check_leds_interface(state))) { - return err; - } - - force_leds_active(state); - user_activity(USER_ACTIVITY_LEDS); - - // animate - TickType_t tick = xTaskGetTickCount(); - int ticks; - - for (unsigned frame = 0; ; frame++) { - if ((ticks = leds_set_test(state->leds, mode, frame)) < 0) { - LOG_ERROR("leds%d: leds_set_test(%d, %u)", state->index + 1, mode, frame); - goto error; - } - - if ((err = leds_tx(state->leds))) { - LOG_ERROR("leds%d: leds_tx", state->index + 1); - goto error; - } - - if (ticks) { - vTaskDelayUntil(&tick, ticks); - } else { - break; - } - } - -error: - update_leds_active(state); - - return err; -} - -int test_leds(struct leds_state *state) -{ - int err; - - for (enum leds_test_mode mode = 0; mode < TEST_MODE_COUNT; mode++) { - if ((err = test_leds_mode(state, mode))) { - LOG_ERROR("leds%d: test_leds", state->index + 1); - return err; - } - } - - return 0; -} diff --git a/main/leds_cmd.c b/main/leds_cmd.c index 4b5a2a78..afb9409b 100644 --- a/main/leds_cmd.c +++ b/main/leds_cmd.c @@ -144,14 +144,22 @@ int leds_cmd_clear(int argc, char **argv, void *ctx) continue; } - if ((err = leds_clear_all(state->leds))) { - LOG_ERROR("leds_set_all"); - return err; - } + if (state->test) { + if ((err = clear_leds_test(state))) { + LOG_ERROR("clear_leds_test"); + return err; + } + } else { + // XXX: unsafe + if ((err = leds_clear_all(state->leds))) { + LOG_ERROR("leds_set_all"); + return err; + } - if ((err = update_leds(state, USER_ACTIVITY_LEDS_CMD))) { - LOG_ERROR("update_leds"); - return err; + if ((err = update_leds(state, USER_ACTIVITY_LEDS_CMD))) { + LOG_ERROR("update_leds"); + return err; + } } } @@ -263,37 +271,36 @@ int leds_cmd_set(int argc, char **argv, void *ctx) int leds_cmd_test(int argc, char **argv, void *ctx) { - const struct leds_config *config; - struct leds_state *state; - unsigned leds_id; const char *mode_arg = NULL; enum leds_test_mode mode = 0; int err; - if ((err = cmd_arg_uint(argc, argv, 1, &leds_id))) - return err; - - if (argc > 2 && (err = cmd_arg_str(argc, argv, 2, &mode_arg))) - return err; + if (argc == 1){ - if ((err = lookup_leds(leds_id, &config, &state))) { + } else if ((err = cmd_arg_str(argc, argv, 1, &mode_arg))) { return err; - } - - if (mode_arg && (mode = config_enum_to_value(leds_test_mode_enum, mode_arg)) < 0) { + } else if ((mode = config_enum_to_value(leds_test_mode_enum, mode_arg)) < 0) { LOG_ERROR("invalid mode=%s", mode_arg); return -1; } - if (mode) { - if ((err = test_leds_mode(state, mode))) { - LOG_ERROR("test_leds_mode"); - return err; + for (int i = 0; i < LEDS_COUNT; i++) { + struct leds_state *state = &leds_states[i]; + + if (!state->leds || !state->test) { + continue; } - } else { - if ((err = test_leds(state))) { - LOG_ERROR("test_leds"); - return err; + + if (mode) { + if ((err = set_leds_test(state, mode, false))) { + LOG_ERROR("set_leds_test"); + return err; + } + } else { + if ((err = step_leds_test(state))) { + LOG_ERROR("step_leds_test"); + return err; + } } } @@ -429,11 +436,11 @@ int leds_cmd_stats(int argc, char **argv, void *ctx) const struct cmd leds_commands[] = { { "info", leds_cmd_info, .describe = "Show LED info" }, { "status", leds_cmd_status, .describe = "Show LED status" }, - { "clear", leds_cmd_clear, .describe = "Clear all output values" }, + { "clear", leds_cmd_clear, .describe = "Clear test patterns" }, { "static", leds_cmd_static, .usage = "RGB [A]", .describe = "Set static LEDs color" }, { "set", leds_cmd_set, .usage = "LEDS-ID LED-INDEX RGB [A]", .describe = "Set one output pixel to value" }, { "update", leds_cmd_update, .usage = "[LEDS-ID]", .describe = "Refresh one or all LED outputs" }, - { "test", leds_cmd_test, .usage = "LEDS-ID [MODE]", .describe = "Output test patterns" }, + { "test", leds_cmd_test, .usage = "[MODE]", .describe = "Output test patterns" }, { "stats", leds_cmd_stats, .usage = "[reset]", .describe = "Show/reset LED stats" }, { } }; diff --git a/main/leds_http_post.c b/main/leds_http_post.c index 26a7a5ef..abb348af 100644 --- a/main/leds_http_post.c +++ b/main/leds_http_post.c @@ -1,6 +1,7 @@ #include "leds.h" #include "leds_config.h" #include "leds_state.h" +#include "leds_test.h" #include "http_routes.h" #include "http_handlers.h" @@ -146,15 +147,22 @@ int leds_api_post(struct http_request *request, struct http_response *response, /* POST /api/leds/test */ struct leds_api_test_params { - int index; + struct leds_state *state; enum leds_test_mode mode; + bool auto_mode; }; int leds_api_test_params_set(struct leds_api_test_params *params, const char *key, const char *value) { if (strcmp(key, "index") == 0) { - if (sscanf(value, "%d", ¶ms->index) <= 0) { + unsigned index; + + if (sscanf(value, "%d", &index) <= 0) { return HTTP_UNPROCESSABLE_ENTITY; + } else if (index <= 0 || index > LEDS_COUNT) { + return HTTP_UNPROCESSABLE_ENTITY; + } else { + params->state = &leds_states[index - 1]; } } else if (strcmp(key, "mode") == 0) { int mode; @@ -164,6 +172,14 @@ int leds_api_test_params_set(struct leds_api_test_params *params, const char *ke } else { params->mode = mode; } + } else if (strcmp(key, "auto") == 0) { + if (strcmp(value, "true") == 0) { + params->auto_mode = true; + } else if (strcmp(value, "false") == 0) { + params->auto_mode = false; + } else { + return HTTP_UNPROCESSABLE_ENTITY; + } } else { return HTTP_UNPROCESSABLE_ENTITY; } @@ -220,22 +236,15 @@ int leds_api_test_post(struct http_request *request, struct http_response *respo return HTTP_UNSUPPORTED_MEDIA_TYPE; } - // decode - struct leds_state *state; - - if (params.index <= 0 || params.index > LEDS_COUNT) { - LOG_WARN("invalid index=%d", params.index); + // TODO: clear? + if (!params.state) { + LOG_WARN("missing index"); return HTTP_UNPROCESSABLE_ENTITY; - } else { - state = &leds_states[params.index - 1]; - } - - // XXX: may block for some time - if ((err = test_leds_mode(state, params.mode)) < 0) { - LOG_ERROR("test_leds"); + } else if ((err = set_leds_test(state, params.mode, params.auto_mode)) < 0) { + LOG_ERROR("set_leds_test"); return HTTP_INTERNAL_SERVER_ERROR; } else if (err) { - LOG_WARN("test_leds"); + LOG_WARN("set_leds_test"); return HTTP_CONFLICT; } diff --git a/main/leds_state.h b/main/leds_state.h index f8c9d050..b1ffc77c 100644 --- a/main/leds_state.h +++ b/main/leds_state.h @@ -77,6 +77,3 @@ int update_leds(struct leds_state *state, enum user_activity leds_activity); * Variant of update_leds() that turns off all LEDs, and does NOT flash the status leds. */ int clear_leds(struct leds_state *state); - -int test_leds_mode(struct leds_state *state, enum leds_test_mode mode); -int test_leds(struct leds_state *state); diff --git a/main/leds_test.c b/main/leds_test.c index 57b10732..29e398c2 100644 --- a/main/leds_test.c +++ b/main/leds_test.c @@ -87,6 +87,64 @@ void reset_leds_test() } } +int set_leds_test(struct leds_state *state, enum leds_test_mode mode, bool auto_mode) +{ + if (!state->test) { + LOG_ERROR("disabled"); + return -1; + } + + state->test->mode = mode; + state->test->frame = 0; + state->test->auto_mode = auto_mode; + + LOG_DEBUG("mode=%d auto_mode=%d", state->test->mode, state->test->auto_mode); + + notify_leds_task(state, 1 << LEDS_EVENT_TEST_BIT); + + return 0; +} + +int step_leds_test(struct leds_state *state) +{ + if (!state->test) { + LOG_ERROR("disabled"); + return -1; + } + + if (state->test->mode >= TEST_MODE_BLACK) { + // cycle + state->test->mode = 0; + } + + state->test->mode++; + state->test->frame = 0; + + LOG_DEBUG("mode=%d auto_mode=%d", state->test->mode, state->test->auto_mode); + + notify_leds_task(state, 1 << LEDS_EVENT_TEST_BIT); + + return 0; +} + +int clear_leds_test(struct leds_state *state) +{ + if (!state->test) { + LOG_ERROR("disabled"); + return -1; + } + + // will be reset to 0 by leds_test_update() + state->test->mode = TEST_MODE_BLACK; + state->test->auto_mode = false; + + LOG_DEBUG("mode=%d auto_mode=%d", state->test->mode, state->test->auto_mode); + + notify_leds_task(state, 1 << LEDS_EVENT_TEST_BIT); + + return 0; +} + static void leds_test_reset(struct leds_state *state) { state->test->mode = 0; diff --git a/main/leds_test.h b/main/leds_test.h index af839b40..eae57ce9 100644 --- a/main/leds_test.h +++ b/main/leds_test.h @@ -27,6 +27,11 @@ void auto_leds_test(); /* Clear test modes */ void reset_leds_test(); +/* Set test mode */ +int set_leds_test(struct leds_state *state, enum leds_test_mode mode, bool auto_mode); +int step_leds_test(struct leds_state *state); +int clear_leds_test(struct leds_state *state); + /* Clear test mode */ void leds_test_clear(struct leds_state *state); From b1f9daf487d3ccf6aeb5040d1201408479dff588 Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Tue, 9 Jun 2026 22:48:01 +0300 Subject: [PATCH 09/55] http config api color type --- main/config_http_get.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/main/config_http_get.c b/main/config_http_get.c index 8ccb8b0a..8d673725 100644 --- a/main/config_http_get.c +++ b/main/config_http_get.c @@ -30,6 +30,13 @@ static int config_api_write_file_value(struct json_writer *w, const struct confi } } +static int config_api_write_color_value(struct json_writer *w, const struct configtab *tab, unsigned index) +{ + struct config_color c = tab->color_type.value[index]; + + return json_write_raw(w, "\"%02x%02x%02x%02x\"", c.r, c.g, c.b, c.a); +} + static int config_api_write_configtab_value(struct json_writer *w, const struct configtab *tab, unsigned index) { @@ -49,6 +56,9 @@ static int config_api_write_configtab_value(struct json_writer *w, const struct case CONFIG_TYPE_FILE: return config_api_write_file_value(w, tab, index); + case CONFIG_TYPE_COLOR: + return config_api_write_color_value(w, tab, index); + default: LOG_ERROR("unknown type=%d", tab->type); return -1; @@ -166,6 +176,13 @@ static int config_api_write_configtab_members_file(struct json_writer *w, const ); } +static int config_api_write_configtab_members_color(struct json_writer *w, const struct configtab *tab) +{ + return ( + config_api_write_configtab_type_members(w, tab, "color") + ); +} + static int config_api_write_configtab_members(struct json_writer *w, const struct config_path *path) { const struct configtab *tab = path->tab; @@ -181,6 +198,8 @@ static int config_api_write_configtab_members(struct json_writer *w, const struc return config_api_write_configtab_members_enum(w, tab); case CONFIG_TYPE_FILE: return config_api_write_configtab_members_file(w, tab); + case CONFIG_TYPE_COLOR: + return config_api_write_configtab_members_color(w, tab); default: return 0; } From 5bd7d99eab9283f1be40b4ce83efc230fceaadac Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Tue, 9 Jun 2026 23:07:22 +0300 Subject: [PATCH 10/55] web: config type color --- web/src/components/ConfigView.vue | 15 ++++++++++++ web/src/services/config.service.js | 38 +++++++++++++++++++++--------- 2 files changed, 42 insertions(+), 11 deletions(-) diff --git a/web/src/components/ConfigView.vue b/web/src/components/ConfigView.vue index 10491d85..0e78c6a4 100644 --- a/web/src/components/ConfigView.vue +++ b/web/src/components/ConfigView.vue @@ -148,6 +148,15 @@ + + + @@ -266,9 +275,15 @@ export default { }, syncInput(event) { let name = event.target.name; + let type = event.target.type; let value = event.target.value; let index = event.target.dataset.index; + if (type == "color") { + // strip # prefix + value = value.substring(1); + } + // TODO: null vs ""? // TODO: 0 vs "0"? this.$set(this.configValues[name], index, value); diff --git a/web/src/services/config.service.js b/web/src/services/config.service.js index c796745c..dec6ac89 100644 --- a/web/src/services/config.service.js +++ b/web/src/services/config.service.js @@ -1,3 +1,26 @@ + +// normalize config API value from HTML formdata based on configtab type +function normalizeFormValue(type, value) { + if (type == 'color') { + return value.substring(1); + } else if (type == 'bool' && !value) { + // explicitly set unchecked bool values to false + return 'false'; + } else { + return value; + } +} + +// normalize config API value from HTML formdata based on configtab type +function normalizeFormValues(type, values) { + if (type == 'color') { + return values.map((v) => normalizeFormValue(type, v)); + } else { + // exclude empty values + return values.filter((v) => v && v != ""); + } +} + export default class ConfigService { constructor(apiService) { this.apiService = apiService; @@ -9,6 +32,7 @@ export default class ConfigService { return response.data } + async post(config, formdata) { let data = new URLSearchParams(); @@ -23,19 +47,11 @@ export default class ConfigService { // clear to override existing values data.append(name, ""); - for (let value of formdata.getAll(name)) { - // exclude empty values - if (value && value != "") { - data.append(name, value); - } + for (let value of normalizeFormValues(tab.type, formdata.getAll(name))) { + data.append(name, value); } } else { - let value = formdata.get(name); - - // explicitly set unchecked bool values to false - if (tab.type == "bool" && !value) { - value = "false"; - } + let value = normalizeFormValue(tab.type, formdata.get(name)); data.append(name, value); } From 0670aa4f0bbc8f3468ee0b8f9f2e83a20d8cf2ad Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Tue, 9 Jun 2026 23:20:03 +0300 Subject: [PATCH 11/55] main: interpret static config color transparency for RGBW LEDs --- main/leds_static.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main/leds_static.c b/main/leds_static.c index cc156ef6..8ea3f07d 100644 --- a/main/leds_static.c +++ b/main/leds_static.c @@ -30,8 +30,8 @@ static struct leds_color config_leds_color(struct config_color config_color, enu .g = config_color.g, .b = config_color.b, - // XXX: totally wrong - .white = config_color.a, + // TODO: fully transparent = only white, 50% = colors + 100% white? + .white = (255 - config_color.a), }; default: From 9fa0314b8c6f551a1517bf79d77a66f971d957d0 Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Tue, 9 Jun 2026 23:21:06 +0300 Subject: [PATCH 12/55] config: color compat with RGB, RRGGBB, RRGGBBAA values --- components/config/cmd.c | 2 +- components/config/set.c | 34 ++++++++++++++++++++++++++++++++-- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/components/config/cmd.c b/components/config/cmd.c index b3375fc5..3f8c3859 100644 --- a/components/config/cmd.c +++ b/components/config/cmd.c @@ -166,7 +166,7 @@ static void print_configtab(const struct config_path path) break; case CONFIG_TYPE_COLOR: - printf(""); + printf(""); break; default: diff --git a/components/config/set.c b/components/config/set.c index 99ad6830..b3bbea97 100644 --- a/components/config/set.c +++ b/components/config/set.c @@ -169,8 +169,38 @@ static int config_set_color(const struct config_path path, unsigned index, const const struct configtab *tab = path.tab; int r = 0, g = 0, b = 0, a = 0; - if (sscanf(str, "%2x%2x%2x%2x", &r, &g, &b, &a) < 4) { - return -1; + switch (strlen(str)) { + case 3: + a = 0xff; + + if (sscanf(str, "%1x%1x%1x", &r, &g, &b) < 3) { + return -1; + } + + r = (r << 4) | r; + g = (g << 4) | g; + b = (b << 4) | b; + + break; + + case 6: + a = 0xff; + + if (sscanf(str, "%2x%2x%2x", &r, &g, &b) < 3) { + return -1; + } + + break; + + case 8: + if (sscanf(str, "%2x%2x%2x%2x", &r, &g, &b, &a) < 4) { + return -1; + } + + break; + + default: + return -1; } tab->color_type.value[index] = (struct config_color) { r, g, b, a }; From 8212ccda7ecd9e56caaf9f9ed060d87ee033a04a Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Sat, 13 Jun 2026 12:37:21 +0300 Subject: [PATCH 13/55] user_buttons: simplify test mode state --- main/user_buttons.c | 20 ++++---------------- main/user_events.c | 26 ++++++++++++++++++++------ 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/main/user_buttons.c b/main/user_buttons.c index ae9a300c..e659417d 100644 --- a/main/user_buttons.c +++ b/main/user_buttons.c @@ -49,12 +49,8 @@ void user_config_release() } /* test */ -static bool user_test_held = false; - void user_test_press() { - user_test_held = false; - LOG_INFO("trigger test mode"); trigger_leds_test(); @@ -62,22 +58,14 @@ void user_test_press() void user_test_hold() { - if (!user_test_held) { - LOG_INFO("auto test mode"); - - auto_leds_test(); - } + LOG_INFO("auto test mode"); - user_test_held = true; + auto_leds_test(); } void user_test_release() { - if (user_test_held) { - LOG_INFO("reset test mode"); - - reset_leds_test(); - } + LOG_INFO("reset test mode"); - user_test_held = false; + reset_leds_test(); } diff --git a/main/user_events.c b/main/user_events.c index 969c8d45..81cb1fba 100644 --- a/main/user_events.c +++ b/main/user_events.c @@ -10,7 +10,7 @@ // when held for >5s #define USER_LEDS_CONFIG_HOLD_THRESHOLD (5000 / portTICK_RATE_MS) -// when held for >1s +// when held for >500ms #define USER_LEDS_TEST_HOLD_THRESHOLD (500 / portTICK_RATE_MS) xTaskHandle user_events_task; @@ -73,32 +73,46 @@ void on_user_config_input(struct user_leds_input input) void on_user_test_input(struct user_leds_input input) { + static bool hold = false; + switch (input.event) { case USER_LEDS_INPUT_PRESS: LOG_INFO("press"); + user_test_press(); + hold = false; + break; case USER_LEDS_INPUT_HOLD: - if (input.hold > USER_LEDS_TEST_HOLD_THRESHOLD) { + if (hold) { + LOG_INFO("hold (repeat)"); + + } else if (input.hold > USER_LEDS_TEST_HOLD_THRESHOLD) { LOG_INFO("hold"); + user_test_hold(); + + hold = true; + } else { - LOG_INFO("wait"); + LOG_INFO("hold (wait)"); } break; case USER_LEDS_INPUT_RELEASE: - if (input.hold > USER_LEDS_TEST_HOLD_THRESHOLD) { + if (hold) { LOG_INFO("release (hold)"); + + user_test_release(); + + hold = false; } else { LOG_INFO("release (press)"); } - user_test_release(); - break; } } From 778def9fecb07eb3cedc434ce3be3ab9b392213a Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Sat, 13 Jun 2026 12:47:11 +0300 Subject: [PATCH 14/55] leds test: fix frame skips on premature update --- main/leds_test.c | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/main/leds_test.c b/main/leds_test.c index 29e398c2..078f5a66 100644 --- a/main/leds_test.c +++ b/main/leds_test.c @@ -176,7 +176,6 @@ TickType_t leds_test_wait(struct leds_state *state) return state->test->frame_tick; } - bool leds_test_active(struct leds_state *state, EventBits_t bits) { if (state->test->mode) { @@ -192,16 +191,21 @@ bool leds_test_active(struct leds_state *state, EventBits_t bits) int leds_test_update(struct leds_state *state, EventBits_t bits) { + TickType_t tick = xTaskGetTickCount(); int frame_ticks; if (state->test->frame == 0) { - state->test->frame_tick = xTaskGetTickCount(); + state->test->frame_tick = tick; - LOG_INFO("test mode=%d", state->test->mode); - } else if (state->test->frame_tick) { + LOG_DEBUG("test mode=%d auto=%d frame=%d frame_tick=%d", state->test->mode, state->test->auto_mode, state->test->frame, state->test->frame_tick); - } else { - LOG_DEBUG("mode=%d auto=%d frame=%d frame_tick=%d -> idle", state->test->mode, state->test->auto_mode, state->test->frame, state->test->frame_tick); + } else if (!state->test->frame_tick) { + LOG_DEBUG("idle mode=%d auto=%d frame=%d frame_tick=%d", state->test->mode, state->test->auto_mode, state->test->frame, state->test->frame_tick); + + return 0; + + } else if (state->test->frame_tick < tick) { + LOG_DEBUG("wait mode=%d auto=%d frame=%d frame_tick=%d", state->test->mode, state->test->auto_mode, state->test->frame, state->test->frame_tick); return 0; } @@ -209,23 +213,27 @@ int leds_test_update(struct leds_state *state, EventBits_t bits) if ((frame_ticks = leds_set_test(state->leds, state->test->mode, state->test->frame)) < 0) { LOG_ERROR("leds_set_test"); return -1; + } else if (frame_ticks) { // tick for next frame LOG_DEBUG("mode=%d auto=%d frame=%d frame_tick=%d -> wait frame_ticks=%d", state->test->mode, state->test->auto_mode, state->test->frame, state->test->frame_tick, frame_ticks); state->test->frame++; state->test->frame_tick += frame_ticks; + } else if (state->test->mode == TEST_MODE_BLACK) { // end LOG_DEBUG("mode=%d auto=%d frame=%d frame_tick=%d -> reset", state->test->mode, state->test->auto_mode, state->test->frame, state->test->frame_tick); leds_test_reset(state); + } else if (state->test->auto_mode) { // advance to next mode - LOG_DEBUG("mode=%d auto=%d frame=%d frame_tick=%d -> next mode", state->test->mode, state->test->auto_mode, state->test->frame, state->test->frame_tick); + LOG_DEBUG("mode=%d auto=%d frame=%d frame_tick=%d -> next", state->test->mode, state->test->auto_mode, state->test->frame, state->test->frame_tick); state->test->mode++; state->test->frame = 0; + } else { // pause LOG_DEBUG("mode=%d auto=%d frame=%d frame_tick=%d -> pause", state->test->mode, state->test->auto_mode, state->test->frame, state->test->frame_tick); From faa48d93988fdf554f5c73815c00d8230d35ad0c Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Sat, 13 Jun 2026 12:52:46 +0300 Subject: [PATCH 15/55] leds test: refactor --- main/leds_cmd.c | 4 ++-- main/leds_test.c | 56 +++++++++++++++++++++++++----------------------- main/leds_test.h | 3 ++- 3 files changed, 33 insertions(+), 30 deletions(-) diff --git a/main/leds_cmd.c b/main/leds_cmd.c index afb9409b..181587fc 100644 --- a/main/leds_cmd.c +++ b/main/leds_cmd.c @@ -297,8 +297,8 @@ int leds_cmd_test(int argc, char **argv, void *ctx) return err; } } else { - if ((err = step_leds_test(state))) { - LOG_ERROR("step_leds_test"); + if ((err = set_leds_test_next(state))) { + LOG_ERROR("set_leds_test_next"); return err; } } diff --git a/main/leds_test.c b/main/leds_test.c index 078f5a66..bdcb9129 100644 --- a/main/leds_test.c +++ b/main/leds_test.c @@ -33,17 +33,9 @@ void trigger_leds_test() continue; } - if (state->test->mode >= TEST_MODE_BLACK) { - // cycle - state->test->mode = 0; + if (set_leds_test_next(state)) { + LOG_ERROR("set_leds_test_next"); } - - state->test->mode++; - state->test->frame = 0; - - LOG_DEBUG("mode=%d auto_mode=%d", state->test->mode, state->test->auto_mode); - - notify_leds_task(state, 1 << LEDS_EVENT_TEST_BIT); } } @@ -56,15 +48,9 @@ void auto_leds_test() continue; } - if (!state->test->mode) { - state->test->mode++; + if (set_leds_test_auto(state)) { + LOG_ERROR("set_leds_test_auto"); } - - state->test->auto_mode = true; - - LOG_DEBUG("mode=%d auto_mode=%d", state->test->mode, state->test->auto_mode); - - notify_leds_task(state, 1 << LEDS_EVENT_TEST_BIT); } } @@ -77,13 +63,9 @@ void reset_leds_test() continue; } - // will be reset to 0 by leds_test_update() - state->test->mode = TEST_MODE_BLACK; - state->test->auto_mode = false; - - LOG_DEBUG("mode=%d auto_mode=%d", state->test->mode, state->test->auto_mode); - - notify_leds_task(state, 1 << LEDS_EVENT_TEST_BIT); + if (clear_leds_test(state)) { + LOG_ERROR("clear_leds_test"); + } } } @@ -105,7 +87,27 @@ int set_leds_test(struct leds_state *state, enum leds_test_mode mode, bool auto_ return 0; } -int step_leds_test(struct leds_state *state) +int set_leds_test_auto(struct leds_state *state) +{ + if (!state->test) { + LOG_ERROR("disabled"); + return -1; + } + + state->test->auto_mode = true; + + if (!state->test->mode) { + state->test->mode++; + } + + LOG_DEBUG("mode=%d auto_mode=%d", state->test->mode, state->test->auto_mode); + + notify_leds_task(state, 1 << LEDS_EVENT_TEST_BIT); + + return 0; +} + +int set_leds_test_next(struct leds_state *state) { if (!state->test) { LOG_ERROR("disabled"); @@ -233,7 +235,7 @@ int leds_test_update(struct leds_state *state, EventBits_t bits) state->test->mode++; state->test->frame = 0; - + } else { // pause LOG_DEBUG("mode=%d auto=%d frame=%d frame_tick=%d -> pause", state->test->mode, state->test->auto_mode, state->test->frame, state->test->frame_tick); diff --git a/main/leds_test.h b/main/leds_test.h index eae57ce9..0583e299 100644 --- a/main/leds_test.h +++ b/main/leds_test.h @@ -29,7 +29,8 @@ void reset_leds_test(); /* Set test mode */ int set_leds_test(struct leds_state *state, enum leds_test_mode mode, bool auto_mode); -int step_leds_test(struct leds_state *state); +int set_leds_test_auto(struct leds_state *state); +int set_leds_test_next(struct leds_state *state); int clear_leds_test(struct leds_state *state); /* Clear test mode */ From 53e648392ad5119a213664e3c8def26c636d1cb3 Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Sat, 13 Jun 2026 13:08:47 +0300 Subject: [PATCH 16/55] leds: remove TEST_MODE_BLACK, clear on TEST_MODE_NONE --- components/leds/include/leds.h | 3 +-- components/leds/test.c | 3 --- main/leds_config.c | 1 - main/leds_test.c | 33 ++++++++++++++++++++------------- 4 files changed, 21 insertions(+), 19 deletions(-) diff --git a/components/leds/include/leds.h b/components/leds/include/leds.h index 72bf06b5..590adf82 100644 --- a/components/leds/include/leds.h +++ b/components/leds/include/leds.h @@ -379,10 +379,9 @@ enum leds_test_mode { TEST_MODE_RGB_BLACK, TEST_MODE_RAINBOW, - TEST_MODE_BLACK, }; -#define TEST_MODE_COUNT (TEST_MODE_BLACK + 1) +#define TEST_MODE_COUNT (TEST_MODE_RAINBOW) int leds_new(struct leds **ledsp, const struct leds_options *options); diff --git a/components/leds/test.c b/components/leds/test.c index 60d521c3..624308a5 100644 --- a/components/leds/test.c +++ b/components/leds/test.c @@ -260,9 +260,6 @@ int leds_set_test(struct leds *leds, enum leds_test_mode mode, unsigned frame) case TEST_MODE_RAINBOW: return leds_test_rainbow_frame(leds, frame); - case TEST_MODE_BLACK: - return leds_test_black_frame(leds, frame); - default: LOG_ERROR("unknown mode=%d", mode); return -1; diff --git a/main/leds_config.c b/main/leds_config.c index 02353d47..d0150858 100644 --- a/main/leds_config.c +++ b/main/leds_config.c @@ -131,7 +131,6 @@ const struct config_enum leds_test_mode_enum[] = { { "RGBW_RGB", .value = TEST_MODE_RGBW_RGB }, { "RGB_BLACK", .value = TEST_MODE_RGB_BLACK }, { "RAINBOW", .value = TEST_MODE_RAINBOW }, - { "BLACK", .value = TEST_MODE_BLACK }, {} }; diff --git a/main/leds_test.c b/main/leds_test.c index bdcb9129..3c06e595 100644 --- a/main/leds_test.c +++ b/main/leds_test.c @@ -114,12 +114,14 @@ int set_leds_test_next(struct leds_state *state) return -1; } - if (state->test->mode >= TEST_MODE_BLACK) { + if (state->test->mode >= TEST_MODE_COUNT) { // cycle state->test->mode = 0; + } else { + // next; + state->test->mode++; } - state->test->mode++; state->test->frame = 0; LOG_DEBUG("mode=%d auto_mode=%d", state->test->mode, state->test->auto_mode); @@ -137,7 +139,7 @@ int clear_leds_test(struct leds_state *state) } // will be reset to 0 by leds_test_update() - state->test->mode = TEST_MODE_BLACK; + state->test->mode = TEST_MODE_NONE; state->test->auto_mode = false; LOG_DEBUG("mode=%d auto_mode=%d", state->test->mode, state->test->auto_mode); @@ -196,10 +198,21 @@ int leds_test_update(struct leds_state *state, EventBits_t bits) TickType_t tick = xTaskGetTickCount(); int frame_ticks; - if (state->test->frame == 0) { + if (!state->test->mode) { + LOG_DEBUG("clear mode=%d auto=%d frame=%d frame_tick=%d", state->test->mode, state->test->auto_mode, state->test->frame, state->test->frame_tick); + + if (leds_clear_all(state->leds)) { + LOG_WARN("leds_clear_all"); + } + + leds_test_reset(state); + + return 1; + + } else if (state->test->frame == 0) { state->test->frame_tick = tick; - LOG_DEBUG("test mode=%d auto=%d frame=%d frame_tick=%d", state->test->mode, state->test->auto_mode, state->test->frame, state->test->frame_tick); + LOG_DEBUG("start mode=%d auto=%d frame=%d frame_tick=%d", state->test->mode, state->test->auto_mode, state->test->frame, state->test->frame_tick); } else if (!state->test->frame_tick) { LOG_DEBUG("idle mode=%d auto=%d frame=%d frame_tick=%d", state->test->mode, state->test->auto_mode, state->test->frame, state->test->frame_tick); @@ -218,20 +231,14 @@ int leds_test_update(struct leds_state *state, EventBits_t bits) } else if (frame_ticks) { // tick for next frame - LOG_DEBUG("mode=%d auto=%d frame=%d frame_tick=%d -> wait frame_ticks=%d", state->test->mode, state->test->auto_mode, state->test->frame, state->test->frame_tick, frame_ticks); + LOG_DEBUG("mode=%d auto=%d frame=%d frame_tick=%d -> next frame_ticks=%d", state->test->mode, state->test->auto_mode, state->test->frame, state->test->frame_tick, frame_ticks); state->test->frame++; state->test->frame_tick += frame_ticks; - } else if (state->test->mode == TEST_MODE_BLACK) { - // end - LOG_DEBUG("mode=%d auto=%d frame=%d frame_tick=%d -> reset", state->test->mode, state->test->auto_mode, state->test->frame, state->test->frame_tick); - - leds_test_reset(state); - } else if (state->test->auto_mode) { // advance to next mode - LOG_DEBUG("mode=%d auto=%d frame=%d frame_tick=%d -> next", state->test->mode, state->test->auto_mode, state->test->frame, state->test->frame_tick); + LOG_DEBUG("mode=%d auto=%d frame=%d frame_tick=%d -> next mode", state->test->mode, state->test->auto_mode, state->test->frame, state->test->frame_tick); state->test->mode++; state->test->frame = 0; From 59de03dd69853b347aacb5391ffd2ab61014ca68 Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Sat, 13 Jun 2026 13:10:12 +0300 Subject: [PATCH 17/55] fixup step test mode --- main/leds_test.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/leds_test.c b/main/leds_test.c index 3c06e595..a73a4a54 100644 --- a/main/leds_test.c +++ b/main/leds_test.c @@ -116,7 +116,7 @@ int set_leds_test_next(struct leds_state *state) if (state->test->mode >= TEST_MODE_COUNT) { // cycle - state->test->mode = 0; + state->test->mode = 1; } else { // next; state->test->mode++; From 03a0eb58057fccd046de1810a79e53cf16b79903 Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Sat, 13 Jun 2026 13:15:21 +0300 Subject: [PATCH 18/55] leds test: fix auto mode on or after last frame --- main/leds_test.c | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/main/leds_test.c b/main/leds_test.c index a73a4a54..02e2835d 100644 --- a/main/leds_test.c +++ b/main/leds_test.c @@ -69,6 +69,20 @@ void reset_leds_test() } } +static void leds_test_next_mode(struct leds_state *state) +{ + if (state->test->mode >= TEST_MODE_COUNT) { + // cycle + state->test->mode = 1; + } else { + // next; + state->test->mode++; + } + + state->test->frame = 0; + state->test->frame_tick = 0; +} + int set_leds_test(struct leds_state *state, enum leds_test_mode mode, bool auto_mode) { if (!state->test) { @@ -96,8 +110,8 @@ int set_leds_test_auto(struct leds_state *state) state->test->auto_mode = true; - if (!state->test->mode) { - state->test->mode++; + if (!state->test->mode || !state->test->frame_tick) { + leds_test_next_mode(state); } LOG_DEBUG("mode=%d auto_mode=%d", state->test->mode, state->test->auto_mode); @@ -114,15 +128,7 @@ int set_leds_test_next(struct leds_state *state) return -1; } - if (state->test->mode >= TEST_MODE_COUNT) { - // cycle - state->test->mode = 1; - } else { - // next; - state->test->mode++; - } - - state->test->frame = 0; + leds_test_next_mode(state); LOG_DEBUG("mode=%d auto_mode=%d", state->test->mode, state->test->auto_mode); @@ -240,12 +246,11 @@ int leds_test_update(struct leds_state *state, EventBits_t bits) // advance to next mode LOG_DEBUG("mode=%d auto=%d frame=%d frame_tick=%d -> next mode", state->test->mode, state->test->auto_mode, state->test->frame, state->test->frame_tick); - state->test->mode++; - state->test->frame = 0; + leds_test_next_mode(state); } else { - // pause - LOG_DEBUG("mode=%d auto=%d frame=%d frame_tick=%d -> pause", state->test->mode, state->test->auto_mode, state->test->frame, state->test->frame_tick); + // stop + LOG_DEBUG("mode=%d auto=%d frame=%d frame_tick=%d -> stop", state->test->mode, state->test->auto_mode, state->test->frame, state->test->frame_tick); state->test->frame_tick = 0; } From 1126d016f53d8cfed27593d4538d16760bf9fcba Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Sat, 13 Jun 2026 13:52:43 +0300 Subject: [PATCH 19/55] main: refactor leds artnet timeout --- main/leds_artnet.c | 63 ++++++++++++++++++++++------------------------ 1 file changed, 30 insertions(+), 33 deletions(-) diff --git a/main/leds_artnet.c b/main/leds_artnet.c index 2f7eecaf..d75982df 100644 --- a/main/leds_artnet.c +++ b/main/leds_artnet.c @@ -163,11 +163,27 @@ static void leds_artnet_timeout_reset(struct leds_state *state) } } +static bool leds_artnet_timeout_expired(struct leds_state *state) +{ + return state->artnet->timeout_tick && xTaskGetTickCount() >= state->artnet->timeout_tick; +} + void leds_artnet_timeout_clear(struct leds_state *state) { state->artnet->timeout_tick = 0; } +static void leds_artnet_timeout(struct leds_state *state) +{ + LOG_INFO("leds%d: timeout", state->index + 1); + + // TODO: flash user alert? + + if (leds_clear_all(state->leds)) { + LOG_WARN("leds_clear_all"); + } +} + TickType_t leds_artnet_wait(struct leds_state *state) { if (state->artnet->sync_missed) { @@ -228,25 +244,6 @@ static int leds_artnet_set(struct leds_state *state, unsigned index, struct artn return 0; } -static int leds_artnet_timeout(struct leds_state *state) -{ - struct leds_stats *stats = &leds_stats[state->index]; - int err; - - LOG_INFO("leds%d: timeout", state->index + 1); - - stats_counter_increment(&stats->artnet_timeout); - - // TODO: flash user alert? - - if ((err = leds_clear_all(state->leds))) { - LOG_WARN("leds_clear_all"); - return err; - } - - return 0; -} - bool leds_artnet_active(struct leds_state *state, EventBits_t event_bits) { if (event_bits & (1 << LEDS_EVENT_ARTNET_DMX_BIT)) { @@ -341,6 +338,7 @@ int leds_artnet_update(struct leds_state *state, EventBits_t event_bits) } } + // update? if (sync) { // hard art-net sync stats_counter_increment(&stats->artnet_sync); @@ -351,26 +349,24 @@ int leds_artnet_update(struct leds_state *state, EventBits_t event_bits) update = true; } - // timeouts + if (update) { + leds_artnet_sync_reset(state); + } + + // timeout? if (dmx || sync) { state->artnet->dmx_tick = xTaskGetTickCount(); - leds_artnet_timeout_reset(state); - } else if (state->artnet->timeout_tick) { - if (xTaskGetTickCount() >= state->artnet->timeout_tick) { - timeout = true; - if (leds_artnet_timeout(state)) { - LOG_WARN("leds_artnet_timeout"); - } + leds_artnet_timeout_reset(state); - leds_artnet_timeout_clear(state); - } - } + } else if (leds_artnet_timeout_expired(state)) { + timeout = true; - if (update) { - leds_artnet_sync_reset(state); + leds_artnet_timeout(state); + leds_artnet_timeout_clear(state); } - + + // return if (timeout) { return LEDS_ARTNET_UPDATE_TIMEOUT; } else if (update) { @@ -420,6 +416,7 @@ int init_leds_artnet(struct leds_state *state, int index, const struct leds_conf return -1; } + // activate timeout, delayed output update at boot leds_artnet_timeout_reset(state); return 0; From 0fbe82e3db3dc8ce456a6d4682fc0cb1fa63c52e Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Sat, 13 Jun 2026 14:20:51 +0300 Subject: [PATCH 20/55] leds: track task update state to make artnet -> leds clear apply to both test and static output --- main/leds_artnet.c | 19 +++++++++++------- main/leds_state.h | 9 +++++++++ main/leds_task.c | 48 +++++++++++++++++++++++++++------------------- main/leds_test.c | 18 ++--------------- 4 files changed, 51 insertions(+), 43 deletions(-) diff --git a/main/leds_artnet.c b/main/leds_artnet.c index d75982df..89c8c88a 100644 --- a/main/leds_artnet.c +++ b/main/leds_artnet.c @@ -286,13 +286,6 @@ int leds_artnet_update(struct leds_state *state, EventBits_t event_bits) bool update = false; bool timeout = false; - if (state->test) { - if (dmx || sync) { - // clear any test mode output - leds_test_clear(state); - } - } - if (state->artnet->sync_missed) { LOG_DEBUG("event_bits=%08x + sync_missed=%08x", event_bits, state->artnet->sync_missed); @@ -305,6 +298,18 @@ int leds_artnet_update(struct leds_state *state, EventBits_t event_bits) // wait until either artnet-sync or (non-sync) dmx to not trigger soft-sync on partial data in artnet sync mode if (dmx || sync || miss) { + if (state->test) { + // disable test mode + leds_test_clear(state); + } + + if (state->update_state != LEDS_UPDATE_ARTNET) { + // incoming artnet data overrides any other output, even with missing universes + if (leds_clear_all(state->leds)) { + LOG_ERROR("leds_clear_all"); + } + } + // set output from artnet universe for (unsigned index = 0; index < state->artnet->universe_count; index++) { if (!(data_bits & (1 << index))) { diff --git a/main/leds_state.h b/main/leds_state.h index b1ffc77c..3d72877a 100644 --- a/main/leds_state.h +++ b/main/leds_state.h @@ -15,12 +15,21 @@ struct leds_test_state; struct leds_artnet_state; struct leds_sequence_state; +enum leds_update_state { + LEDS_UPDATE_NONE, + LEDS_UPDATE_STATIC, + LEDS_UPDATE_TEST, + LEDS_UPDATE_SEQUENCE, + LEDS_UPDATE_ARTNET, +}; + struct leds_state { int index; const struct leds_config *config; struct leds *leds; TickType_t update_tick; + enum leds_update_state update_state; xTaskHandle task; EventGroupHandle_t event_group; diff --git a/main/leds_task.c b/main/leds_task.c index 310ffa5f..0f48083f 100644 --- a/main/leds_task.c +++ b/main/leds_task.c @@ -122,6 +122,30 @@ static void leds_main(void *ctx) loop_start = stats_timer_start(&stats->loop); + if (leds_static_active(state, event_bits)) { + LOG_DEBUG("static"); + + WITH_STATS_TIMER(&stats->static_) { + if (leds_static_update(state, event_bits)) { + update_activity = USER_ACTIVITY_LEDS_STATIC; + } + } + + state->update_state = LEDS_UPDATE_STATIC; + } + + if (state->test && leds_test_active(state, event_bits)) { + LOG_DEBUG("test"); + + WITH_STATS_TIMER(&stats->test) { + if (leds_test_update(state, event_bits)) { + update_activity = USER_ACTIVITY_LEDS_TEST; + } + } + + state->update_state = LEDS_UPDATE_TEST; + } + if (state->sequence && leds_sequence_active(state, event_bits)) { LOG_DEBUG("sequence"); @@ -130,6 +154,8 @@ static void leds_main(void *ctx) update_activity = USER_ACTIVITY_LEDS_SEQUENCE; } } + + state->update_state = LEDS_UPDATE_SEQUENCE; } if (state->artnet && leds_artnet_active(state, event_bits)) { @@ -152,26 +178,8 @@ static void leds_main(void *ctx) LOG_ERROR("leds_artnet_update"); } } - } - - if (state->test && leds_test_active(state, event_bits)) { - LOG_DEBUG("test"); - - WITH_STATS_TIMER(&stats->test) { - if (leds_test_update(state, event_bits)) { - update_activity = USER_ACTIVITY_LEDS_TEST; - } - } - } - - if (leds_static_active(state, event_bits)) { - LOG_DEBUG("static"); - - WITH_STATS_TIMER(&stats->static_) { - if (leds_static_update(state, event_bits)) { - update_activity = USER_ACTIVITY_LEDS_STATIC; - } - } + + state->update_state = LEDS_UPDATE_ARTNET; } if (leds_update_active(state)) { diff --git a/main/leds_test.c b/main/leds_test.c index 02e2835d..d905f4e3 100644 --- a/main/leds_test.c +++ b/main/leds_test.c @@ -155,27 +155,13 @@ int clear_leds_test(struct leds_state *state) return 0; } -static void leds_test_reset(struct leds_state *state) +void leds_test_clear(struct leds_state *state) { state->test->mode = 0; state->test->frame = 0; state->test->frame_tick = 0; } -void leds_test_clear(struct leds_state *state) -{ - if (!state->test->mode) { - return; - } - - // incoming artnet data overrides test mode - if (leds_clear_all(state->leds)) { - LOG_WARN("leds_clear_all"); - } - - leds_test_reset(state); -} - TickType_t leds_test_wait(struct leds_state *state) { if (!state->test->mode) { @@ -211,7 +197,7 @@ int leds_test_update(struct leds_state *state, EventBits_t bits) LOG_WARN("leds_clear_all"); } - leds_test_reset(state); + leds_test_clear(state); return 1; From 98f3a4b13b59fd6275713457cd4544bd971d1e22 Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Sat, 13 Jun 2026 14:27:53 +0300 Subject: [PATCH 21/55] main: TODO leds source priorities --- main/leds_artnet.c | 5 ++++- main/leds_test.c | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/main/leds_artnet.c b/main/leds_artnet.c index 89c8c88a..b5475b00 100644 --- a/main/leds_artnet.c +++ b/main/leds_artnet.c @@ -178,6 +178,7 @@ static void leds_artnet_timeout(struct leds_state *state) LOG_INFO("leds%d: timeout", state->index + 1); // TODO: flash user alert? + // TODO: fallback to static, sequence? if (leds_clear_all(state->leds)) { LOG_WARN("leds_clear_all"); @@ -298,8 +299,9 @@ int leds_artnet_update(struct leds_state *state, EventBits_t event_bits) // wait until either artnet-sync or (non-sync) dmx to not trigger soft-sync on partial data in artnet sync mode if (dmx || sync || miss) { + // TODO: sequence? if (state->test) { - // disable test mode + // override test mode leds_test_clear(state); } @@ -369,6 +371,7 @@ int leds_artnet_update(struct leds_state *state, EventBits_t event_bits) leds_artnet_timeout(state); leds_artnet_timeout_clear(state); + // TODO: sync clear? } // return diff --git a/main/leds_test.c b/main/leds_test.c index d905f4e3..6d58a629 100644 --- a/main/leds_test.c +++ b/main/leds_test.c @@ -217,6 +217,8 @@ int leds_test_update(struct leds_state *state, EventBits_t bits) return 0; } + // TODO: disable sequence? + if ((frame_ticks = leds_set_test(state->leds, state->test->mode, state->test->frame)) < 0) { LOG_ERROR("leds_set_test"); return -1; From 326f8bfb46c369d934bf4d700c1659aecf43c060 Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Sat, 13 Jun 2026 16:13:24 +0300 Subject: [PATCH 22/55] leds: artnet sync clear --- main/leds_artnet.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/main/leds_artnet.c b/main/leds_artnet.c index b5475b00..ed3e317f 100644 --- a/main/leds_artnet.c +++ b/main/leds_artnet.c @@ -152,6 +152,13 @@ static void leds_artnet_sync_reset(struct leds_state *state) } } +void leds_artnet_sync_clear(struct leds_state *state) +{ + state->artnet->sync_bits = 0; + state->artnet->sync_missed = 0; + state->artnet->sync_tick = 0; +} + static void leds_artnet_timeout_reset(struct leds_state *state) { const struct leds_config *config = state->config; @@ -371,7 +378,7 @@ int leds_artnet_update(struct leds_state *state, EventBits_t event_bits) leds_artnet_timeout(state); leds_artnet_timeout_clear(state); - // TODO: sync clear? + leds_artnet_sync_clear(state); } // return From 9805d55f8cdb7ed75305f268b99a24d372f2cb10 Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Sat, 13 Jun 2026 16:14:15 +0300 Subject: [PATCH 23/55] main: handle leds update state overrides --- main/leds_artnet.c | 37 +++++++++++++++++++++++++------------ main/leds_artnet.h | 5 ++++- main/leds_static.c | 10 ---------- main/leds_task.c | 33 +++++++++++++++++++++++++-------- main/leds_test.c | 11 ++++++----- main/leds_test.h | 4 ++-- 6 files changed, 62 insertions(+), 38 deletions(-) diff --git a/main/leds_artnet.c b/main/leds_artnet.c index ed3e317f..9c6f3d60 100644 --- a/main/leds_artnet.c +++ b/main/leds_artnet.c @@ -281,6 +281,27 @@ bool leds_artnet_active(struct leds_state *state, EventBits_t event_bits) return false; } +void leds_artnet_update_override(struct leds_state *state) +{ + state->artnet->update_clean = false; + + // do not wait for artnet data + leds_artnet_timeout_clear(state); + leds_artnet_sync_clear(state); +} + +static void leds_artnet_clean(struct leds_state *state) +{ + if (!state->artnet->update_clean) { + // incoming artnet data overrides any other type of output, ensure leds for any missing universes are cleared + if (leds_clear_all(state->leds)) { + LOG_ERROR("leds_clear_all"); + } + + state->artnet->update_clean = true; + } +} + int leds_artnet_update(struct leds_state *state, EventBits_t event_bits) { struct leds_stats *stats = &leds_stats[state->index]; @@ -306,18 +327,7 @@ int leds_artnet_update(struct leds_state *state, EventBits_t event_bits) // wait until either artnet-sync or (non-sync) dmx to not trigger soft-sync on partial data in artnet sync mode if (dmx || sync || miss) { - // TODO: sequence? - if (state->test) { - // override test mode - leds_test_clear(state); - } - - if (state->update_state != LEDS_UPDATE_ARTNET) { - // incoming artnet data overrides any other output, even with missing universes - if (leds_clear_all(state->leds)) { - LOG_ERROR("leds_clear_all"); - } - } + leds_artnet_clean(state); // set output from artnet universe for (unsigned index = 0; index < state->artnet->universe_count; index++) { @@ -434,6 +444,9 @@ int init_leds_artnet(struct leds_state *state, int index, const struct leds_conf // activate timeout, delayed output update at boot leds_artnet_timeout_reset(state); + // cleared if leds is updated by anything else + state->artnet->update_clean = true; + return 0; } diff --git a/main/leds_artnet.h b/main/leds_artnet.h index e67b64d0..dc85495e 100644 --- a/main/leds_artnet.h +++ b/main/leds_artnet.h @@ -22,6 +22,8 @@ struct leds_artnet_state { TickType_t sync_tick; // tick for soft sync TickType_t timeout_tick; // tick for forced reset + + bool update_clean; // leds has not been updated by anything other than artnet }; unsigned count_leds_artnet_outputs(); @@ -29,7 +31,8 @@ unsigned count_leds_artnet_outputs(); int init_leds_artnet(struct leds_state *state, int index, const struct leds_config *config); int start_leds_artnet(struct leds_state *state, const struct leds_config *config); -void leds_artnet_timeout_clear(struct leds_state *state); +/* Leds output has been dirtied by non-artnet sources, and must be cleared for art-net output */ +void leds_artnet_update_override(struct leds_state *state); /* Return next tick for possible artnet timeout */ TickType_t leds_artnet_wait(struct leds_state *state); diff --git a/main/leds_static.c b/main/leds_static.c index 8ea3f07d..7403d1a4 100644 --- a/main/leds_static.c +++ b/main/leds_static.c @@ -82,20 +82,10 @@ int leds_static_update(struct leds_state *state, EventBits_t bits) { LOG_INFO("color=%02x%02x%02x.%02x", state->static_.color.r, state->static_.color.g, state->static_.color.b, state->static_.color.parameter); - if (state->test) { - // clear any test mode output - leds_test_clear(state); - } - if (leds_set_all(state->leds, state->static_.color)) { LOG_ERROR("leds_set_all"); return -1; } - if (state->artnet) { - // inhibit artnet timeout - leds_artnet_timeout_clear(state); - } - return 1; } diff --git a/main/leds_task.c b/main/leds_task.c index 0f48083f..08f7aa46 100644 --- a/main/leds_task.c +++ b/main/leds_task.c @@ -105,6 +105,23 @@ static EventBits_t leds_task_wait(struct leds_state *state) return event_bits; } +static void leds_update_state(struct leds_state *state, enum leds_update_state update_state) +{ + if (update_state == state->update_state) { + return; + } + + if (update_state != LEDS_UPDATE_TEST && state->test) { + leds_test_update_override(state); + } + + if (update_state != LEDS_UPDATE_ARTNET && state->artnet) { + leds_artnet_update_override(state); + } + + state->update_state = update_state; +} + static void leds_main(void *ctx) { struct leds_state *state = ctx; @@ -123,6 +140,8 @@ static void leds_main(void *ctx) loop_start = stats_timer_start(&stats->loop); if (leds_static_active(state, event_bits)) { + leds_update_state(state, LEDS_UPDATE_STATIC); + LOG_DEBUG("static"); WITH_STATS_TIMER(&stats->static_) { @@ -130,11 +149,11 @@ static void leds_main(void *ctx) update_activity = USER_ACTIVITY_LEDS_STATIC; } } - - state->update_state = LEDS_UPDATE_STATIC; } if (state->test && leds_test_active(state, event_bits)) { + leds_update_state(state, LEDS_UPDATE_TEST); + LOG_DEBUG("test"); WITH_STATS_TIMER(&stats->test) { @@ -142,11 +161,11 @@ static void leds_main(void *ctx) update_activity = USER_ACTIVITY_LEDS_TEST; } } - - state->update_state = LEDS_UPDATE_TEST; } if (state->sequence && leds_sequence_active(state, event_bits)) { + leds_update_state(state, LEDS_UPDATE_SEQUENCE); + LOG_DEBUG("sequence"); WITH_STATS_TIMER(&stats->sequence) { @@ -154,11 +173,11 @@ static void leds_main(void *ctx) update_activity = USER_ACTIVITY_LEDS_SEQUENCE; } } - - state->update_state = LEDS_UPDATE_SEQUENCE; } if (state->artnet && leds_artnet_active(state, event_bits)) { + leds_update_state(state, LEDS_UPDATE_ARTNET); + LOG_DEBUG("artnet"); WITH_STATS_TIMER(&stats->artnet) { @@ -178,8 +197,6 @@ static void leds_main(void *ctx) LOG_ERROR("leds_artnet_update"); } } - - state->update_state = LEDS_UPDATE_ARTNET; } if (leds_update_active(state)) { diff --git a/main/leds_test.c b/main/leds_test.c index 6d58a629..080bc3b3 100644 --- a/main/leds_test.c +++ b/main/leds_test.c @@ -155,13 +155,18 @@ int clear_leds_test(struct leds_state *state) return 0; } -void leds_test_clear(struct leds_state *state) +static void leds_test_clear(struct leds_state *state) { state->test->mode = 0; state->test->frame = 0; state->test->frame_tick = 0; } +void leds_test_update_override(struct leds_state *state) +{ + leds_test_clear(state); +} + TickType_t leds_test_wait(struct leds_state *state) { if (!state->test->mode) { @@ -243,9 +248,5 @@ int leds_test_update(struct leds_state *state, EventBits_t bits) state->test->frame_tick = 0; } - if (state->artnet) { - leds_artnet_timeout_clear(state); - } - return 1; } diff --git a/main/leds_test.h b/main/leds_test.h index 0583e299..0ed921c0 100644 --- a/main/leds_test.h +++ b/main/leds_test.h @@ -33,8 +33,8 @@ int set_leds_test_auto(struct leds_state *state); int set_leds_test_next(struct leds_state *state); int clear_leds_test(struct leds_state *state); -/* Clear test mode */ -void leds_test_clear(struct leds_state *state); +/* LEDS output updated by other sources */ +void leds_test_update_override(struct leds_state *state); /* Return next tick for test mode */ TickType_t leds_test_wait(struct leds_state *state); From 8781e2c7f523fd0ba90e269e71151664df8dcbb7 Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Sat, 13 Jun 2026 16:16:37 +0300 Subject: [PATCH 24/55] main: fix artnet timeout stats --- main/leds_artnet.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/main/leds_artnet.c b/main/leds_artnet.c index 9c6f3d60..f3a94bc1 100644 --- a/main/leds_artnet.c +++ b/main/leds_artnet.c @@ -384,6 +384,8 @@ int leds_artnet_update(struct leds_state *state, EventBits_t event_bits) leds_artnet_timeout_reset(state); } else if (leds_artnet_timeout_expired(state)) { + stats_counter_increment(&stats->artnet_timeout); + timeout = true; leds_artnet_timeout(state); From e3638ccbbbc857ae7f8e0b3d52b62bedaa706fae Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Sat, 13 Jun 2026 16:20:00 +0300 Subject: [PATCH 25/55] leds test: skip over CHASE mode in hold -> auto --- main/leds_test.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/main/leds_test.c b/main/leds_test.c index 080bc3b3..de2d6e29 100644 --- a/main/leds_test.c +++ b/main/leds_test.c @@ -114,6 +114,11 @@ int set_leds_test_auto(struct leds_state *state) leds_test_next_mode(state); } + if (state->test->mode == TEST_MODE_CHASE) { + // CHASE mode will never end + leds_test_next_mode(state); + } + LOG_DEBUG("mode=%d auto_mode=%d", state->test->mode, state->test->auto_mode); notify_leds_task(state, 1 << LEDS_EVENT_TEST_BIT); From 2a44685d0657126df7bf249293e20f1fa8eda0d2 Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Sat, 13 Jun 2026 16:25:23 +0300 Subject: [PATCH 26/55] main: split leds_test_http.c --- main/http_routes.h | 1 + main/leds_http.c | 38 ----------- main/leds_http_post.c | 106 ----------------------------- main/leds_test_http.c | 155 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 156 insertions(+), 144 deletions(-) create mode 100644 main/leds_test_http.c diff --git a/main/http_routes.h b/main/http_routes.h index eb46b4f8..9c8d1f3b 100644 --- a/main/http_routes.h +++ b/main/http_routes.h @@ -42,6 +42,7 @@ int leds_api_get(struct http_request *request, struct http_response *response, v int leds_api_get_status(struct http_request *request, struct http_response *response, void *ctx); int leds_api_post(struct http_request *request, struct http_response *response, void *ctx); +/* leds_test_http.c */ int leds_api_test_get(struct http_request *request, struct http_response *response, void *ctx); int leds_api_test_post(struct http_request *request, struct http_response *response, void *ctx); diff --git a/main/leds_http.c b/main/leds_http.c index eb00e0de..0639a33e 100644 --- a/main/leds_http.c +++ b/main/leds_http.c @@ -223,41 +223,3 @@ int leds_api_get_status(struct http_request *request, struct http_response *resp return 0; } - -/* GET /api/leds/test */ -static int leds_api_write_test_array(struct json_writer *w) -{ - int err; - - for (const struct config_enum *e = leds_test_mode_enum; e->name; e++) { - if ((err = JSON_WRITE_OBJECT(w, - JSON_WRITE_MEMBER_STRING(w, "mode", e->name) - ))) { - return err; - } - } - - return 0; -} - -static int leds_api_write_test(struct json_writer *w, void *ctx) -{ - return JSON_WRITE_ARRAY(w, leds_api_write_test_array(w)); -} - -int leds_api_test_get(struct http_request *request, struct http_response *response, void *ctx) -{ - int err; - - if ((err = http_request_headers(request, NULL))) { - LOG_WARN("http_request_headers"); - return err; - } - - if ((err = write_http_response_json(response, leds_api_write_test, NULL))) { - LOG_WARN("write_http_response_json -> leds_api_write_test"); - return err; - } - - return 0; -} diff --git a/main/leds_http_post.c b/main/leds_http_post.c index abb348af..060e981c 100644 --- a/main/leds_http_post.c +++ b/main/leds_http_post.c @@ -144,109 +144,3 @@ int leds_api_post(struct http_request *request, struct http_response *response, return HTTP_UNSUPPORTED_MEDIA_TYPE; } } - -/* POST /api/leds/test */ -struct leds_api_test_params { - struct leds_state *state; - enum leds_test_mode mode; - bool auto_mode; -}; - -int leds_api_test_params_set(struct leds_api_test_params *params, const char *key, const char *value) -{ - if (strcmp(key, "index") == 0) { - unsigned index; - - if (sscanf(value, "%d", &index) <= 0) { - return HTTP_UNPROCESSABLE_ENTITY; - } else if (index <= 0 || index > LEDS_COUNT) { - return HTTP_UNPROCESSABLE_ENTITY; - } else { - params->state = &leds_states[index - 1]; - } - } else if (strcmp(key, "mode") == 0) { - int mode; - - if ((mode = config_enum_to_value(leds_test_mode_enum, value)) < 0) { - return HTTP_UNPROCESSABLE_ENTITY; - } else { - params->mode = mode; - } - } else if (strcmp(key, "auto") == 0) { - if (strcmp(value, "true") == 0) { - params->auto_mode = true; - } else if (strcmp(value, "false") == 0) { - params->auto_mode = false; - } else { - return HTTP_UNPROCESSABLE_ENTITY; - } - } else { - return HTTP_UNPROCESSABLE_ENTITY; - } - - return 0; -} - -/* POST /api/config application/x-www-form-urlencoded */ -int leds_api_test_read_form_params(struct http_request *request, struct leds_api_test_params *params) -{ - char *key, *value; - int err; - - while (!(err = http_request_form(request, &key, &value))) { - if ((err = leds_api_test_params_set(params, key, value))) { - LOG_WARN("leds_api_test_params_set: %s=%s", key, value ? value : ""); - return err; - } - } - - if (err < 0) { - LOG_ERROR("http_request_form"); - return err; - } - - return 0; -} - -int leds_api_test_post(struct http_request *request, struct http_response *response, void *ctx) -{ - const struct http_request_headers *headers; - struct leds_api_test_params params = { - .mode = TEST_MODE_CHASE, - }; - int err; - - if ((err = http_request_headers(request, &headers))) { - LOG_WARN("http_request_headers"); - return err; - } - - switch (headers->content_type) { - case HTTP_CONTENT_TYPE_APPLICATION_X_WWW_FORM_URLENCODED: - if ((err = leds_api_test_read_form_params(request, ¶ms))) { - LOG_WARN("leds_api_test_read_form_params"); - return err; - } - - break; - - default: - LOG_WARN("Unknown Content-Type"); - - return HTTP_UNSUPPORTED_MEDIA_TYPE; - } - - // TODO: clear? - if (!params.state) { - LOG_WARN("missing index"); - return HTTP_UNPROCESSABLE_ENTITY; - } else if ((err = set_leds_test(state, params.mode, params.auto_mode)) < 0) { - LOG_ERROR("set_leds_test"); - return HTTP_INTERNAL_SERVER_ERROR; - } else if (err) { - LOG_WARN("set_leds_test"); - return HTTP_CONFLICT; - } - - return HTTP_NO_CONTENT; -} diff --git a/main/leds_test_http.c b/main/leds_test_http.c new file mode 100644 index 00000000..f6a88495 --- /dev/null +++ b/main/leds_test_http.c @@ -0,0 +1,155 @@ +#include "leds.h" +#include "leds_state.h" +#include "leds_test.h" +#include "http_routes.h" +#include "http_handlers.h" + +#include +#include + +#include + +/* GET /api/leds/test */ +static int leds_api_write_test_array(struct json_writer *w) +{ + int err; + + for (const struct config_enum *e = leds_test_mode_enum; e->name; e++) { + if ((err = JSON_WRITE_OBJECT(w, + JSON_WRITE_MEMBER_STRING(w, "mode", e->name) + ))) { + return err; + } + } + + return 0; +} + +static int leds_api_write_test(struct json_writer *w, void *ctx) +{ + return JSON_WRITE_ARRAY(w, leds_api_write_test_array(w)); +} + +int leds_api_test_get(struct http_request *request, struct http_response *response, void *ctx) +{ + int err; + + if ((err = http_request_headers(request, NULL))) { + LOG_WARN("http_request_headers"); + return err; + } + + if ((err = write_http_response_json(response, leds_api_write_test, NULL))) { + LOG_WARN("write_http_response_json -> leds_api_write_test"); + return err; + } + + return 0; +} + + +/* POST /api/leds/test */ +struct leds_api_test_params { + struct leds_state *state; + enum leds_test_mode mode; + bool auto_mode; +}; + +int leds_api_test_params_set(struct leds_api_test_params *params, const char *key, const char *value) +{ + if (strcmp(key, "index") == 0) { + unsigned index; + + if (sscanf(value, "%d", &index) <= 0) { + return HTTP_UNPROCESSABLE_ENTITY; + } else if (index <= 0 || index > LEDS_COUNT) { + return HTTP_UNPROCESSABLE_ENTITY; + } else { + params->state = &leds_states[index - 1]; + } + } else if (strcmp(key, "mode") == 0) { + int mode; + + if ((mode = config_enum_to_value(leds_test_mode_enum, value)) < 0) { + return HTTP_UNPROCESSABLE_ENTITY; + } else { + params->mode = mode; + } + } else if (strcmp(key, "auto") == 0) { + if (strcmp(value, "true") == 0) { + params->auto_mode = true; + } else if (strcmp(value, "false") == 0) { + params->auto_mode = false; + } else { + return HTTP_UNPROCESSABLE_ENTITY; + } + } else { + return HTTP_UNPROCESSABLE_ENTITY; + } + + return 0; +} + +/* POST /api/config application/x-www-form-urlencoded */ +int leds_api_test_read_form_params(struct http_request *request, struct leds_api_test_params *params) +{ + char *key, *value; + int err; + + while (!(err = http_request_form(request, &key, &value))) { + if ((err = leds_api_test_params_set(params, key, value))) { + LOG_WARN("leds_api_test_params_set: %s=%s", key, value ? value : ""); + return err; + } + } + + if (err < 0) { + LOG_ERROR("http_request_form"); + return err; + } + + return 0; +} + +int leds_api_test_post(struct http_request *request, struct http_response *response, void *ctx) +{ + const struct http_request_headers *headers; + struct leds_api_test_params params = { + .mode = TEST_MODE_CHASE, + }; + int err; + + if ((err = http_request_headers(request, &headers))) { + LOG_WARN("http_request_headers"); + return err; + } + + switch (headers->content_type) { + case HTTP_CONTENT_TYPE_APPLICATION_X_WWW_FORM_URLENCODED: + if ((err = leds_api_test_read_form_params(request, ¶ms))) { + LOG_WARN("leds_api_test_read_form_params"); + return err; + } + + break; + + default: + LOG_WARN("Unknown Content-Type"); + + return HTTP_UNSUPPORTED_MEDIA_TYPE; + } + + // TODO: clear? + if (!params.state) { + LOG_WARN("missing index"); + return HTTP_UNPROCESSABLE_ENTITY; + } else if ((err = set_leds_test(state, params.mode, params.auto_mode)) < 0) { + LOG_ERROR("set_leds_test"); + return HTTP_INTERNAL_SERVER_ERROR; + } else if (err) { + LOG_WARN("set_leds_test"); + return HTTP_CONFLICT; + } + + return HTTP_NO_CONTENT; +} From aab96ee106b05e8825da4d2b60eb6109088a041a Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Sat, 13 Jun 2026 16:53:15 +0300 Subject: [PATCH 27/55] config_color_parse --- components/config/color.c | 48 ++++++++++++++++++++++++++++++ components/config/include/config.h | 3 ++ components/config/set.c | 40 ++----------------------- 3 files changed, 53 insertions(+), 38 deletions(-) create mode 100644 components/config/color.c diff --git a/components/config/color.c b/components/config/color.c new file mode 100644 index 00000000..12e672fb --- /dev/null +++ b/components/config/color.c @@ -0,0 +1,48 @@ +#include + +#include + +#include + +int config_color_parse(struct config_color *color, const char *value) +{ + int r = 0, g = 0, b = 0, a = 0; + + switch (strlen(value)) { + case 3: + a = 0xff; + + if (sscanf(value, "%1x%1x%1x", &r, &g, &b) < 3) { + return -1; + } + + r = (r << 4) | r; + g = (g << 4) | g; + b = (b << 4) | b; + + break; + + case 6: + a = 0xff; + + if (sscanf(value, "%2x%2x%2x", &r, &g, &b) < 3) { + return -1; + } + + break; + + case 8: + if (sscanf(value, "%2x%2x%2x%2x", &r, &g, &b, &a) < 4) { + return -1; + } + + break; + + default: + return -1; + } + + *color = (struct config_color) { r, g, b, a }; + + return 0; +} diff --git a/components/config/include/config.h b/components/config/include/config.h index 2a2d5a3f..e4fd3152 100644 --- a/components/config/include/config.h +++ b/components/config/include/config.h @@ -177,6 +177,9 @@ int config_file_walk(const struct config_file_path *paths, int (*func)(const str FILE *config_file_open(const struct config_file_path *paths, const char *value); +/* Color*/ +int config_color_parse(struct config_color *color, const char *value); + int configmod_lookup(const struct configmod *modules, const char *name, const struct configmod **modp, unsigned *indexp, const struct configtab **tablep); int configtab_lookup(const struct configmod *module, unsigned index, const struct configtab *table, const char *name, const struct configtab **tabp); diff --git a/components/config/set.c b/components/config/set.c index b3bbea97..3bbe18c8 100644 --- a/components/config/set.c +++ b/components/config/set.c @@ -167,45 +167,9 @@ static int config_set_file(const struct config_path path, unsigned index, const static int config_set_color(const struct config_path path, unsigned index, const char *str) { const struct configtab *tab = path.tab; - int r = 0, g = 0, b = 0, a = 0; - - switch (strlen(str)) { - case 3: - a = 0xff; - - if (sscanf(str, "%1x%1x%1x", &r, &g, &b) < 3) { - return -1; - } - - r = (r << 4) | r; - g = (g << 4) | g; - b = (b << 4) | b; - - break; - - case 6: - a = 0xff; - - if (sscanf(str, "%2x%2x%2x", &r, &g, &b) < 3) { - return -1; - } - - break; - - case 8: - if (sscanf(str, "%2x%2x%2x%2x", &r, &g, &b, &a) < 4) { - return -1; - } - - break; - - default: - return -1; - } - - tab->color_type.value[index] = (struct config_color) { r, g, b, a }; + int err; - return 0; + return config_color_parse(&tab->color_type.value[index], str); } int config_set(const struct config_path path, const char *value) From 3e480649e5f731a7fbb6c7651b38f340e4f25ef0 Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Sat, 13 Jun 2026 16:53:36 +0300 Subject: [PATCH 28/55] move config_leds_color --- main/leds_config.c | 34 ++++++++++++++++++++++++++++++++++ main/leds_config.h | 2 ++ main/leds_static.c | 35 +---------------------------------- 3 files changed, 37 insertions(+), 34 deletions(-) diff --git a/main/leds_config.c b/main/leds_config.c index d0150858..91a7a01f 100644 --- a/main/leds_config.c +++ b/main/leds_config.c @@ -349,3 +349,37 @@ int config_leds(struct leds_state *state, const struct leds_config *config) return 0; } + +struct leds_color config_leds_color(const struct config_color config_color, enum leds_parameter_type parameter_type) +{ + switch (parameter_type) { + case LEDS_PARAMETER_NONE: + return (struct leds_color) { + .r = config_color.r * config_color.a / 255, + .g = config_color.g * config_color.a / 255, + .b = config_color.b * config_color.a / 255, + }; + + case LEDS_PARAMETER_DIMMER: + return (struct leds_color) { + .r = config_color.r, + .g = config_color.g, + .b = config_color.b, + + .dimmer = config_color.a, + }; + + case LEDS_PARAMETER_WHITE: + return (struct leds_color) { + .r = config_color.r, + .g = config_color.g, + .b = config_color.b, + + // TODO: fully transparent = only white, 50% = colors + 100% white? + .white = (255 - config_color.a), + }; + + default: + LOG_FATAL("parameter_type=%d", parameter_type); + } +} diff --git a/main/leds_config.h b/main/leds_config.h index 224b39df..4d7eafa5 100644 --- a/main/leds_config.h +++ b/main/leds_config.h @@ -195,6 +195,8 @@ extern const struct config_file_path leds_sequence_paths[]; int config_leds(struct leds_state *state, const struct leds_config *config); +struct leds_color config_leds_color(struct config_color config_color, enum leds_parameter_type parameter_type); + unsigned config_leds_artnet_universe_leds_count(const struct leds_config *config); unsigned config_leds_artnet_universe_count(const struct leds_config *config); diff --git a/main/leds_static.c b/main/leds_static.c index 7403d1a4..e13993c4 100644 --- a/main/leds_static.c +++ b/main/leds_static.c @@ -1,44 +1,11 @@ #include "leds_static.h" +#include "leds_config.h" #include "leds_task.h" #include "leds_test.h" #include "leds_artnet.h" #include -static struct leds_color config_leds_color(struct config_color config_color, enum leds_parameter_type parameter_type) -{ - switch (parameter_type) { - case LEDS_PARAMETER_NONE: - return (struct leds_color) { - .r = config_color.r * config_color.a / 255, - .g = config_color.g * config_color.a / 255, - .b = config_color.b * config_color.a / 255, - }; - - case LEDS_PARAMETER_DIMMER: - return (struct leds_color) { - .r = config_color.r, - .g = config_color.g, - .b = config_color.b, - - .dimmer = config_color.a, - }; - - case LEDS_PARAMETER_WHITE: - return (struct leds_color) { - .r = config_color.r, - .g = config_color.g, - .b = config_color.b, - - // TODO: fully transparent = only white, 50% = colors + 100% white? - .white = (255 - config_color.a), - }; - - default: - LOG_FATAL("parameter_type=%d", parameter_type); - } -} - int config_leds_static(struct leds_state *state, const struct leds_config *config) { state->static_.color = config_leds_color(config->static_color, leds_parameter_type_for_protocol(leds_protocol(state->leds))); From 3507327fa97b1adcd0a4839ff1f9bb6cca07b7b9 Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Sat, 13 Jun 2026 16:57:54 +0300 Subject: [PATCH 29/55] main: POST /api/leds/static --- main/http_routes.c | 2 + main/http_routes.h | 3 ++ main/leds_static_http.c | 112 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 117 insertions(+) create mode 100644 main/leds_static_http.c diff --git a/main/http_routes.c b/main/http_routes.c index 3bc485b4..18b4fad8 100644 --- a/main/http_routes.c +++ b/main/http_routes.c @@ -41,6 +41,8 @@ const struct http_route http_routes[] = { { "GET", "api/leds/test", leds_api_test_get, NULL }, { "POST", "api/leds/test", leds_api_test_post, NULL }, + { "POST", "api/leds/static", leds_api_static_post, NULL }, + /* user_http.c */ { "GET", "api/status", user_api_get_status, NULL }, { "POST", "api/button", user_api_post_button, NULL }, diff --git a/main/http_routes.h b/main/http_routes.h index 9c8d1f3b..aa910dbd 100644 --- a/main/http_routes.h +++ b/main/http_routes.h @@ -46,6 +46,9 @@ int leds_api_post(struct http_request *request, struct http_response *response, int leds_api_test_get(struct http_request *request, struct http_response *response, void *ctx); int leds_api_test_post(struct http_request *request, struct http_response *response, void *ctx); +/* leds_static_http.c */ +int leds_api_static_post(struct http_request *request, struct http_response *response, void *ctx); + /* user_http.c */ int user_api_get_status(struct http_request *request, struct http_response *response, void *ctx); int user_api_post_button(struct http_request *request, struct http_response *response, void *ctx); diff --git a/main/leds_static_http.c b/main/leds_static_http.c new file mode 100644 index 00000000..cc9d618b --- /dev/null +++ b/main/leds_static_http.c @@ -0,0 +1,112 @@ +#include "leds.h" +#include "leds_config.h" +#include "leds_state.h" +#include "leds_static.h" +#include "http_routes.h" +#include "http_handlers.h" + +#include +#include +#include + +#include + +/* POST /api/leds/static */ +struct leds_api_static_params { + struct leds_state *state; + struct leds_color color; +}; + +int leds_api_static_params_set(struct leds_api_static_params *params, const char *key, const char *value) +{ + struct config_color config_color; + + if (strcmp(key, "index") == 0) { + unsigned index; + + if (sscanf(value, "%d", &index) <= 0) { + return HTTP_UNPROCESSABLE_ENTITY; + } else if (index <= 0 || index > LEDS_COUNT) { + return HTTP_UNPROCESSABLE_ENTITY; + } else { + params->state = &leds_states[index - 1]; + } + + if (!params->state->leds) { + return HTTP_UNPROCESSABLE_ENTITY; + } + } else if (strcmp(key, "color") == 0) { + if (config_color_parse(&config_color, value)) { + return HTTP_UNPROCESSABLE_ENTITY; + } else if (!params->state) { + return HTTP_UNPROCESSABLE_ENTITY; + } else { + params->color = config_leds_color(config_color, leds_parameter_type_for_protocol(leds_protocol(params->state->leds))); + } + } else { + return HTTP_UNPROCESSABLE_ENTITY; + } + + return 0; +} + +int leds_api_static_read_form_params(struct http_request *request, struct leds_api_static_params *params) +{ + char *key, *value; + int err; + + while (!(err = http_request_form(request, &key, &value))) { + if ((err = leds_api_static_params_set(params, key, value))) { + LOG_WARN("leds_api_static_params_set: %s=%s", key, value ? value : ""); + return err; + } + } + + if (err < 0) { + LOG_ERROR("http_request_form"); + return err; + } + + if (!params->state) { + LOG_WARN("missing index"); + return HTTP_UNPROCESSABLE_ENTITY; + } + + return 0; +} + +int leds_api_static_post(struct http_request *request, struct http_response *response, void *ctx) +{ + const struct http_request_headers *headers; + struct leds_api_static_params params = { + + }; + int err; + + if ((err = http_request_headers(request, &headers))) { + LOG_WARN("http_request_headers"); + return err; + } + + switch (headers->content_type) { + case HTTP_CONTENT_TYPE_APPLICATION_X_WWW_FORM_URLENCODED: + if ((err = leds_api_static_read_form_params(request, ¶ms))) { + LOG_WARN("leds_api_static_read_form_params"); + return err; + } + + break; + + default: + LOG_WARN("Unknown Content-Type"); + + return HTTP_UNSUPPORTED_MEDIA_TYPE; + } + + if ((err = set_leds_static(params.state, params.color)) < 0) { + LOG_ERROR("set_leds_static"); + return HTTP_INTERNAL_SERVER_ERROR; + } else { + return HTTP_NO_CONTENT; + } +} From 876dd9ee45bc179e3cb26ec403095fc31c06149f Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Sat, 13 Jun 2026 17:16:49 +0300 Subject: [PATCH 30/55] fixup config_leds_color --- main/leds_config.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/leds_config.c b/main/leds_config.c index 91a7a01f..0124dbfb 100644 --- a/main/leds_config.c +++ b/main/leds_config.c @@ -350,7 +350,7 @@ int config_leds(struct leds_state *state, const struct leds_config *config) return 0; } -struct leds_color config_leds_color(const struct config_color config_color, enum leds_parameter_type parameter_type) +struct leds_color config_leds_color(struct config_color config_color, enum leds_parameter_type parameter_type) { switch (parameter_type) { case LEDS_PARAMETER_NONE: From 99210a4c78d4da3c7867f47f65b4d598f40805a3 Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Sat, 13 Jun 2026 17:17:00 +0300 Subject: [PATCH 31/55] config_color_str --- components/config/color.c | 9 +++++++++ components/config/get.c | 7 +------ components/config/include/config.h | 5 ++++- components/config/set.c | 1 - main/config_http_get.c | 8 ++++++-- 5 files changed, 20 insertions(+), 10 deletions(-) diff --git a/components/config/color.c b/components/config/color.c index 12e672fb..adbcb107 100644 --- a/components/config/color.c +++ b/components/config/color.c @@ -46,3 +46,12 @@ int config_color_parse(struct config_color *color, const char *value) return 0; } + +int config_color_str(char *buf, size_t size, struct config_color color) +{ + if (snprintf(buf, size, "%02x%02x%02x%02x", color.r, color.g, color.b, color.a) >= size) { + return -1; + } + + return 0; +} diff --git a/components/config/get.c b/components/config/get.c index 0ca1dc35..339df493 100644 --- a/components/config/get.c +++ b/components/config/get.c @@ -72,12 +72,7 @@ int config_get(const struct config_path path, unsigned index, char *buf, size_t } case CONFIG_TYPE_COLOR: - if (snprintf(buf, size, "%02x%02x%02x%02x", - tab->color_type.value[index].r, - tab->color_type.value[index].g, - tab->color_type.value[index].b, - tab->color_type.value[index].a - ) >= size) { + if (config_color_str(buf, size, tab->color_type.value[index])) { return -1; } else { break; diff --git a/components/config/include/config.h b/components/config/include/config.h index e4fd3152..a969c73b 100644 --- a/components/config/include/config.h +++ b/components/config/include/config.h @@ -177,8 +177,11 @@ int config_file_walk(const struct config_file_path *paths, int (*func)(const str FILE *config_file_open(const struct config_file_path *paths, const char *value); -/* Color*/ +/* Color */ +#define CONFIG_COLOR_BUF_SIZE (8+1) + int config_color_parse(struct config_color *color, const char *value); +int config_color_str(char *buf, size_t size, struct config_color color); int configmod_lookup(const struct configmod *modules, const char *name, const struct configmod **modp, unsigned *indexp, const struct configtab **tablep); int configtab_lookup(const struct configmod *module, unsigned index, const struct configtab *table, const char *name, const struct configtab **tabp); diff --git a/components/config/set.c b/components/config/set.c index 3bbe18c8..32b0c36e 100644 --- a/components/config/set.c +++ b/components/config/set.c @@ -167,7 +167,6 @@ static int config_set_file(const struct config_path path, unsigned index, const static int config_set_color(const struct config_path path, unsigned index, const char *str) { const struct configtab *tab = path.tab; - int err; return config_color_parse(&tab->color_type.value[index], str); } diff --git a/main/config_http_get.c b/main/config_http_get.c index 8d673725..b078a8f6 100644 --- a/main/config_http_get.c +++ b/main/config_http_get.c @@ -32,9 +32,13 @@ static int config_api_write_file_value(struct json_writer *w, const struct confi static int config_api_write_color_value(struct json_writer *w, const struct configtab *tab, unsigned index) { - struct config_color c = tab->color_type.value[index]; + char buf[CONFIG_COLOR_BUF_SIZE]; - return json_write_raw(w, "\"%02x%02x%02x%02x\"", c.r, c.g, c.b, c.a); + if (config_color_str(buf, sizeof(buf), tab->color_type.value[index])) { + return -1; + } + + return json_write_string(w, buf); } static int config_api_write_configtab_value(struct json_writer *w, const struct configtab *tab, unsigned index) From c53bcfffc908a15cbe1ce007a726a608f510a632 Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Sat, 13 Jun 2026 17:17:21 +0300 Subject: [PATCH 32/55] leds: include static.color in leds http --- main/leds_http.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/main/leds_http.c b/main/leds_http.c index 0639a33e..85de3d39 100644 --- a/main/leds_http.c +++ b/main/leds_http.c @@ -16,6 +16,21 @@ #define TICK_MS(current_tick, tick) (tick ? (current_tick - tick) * portTICK_RATE_MS : 0) +static int leds_api_write_leds_color(struct json_writer *w, struct leds_color c, enum leds_parameter_type parameter_type) +{ + switch (parameter_type) { + case LEDS_PARAMETER_NONE: + return json_write_raw(w, "\"%02x%02x%02x\"", c.r, c.g, c.b); + + case LEDS_PARAMETER_DIMMER: + case LEDS_PARAMETER_WHITE: + return json_write_raw(w, "\"%02x%02x%02x.%02x\"", c.r, c.g, c.b, c.parameter); + + default: + LOG_FATAL("%d", parameter_type); + } +} + static int leds_api_write_object_options(struct json_writer *w, struct leds_state *state) { const struct leds_options *options = leds_options(state->leds); @@ -105,6 +120,9 @@ static int leds_api_write_object(struct json_writer *w, struct leds_state *state JSON_WRITE_MEMBER_UINT(w, "index", state->index + 1) || JSON_WRITE_MEMBER_OBJECT(w, "options", leds_api_write_object_options(w, state)) || JSON_WRITE_MEMBER_OBJECT(w, "status", leds_api_write_object_status(w, state)) + || JSON_WRITE_MEMBER_OBJECT(w, "static", + JSON_WRITE_MEMBER(w, "color", leds_api_write_leds_color(w, state->static_.color, leds_parameter_type_for_protocol(leds_protocol(state->leds)))) + ) || (state->artnet ? JSON_WRITE_MEMBER_OBJECT(w, "artnet", leds_api_write_object_artnet(w, state)) : 0) ); } From 414589c54a6ac4d14abf73a011cbbff19a9bcd5d Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Sat, 13 Jun 2026 17:24:17 +0300 Subject: [PATCH 33/55] main: common leds api color format --- main/leds_api.c | 44 +++++++++++++++++++++++++++++++++++++++++ main/leds_api.h | 8 ++++++++ main/leds_http.c | 18 ++--------------- main/leds_http_post.c | 25 +---------------------- main/leds_static_http.c | 9 +++------ 5 files changed, 58 insertions(+), 46 deletions(-) create mode 100644 main/leds_api.c create mode 100644 main/leds_api.h diff --git a/main/leds_api.c b/main/leds_api.c new file mode 100644 index 00000000..a63cc002 --- /dev/null +++ b/main/leds_api.c @@ -0,0 +1,44 @@ +#include "leds_api.h" + +#include +#include + +int leds_api_write_color(struct json_writer *w, struct leds_color c, enum leds_parameter_type parameter_type) +{ + switch (parameter_type) { + case LEDS_PARAMETER_NONE: + return json_write_raw(w, "\"%02x%02x%02x\"", c.r, c.g, c.b); + + case LEDS_PARAMETER_DIMMER: + case LEDS_PARAMETER_WHITE: + return json_write_raw(w, "\"%02x%02x%02x.%02x\"", c.r, c.g, c.b, c.parameter); + + default: + LOG_FATAL("%d", parameter_type); + } +} + +int leds_api_color_parse(struct leds_color *color, enum leds_parameter_type parameter_type, const char *value) +{ + int rgb; + int parameter = leds_parameter_default_for_type(parameter_type); + + if (!value) { + return HTTP_UNPROCESSABLE_ENTITY; + } + + if (sscanf(value, "%x.%x", &rgb, ¶meter) <= 0) { + return HTTP_UNPROCESSABLE_ENTITY; + } + + if (parameter < 0 || parameter > UINT8_MAX) { + return HTTP_UNPROCESSABLE_ENTITY; + } + + color->r = (rgb >> 16) & 0xFF; + color->g = (rgb >> 8) & 0xFF; + color->b = (rgb >> 0) & 0xFF; + color->parameter = parameter; + + return 0; +} diff --git a/main/leds_api.h b/main/leds_api.h new file mode 100644 index 00000000..bcdc6ded --- /dev/null +++ b/main/leds_api.h @@ -0,0 +1,8 @@ +#pragma once + +#include +#include + +int leds_api_write_color(struct json_writer *w, struct leds_color c, enum leds_parameter_type parameter_type); + +int leds_api_color_parse(struct leds_color *color, enum leds_parameter_type parameter_type, const char *value); diff --git a/main/leds_http.c b/main/leds_http.c index 85de3d39..d12e1193 100644 --- a/main/leds_http.c +++ b/main/leds_http.c @@ -1,4 +1,5 @@ #include "leds.h" +#include "leds_api.h" #include "leds_state.h" #include "leds_status.h" #include "leds_artnet.h" @@ -16,21 +17,6 @@ #define TICK_MS(current_tick, tick) (tick ? (current_tick - tick) * portTICK_RATE_MS : 0) -static int leds_api_write_leds_color(struct json_writer *w, struct leds_color c, enum leds_parameter_type parameter_type) -{ - switch (parameter_type) { - case LEDS_PARAMETER_NONE: - return json_write_raw(w, "\"%02x%02x%02x\"", c.r, c.g, c.b); - - case LEDS_PARAMETER_DIMMER: - case LEDS_PARAMETER_WHITE: - return json_write_raw(w, "\"%02x%02x%02x.%02x\"", c.r, c.g, c.b, c.parameter); - - default: - LOG_FATAL("%d", parameter_type); - } -} - static int leds_api_write_object_options(struct json_writer *w, struct leds_state *state) { const struct leds_options *options = leds_options(state->leds); @@ -121,7 +107,7 @@ static int leds_api_write_object(struct json_writer *w, struct leds_state *state || JSON_WRITE_MEMBER_OBJECT(w, "options", leds_api_write_object_options(w, state)) || JSON_WRITE_MEMBER_OBJECT(w, "status", leds_api_write_object_status(w, state)) || JSON_WRITE_MEMBER_OBJECT(w, "static", - JSON_WRITE_MEMBER(w, "color", leds_api_write_leds_color(w, state->static_.color, leds_parameter_type_for_protocol(leds_protocol(state->leds)))) + JSON_WRITE_MEMBER(w, "color", leds_api_write_color(w, state->static_.color, leds_parameter_type_for_protocol(leds_protocol(state->leds)))) ) || (state->artnet ? JSON_WRITE_MEMBER_OBJECT(w, "artnet", leds_api_write_object_artnet(w, state)) : 0) ); diff --git a/main/leds_http_post.c b/main/leds_http_post.c index 060e981c..8e677e14 100644 --- a/main/leds_http_post.c +++ b/main/leds_http_post.c @@ -1,4 +1,5 @@ #include "leds.h" +#include "leds_api.h" #include "leds_config.h" #include "leds_state.h" #include "leds_test.h" @@ -14,30 +15,6 @@ struct leds_api_req { struct leds_state *state; }; -int leds_api_color_parse(struct leds_color *color, enum leds_parameter_type parameter_type, const char *value) -{ - int rgb; - int parameter = leds_parameter_default_for_type(parameter_type); - - if (!value) { - return HTTP_UNPROCESSABLE_ENTITY; - } - - if (sscanf(value, "%x.%x", &rgb, ¶meter) <= 0) { - return HTTP_UNPROCESSABLE_ENTITY; - } - - if (parameter < 0 || parameter > UINT8_MAX) { - return HTTP_UNPROCESSABLE_ENTITY; - } - - color->r = (rgb >> 16) & 0xFF; - color->g = (rgb >> 8) & 0xFF; - color->b = (rgb >> 0) & 0xFF; - color->parameter = parameter; - - return 0; -} int leds_api_state_parse(struct leds_api_req *req, const char *key, const char *value) { diff --git a/main/leds_static_http.c b/main/leds_static_http.c index cc9d618b..02a09133 100644 --- a/main/leds_static_http.c +++ b/main/leds_static_http.c @@ -1,4 +1,5 @@ #include "leds.h" +#include "leds_api.h" #include "leds_config.h" #include "leds_state.h" #include "leds_static.h" @@ -19,8 +20,6 @@ struct leds_api_static_params { int leds_api_static_params_set(struct leds_api_static_params *params, const char *key, const char *value) { - struct config_color config_color; - if (strcmp(key, "index") == 0) { unsigned index; @@ -36,12 +35,10 @@ int leds_api_static_params_set(struct leds_api_static_params *params, const char return HTTP_UNPROCESSABLE_ENTITY; } } else if (strcmp(key, "color") == 0) { - if (config_color_parse(&config_color, value)) { + if (!params->state) { return HTTP_UNPROCESSABLE_ENTITY; - } else if (!params->state) { + } else if (leds_api_color_parse(¶ms->color, leds_parameter_type_for_protocol(leds_protocol(params->state->leds)), value)) { return HTTP_UNPROCESSABLE_ENTITY; - } else { - params->color = config_leds_color(config_color, leds_parameter_type_for_protocol(leds_protocol(params->state->leds))); } } else { return HTTP_UNPROCESSABLE_ENTITY; From 38382902b32fd86e2874d53934b0fc49c38f4d19 Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Sat, 13 Jun 2026 17:50:47 +0300 Subject: [PATCH 34/55] web: LedsView default to first available leds --- web/src/components/LedsView.vue | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/web/src/components/LedsView.vue b/web/src/components/LedsView.vue index eee6b9a6..517356ce 100644 --- a/web/src/components/LedsView.vue +++ b/web/src/components/LedsView.vue @@ -13,7 +13,7 @@ {{ id }} @@ -170,7 +170,7 @@ export default { data: () => ({ loading: true, loadingStatus: false, - activeID: "leds1", + selectedID: null }), created() { this.load(); @@ -181,8 +181,17 @@ export default { return [...this.$store.state.leds.keys()]; } }, + activeID() { + if (this.selectedID) { + return this.selectedID; + } else if (this.$store.state.leds && this.$store.state.leds.size > 0) { + return this.$store.state.leds.keys().next().value; + } else { + return null; + } + }, activeLeds() { - if (this.$store.state.leds) { + if (this.$store.state.leds && this.activeID) { return this.$store.state.leds.get(this.activeID); } }, @@ -258,8 +267,8 @@ export default { this.loadingStatus = false; } }, - switchActive(id) { - this.activeID = id; + selectID(id) { + this.selectedID = id; }, } } From 3a629bbee013851ce2b6fd8ac6cfcc69aa9e96c6 Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Sat, 13 Jun 2026 18:15:03 +0300 Subject: [PATCH 35/55] main: leds http common leds=leds%d params --- main/leds_api.c | 26 ++++++++++++++++++++++++++ main/leds_api.h | 4 ++++ main/leds_http.c | 15 ++------------- main/leds_static_http.c | 18 +++--------------- main/leds_test_http.c | 23 +++++++++-------------- 5 files changed, 44 insertions(+), 42 deletions(-) diff --git a/main/leds_api.c b/main/leds_api.c index a63cc002..c51af09f 100644 --- a/main/leds_api.c +++ b/main/leds_api.c @@ -2,6 +2,32 @@ #include #include +#include + +int leds_api_leds_parse(struct leds_state **statep, const char *value) +{ + struct leds_state *state; + int index; + + if (sscanf(value, "leds%d", &index) <= 0) { + LOG_WARN("invalid leds=%s", value); + return HTTP_UNPROCESSABLE_ENTITY; + } else if (index <= 0 || index > LEDS_COUNT) { + LOG_WARN("invalid leds=%s index", value); + return HTTP_UNPROCESSABLE_ENTITY; + } else { + state = &leds_states[index - 1]; + } + + if (!state || !state->leds) { + LOG_WARN("disabled leds=%s", value); + return HTTP_UNPROCESSABLE_ENTITY; + } + + *statep = state; + + return 0; +} int leds_api_write_color(struct json_writer *w, struct leds_color c, enum leds_parameter_type parameter_type) { diff --git a/main/leds_api.h b/main/leds_api.h index bcdc6ded..d12c0676 100644 --- a/main/leds_api.h +++ b/main/leds_api.h @@ -1,8 +1,12 @@ #pragma once +#include "leds_state.h" + #include #include +int leds_api_leds_parse(struct leds_state **statep, const char *value); + int leds_api_write_color(struct json_writer *w, struct leds_color c, enum leds_parameter_type parameter_type); int leds_api_color_parse(struct leds_color *color, enum leds_parameter_type parameter_type, const char *value); diff --git a/main/leds_http.c b/main/leds_http.c index d12e1193..f3595985 100644 --- a/main/leds_http.c +++ b/main/leds_http.c @@ -165,22 +165,11 @@ struct leds_api_query { int leds_api_query(struct http_request *request, struct leds_api_query *query) { char *key, *value; - int index; int err; while (!(err = http_request_query(request, &key, &value))) { if (strcmp(key, "leds") == 0) { - if (sscanf(value, "leds%d", &index) <= 0) { - LOG_WARN("invalid ?leds=%s", value); - return HTTP_UNPROCESSABLE_ENTITY; - } else if (index > 0 && index <= LEDS_COUNT) { - query->state = &leds_states[index - 1]; - } else { - LOG_WARN("invalid ?leds=%s", value); - return HTTP_UNPROCESSABLE_ENTITY; - } - - return 0; + return leds_api_leds_parse(&query->state, value); } } @@ -190,7 +179,7 @@ int leds_api_query(struct http_request *request, struct leds_api_query *query) } if (!query->state) { - LOG_WARN("missing ?leds=..."); + LOG_WARN("missing leds=..."); return HTTP_UNPROCESSABLE_ENTITY; } diff --git a/main/leds_static_http.c b/main/leds_static_http.c index 02a09133..1a88393a 100644 --- a/main/leds_static_http.c +++ b/main/leds_static_http.c @@ -20,20 +20,8 @@ struct leds_api_static_params { int leds_api_static_params_set(struct leds_api_static_params *params, const char *key, const char *value) { - if (strcmp(key, "index") == 0) { - unsigned index; - - if (sscanf(value, "%d", &index) <= 0) { - return HTTP_UNPROCESSABLE_ENTITY; - } else if (index <= 0 || index > LEDS_COUNT) { - return HTTP_UNPROCESSABLE_ENTITY; - } else { - params->state = &leds_states[index - 1]; - } - - if (!params->state->leds) { - return HTTP_UNPROCESSABLE_ENTITY; - } + if (strcmp(key, "leds") == 0) { + return leds_api_leds_parse(¶ms->state, value); } else if (strcmp(key, "color") == 0) { if (!params->state) { return HTTP_UNPROCESSABLE_ENTITY; @@ -65,7 +53,7 @@ int leds_api_static_read_form_params(struct http_request *request, struct leds_a } if (!params->state) { - LOG_WARN("missing index"); + LOG_WARN("missing leds="); return HTTP_UNPROCESSABLE_ENTITY; } diff --git a/main/leds_test_http.c b/main/leds_test_http.c index f6a88495..09ea2b30 100644 --- a/main/leds_test_http.c +++ b/main/leds_test_http.c @@ -1,4 +1,5 @@ #include "leds.h" +#include "leds_api.h" #include "leds_state.h" #include "leds_test.h" #include "http_routes.h" @@ -57,16 +58,8 @@ struct leds_api_test_params { int leds_api_test_params_set(struct leds_api_test_params *params, const char *key, const char *value) { - if (strcmp(key, "index") == 0) { - unsigned index; - - if (sscanf(value, "%d", &index) <= 0) { - return HTTP_UNPROCESSABLE_ENTITY; - } else if (index <= 0 || index > LEDS_COUNT) { - return HTTP_UNPROCESSABLE_ENTITY; - } else { - params->state = &leds_states[index - 1]; - } + if (strcmp(key, "leds") == 0) { + return leds_api_leds_parse(¶ms->state, value); } else if (strcmp(key, "mode") == 0) { int mode; @@ -108,6 +101,11 @@ int leds_api_test_read_form_params(struct http_request *request, struct leds_api return err; } + if (!params->state) { + LOG_WARN("missing leds="); + return HTTP_UNPROCESSABLE_ENTITY; + } + return 0; } @@ -140,10 +138,7 @@ int leds_api_test_post(struct http_request *request, struct http_response *respo } // TODO: clear? - if (!params.state) { - LOG_WARN("missing index"); - return HTTP_UNPROCESSABLE_ENTITY; - } else if ((err = set_leds_test(state, params.mode, params.auto_mode)) < 0) { + if ((err = set_leds_test(state, params.mode, params.auto_mode)) < 0) { LOG_ERROR("set_leds_test"); return HTTP_INTERNAL_SERVER_ERROR; } else if (err) { From 2dfaa9f76d7a19a6b4389ecc2db17795dbb20cf1 Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Sat, 13 Jun 2026 18:16:02 +0300 Subject: [PATCH 36/55] web: drop experimental color input alpha --- web/src/components/ConfigView.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/components/ConfigView.vue b/web/src/components/ConfigView.vue index 0e78c6a4..b7a21d06 100644 --- a/web/src/components/ConfigView.vue +++ b/web/src/components/ConfigView.vue @@ -149,7 +149,7 @@ - Date: Sat, 13 Jun 2026 18:17:11 +0300 Subject: [PATCH 37/55] web: tabbed split view, controls actions fieldset button margin --- web/src/styles/form.css | 1 + web/src/styles/main.css | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/web/src/styles/form.css b/web/src/styles/form.css index 8c880e5b..ab2a791a 100644 --- a/web/src/styles/form.css +++ b/web/src/styles/form.css @@ -223,6 +223,7 @@ fieldset.actions label { fieldset.actions button { grid-column: 2; + margin: 0.5em 1em; padding: 0.5em 1em; background-color: #133337; diff --git a/web/src/styles/main.css b/web/src/styles/main.css index da6a3d56..730f88c8 100644 --- a/web/src/styles/main.css +++ b/web/src/styles/main.css @@ -130,7 +130,7 @@ nav a.tabbed-active { /* View */ @media (orientation: landscape) { - main.split { + .split { padding: 0; display: flex; @@ -143,13 +143,13 @@ nav a.tabbed-active { padding: 1em 20%; } - main.split .view { + .split .view { padding: 1em; } } @media (orientation: portrait) { - main.split { + .split { padding: 0; display: flex; @@ -162,16 +162,16 @@ nav a.tabbed-active { padding: 1em; } - main.split .view { + .split .view { padding: 0.5em; } } -main.split .view { +.split .view { flex: 2; } -main.split .controls { +.split .controls { flex: 1; } From c35ae15b5e3059dc04fb50b3df200c8318e250d1 Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Sat, 13 Jun 2026 18:18:56 +0300 Subject: [PATCH 38/55] web: leds static color picker --- web/src/components/LedsView.vue | 270 ++++++++++++++++++------------- web/src/services/leds.service.js | 3 + web/src/store.js | 3 + web/src/styles/form.css | 4 + 4 files changed, 165 insertions(+), 115 deletions(-) diff --git a/web/src/components/LedsView.vue b/web/src/components/LedsView.vue index 517356ce..b1b3db06 100644 --- a/web/src/components/LedsView.vue +++ b/web/src/components/LedsView.vue @@ -18,145 +18,158 @@ {{ id }} -
-

- LEDS ({{ activeID }}) - Loading... - -

+
+
+

+ LEDS ({{ activeID }}) + Loading... + +

- - - - + +
+
+

Static

+
+ + + Applying... + + +
+ +
@@ -170,7 +183,9 @@ export default { data: () => ({ loading: true, loadingStatus: false, - selectedID: null + selectedID: null, + applyingStatic: false, + staticColor: null, }), created() { this.load(); @@ -210,6 +225,11 @@ export default { return this.activeLeds.status; } }, + static() { + if (this.activeLeds) { + return this.activeLeds.static; + } + }, }, filters: { limitPower: function(limit) { @@ -257,6 +277,8 @@ export default { } finally { this.loading = false; } + + this.staticColor = this.static.color; }, async loadStatus() { this.loadingStatus = true; @@ -270,6 +292,24 @@ export default { selectID(id) { this.selectedID = id; }, + async postStaticColor() { + let type = event.target.type; + let value = event.target.value; + + if (type == "color") { + // strip # prefix + value = value.substring(1); + } + + this.staticColor = value; + this.applyingStatic = true; + + try { + await this.$store.dispatch('postLedsStatic', { leds: this.activeID, color: this.staticColor }); + } finally { + this.applyingStatic = false; + } + }, } } diff --git a/web/src/services/leds.service.js b/web/src/services/leds.service.js index 34a9d4eb..3f4f8c7a 100644 --- a/web/src/services/leds.service.js +++ b/web/src/services/leds.service.js @@ -13,5 +13,8 @@ export default class LedsService { return response.data; } + async postStatic(leds, color) { + const response = await this.apiService.post('/api/leds/static', new URLSearchParams({ leds, color })); + } } \ No newline at end of file diff --git a/web/src/store.js b/web/src/store.js index 2fa66679..d7c41723 100644 --- a/web/src/store.js +++ b/web/src/store.js @@ -156,6 +156,9 @@ export default new Vuex.Store({ commit('updateLedsStatus', {id, status}); }, + async postLedsStatic({ commit }, {leds, color}) { + const status = await ledsService.postStatic(leds, color); + }, /* VFS */ async loadVFS({ commit }) { diff --git a/web/src/styles/form.css b/web/src/styles/form.css index ab2a791a..a58920fe 100644 --- a/web/src/styles/form.css +++ b/web/src/styles/form.css @@ -160,6 +160,10 @@ fieldset input[type="file"] { margin: auto 0; } +fieldset input[type="color"] { + background: none; +} + fieldset div.actions button { width: 1.8em; height: 1.8em; From 3afe165ae76345b4e867c84607555ddb60de763c Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Sat, 13 Jun 2026 18:32:49 +0300 Subject: [PATCH 39/55] web: leds static color save --- web/src/components/LedsView.vue | 18 ++++++++++++++++-- web/src/services/config.service.js | 10 ++++++++++ web/src/store.js | 12 ++++++++++++ 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/web/src/components/LedsView.vue b/web/src/components/LedsView.vue index b1b3db06..f579a128 100644 --- a/web/src/components/LedsView.vue +++ b/web/src/components/LedsView.vue @@ -164,9 +164,9 @@
- Applying... + Applying... - +
@@ -185,6 +185,7 @@ export default { loadingStatus: false, selectedID: null, applyingStatic: false, + savingStatic: false, staticColor: null, }), created() { @@ -310,6 +311,19 @@ export default { this.applyingStatic = false; } }, + async saveStaticColor() { + this.savingStatic = true; + + try { + await this.$store.dispatch('setConfig', { + modname: this.activeID, + tabname: 'static_color', + value: this.staticColor + }); + } finally { + this.savingStatic = false; + } + }, } } diff --git a/web/src/services/config.service.js b/web/src/services/config.service.js index dec6ac89..ff54102a 100644 --- a/web/src/services/config.service.js +++ b/web/src/services/config.service.js @@ -32,6 +32,16 @@ export default class ConfigService { return response.data } + async set(modname, tabname, value) { + let data = new URLSearchParams(); + let name = '[' + modname + ']' + tabname; + + data.append(name, value); + + const response = await this.apiService.post('/api/config', data); + + return response.data; + } async post(config, formdata) { let data = new URLSearchParams(); diff --git a/web/src/store.js b/web/src/store.js index d7c41723..67a8d226 100644 --- a/web/src/store.js +++ b/web/src/store.js @@ -100,6 +100,18 @@ export default new Vuex.Store({ async uploadConfig({ commit }, file) { const configState = await configService.upload(file); }, + async setConfig({ state, commit }, { modname, tabname, value }) { + try { + const configState = await configService.set(modname, tabname, value); + + commit('updateConfigState', configState); + } catch (error) { + if (error.name == "APIError" && error.data) { + commit('updateConfigState', error.data); + } + throw error; + } + }, /* system */ async loadSystem({ commit }) { From c3c498bb9a2f87195bb7a81a7c68f8f5d445ab33 Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Sat, 13 Jun 2026 18:33:17 +0300 Subject: [PATCH 40/55] web: form color input styles --- web/src/styles/form.css | 1 + 1 file changed, 1 insertion(+) diff --git a/web/src/styles/form.css b/web/src/styles/form.css index a58920fe..000076e5 100644 --- a/web/src/styles/form.css +++ b/web/src/styles/form.css @@ -162,6 +162,7 @@ fieldset input[type="file"] { fieldset input[type="color"] { background: none; + padding: 0; } fieldset div.actions button { From 328a0f135ee00fd4b30ba42578164c85e085c2e2 Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Sat, 13 Jun 2026 18:33:51 +0300 Subject: [PATCH 41/55] main: fix leds config static_color description --- main/leds_configtab.i | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main/leds_configtab.i b/main/leds_configtab.i index 710173cc..448446ed 100644 --- a/main/leds_configtab.i +++ b/main/leds_configtab.i @@ -262,11 +262,11 @@ const struct configtab LEDS_CONFIGTAB[] = { }, { CONFIG_TYPE_BOOL, "static_enabled", - .description = "Output static color", + .description = "Output static color at boot", .bool_type = { .value = &LEDS_CONFIG.static_enabled }, }, { CONFIG_TYPE_COLOR, "static_color", - .description = "LED color format for sequence channels", + .description = "Static color to output at boot", .color_type = { .value = &LEDS_CONFIG.static_color }, }, From 26122903f4db9172ce515018209884c0dbfe31ce Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Sat, 13 Jun 2026 18:39:14 +0300 Subject: [PATCH 42/55] web: config static_enabled=true on leds static color save --- web/src/components/LedsView.vue | 9 ++++----- web/src/services/config.service.js | 9 ++++++--- web/src/store.js | 4 ++-- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/web/src/components/LedsView.vue b/web/src/components/LedsView.vue index f579a128..51005f8d 100644 --- a/web/src/components/LedsView.vue +++ b/web/src/components/LedsView.vue @@ -315,11 +315,10 @@ export default { this.savingStatic = true; try { - await this.$store.dispatch('setConfig', { - modname: this.activeID, - tabname: 'static_color', - value: this.staticColor - }); + await this.$store.dispatch('setConfig', [ + { modname: this.activeID, tabname: 'static_enabled', value: true }, + { modname: this.activeID, tabname: 'static_color', value: this.staticColor }, + ]); } finally { this.savingStatic = false; } diff --git a/web/src/services/config.service.js b/web/src/services/config.service.js index ff54102a..10f657a1 100644 --- a/web/src/services/config.service.js +++ b/web/src/services/config.service.js @@ -32,11 +32,14 @@ export default class ConfigService { return response.data } - async set(modname, tabname, value) { + async set(entries) { let data = new URLSearchParams(); - let name = '[' + modname + ']' + tabname; - data.append(name, value); + for (let e of entries) { + let name = '[' + e.modname + ']' + e.tabname; + + data.append(name, e.value); + } const response = await this.apiService.post('/api/config', data); diff --git a/web/src/store.js b/web/src/store.js index 67a8d226..92b21133 100644 --- a/web/src/store.js +++ b/web/src/store.js @@ -100,9 +100,9 @@ export default new Vuex.Store({ async uploadConfig({ commit }, file) { const configState = await configService.upload(file); }, - async setConfig({ state, commit }, { modname, tabname, value }) { + async setConfig({ state, commit }, entries) { try { - const configState = await configService.set(modname, tabname, value); + const configState = await configService.set(entries); commit('updateConfigState', configState); } catch (error) { From 56a494f98a2f8b6472cb756fdb6a1332e7b3711c Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Sat, 13 Jun 2026 19:08:09 +0300 Subject: [PATCH 43/55] main: remove unused clear_leds() --- main/leds.c | 23 ----------------------- main/leds_state.h | 5 ----- 2 files changed, 28 deletions(-) diff --git a/main/leds.c b/main/leds.c index fd4b81a1..79827e05 100644 --- a/main/leds.c +++ b/main/leds.c @@ -272,26 +272,3 @@ int update_leds(struct leds_state *state, enum user_activity leds_activity) return 0; } - -int clear_leds(struct leds_state *state) -{ - int err; - - if ((err = check_leds_interface(state))) { - return err; - } - - if ((err = leds_clear_all(state->leds))) { - LOG_ERROR("leds_set_all"); - return err; - } - - if ((err = leds_tx(state->leds))) { - LOG_ERROR("leds_tx"); - return err; - } - - clear_leds_active(state); - - return 0; -} diff --git a/main/leds_state.h b/main/leds_state.h index 3d72877a..ca6d2ea6 100644 --- a/main/leds_state.h +++ b/main/leds_state.h @@ -81,8 +81,3 @@ int reset_leds(struct leds_state *state); * Update LEDs output with given USER_ACTIVITY_LEDS_* source. */ int update_leds(struct leds_state *state, enum user_activity leds_activity); - -/* - * Variant of update_leds() that turns off all LEDs, and does NOT flash the status leds. - */ -int clear_leds(struct leds_state *state); From 5096be5f9791bc6d4d507b7927b676fc240b5e25 Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Sat, 13 Jun 2026 19:09:08 +0300 Subject: [PATCH 44/55] main: route update_leds() via task --- main/leds.c | 6 +--- main/leds_cmd.c | 4 ++- main/leds_http_post.c | 2 +- main/leds_state.h | 4 +-- main/leds_stats.c | 3 +- main/leds_stats.h | 3 +- main/leds_task.c | 75 ++++++++++++++++++++++++++++++++++--------- main/leds_task.h | 6 +++- 8 files changed, 75 insertions(+), 28 deletions(-) diff --git a/main/leds.c b/main/leds.c index 79827e05..535320ed 100644 --- a/main/leds.c +++ b/main/leds.c @@ -251,7 +251,7 @@ void clear_leds_active(struct leds_state *state) clear_atx_psu_bit(ATX_PSU_BIT_LEDS1 + state->index); } -int update_leds(struct leds_state *state, enum user_activity leds_activity) +int output_leds(struct leds_state *state) { int err; @@ -261,10 +261,6 @@ int update_leds(struct leds_state *state, enum user_activity leds_activity) return err; } - if (leds_activity) { - user_activity(leds_activity); - } - if ((err = leds_tx(state->leds))) { LOG_ERROR("leds_tx"); return err; diff --git a/main/leds_cmd.c b/main/leds_cmd.c index 181587fc..9931b1bd 100644 --- a/main/leds_cmd.c +++ b/main/leds_cmd.c @@ -3,6 +3,7 @@ #include "leds_static.h" #include "leds_status.h" #include "leds_artnet.h" +#include "leds_task.h" #include "leds_test.h" #include "leds_config.h" #include "leds_stats.h" @@ -410,7 +411,7 @@ int leds_cmd_stats(int argc, char **argv, void *ctx) print_stats_timer("task", "artnet", &stats->artnet); print_stats_timer("task", "sequence", &stats->sequence); print_stats_timer("task", "static", &stats->static_); - print_stats_timer("task", "update", &stats->update); + print_stats_timer("task", "output", &stats->output); printf("\n"); print_stats_counter("artnet", "timeout", &stats->artnet_timeout); print_stats_counter("artnet", "sync", &stats->artnet_sync); @@ -418,6 +419,7 @@ int leds_cmd_stats(int argc, char **argv, void *ctx) print_stats_counter("sync", "timeout", &stats->sync_timeout); print_stats_counter("sync", "missed", &stats->sync_missed); print_stats_counter("sync", "full", &stats->sync_full); + print_stats_counter("update", "", &stats->update); print_stats_counter("update", "timeout", &stats->update_timeout); printf("\n"); } diff --git a/main/leds_http_post.c b/main/leds_http_post.c index 8e677e14..cf6397db 100644 --- a/main/leds_http_post.c +++ b/main/leds_http_post.c @@ -2,7 +2,7 @@ #include "leds_api.h" #include "leds_config.h" #include "leds_state.h" -#include "leds_test.h" +#include "leds_task.h" #include "http_routes.h" #include "http_handlers.h" diff --git a/main/leds_state.h b/main/leds_state.h index ca6d2ea6..e54f0f63 100644 --- a/main/leds_state.h +++ b/main/leds_state.h @@ -78,6 +78,6 @@ int setup_leds(struct leds_state *state); int reset_leds(struct leds_state *state); /* - * Update LEDs output with given USER_ACTIVITY_LEDS_* source. + * Update LEDs output. */ -int update_leds(struct leds_state *state, enum user_activity leds_activity); +int output_leds(struct leds_state *state); diff --git a/main/leds_stats.c b/main/leds_stats.c index 2f285495..3eafabb9 100644 --- a/main/leds_stats.c +++ b/main/leds_stats.c @@ -22,7 +22,7 @@ void init_leds_stats() stats_timer_init(&stats->artnet); stats_timer_init(&stats->sequence); stats_timer_init(&stats->static_); - stats_timer_init(&stats->update); + stats_timer_init(&stats->output); stats_counter_init(&stats->artnet_timeout); stats_counter_init(&stats->artnet_sync); @@ -30,6 +30,7 @@ void init_leds_stats() stats_counter_init(&stats->sync_timeout); stats_counter_init(&stats->sync_missed); stats_counter_init(&stats->sync_full); + stats_counter_init(&stats->update); stats_counter_init(&stats->update_timeout); } } diff --git a/main/leds_stats.h b/main/leds_stats.h index 046dc464..c977b1d6 100644 --- a/main/leds_stats.h +++ b/main/leds_stats.h @@ -28,7 +28,8 @@ struct leds_stats { struct stats_timer static_; - struct stats_timer update; + struct stats_timer output; + struct stats_counter update; struct stats_counter update_timeout; }; diff --git a/main/leds_task.c b/main/leds_task.c index 08f7aa46..ae4fef4b 100644 --- a/main/leds_task.c +++ b/main/leds_task.c @@ -22,7 +22,16 @@ static TickType_t leds_update_wait(struct leds_state *state) } -static bool leds_update_active(struct leds_state *state) +static bool leds_update_active(struct leds_state *state, EventBits_t bits) +{ + if (bits & (1 << LEDS_EVENT_UPDATE_BIT)) { + return true; + } + + return false; +} + +static bool leds_update_timeout_expired(struct leds_state *state) { // not configured if (!state->config->update_timeout) { @@ -94,12 +103,13 @@ static EventBits_t leds_task_wait(struct leds_state *state) const bool wait_for_all_bits = false; EventBits_t event_bits = xEventGroupWaitBits(state->event_group, LEDS_EVENT_BITS, clear_on_exit, wait_for_all_bits, wait_ticks); - LOG_DEBUG("leds%d: test=%d artnet_dmx=%d artnet_sync=%d sequence=%d static=%d", state->index + 1, + LOG_DEBUG("leds%d: test=%d artnet_dmx=%d artnet_sync=%d sequence=%d static=%d update=%d", state->index + 1, !!(event_bits & (1 << LEDS_EVENT_TEST_BIT)), !!(event_bits & (1 << LEDS_EVENT_ARTNET_DMX_BIT)), !!(event_bits & (1 << LEDS_EVENT_ARTNET_SYNC_BIT)), !!(event_bits & (1 << LEDS_EVENT_SEQUENCE_BIT)), - !!(event_bits & (1 << LEDS_EVENT_STATIC_BIT)) + !!(event_bits & (1 << LEDS_EVENT_STATIC_BIT)), + !!(event_bits & (1 << LEDS_EVENT_UPDATE_BIT)) ); return event_bits; @@ -134,8 +144,7 @@ static void leds_main(void *ctx) for(stats_timer_start_t loop_start;; stats_timer_stop(&stats->loop, &loop_start)) { EventBits_t event_bits = leds_task_wait(state); - enum user_activity update_activity = 0; - bool update_timeout = false; + bool update = false; loop_start = stats_timer_start(&stats->loop); @@ -146,7 +155,9 @@ static void leds_main(void *ctx) WITH_STATS_TIMER(&stats->static_) { if (leds_static_update(state, event_bits)) { - update_activity = USER_ACTIVITY_LEDS_STATIC; + user_activity(USER_ACTIVITY_LEDS_STATIC); + + update = true; } } } @@ -158,7 +169,9 @@ static void leds_main(void *ctx) WITH_STATS_TIMER(&stats->test) { if (leds_test_update(state, event_bits)) { - update_activity = USER_ACTIVITY_LEDS_TEST; + user_activity(USER_ACTIVITY_LEDS_TEST); + + update = true; } } } @@ -170,7 +183,9 @@ static void leds_main(void *ctx) WITH_STATS_TIMER(&stats->sequence) { if (leds_sequence_update(state, event_bits)) { - update_activity = USER_ACTIVITY_LEDS_SEQUENCE; + user_activity(USER_ACTIVITY_LEDS_SEQUENCE); + + update = true; } } } @@ -186,11 +201,15 @@ static void leds_main(void *ctx) break; case LEDS_ARTNET_UPDATE: - update_activity = USER_ACTIVITY_LEDS_ARTNET; + user_activity(USER_ACTIVITY_LEDS_ARTNET); + + update = true; break; case LEDS_ARTNET_UPDATE_TIMEOUT: - update_activity = USER_ACTIVITY_LEDS_ARTNET_TIMEOUT; + user_activity(USER_ACTIVITY_LEDS_ARTNET_TIMEOUT); + + update = true; break; default: @@ -199,23 +218,31 @@ static void leds_main(void *ctx) } } - if (leds_update_active(state)) { + if (leds_update_active(state, event_bits)) { + LOG_DEBUG("update"); + + // external + update = true; + + stats_counter_increment(&stats->update); + + } else if (leds_update_timeout_expired(state)) { LOG_DEBUG("update timeout"); // update without activity - update_timeout = true; + update = true; stats_counter_increment(&stats->update_timeout); } - if (update_activity || update_timeout) { + if (update) { LOG_DEBUG("update"); state->update_tick = xTaskGetTickCount(); - WITH_STATS_TIMER(&stats->update) { - if (update_leds(state, update_activity)) { - LOG_WARN("leds%d: update_leds", state->index + 1); + WITH_STATS_TIMER(&stats->output) { + if (output_leds(state)) { + LOG_WARN("leds%d: output_leds", state->index + 1); user_alert(USER_ALERT_ERROR_LEDS); reset_leds(state); } @@ -286,3 +313,19 @@ void notify_leds_tasks(EventBits_t bits) notify_leds_task(state, bits); } } + +int update_leds(struct leds_state *state, enum user_activity activity) +{ + if (!state->event_group) { + LOG_WARN("leds%d: not initialized", state->index + 1); + return -1; + } + + if (activity) { + user_activity(activity); + } + + xEventGroupSetBits(state->event_group, 1 << LEDS_EVENT_UPDATE_BIT); + + return 0; +} diff --git a/main/leds_task.h b/main/leds_task.h index af379f44..f04e17ff 100644 --- a/main/leds_task.h +++ b/main/leds_task.h @@ -5,7 +5,7 @@ #include -#define LEDS_EVENT_BITS 0x1f +#define LEDS_EVENT_BITS 0x003f enum leds_event_bit { LEDS_EVENT_TEST_BIT = 0, @@ -13,6 +13,7 @@ enum leds_event_bit { LEDS_EVENT_ARTNET_SYNC_BIT = 2, LEDS_EVENT_SEQUENCE_BIT = 3, LEDS_EVENT_STATIC_BIT = 4, + LEDS_EVENT_UPDATE_BIT = 5, }; int init_leds_task(struct leds_state *state, const struct leds_config *config); @@ -20,3 +21,6 @@ int start_leds_task(struct leds_state *state, const struct leds_config *config); void notify_leds_task(struct leds_state *state, EventBits_t bits); void notify_leds_tasks(EventBits_t bits); + +/* Trigger task for leds output update */ +int update_leds(struct leds_state *state, enum user_activity activity); From 84c17cef8074625cf99943fcc6604614deddc531 Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Sat, 13 Jun 2026 19:13:48 +0300 Subject: [PATCH 45/55] leds_clear_all() cannot fail --- components/leds/include/leds.h | 2 +- components/leds/leds.c | 4 +--- main/leds_artnet.c | 8 ++------ main/leds_cmd.c | 5 +---- main/leds_test.c | 4 +--- 5 files changed, 6 insertions(+), 17 deletions(-) diff --git a/components/leds/include/leds.h b/components/leds/include/leds.h index 590adf82..0f8d0ab6 100644 --- a/components/leds/include/leds.h +++ b/components/leds/include/leds.h @@ -400,7 +400,7 @@ unsigned leds_count(struct leds *leds); /* * Set all LEDs off. */ -int leds_clear_all(struct leds *leds); +void leds_clear_all(struct leds *leds); /* * @param index 0-based index diff --git a/components/leds/leds.c b/components/leds/leds.c index 9e629c6d..184a56a9 100644 --- a/components/leds/leds.c +++ b/components/leds/leds.c @@ -101,7 +101,7 @@ unsigned leds_count(struct leds *leds) return leds->options.count; } -int leds_clear_all(struct leds *leds) +void leds_clear_all(struct leds *leds) { struct leds_color color = {}; // all off @@ -110,8 +110,6 @@ int leds_clear_all(struct leds *leds) for (unsigned i = 0; i < leds->options.count; i++) { leds->pixels[i] = color; } - - return 0; } int leds_set(struct leds *leds, unsigned index, struct leds_color color) diff --git a/main/leds_artnet.c b/main/leds_artnet.c index f3a94bc1..05cbfe6b 100644 --- a/main/leds_artnet.c +++ b/main/leds_artnet.c @@ -187,9 +187,7 @@ static void leds_artnet_timeout(struct leds_state *state) // TODO: flash user alert? // TODO: fallback to static, sequence? - if (leds_clear_all(state->leds)) { - LOG_WARN("leds_clear_all"); - } + leds_clear_all(state->leds); } TickType_t leds_artnet_wait(struct leds_state *state) @@ -294,9 +292,7 @@ static void leds_artnet_clean(struct leds_state *state) { if (!state->artnet->update_clean) { // incoming artnet data overrides any other type of output, ensure leds for any missing universes are cleared - if (leds_clear_all(state->leds)) { - LOG_ERROR("leds_clear_all"); - } + leds_clear_all(state->leds); state->artnet->update_clean = true; } diff --git a/main/leds_cmd.c b/main/leds_cmd.c index 9931b1bd..8b88cda8 100644 --- a/main/leds_cmd.c +++ b/main/leds_cmd.c @@ -152,10 +152,7 @@ int leds_cmd_clear(int argc, char **argv, void *ctx) } } else { // XXX: unsafe - if ((err = leds_clear_all(state->leds))) { - LOG_ERROR("leds_set_all"); - return err; - } + _leds_cmd_clear(state); if ((err = update_leds(state, USER_ACTIVITY_LEDS_CMD))) { LOG_ERROR("update_leds"); diff --git a/main/leds_test.c b/main/leds_test.c index de2d6e29..347ec9ef 100644 --- a/main/leds_test.c +++ b/main/leds_test.c @@ -203,9 +203,7 @@ int leds_test_update(struct leds_state *state, EventBits_t bits) if (!state->test->mode) { LOG_DEBUG("clear mode=%d auto=%d frame=%d frame_tick=%d", state->test->mode, state->test->auto_mode, state->test->frame, state->test->frame_tick); - if (leds_clear_all(state->leds)) { - LOG_WARN("leds_clear_all"); - } + leds_clear_all(state->leds); leds_test_clear(state); From db463ac3692bf1bb5113e9a76d8eb0d28b08c4a7 Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Sat, 13 Jun 2026 20:14:35 +0300 Subject: [PATCH 46/55] leds: leds_set_all() cannot fail --- components/leds/include/leds.h | 2 +- components/leds/leds.c | 4 +--- components/leds/test.c | 22 ++-------------------- main/leds_http_post.c | 4 +++- main/leds_static.c | 5 +---- 5 files changed, 8 insertions(+), 29 deletions(-) diff --git a/components/leds/include/leds.h b/components/leds/include/leds.h index 0f8d0ab6..9fa7ff1a 100644 --- a/components/leds/include/leds.h +++ b/components/leds/include/leds.h @@ -413,7 +413,7 @@ int leds_set(struct leds *leds, unsigned index, struct leds_color color); * @param global 5-bit global brightness 0-31 * @param b, g, r 8-bit RGB value */ -int leds_set_all(struct leds *leds, struct leds_color color); +void leds_set_all(struct leds *leds, struct leds_color color); /* * Decode LED colors from binary data, using given format. diff --git a/components/leds/leds.c b/components/leds/leds.c index 184a56a9..0783bf2e 100644 --- a/components/leds/leds.c +++ b/components/leds/leds.c @@ -127,7 +127,7 @@ int leds_set(struct leds *leds, unsigned index, struct leds_color color) return 0; } -int leds_set_all(struct leds *leds, struct leds_color color) +void leds_set_all(struct leds *leds, struct leds_color color) { LOG_DEBUG("[%03d] %02x:%02x%02x%02x", leds->options.count, color.parameter, color.r, color.g, color.b); @@ -136,8 +136,6 @@ int leds_set_all(struct leds *leds, struct leds_color color) for (unsigned i = 0; i < leds->options.count; i++) { leds->pixels[i] = color; } - - return 0; } unsigned leds_count_active(struct leds *leds) diff --git a/components/leds/test.c b/components/leds/test.c index 624308a5..5d8e52cf 100644 --- a/components/leds/test.c +++ b/components/leds/test.c @@ -35,9 +35,7 @@ int leds_test_chase_frame(struct leds *leds, unsigned frame, struct leds_color c } // black - if ((err = leds_set_all(leds, (struct leds_color){ }))) { - return err; - } + leds_clear_all(leds); if ((err = leds_set(leds, frame % count, color))) { return err; @@ -48,8 +46,6 @@ int leds_test_chase_frame(struct leds *leds, unsigned frame, struct leds_color c int leds_test_color_frame(struct leds *leds, unsigned frame, struct leds_color color) { - int err; - switch (leds_parameter_type(leds)) { case LEDS_PARAMETER_NONE: break; @@ -62,9 +58,7 @@ int leds_test_color_frame(struct leds *leds, unsigned frame, struct leds_color c break; } - if ((err = leds_set_all(leds, color))) { - return err; - } + leds_set_all(leds, color); if (frame < TEST_MODE_COLOR_FRAMES) { return TEST_FRAME_TICKS; @@ -154,18 +148,6 @@ int leds_test_rainbow_frame(struct leds *leds, unsigned frame) return TEST_FRAME_TICKS; } -int leds_test_black_frame(struct leds *leds, unsigned frame) -{ - int err; - - // black - if ((err = leds_set_all(leds, (struct leds_color){ }))) { - return err; - } - - return 0; -} - /* * Return number of ticks for this frame, 0 for last frame, <0 on error. */ diff --git a/main/leds_http_post.c b/main/leds_http_post.c index cf6397db..9bc8dd5a 100644 --- a/main/leds_http_post.c +++ b/main/leds_http_post.c @@ -53,7 +53,9 @@ int leds_api_state_parse(struct leds_api_req *req, const char *key, const char * return ret; } - return leds_set_all(leds, color); + leds_set_all(leds, color); + + return 0; } else if (sscanf(key, "%u", &index) > 0) { if ((ret = leds_api_color_parse(&color, parameter_type, value))) { diff --git a/main/leds_static.c b/main/leds_static.c index e13993c4..7bb6442a 100644 --- a/main/leds_static.c +++ b/main/leds_static.c @@ -49,10 +49,7 @@ int leds_static_update(struct leds_state *state, EventBits_t bits) { LOG_INFO("color=%02x%02x%02x.%02x", state->static_.color.r, state->static_.color.g, state->static_.color.b, state->static_.color.parameter); - if (leds_set_all(state->leds, state->static_.color)) { - LOG_ERROR("leds_set_all"); - return -1; - } + leds_set_all(state->leds, state->static_.color); return 1; } From aa0554feed0c412e92c5be439b673f4bf88ac78d Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Sat, 13 Jun 2026 20:28:01 +0300 Subject: [PATCH 47/55] main: use mutex and start_leds_update() / end_leds_update() for cmd/http updates --- main/leds.c | 5 ++ main/leds_cmd.c | 104 ++++++++++++++++++++++++++++++++---------- main/leds_http_post.c | 63 +++++++++++++------------ main/leds_state.h | 12 ++++- main/leds_task.c | 47 +++++++++++++++++-- main/leds_task.h | 3 -- 6 files changed, 169 insertions(+), 65 deletions(-) diff --git a/main/leds.c b/main/leds.c index 535320ed..4b29975d 100644 --- a/main/leds.c +++ b/main/leds.c @@ -76,6 +76,11 @@ int init_leds() continue; } + if (!(state->mutex = xSemaphoreCreateRecursiveMutex())) { + LOG_ERROR("xSemaphoreCreateRecursiveMutex"); + return -1; + } + if ((err = config_leds(state, config))) { LOG_ERROR("leds%d: config_leds", i+1); return err; diff --git a/main/leds_cmd.c b/main/leds_cmd.c index 8b88cda8..c6f6fd67 100644 --- a/main/leds_cmd.c +++ b/main/leds_cmd.c @@ -16,6 +16,8 @@ #include +#define CMD_LEDS_MUTEX_TIMEOUT (1000 / portTICK_RATE_MS) + int leds_cmd_info(int argc, char **argv, void *ctx) { for (int i = 0; i < LEDS_COUNT; i++) { @@ -145,20 +147,14 @@ int leds_cmd_clear(int argc, char **argv, void *ctx) continue; } - if (state->test) { - if ((err = clear_leds_test(state))) { - LOG_ERROR("clear_leds_test"); - return err; - } - } else { - // XXX: unsafe - _leds_cmd_clear(state); - - if ((err = update_leds(state, USER_ACTIVITY_LEDS_CMD))) { - LOG_ERROR("update_leds"); - return err; - } + if ((err = start_leds_update(state, LEDS_UPDATE_CMD))) { + LOG_ERROR("start_leds_update"); + continue; } + + leds_clear_all(state->leds); + + end_leds_update(state); } return 0; @@ -212,6 +208,58 @@ int leds_cmd_static(int argc, char **argv, void *ctx) return 0; } +int leds_cmd_all(int argc, char **argv, void *ctx) +{ + int rgb, a = 0xff, w = 0; + int err; + + if ((err = cmd_arg_int(argc, argv, 1, &rgb))) + return err; + if ((argc > 2) && (err = cmd_arg_int(argc, argv, 2, &a))) + return err; + if ((argc > 2) && (err = cmd_arg_int(argc, argv, 2, &w))) + return err; + + struct leds_color leds_color = { + .r = (rgb >> 16) & 0xFF, + .g = (rgb >> 8) & 0xFF, + .b = (rgb >> 0) & 0xFF, + }; + + for (int i = 0; i < LEDS_COUNT; i++) { + const struct leds_config *config = &leds_configs[i]; + struct leds_state *state = &leds_states[i]; + + if (!config->enabled || !state->leds) { + continue; + } + + switch (leds_parameter_type(state->leds)) { + case LEDS_PARAMETER_NONE: + break; + + case LEDS_PARAMETER_DIMMER: + leds_color.dimmer = a; + break; + + case LEDS_PARAMETER_WHITE: + leds_color.white = w; + break; + } + + if ((err = start_leds_update(state, LEDS_UPDATE_CMD))) { + LOG_ERROR("start_leds_update"); + continue; + } + + leds_set_all(state->leds, leds_color); + + end_leds_update(state); + } + + return 0; +} + int leds_cmd_set(int argc, char **argv, void *ctx) { const struct leds_config *config; @@ -254,17 +302,20 @@ int leds_cmd_set(int argc, char **argv, void *ctx) break; } - if ((err = leds_set(state->leds, index, leds_color))) { - LOG_ERROR("leds_set"); - return err; + if ((err = start_leds_update(state, LEDS_UPDATE_CMD))) { + LOG_ERROR("start_leds_update"); + return -1; } - if ((err = update_leds(state, USER_ACTIVITY_LEDS_CMD))) { - LOG_ERROR("update_leds"); - return err; + if ((err = leds_set(state->leds, index, leds_color))) { + LOG_WARN("leds_set"); + goto error; } - return 0; +error: + end_leds_update(state); + + return err; } int leds_cmd_test(int argc, char **argv, void *ctx) @@ -320,10 +371,12 @@ int leds_cmd_update(int argc, char **argv, void *ctx) return err; } - if ((err = update_leds(state, USER_ACTIVITY_LEDS_CMD))) { - LOG_ERROR("update_leds"); + if ((err = start_leds_update(state, LEDS_UPDATE_CMD))) { + LOG_ERROR("start_leds_update"); return err; } + + end_leds_update(state); } else { for (int i = 0; i < LEDS_COUNT; i++) { const struct leds_config *config = &leds_configs[i]; @@ -333,10 +386,12 @@ int leds_cmd_update(int argc, char **argv, void *ctx) continue; } - if ((err = update_leds(state, USER_ACTIVITY_LEDS_CMD))) { - LOG_ERROR("update_leds"); + if ((err = start_leds_update(state, LEDS_UPDATE_CMD))) { + LOG_ERROR("start_leds_update"); return err; } + + end_leds_update(state); } } @@ -437,6 +492,7 @@ const struct cmd leds_commands[] = { { "status", leds_cmd_status, .describe = "Show LED status" }, { "clear", leds_cmd_clear, .describe = "Clear test patterns" }, { "static", leds_cmd_static, .usage = "RGB [A]", .describe = "Set static LEDs color" }, + { "all", leds_cmd_all, .usage = "RGB [A]", .describe = "Set all output pixels to value" }, { "set", leds_cmd_set, .usage = "LEDS-ID LED-INDEX RGB [A]", .describe = "Set one output pixel to value" }, { "update", leds_cmd_update, .usage = "[LEDS-ID]", .describe = "Refresh one or all LED outputs" }, { "test", leds_cmd_test, .usage = "[MODE]", .describe = "Output test patterns" }, diff --git a/main/leds_http_post.c b/main/leds_http_post.c index 9bc8dd5a..8cefddb3 100644 --- a/main/leds_http_post.c +++ b/main/leds_http_post.c @@ -11,12 +11,11 @@ #include -struct leds_api_req { +struct leds_api_params { struct leds_state *state; }; - -int leds_api_state_parse(struct leds_api_req *req, const char *key, const char *value) +int leds_api_state_parse(struct leds_api_params *params, const char *key, const char *value) { struct leds *leds = NULL; enum leds_parameter_type parameter_type = 0; @@ -24,28 +23,30 @@ int leds_api_state_parse(struct leds_api_req *req, const char *key, const char * unsigned index; int ret; - // XXX: will only update last state - if (strcmp(key, "id") == 0) { - if (sscanf(value, "%d", &index) <= 0) { - return HTTP_UNPROCESSABLE_ENTITY; - } else if (index <= 0 || index > LEDS_COUNT) { + if (strcmp(key, "leds") == 0) { + if (params->state) { + LOG_WARN("duplicate leds="); return HTTP_UNPROCESSABLE_ENTITY; - } else { - req->state = &leds_states[index - 1]; + } else if ((ret = leds_api_leds_parse(¶ms->state, value))) { + LOG_WARN("leds_api_leds_parse"); + return ret; } - + + if ((ret = start_leds_update(params->state, LEDS_UPDATE_HTTP))) { + LOG_ERROR("start_leds_update"); + params->state = NULL; + return ret; + } + return 0; - } else if (!req->state) { - LOG_WARN("missing id= in request"); + } else if (!params->state) { + LOG_WARN("missing leds= in request"); return HTTP_UNPROCESSABLE_ENTITY; - } else if (!req->state->leds) { - LOG_WARN("disabled id= in request"); - return HTTP_UNPROCESSABLE_ENTITY; } else { - leds = req->state->leds; - parameter_type = leds_parameter_type(req->state->leds); + leds = params->state->leds; + parameter_type = leds_parameter_type(params->state->leds); } if (strcmp(key, "all") == 0) { @@ -71,17 +72,18 @@ int leds_api_state_parse(struct leds_api_req *req, const char *key, const char * int leds_api_form(struct http_request *request, struct http_response *response) { - struct leds_api_req req = {}; + struct leds_api_params params = {}; char *key, *value; int err; + // leds_api_state_parse() will implicitly start_leds_update(), we must end_leds_update() while (!(err = http_request_form(request, &key, &value))) { - if ((err = leds_api_state_parse(&req, key, value)) < 0) { + if ((err = leds_api_state_parse(¶ms, key, value)) < 0) { LOG_ERROR("leds_api_state_parse"); - return err; + goto error; } else if (err) { - LOG_WARN("leds_api_state_parse: %s=%s", key, value ? value : ""); - return err; + LOG_WARN("leds_api_state_parse: %s=%s -> %d", key, value ? value : "", err); + goto error; } } @@ -90,17 +92,14 @@ int leds_api_form(struct http_request *request, struct http_response *response) return err; } - if (req.state && req.state->leds) { - if ((err = update_leds(req.state, USER_ACTIVITY_LEDS_HTTP)) < 0) { - LOG_ERROR("update_leds"); - return HTTP_INTERNAL_SERVER_ERROR; - } else if (err) { - LOG_WARN("update_leds"); - return HTTP_CONFLICT; - } + err = HTTP_NO_CONTENT; + +error: + if (params.state) { + end_leds_update(params.state); } - return HTTP_NO_CONTENT; + return err; } int leds_api_post(struct http_request *request, struct http_response *response, void *ctx) diff --git a/main/leds_state.h b/main/leds_state.h index e54f0f63..9b4b7d72 100644 --- a/main/leds_state.h +++ b/main/leds_state.h @@ -8,6 +8,7 @@ #include #include #include +#include struct leds_config; @@ -21,12 +22,15 @@ enum leds_update_state { LEDS_UPDATE_TEST, LEDS_UPDATE_SEQUENCE, LEDS_UPDATE_ARTNET, + LEDS_UPDATE_CMD, + LEDS_UPDATE_HTTP, }; struct leds_state { int index; const struct leds_config *config; - + SemaphoreHandle_t mutex; + struct leds *leds; TickType_t update_tick; enum leds_update_state update_state; @@ -81,3 +85,9 @@ int reset_leds(struct leds_state *state); * Update LEDs output. */ int output_leds(struct leds_state *state); + +/* Lock LEDs for out-of-task update */ +int start_leds_update(struct leds_state *state, enum leds_update_state update_state); + +/* Release LEDS and trigger task */ +void end_leds_update(struct leds_state *state); diff --git a/main/leds_task.c b/main/leds_task.c index ae4fef4b..cb6c22c1 100644 --- a/main/leds_task.c +++ b/main/leds_task.c @@ -11,6 +11,8 @@ #include +#define LEDS_MUTEX_TIMEOUT (1000 / portTICK_RATE_MS) + static TickType_t leds_update_wait(struct leds_state *state) { if (state->config->update_timeout && state->update_tick) { @@ -99,6 +101,8 @@ static EventBits_t leds_task_wait(struct leds_state *state) LOG_DEBUG("leds%d: wait_tick=%d wait_ticks=%d", state->index + 1, wait_tick, wait_ticks); + xSemaphoreGiveRecursive(state->mutex); + const bool clear_on_exit = true; const bool wait_for_all_bits = false; EventBits_t event_bits = xEventGroupWaitBits(state->event_group, LEDS_EVENT_BITS, clear_on_exit, wait_for_all_bits, wait_ticks); @@ -112,6 +116,10 @@ static EventBits_t leds_task_wait(struct leds_state *state) !!(event_bits & (1 << LEDS_EVENT_UPDATE_BIT)) ); + if (!xSemaphoreTakeRecursive(state->mutex, LEDS_MUTEX_TIMEOUT)) { + LOG_FATAL("xSemaphoreTakeRecursive: timeout"); + } + return event_bits; } @@ -137,6 +145,10 @@ static void leds_main(void *ctx) struct leds_state *state = ctx; struct leds_stats *stats = &leds_stats[state->index]; + if (!xSemaphoreTakeRecursive(state->mutex, LEDS_MUTEX_TIMEOUT)) { + LOG_FATAL("xSemaphoreTakeRecursive: timeout"); + } + if (setup_leds(state)) { LOG_ERROR("setup_leds"); goto error; @@ -251,6 +263,8 @@ static void leds_main(void *ctx) } error: + xSemaphoreGiveRecursive(state->mutex); + user_alert(USER_ALERT_ERROR_LEDS); LOG_ERROR("task=%p stopped", state->task); state->task = NULL; @@ -314,18 +328,41 @@ void notify_leds_tasks(EventBits_t bits) } } -int update_leds(struct leds_state *state, enum user_activity activity) +// TODO: start/stop update timer? +int start_leds_update(struct leds_state *state, enum leds_update_state update_state) { - if (!state->event_group) { + if (!state->mutex || !state->event_group) { LOG_WARN("leds%d: not initialized", state->index + 1); return -1; } - if (activity) { - user_activity(activity); + switch (update_state) { + case LEDS_UPDATE_CMD: + user_activity(USER_ACTIVITY_LEDS_CMD); + break; + + case LEDS_UPDATE_HTTP: + user_activity(USER_ACTIVITY_LEDS_HTTP); + break; + + default: + LOG_ERROR("update_state=%d", update_state); + return -1; } - xEventGroupSetBits(state->event_group, 1 << LEDS_EVENT_UPDATE_BIT); + if (!xSemaphoreTakeRecursive(state->mutex, LEDS_MUTEX_TIMEOUT)) { + LOG_ERROR("xSemaphoreTakeRecursive"); + return -1; + } + + leds_update_state(state, update_state); return 0; } + +void end_leds_update(struct leds_state *state) +{ + xSemaphoreGiveRecursive(state->mutex); + + xEventGroupSetBits(state->event_group, 1 << LEDS_EVENT_UPDATE_BIT); +} diff --git a/main/leds_task.h b/main/leds_task.h index f04e17ff..e3c59d22 100644 --- a/main/leds_task.h +++ b/main/leds_task.h @@ -21,6 +21,3 @@ int start_leds_task(struct leds_state *state, const struct leds_config *config); void notify_leds_task(struct leds_state *state, EventBits_t bits); void notify_leds_tasks(EventBits_t bits); - -/* Trigger task for leds output update */ -int update_leds(struct leds_state *state, enum user_activity activity); From 1a0312936b8a298ae1da2aea71492efb5d86614b Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Sat, 13 Jun 2026 20:32:57 +0300 Subject: [PATCH 48/55] main: leds update timer --- main/leds_cmd.c | 2 +- main/leds_state.h | 1 + main/leds_stats.c | 2 +- main/leds_stats.h | 2 +- main/leds_task.c | 14 ++++++++++++-- 5 files changed, 16 insertions(+), 5 deletions(-) diff --git a/main/leds_cmd.c b/main/leds_cmd.c index c6f6fd67..64f472e0 100644 --- a/main/leds_cmd.c +++ b/main/leds_cmd.c @@ -458,6 +458,7 @@ int leds_cmd_stats(int argc, char **argv, void *ctx) printf("leds%u:\n", i + 1); + print_stats_timer("update", "", &stats->update); print_stats_timer("task", "loop", &stats->loop); print_stats_timer("task", "test", &stats->test); print_stats_timer("task", "artnet", &stats->artnet); @@ -471,7 +472,6 @@ int leds_cmd_stats(int argc, char **argv, void *ctx) print_stats_counter("sync", "timeout", &stats->sync_timeout); print_stats_counter("sync", "missed", &stats->sync_missed); print_stats_counter("sync", "full", &stats->sync_full); - print_stats_counter("update", "", &stats->update); print_stats_counter("update", "timeout", &stats->update_timeout); printf("\n"); } diff --git a/main/leds_state.h b/main/leds_state.h index 9b4b7d72..0ec15f14 100644 --- a/main/leds_state.h +++ b/main/leds_state.h @@ -34,6 +34,7 @@ struct leds_state { struct leds *leds; TickType_t update_tick; enum leds_update_state update_state; + stats_timer_start_t update_start; xTaskHandle task; EventGroupHandle_t event_group; diff --git a/main/leds_stats.c b/main/leds_stats.c index 3eafabb9..9145cea7 100644 --- a/main/leds_stats.c +++ b/main/leds_stats.c @@ -17,6 +17,7 @@ void init_leds_stats() for (unsigned i = 0; i < LEDS_COUNT; i++) { struct leds_stats *stats = &leds_stats[i]; + stats_timer_init(&stats->update); stats_timer_init(&stats->loop); stats_timer_init(&stats->test); stats_timer_init(&stats->artnet); @@ -30,7 +31,6 @@ void init_leds_stats() stats_counter_init(&stats->sync_timeout); stats_counter_init(&stats->sync_missed); stats_counter_init(&stats->sync_full); - stats_counter_init(&stats->update); stats_counter_init(&stats->update_timeout); } } diff --git a/main/leds_stats.h b/main/leds_stats.h index c977b1d6..02aaba5b 100644 --- a/main/leds_stats.h +++ b/main/leds_stats.h @@ -11,6 +11,7 @@ struct leds_sequence_stats { }; struct leds_stats { + struct stats_timer update; struct stats_timer loop; struct stats_timer test; @@ -29,7 +30,6 @@ struct leds_stats { struct stats_timer static_; struct stats_timer output; - struct stats_counter update; struct stats_counter update_timeout; }; diff --git a/main/leds_task.c b/main/leds_task.c index cb6c22c1..8ab3a937 100644 --- a/main/leds_task.c +++ b/main/leds_task.c @@ -236,8 +236,6 @@ static void leds_main(void *ctx) // external update = true; - stats_counter_increment(&stats->update); - } else if (leds_update_timeout_expired(state)) { LOG_DEBUG("update timeout"); @@ -331,6 +329,8 @@ void notify_leds_tasks(EventBits_t bits) // TODO: start/stop update timer? int start_leds_update(struct leds_state *state, enum leds_update_state update_state) { + struct leds_stats *stats = &leds_stats[state->index]; + if (!state->mutex || !state->event_group) { LOG_WARN("leds%d: not initialized", state->index + 1); return -1; @@ -355,6 +355,10 @@ int start_leds_update(struct leds_state *state, enum leds_update_state update_st return -1; } + assert(!state->update_start); + + state->update_start = stats_timer_start(&stats->update); + leds_update_state(state, update_state); return 0; @@ -362,6 +366,12 @@ int start_leds_update(struct leds_state *state, enum leds_update_state update_st void end_leds_update(struct leds_state *state) { + struct leds_stats *stats = &leds_stats[state->index]; + + assert(state->update_start); + + stats_timer_stop(&stats->update, &state->update_start); + xSemaphoreGiveRecursive(state->mutex); xEventGroupSetBits(state->event_group, 1 << LEDS_EVENT_UPDATE_BIT); From 9e7c1abb6d817cb7b1d3ea8fdbe4236d63c6ca5e Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Wed, 17 Jun 2026 17:29:37 +0300 Subject: [PATCH 49/55] main: leds static_enabled description --- main/leds_configtab.i | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/leds_configtab.i b/main/leds_configtab.i index 448446ed..a6ada7e0 100644 --- a/main/leds_configtab.i +++ b/main/leds_configtab.i @@ -262,7 +262,7 @@ const struct configtab LEDS_CONFIGTAB[] = { }, { CONFIG_TYPE_BOOL, "static_enabled", - .description = "Output static color at boot", + .description = "Output static color at boot. Default false, LEDs hold their value over a reboot until the first update/timeout.", .bool_type = { .value = &LEDS_CONFIG.static_enabled }, }, { CONFIG_TYPE_COLOR, "static_color", From fa260d7df2c1fcea79378ace8a4e532e342c03e2 Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Wed, 17 Jun 2026 17:35:15 +0300 Subject: [PATCH 50/55] main: review --- main/leds_cmd.c | 7 +++++++ main/leds_http_post.c | 19 ++++++++++++------- main/leds_task.c | 1 - 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/main/leds_cmd.c b/main/leds_cmd.c index 64f472e0..d01f6084 100644 --- a/main/leds_cmd.c +++ b/main/leds_cmd.c @@ -172,6 +172,7 @@ int leds_cmd_static(int argc, char **argv, void *ctx) if ((argc > 2) && (err = cmd_arg_int(argc, argv, 2, &w))) return err; + // TODO: use leds_api_color_parse()? struct leds_color leds_color = { .r = (rgb >> 16) & 0xFF, .g = (rgb >> 8) & 0xFF, @@ -220,6 +221,7 @@ int leds_cmd_all(int argc, char **argv, void *ctx) if ((argc > 2) && (err = cmd_arg_int(argc, argv, 2, &w))) return err; + // TODO: use leds_api_color_parse()? struct leds_color leds_color = { .r = (rgb >> 16) & 0xFF, .g = (rgb >> 8) & 0xFF, @@ -283,6 +285,7 @@ int leds_cmd_set(int argc, char **argv, void *ctx) return err; } + // TODO: use leds_api_color_parse()? struct leds_color leds_color = { .r = (rgb >> 16) & 0xFF, .g = (rgb >> 8) & 0xFF, @@ -375,6 +378,8 @@ int leds_cmd_update(int argc, char **argv, void *ctx) LOG_ERROR("start_leds_update"); return err; } + + // no-op update end_leds_update(state); } else { @@ -391,6 +396,8 @@ int leds_cmd_update(int argc, char **argv, void *ctx) return err; } + // no-op update + end_leds_update(state); } } diff --git a/main/leds_http_post.c b/main/leds_http_post.c index 8cefddb3..6d723bfd 100644 --- a/main/leds_http_post.c +++ b/main/leds_http_post.c @@ -15,7 +15,7 @@ struct leds_api_params { struct leds_state *state; }; -int leds_api_state_parse(struct leds_api_params *params, const char *key, const char *value) +int leds_api_params_parse(struct leds_api_params *params, const char *key, const char *value) { struct leds *leds = NULL; enum leds_parameter_type parameter_type = 0; @@ -63,14 +63,19 @@ int leds_api_state_parse(struct leds_api_params *params, const char *key, const return ret; } - return leds_set(leds, index, color); + if ((ret = leds_set(leds, index, color))) { + LOG_WARN("leds_set leds%d index=%u", params->state->index + 1, index); + return HTTP_UNPROCESSABLE_ENTITY; + } + + return 0; } else { return HTTP_UNPROCESSABLE_ENTITY; } } -int leds_api_form(struct http_request *request, struct http_response *response) +int leds_api_post_form(struct http_request *request, struct http_response *response) { struct leds_api_params params = {}; char *key, *value; @@ -78,11 +83,11 @@ int leds_api_form(struct http_request *request, struct http_response *response) // leds_api_state_parse() will implicitly start_leds_update(), we must end_leds_update() while (!(err = http_request_form(request, &key, &value))) { - if ((err = leds_api_state_parse(¶ms, key, value)) < 0) { - LOG_ERROR("leds_api_state_parse"); + if ((err = leds_api_params_parse(¶ms, key, value)) < 0) { + LOG_ERROR("leds_api_params_parse"); goto error; } else if (err) { - LOG_WARN("leds_api_state_parse: %s=%s -> %d", key, value ? value : "", err); + LOG_WARN("leds_api_params_parse: %s=%s -> %d", key, value ? value : "", err); goto error; } } @@ -114,7 +119,7 @@ int leds_api_post(struct http_request *request, struct http_response *response, switch (headers->content_type) { case HTTP_CONTENT_TYPE_APPLICATION_X_WWW_FORM_URLENCODED: - return leds_api_form(request, response); + return leds_api_post_form(request, response); default: LOG_WARN("Unknown Content-Type"); diff --git a/main/leds_task.c b/main/leds_task.c index 8ab3a937..adac97dd 100644 --- a/main/leds_task.c +++ b/main/leds_task.c @@ -326,7 +326,6 @@ void notify_leds_tasks(EventBits_t bits) } } -// TODO: start/stop update timer? int start_leds_update(struct leds_state *state, enum leds_update_state update_state) { struct leds_stats *stats = &leds_stats[state->index]; From e9bf663ab00bbb4680531fa2be2e4e9154fa53e8 Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Wed, 17 Jun 2026 17:42:12 +0300 Subject: [PATCH 51/55] main: split cmd/http update timers --- main/leds_cmd.c | 5 +++-- main/leds_stats.c | 3 ++- main/leds_stats.h | 4 +++- main/leds_task.c | 36 ++++++++++++++++++++++++------------ 4 files changed, 32 insertions(+), 16 deletions(-) diff --git a/main/leds_cmd.c b/main/leds_cmd.c index d01f6084..265cf84b 100644 --- a/main/leds_cmd.c +++ b/main/leds_cmd.c @@ -378,7 +378,7 @@ int leds_cmd_update(int argc, char **argv, void *ctx) LOG_ERROR("start_leds_update"); return err; } - + // no-op update end_leds_update(state); @@ -465,13 +465,14 @@ int leds_cmd_stats(int argc, char **argv, void *ctx) printf("leds%u:\n", i + 1); - print_stats_timer("update", "", &stats->update); print_stats_timer("task", "loop", &stats->loop); print_stats_timer("task", "test", &stats->test); print_stats_timer("task", "artnet", &stats->artnet); print_stats_timer("task", "sequence", &stats->sequence); print_stats_timer("task", "static", &stats->static_); print_stats_timer("task", "output", &stats->output); + print_stats_timer("cmd", "update", &stats->update_cmd); + print_stats_timer("http", "update", &stats->update_http); printf("\n"); print_stats_counter("artnet", "timeout", &stats->artnet_timeout); print_stats_counter("artnet", "sync", &stats->artnet_sync); diff --git a/main/leds_stats.c b/main/leds_stats.c index 9145cea7..991a772e 100644 --- a/main/leds_stats.c +++ b/main/leds_stats.c @@ -17,13 +17,14 @@ void init_leds_stats() for (unsigned i = 0; i < LEDS_COUNT; i++) { struct leds_stats *stats = &leds_stats[i]; - stats_timer_init(&stats->update); stats_timer_init(&stats->loop); stats_timer_init(&stats->test); stats_timer_init(&stats->artnet); stats_timer_init(&stats->sequence); stats_timer_init(&stats->static_); stats_timer_init(&stats->output); + stats_timer_init(&stats->update_cmd); + stats_timer_init(&stats->update_http); stats_counter_init(&stats->artnet_timeout); stats_counter_init(&stats->artnet_sync); diff --git a/main/leds_stats.h b/main/leds_stats.h index 02aaba5b..9a80856e 100644 --- a/main/leds_stats.h +++ b/main/leds_stats.h @@ -11,7 +11,6 @@ struct leds_sequence_stats { }; struct leds_stats { - struct stats_timer update; struct stats_timer loop; struct stats_timer test; @@ -32,6 +31,9 @@ struct leds_stats { struct stats_timer output; struct stats_counter update_timeout; + struct stats_timer update_cmd; + struct stats_timer update_http; + }; extern struct leds_sequence_stats leds_sequence_stats; diff --git a/main/leds_task.c b/main/leds_task.c index adac97dd..18062a51 100644 --- a/main/leds_task.c +++ b/main/leds_task.c @@ -335,12 +335,23 @@ int start_leds_update(struct leds_state *state, enum leds_update_state update_st return -1; } + if (!xSemaphoreTakeRecursive(state->mutex, LEDS_MUTEX_TIMEOUT)) { + LOG_ERROR("xSemaphoreTakeRecursive"); + return -1; + } + + leds_update_state(state, update_state); + + assert(!state->update_start); + switch (update_state) { case LEDS_UPDATE_CMD: + state->update_start = stats_timer_start(&stats->update_cmd); user_activity(USER_ACTIVITY_LEDS_CMD); break; case LEDS_UPDATE_HTTP: + state->update_start = stats_timer_start(&stats->update_http); user_activity(USER_ACTIVITY_LEDS_HTTP); break; @@ -349,17 +360,6 @@ int start_leds_update(struct leds_state *state, enum leds_update_state update_st return -1; } - if (!xSemaphoreTakeRecursive(state->mutex, LEDS_MUTEX_TIMEOUT)) { - LOG_ERROR("xSemaphoreTakeRecursive"); - return -1; - } - - assert(!state->update_start); - - state->update_start = stats_timer_start(&stats->update); - - leds_update_state(state, update_state); - return 0; } @@ -369,7 +369,19 @@ void end_leds_update(struct leds_state *state) assert(state->update_start); - stats_timer_stop(&stats->update, &state->update_start); + switch(state->update_state) { + case LEDS_UPDATE_CMD: + stats_timer_stop(&stats->update_cmd, &state->update_start); + break; + + case LEDS_UPDATE_HTTP: + stats_timer_stop(&stats->update_http, &state->update_start); + break; + + default: + LOG_ERROR("update_state=%d", state->update_state); + break; + } xSemaphoreGiveRecursive(state->mutex); From afdc370fcc2732f8d180839baa03d57310915db8 Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Wed, 17 Jun 2026 17:43:34 +0300 Subject: [PATCH 52/55] leds: crash on xSemaphoreGiveRecursive() failures --- main/leds_task.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/main/leds_task.c b/main/leds_task.c index 18062a51..f93fecf2 100644 --- a/main/leds_task.c +++ b/main/leds_task.c @@ -101,7 +101,9 @@ static EventBits_t leds_task_wait(struct leds_state *state) LOG_DEBUG("leds%d: wait_tick=%d wait_ticks=%d", state->index + 1, wait_tick, wait_ticks); - xSemaphoreGiveRecursive(state->mutex); + if (!xSemaphoreGiveRecursive(state->mutex)) { + LOG_FATAL("xSemaphoreGiveRecursive: mutex not locked by task"); + } const bool clear_on_exit = true; const bool wait_for_all_bits = false; @@ -261,7 +263,9 @@ static void leds_main(void *ctx) } error: - xSemaphoreGiveRecursive(state->mutex); + if (!xSemaphoreGiveRecursive(state->mutex)) { + LOG_FATAL("xSemaphoreGiveRecursive: mutex not locked by task"); + } user_alert(USER_ALERT_ERROR_LEDS); LOG_ERROR("task=%p stopped", state->task); @@ -383,7 +387,9 @@ void end_leds_update(struct leds_state *state) break; } - xSemaphoreGiveRecursive(state->mutex); + if (!xSemaphoreGiveRecursive(state->mutex)) { + LOG_FATAL("xSemaphoreGiveRecursive: mutex not locked by task"); + } xEventGroupSetBits(state->event_group, 1 << LEDS_EVENT_UPDATE_BIT); } From 62ac2df9d83715f3da01e8f47a97b533a9185451 Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Wed, 17 Jun 2026 18:00:18 +0300 Subject: [PATCH 53/55] main: fix leds test wait tick bug --- main/leds_test.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/main/leds_test.c b/main/leds_test.c index 347ec9ef..64fa0b33 100644 --- a/main/leds_test.c +++ b/main/leds_test.c @@ -178,8 +178,13 @@ TickType_t leds_test_wait(struct leds_state *state) return 0; } - // tick for next test frame - return state->test->frame_tick; + if (state->test->frame_tick) { + // wait for next test frame + return state->test->frame_tick; + } + + // idle + return 0; } bool leds_test_active(struct leds_state *state, EventBits_t bits) @@ -219,7 +224,7 @@ int leds_test_update(struct leds_state *state, EventBits_t bits) return 0; - } else if (state->test->frame_tick < tick) { + } else if (state->test->frame_tick > tick) { LOG_DEBUG("wait mode=%d auto=%d frame=%d frame_tick=%d", state->test->mode, state->test->auto_mode, state->test->frame, state->test->frame_tick); return 0; From eb8091389a19a22363c191e82a28d7730769f3d2 Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Wed, 17 Jun 2026 18:03:04 +0300 Subject: [PATCH 54/55] main: add leds update state to status --- main/leds_cmd.c | 6 ++++-- main/leds_http.c | 1 + main/leds_state.h | 12 +++++++++++- main/leds_status.c | 12 ++++++++++++ main/leds_status.h | 16 ++++------------ web/src/components/LedsView.vue | 6 +++--- 6 files changed, 35 insertions(+), 18 deletions(-) diff --git a/main/leds_cmd.c b/main/leds_cmd.c index 265cf84b..8a265fe2 100644 --- a/main/leds_cmd.c +++ b/main/leds_cmd.c @@ -75,7 +75,9 @@ int leds_cmd_status(int argc, char **argv, void *ctx) printf("leds%d:\n", i + 1); printf("\tActive : %s\n", status.active ? "true" : "false"); - printf("\tUpdate : %dms\n", status.update_tick ? (status.tick - status.update_tick) * portTICK_RATE_MS : 0); + printf("\tUpdate:\n"); + printf("\t\tState : %s\n", config_enum_to_string(leds_update_state_enum, status.update_state)); + printf("\t\tTick : %dms ago\n", status.update_tick ? (status.tick - status.update_tick) * portTICK_RATE_MS : 0); printf("\tTask : %6.1f/s @ %5.1f%% (%.0fs)\n", status.metrics.task.rate, status.metrics.task.util * 100.0f, status.metrics.task.interval); printf("\tInterface : %6.1f/s @ %5.1f%% (%.0fs)\n", status.metrics.interface.rate, status.metrics.interface.util * 100.0f, status.metrics.interface.interval); if (status.test) { @@ -84,7 +86,7 @@ int leds_cmd_status(int argc, char **argv, void *ctx) } if (status.artnet) { printf("\tArt-Net:\n"); - printf("\t\tUpdate : %dms\n", status.artnet_dmx_tick ? (status.tick - status.artnet_dmx_tick) * portTICK_RATE_MS : 0); + printf("\t\tDMX Tick : %dms ago\n", status.artnet_dmx_tick ? (status.tick - status.artnet_dmx_tick) * portTICK_RATE_MS : 0); } printf("\tLimit:\n"); diff --git a/main/leds_http.c b/main/leds_http.c index f3595985..82a41e84 100644 --- a/main/leds_http.c +++ b/main/leds_http.c @@ -72,6 +72,7 @@ static int leds_api_write_object_status(struct json_writer *w, struct leds_state return ( JSON_WRITE_MEMBER_BOOL(w, "active", status.active) + || JSON_WRITE_MEMBER_STRING(w, "update_state", config_enum_to_string(leds_update_state_enum, status.update_state)) || JSON_WRITE_MEMBER_UINT(w, "update_tick", status.update_tick) || JSON_WRITE_MEMBER_UINT(w, "update_ms", TICK_MS(status.tick, status.update_tick)) || JSON_WRITE_MEMBER_UINT(w, "artnet_dmx_ms", status.artnet ? TICK_MS(status.tick, status.artnet_dmx_tick) : 0) diff --git a/main/leds_state.h b/main/leds_state.h index 0ec15f14..c0e4c1bf 100644 --- a/main/leds_state.h +++ b/main/leds_state.h @@ -1,8 +1,8 @@ #pragma once +#include #include #include "leds.h" -#include "leds_status.h" #include "user.h" #include @@ -26,6 +26,16 @@ enum leds_update_state { LEDS_UPDATE_HTTP, }; +struct leds_status_timers { + struct stats_timer task; + struct stats_timer interface; +}; + +struct leds_status_timer_metrics { + struct stats_timer_metrics task; + struct stats_timer_metrics interface; +}; + struct leds_state { int index; const struct leds_config *config; diff --git a/main/leds_status.c b/main/leds_status.c index 33cd49f5..ca2b5367 100644 --- a/main/leds_status.c +++ b/main/leds_status.c @@ -5,6 +5,17 @@ #include "leds_artnet.h" #include "leds.h" +const struct config_enum leds_update_state_enum[] = { + { "NONE", .value = LEDS_UPDATE_NONE }, + { "STATIC", .value = LEDS_UPDATE_STATIC }, + { "TEST", .value = LEDS_UPDATE_TEST }, + { "SEQUENCE", .value = LEDS_UPDATE_SEQUENCE }, + { "ARTNET", .value = LEDS_UPDATE_ARTNET }, + { "CMD", .value = LEDS_UPDATE_CMD }, + { "HTTP", .value = LEDS_UPDATE_HTTP }, + {} +}; + static struct stats_timer get_leds_task_timer(struct leds_state *state) { const struct leds_stats *stats = &leds_stats[state->index]; @@ -57,6 +68,7 @@ void get_leds_status(struct leds_state *state, struct leds_status *status) status->tick = xTaskGetTickCount(); status->update_tick = state->update_tick; + status->update_state = state->update_state; status->active = leds_is_active(state->leds); if ((status->test = !!state->test)) { diff --git a/main/leds_status.h b/main/leds_status.h index 1c4e34ee..d57acf1e 100644 --- a/main/leds_status.h +++ b/main/leds_status.h @@ -3,24 +3,14 @@ #include #include #include "leds_config.h" - -struct leds_state; - -struct leds_status_timers { - struct stats_timer task; - struct stats_timer interface; -}; - -struct leds_status_timer_metrics { - struct stats_timer_metrics task; - struct stats_timer_metrics interface; -}; +#include "leds_state.h" struct leds_status { TickType_t tick; bool active; TickType_t update_tick; + enum leds_update_state update_state; bool test; enum leds_test_mode test_mode; @@ -35,4 +25,6 @@ struct leds_status { struct leds_status_timer_metrics metrics; }; +extern const struct config_enum leds_update_state_enum[]; + void get_leds_status(struct leds_state *leds, struct leds_status *status); diff --git a/web/src/components/LedsView.vue b/web/src/components/LedsView.vue index 51005f8d..43be0b1f 100644 --- a/web/src/components/LedsView.vue +++ b/web/src/components/LedsView.vue @@ -95,12 +95,12 @@
Active
{{ status.active }}
+
Updated
+
{{ status.update_state }} @ {{ status.update_ms | interval('ms') }}
+
Test Mode
{{ status.test_mode }}
-
Updated
-
{{ status.update_ms | interval('ms') }}
-
Art-Net Updated
{{ status.artnet_dmx_ms | interval('ms') }}
From 621b0a1a73c30d7f88c53ddd014f0bd16dff8241 Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Wed, 17 Jun 2026 22:15:19 +0300 Subject: [PATCH 55/55] main: TODO leds update -> output tick/timeout --- main/leds_task.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/main/leds_task.c b/main/leds_task.c index f93fecf2..f7f1895b 100644 --- a/main/leds_task.c +++ b/main/leds_task.c @@ -249,7 +249,8 @@ static void leds_main(void *ctx) if (update) { LOG_DEBUG("update"); - + + // TODO: rename to output_tick, output_timeout? state->update_tick = xTaskGetTickCount(); WITH_STATS_TIMER(&stats->output) {