From 0dcfab4e76dfe199ef19864b45d618e7b1572657 Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Fri, 5 Jun 2026 21:39:15 +0300 Subject: [PATCH 01/34] i2s_out: keep TX_REMPTY interrupt enabled during DMA --- components/i2s_out/esp32/dma.c | 4 +--- components/i2s_out/esp32/i2s.c | 13 +++++++------ components/i2s_out/esp32/intr_iram.c | 29 ++++++++++++++++++++++------ 3 files changed, 31 insertions(+), 15 deletions(-) diff --git a/components/i2s_out/esp32/dma.c b/components/i2s_out/esp32/dma.c index 944fd2ea..39d4a921 100644 --- a/components/i2s_out/esp32/dma.c +++ b/components/i2s_out/esp32/dma.c @@ -556,9 +556,7 @@ int i2s_out_dma_flush(struct i2s_out *i2s_out, TickType_t timeout) taskENTER_CRITICAL(&i2s_out->mux); - dma_done = i2s_out->dma_done; - - if (!dma_done) { + if (!(dma_done = i2s_out->dma_done)) { i2s_out->dma_done_task = xTaskGetCurrentTaskHandle(); } diff --git a/components/i2s_out/esp32/i2s.c b/components/i2s_out/esp32/i2s.c index aafe41c5..6d248bfd 100644 --- a/components/i2s_out/esp32/i2s.c +++ b/components/i2s_out/esp32/i2s.c @@ -139,6 +139,9 @@ void i2s_out_i2s_start(struct i2s_out *i2s_out) // let's hope that the EOF frame is always zeroes, and zero bytes at the start are harmless... i2s_ll_tx_start(i2s_out->dev); + i2s_intr_clear(i2s_out->dev, I2S_TX_REMPTY_INT_CLR); + i2s_intr_enable(i2s_out->dev, I2S_TX_REMPTY_INT_ENA); + taskEXIT_CRITICAL(&i2s_out->mux); } @@ -151,13 +154,8 @@ int i2s_out_i2s_flush(struct i2s_out *i2s_out, TickType_t timeout) taskENTER_CRITICAL(&i2s_out->mux); - i2s_done = i2s_out->i2s_done; - - if (!i2s_done) { + if (!(i2s_done = i2s_out->i2s_done)) { i2s_out->i2s_done_task = xTaskGetCurrentTaskHandle(); - - i2s_intr_clear(i2s_out->dev, I2S_TX_REMPTY_INT_CLR); - i2s_intr_enable(i2s_out->dev, I2S_TX_REMPTY_INT_ENA); } taskEXIT_CRITICAL(&i2s_out->mux); @@ -188,6 +186,9 @@ void i2s_out_i2s_stop(struct i2s_out *i2s_out) taskENTER_CRITICAL(&i2s_out->mux); + i2s_intr_disable(i2s_out->dev, I2S_TX_REMPTY_INT_ENA); + i2s_intr_clear(i2s_out->dev, I2S_TX_REMPTY_INT_CLR); + i2s_ll_tx_stop(i2s_out->dev); taskEXIT_CRITICAL(&i2s_out->mux); diff --git a/components/i2s_out/esp32/intr_iram.c b/components/i2s_out/esp32/intr_iram.c index 72488c9b..c75135d7 100644 --- a/components/i2s_out/esp32/intr_iram.c +++ b/components/i2s_out/esp32/intr_iram.c @@ -40,6 +40,18 @@ static void i2s_out_intr_dma_done(struct i2s_out *i2s_out, BaseType_t *pxHigherP } } +static void i2s_out_intr_i2s_done(struct i2s_out *i2s_out, BaseType_t *pxHigherPriorityTaskWoken) +{ + // unblock flush() task + i2s_out->i2s_done = true; + + if (i2s_out->i2s_done_task) { + xTaskNotifyFromISR(i2s_out->i2s_done_task, I2S_OUT_TASK_NOTIFY_BIT_I2S_DONE, eSetBits, pxHigherPriorityTaskWoken); + + i2s_out->i2s_done_task = NULL; + } +} + static void i2s_intr_out_dscr_err_handler(struct i2s_out *i2s_out) { uint32_t dscr_addr; @@ -76,6 +88,10 @@ static void i2s_intr_out_eof_handler(struct i2s_out *i2s_out, BaseType_t *pxHigh i2s_out_intr_dma_out_desc(i2s_out, eof_desc, pxHigherPriorityTaskWoken); i2s_out_intr_dma_done(i2s_out, pxHigherPriorityTaskWoken); + // XXX: ensure tx rempty intr is enabled, in case it fired during DMA and was disabled? + i2s_intr_clear(i2s_out->dev, I2S_TX_REMPTY_INT_CLR); + i2s_intr_enable(i2s_out->dev, I2S_TX_REMPTY_INT_ENA); + } else { LOG_ISR_ERROR("unknown desc=%p owner=%u len=%u", eof_desc, eof_desc->owner, eof_desc->len); } @@ -89,13 +105,14 @@ static void i2s_intr_tx_rempty_handler(struct i2s_out *i2s_out, BaseType_t *pxHi i2s_intr_disable(i2s_out->dev, I2S_TX_REMPTY_INT_ENA); i2s_intr_clear(i2s_out->dev, I2S_TX_REMPTY_INT_CLR); - // unblock flush() task - i2s_out->i2s_done = true; - - if (i2s_out->i2s_done_task) { - xTaskNotifyFromISR(i2s_out->i2s_done_task, I2S_OUT_TASK_NOTIFY_BIT_I2S_DONE, eSetBits, pxHigherPriorityTaskWoken); + if (!i2s_out->dma_done) { + // XXX: ignore if fired before dma_done, will be re-enabled + // XXX: may indicate a timing glitch in the output data? + LOG_ISR_ERROR("tx rempty dma_done=%u i2s_done=%u", i2s_out->dma_done, i2s_out->i2s_done); + } else { + LOG_ISR_DEBUG("tx rempty dma_done=%u i2s_done=%u", i2s_out->dma_done, i2s_out->i2s_done); - i2s_out->i2s_done_task = NULL; + i2s_out_intr_i2s_done(i2s_out, pxHigherPriorityTaskWoken); } } From 660711d25dc6b62ed6e86d6d2af302c08ebee217 Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Fri, 5 Jun 2026 22:04:25 +0300 Subject: [PATCH 02/34] stats: simplify timer start --- components/leds/stats.c | 1 + components/stats/include/stats_timer.h | 33 ++++++++++++-------------- main/leds_task.c | 4 ++-- 3 files changed, 18 insertions(+), 20 deletions(-) diff --git a/components/leds/stats.c b/components/leds/stats.c index 1d4d9b90..ccbd0665 100644 --- a/components/leds/stats.c +++ b/components/leds/stats.c @@ -19,6 +19,7 @@ void leds_reset_interface_stats() #if LEDS_I2S_INTERFACE_COUNT > 0 stats_timer_init(&leds_interface_stats.i2s0.open); stats_timer_init(&leds_interface_stats.i2s0.write); + stats_timer_init(&leds_interface_stats.i2s0.start); stats_timer_init(&leds_interface_stats.i2s0.flush); #endif #if LEDS_I2S_INTERFACE_COUNT > 1 diff --git a/components/stats/include/stats_timer.h b/components/stats/include/stats_timer.h index eb01176a..6a04fc8d 100644 --- a/components/stats/include/stats_timer.h +++ b/components/stats/include/stats_timer.h @@ -10,10 +10,7 @@ struct stats_timer { uint64_t total; // us }; -struct stats_timer_sample { - uint64_t start; - bool running; -}; +typedef uint64_t stats_timer_start_t; static inline void stats_timer_init(struct stats_timer *timer) { @@ -29,28 +26,28 @@ static inline struct stats_timer stats_timer_copy(const struct stats_timer *time return *timer; } -static inline void stats_timer_update(struct stats_timer *timer, uint32_t count, uint64_t interval) +// IRAM-safe +static inline void stats_timer_update(struct stats_timer *timer, stats_timer_start_t start) { + assert(start); + + uint64_t stop = esp_timer_get_time(); + timer->update = esp_timer_get_time(); - timer->count += count; - timer->total += interval; + timer->count += 1; + timer->total += stop - start; } -static inline struct stats_timer_sample stats_timer_start(struct stats_timer *timer) +static inline stats_timer_start_t stats_timer_start(struct stats_timer *timer) { - return (struct stats_timer_sample) { - .start = esp_timer_get_time(), - .running = true, - }; + return esp_timer_get_time(); } -static inline void stats_timer_stop(struct stats_timer *timer, struct stats_timer_sample *sample) +static inline void stats_timer_stop(struct stats_timer *timer, stats_timer_start_t *startp) { - uint64_t stop = esp_timer_get_time(); - - stats_timer_update(timer, 1, stop - sample->start); + stats_timer_update(timer, *startp); - sample->running = false; + *startp = 0; } static inline float stats_timer_seconds_passed(const struct stats_timer *timer) @@ -100,4 +97,4 @@ static inline float stats_timer_utilization(const struct stats_timer *timer) } #define WITH_STATS_TIMER(timer) \ - for (struct stats_timer_sample _stats_timer_sample = stats_timer_start(timer); _stats_timer_sample.running; stats_timer_stop(timer, &_stats_timer_sample)) + for (stats_timer_start_t _stats_timer_start = stats_timer_start(timer); false; stats_timer_update(timer, _stats_timer_start)) diff --git a/main/leds_task.c b/main/leds_task.c index 21ead1ff..143d36c6 100644 --- a/main/leds_task.c +++ b/main/leds_task.c @@ -107,12 +107,12 @@ static void leds_main(void *ctx) goto error; } - for(struct stats_timer_sample loop_sample;; stats_timer_stop(&stats->loop, &loop_sample)) { + 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; - loop_sample = stats_timer_start(&stats->loop); + loop_start = stats_timer_start(&stats->loop); if (state->sequence && leds_sequence_active(state, event_bits)) { WITH_STATS_TIMER(&stats->sequence) { From 1b37c90af35d7ba4f5fee67f9144bcfbe615cff2 Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Fri, 5 Jun 2026 22:38:36 +0300 Subject: [PATCH 03/34] fixup stats timer --- components/stats/include/stats_timer.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/stats/include/stats_timer.h b/components/stats/include/stats_timer.h index 6a04fc8d..4837df60 100644 --- a/components/stats/include/stats_timer.h +++ b/components/stats/include/stats_timer.h @@ -97,4 +97,4 @@ static inline float stats_timer_utilization(const struct stats_timer *timer) } #define WITH_STATS_TIMER(timer) \ - for (stats_timer_start_t _stats_timer_start = stats_timer_start(timer); false; stats_timer_update(timer, _stats_timer_start)) + for (stats_timer_start_t _stats_timer_start = stats_timer_start(timer); _stats_timer_start; stats_timer_stop(timer, &_stats_timer_start)) From 0d59c9ecf7a8ce8c5fc3da787816935fae6909c3 Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Fri, 5 Jun 2026 22:43:09 +0300 Subject: [PATCH 04/34] i2s_out: out timer for i2s start -> i2s done --- components/i2s_out/CMakeLists.txt | 2 +- components/i2s_out/esp32/i2s.c | 3 +++ components/i2s_out/esp32/intr_iram.c | 3 +++ components/i2s_out/i2s_out.c | 18 +++++++++++++++--- components/i2s_out/i2s_out.h | 11 +++++++++++ components/i2s_out/include/i2s_out_stats.h | 13 +++++++++++++ components/i2s_out/stats.c | 15 +++++++++++++++ 7 files changed, 61 insertions(+), 4 deletions(-) create mode 100644 components/i2s_out/include/i2s_out_stats.h create mode 100644 components/i2s_out/stats.c diff --git a/components/i2s_out/CMakeLists.txt b/components/i2s_out/CMakeLists.txt index 9ceaebd6..e6fb4b04 100644 --- a/components/i2s_out/CMakeLists.txt +++ b/components/i2s_out/CMakeLists.txt @@ -1,7 +1,7 @@ idf_component_register( SRC_DIRS . ${IDF_TARGET} INCLUDE_DIRS "include" - PRIV_REQUIRES logging + PRIV_REQUIRES logging stats LDFRAGMENTS ${IDF_TARGET}.lf ) diff --git a/components/i2s_out/esp32/i2s.c b/components/i2s_out/esp32/i2s.c index 6d248bfd..cc4f2488 100644 --- a/components/i2s_out/esp32/i2s.c +++ b/components/i2s_out/esp32/i2s.c @@ -131,6 +131,9 @@ void i2s_out_i2s_start(struct i2s_out *i2s_out) i2s_out->i2s_done = false; i2s_out->i2s_done_task = NULL; + // track active time + i2s_out->stats_out_timer_start = stats_timer_start(&i2s_out->stats.out_timer); + taskENTER_CRITICAL(&i2s_out->mux); // NOTE: there seems to always be three extra BCK cycles at the start of TX diff --git a/components/i2s_out/esp32/intr_iram.c b/components/i2s_out/esp32/intr_iram.c index c75135d7..dd2604da 100644 --- a/components/i2s_out/esp32/intr_iram.c +++ b/components/i2s_out/esp32/intr_iram.c @@ -50,6 +50,9 @@ static void i2s_out_intr_i2s_done(struct i2s_out *i2s_out, BaseType_t *pxHigherP i2s_out->i2s_done_task = NULL; } + + // stats + stats_timer_stop(&i2s_out->stats.out_timer, &i2s_out->stats_out_timer_start); } static void i2s_intr_out_dscr_err_handler(struct i2s_out *i2s_out) diff --git a/components/i2s_out/i2s_out.c b/components/i2s_out/i2s_out.c index 8069eb87..fae08019 100644 --- a/components/i2s_out/i2s_out.c +++ b/components/i2s_out/i2s_out.c @@ -28,9 +28,11 @@ int i2s_out_init(struct i2s_out *i2s_out, i2s_port_t port) } #endif - #if CONFIG_IDF_TARGET_ESP32 - portMUX_INITIALIZE(&i2s_out->mux); - #endif +#if CONFIG_IDF_TARGET_ESP32 + portMUX_INITIALIZE(&i2s_out->mux); +#endif + + i2s_out_stats_reset(&i2s_out->stats); return 0; } @@ -76,6 +78,16 @@ int i2s_out_new(struct i2s_out **i2s_outp, i2s_port_t port, size_t buffer_size, return err; } +void i2s_out_reset_stats(struct i2s_out *i2s_out) +{ + i2s_out_stats_reset(&i2s_out->stats); +} + +struct i2s_out_stats i2s_out_stats(struct i2s_out *i2s_out) +{ + return i2s_out_stats_copy(&i2s_out->stats); +} + int i2s_out_open(struct i2s_out *i2s_out, const struct i2s_out_options *options, TickType_t timeout) { int err = 0; diff --git a/components/i2s_out/i2s_out.h b/components/i2s_out/i2s_out.h index ba6fd6f6..30af2b7a 100644 --- a/components/i2s_out/i2s_out.h +++ b/components/i2s_out/i2s_out.h @@ -1,4 +1,7 @@ #include +#include + +#include #include #include @@ -73,6 +76,10 @@ struct i2s_out { TaskHandle_t dma_out_task; // task waiting for dma_out_desc to update TaskHandle_t dma_done_task; // task waiting for dma_done to be set TaskHandle_t i2s_done_task; // task waiting for i2s_done to be set + + struct i2s_out_stats stats; + + stats_timer_start_t stats_out_timer_start; }; /* dma.c */ @@ -107,3 +114,7 @@ void i2s_out_pin_teardown(struct i2s_out *i2s_out); /* intr.c */ int i2s_out_intr_setup(struct i2s_out *i2s_out, const struct i2s_out_options *options); void i2s_out_intr_teardown(struct i2s_out *i2s_out); + +/* stats.c */ +void i2s_out_stats_reset(struct i2s_out_stats *stats); +struct i2s_out_stats i2s_out_stats_copy(struct i2s_out_stats *stats); diff --git a/components/i2s_out/include/i2s_out_stats.h b/components/i2s_out/include/i2s_out_stats.h new file mode 100644 index 00000000..39c15939 --- /dev/null +++ b/components/i2s_out/include/i2s_out_stats.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +struct i2s_out; + +struct i2s_out_stats { + struct stats_timer out_timer; +}; + +void i2s_out_reset_stats(struct i2s_out *i2s_out); + +struct i2s_out_stats i2s_out_stats(struct i2s_out *i2s_out); diff --git a/components/i2s_out/stats.c b/components/i2s_out/stats.c new file mode 100644 index 00000000..1889b35f --- /dev/null +++ b/components/i2s_out/stats.c @@ -0,0 +1,15 @@ +#include + +#include "i2s_out.h" + +void i2s_out_stats_reset(struct i2s_out_stats *stats) +{ + stats_timer_init(&stats->out_timer); +} + +struct i2s_out_stats i2s_out_stats_copy(struct i2s_out_stats *stats) +{ + return (struct i2s_out_stats) { + .out_timer = stats_timer_copy(&stats->out_timer), + }; +} From 8fb112a369c7cb6c50838dec2054c7eb7f5324af Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Fri, 5 Jun 2026 22:43:50 +0300 Subject: [PATCH 05/34] main: leds i2s out stats --- main/leds_cmd.c | 7 +++++++ main/leds_i2s.h | 6 ++++++ main/leds_stats.c | 27 +++++++++++++++++++++++++++ main/leds_stats.h | 4 ++++ 4 files changed, 44 insertions(+) create mode 100644 main/leds_i2s.h diff --git a/main/leds_cmd.c b/main/leds_cmd.c index 8253d7ea..2380a745 100644 --- a/main/leds_cmd.c +++ b/main/leds_cmd.c @@ -366,6 +366,9 @@ int leds_cmd_stats(int argc, char **argv, void *ctx) #endif #if LEDS_I2S_INTERFACE_COUNT > 0 + struct i2s_out_stats i2s0_stats = get_leds_i2s_out_stats(0); + + print_stats_timer("i2s0", "out", &i2s0_stats.out_timer); print_stats_timer("i2s0", "open", &stats.i2s0.open); print_stats_timer("i2s0", "write", &stats.i2s0.write); print_stats_timer("i2s0", "start", &stats.i2s0.start); @@ -373,6 +376,9 @@ int leds_cmd_stats(int argc, char **argv, void *ctx) printf("\n"); #endif #if LEDS_I2S_INTERFACE_COUNT > 1 + struct i2s_out_stats i2s1_stats = get_leds_i2s_out_stats(1); + + print_stats_timer("i2s1", "out", &i2s1_stats.out_timer); print_stats_timer("i2s1", "open", &stats.i2s1.open); print_stats_timer("i2s1", "write", &stats.i2s1.write); print_stats_timer("i2s1", "start", &stats.i2s1.start); @@ -419,6 +425,7 @@ int leds_cmd_stats(int argc, char **argv, void *ctx) LOG_INFO("reset leds stats"); init_leds_stats(); + reset_leds_i2s_out_stats(); leds_reset_interface_stats(); } diff --git a/main/leds_i2s.h b/main/leds_i2s.h new file mode 100644 index 00000000..cbe436f5 --- /dev/null +++ b/main/leds_i2s.h @@ -0,0 +1,6 @@ +#pragma once + +#include +#include + +extern struct i2s_out *leds_i2s_out[LEDS_I2S_INTERFACE_COUNT]; diff --git a/main/leds_stats.c b/main/leds_stats.c index be472ca5..610f3d70 100644 --- a/main/leds_stats.c +++ b/main/leds_stats.c @@ -1,4 +1,5 @@ #include "leds_stats.h" +#include "leds_i2s.h" struct leds_sequence_stats leds_sequence_stats; struct leds_stats leds_stats[LEDS_COUNT]; @@ -31,3 +32,29 @@ void init_leds_stats() stats_counter_init(&stats->update_timeout); } } + +struct i2s_out_stats get_leds_i2s_out_stats(unsigned port) +{ + struct i2s_out *i2s_out = NULL; + + if (port < LEDS_I2S_INTERFACE_COUNT) { + i2s_out = leds_i2s_out[port]; + } + + if (i2s_out) { + return i2s_out_stats(i2s_out); + } else { + return (struct i2s_out_stats) {}; + } +} + +void reset_leds_i2s_out_stats() +{ + struct i2s_out *i2s_out = NULL; + + for (unsigned port = 0; port < LEDS_I2S_INTERFACE_COUNT; port++) { + if ((i2s_out = leds_i2s_out[port])) { + i2s_out_reset_stats(i2s_out); + } + } +} diff --git a/main/leds_stats.h b/main/leds_stats.h index 79a6038b..faf35118 100644 --- a/main/leds_stats.h +++ b/main/leds_stats.h @@ -3,6 +3,7 @@ #include "leds.h" #include +#include struct leds_sequence_stats { struct stats_timer read; @@ -33,3 +34,6 @@ extern struct leds_sequence_stats leds_sequence_stats; extern struct leds_stats leds_stats[LEDS_COUNT]; void init_leds_stats(); + +struct i2s_out_stats get_leds_i2s_out_stats(unsigned port); +void reset_leds_i2s_out_stats(); From 1774b98ad631aebaa31cd414c041cc86af05c39b Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Fri, 5 Jun 2026 22:43:56 +0300 Subject: [PATCH 06/34] main: leds task debug --- main/leds_task.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/main/leds_task.c b/main/leds_task.c index 143d36c6..b4147368 100644 --- a/main/leds_task.c +++ b/main/leds_task.c @@ -115,6 +115,8 @@ 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"); + WITH_STATS_TIMER(&stats->sequence) { if (leds_sequence_update(state, event_bits)) { update_activity = USER_ACTIVITY_LEDS_SEQUENCE; @@ -123,6 +125,8 @@ static void leds_main(void *ctx) } if (state->artnet && leds_artnet_active(state, event_bits)) { + LOG_DEBUG("artnet"); + WITH_STATS_TIMER(&stats->artnet) { switch (leds_artnet_update(state, event_bits)) { case 0: @@ -143,6 +147,8 @@ static void leds_main(void *ctx) } 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; @@ -151,15 +157,17 @@ static void leds_main(void *ctx) } if (leds_update_active(state)) { + LOG_DEBUG("update timeout"); + // update without activity update_timeout = true; - LOG_DEBUG("update timeout"); - stats_counter_increment(&stats->update_timeout); } if (update_activity || update_timeout) { + LOG_DEBUG("update"); + state->update_tick = xTaskGetTickCount(); WITH_STATS_TIMER(&stats->update) { From 89191d1563b36bf65013e1316c9a9ab7ad608114 Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Fri, 5 Jun 2026 23:12:23 +0300 Subject: [PATCH 07/34] main: refactor leds status --- main/leds_cmd.c | 55 +++++++++++++++++++++------------------------- main/leds_http.c | 28 ++++++++++------------- main/leds_status.c | 28 +++++++++++++++++++++++ main/leds_status.h | 23 +++++++++++++++++++ 4 files changed, 88 insertions(+), 46 deletions(-) create mode 100644 main/leds_status.c create mode 100644 main/leds_status.h diff --git a/main/leds_cmd.c b/main/leds_cmd.c index 2380a745..e46d7d55 100644 --- a/main/leds_cmd.c +++ b/main/leds_cmd.c @@ -1,5 +1,6 @@ #include "leds.h" #include "leds_state.h" +#include "leds_status.h" #include "leds_artnet.h" #include "leds_test.h" #include "leds_config.h" @@ -56,54 +57,48 @@ int leds_cmd_info(int argc, char **argv, void *ctx) int leds_cmd_status(int argc, char **argv, void *ctx) { + struct leds_status status; + 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) { + if (!state->leds) { continue; } - printf("leds%d:\n", i + 1); + get_leds_status(&leds_states[i], &status); - bool active = leds_is_active(state->leds); - struct leds_limit_status limit_total_status; - struct leds_limit_status limit_groups_status[LEDS_LIMIT_GROUPS_MAX]; - size_t groups = LEDS_LIMIT_GROUPS_MAX; - TickType_t tick = xTaskGetTickCount(); - - leds_get_limit_total_status(state->leds, &limit_total_status); - leds_get_limit_groups_status(state->leds, limit_groups_status, &groups); + printf("leds%d:\n", i + 1); - printf("\tActive : %s\n", active ? "true" : "false"); - printf("\tUpdate : %dms\n", state->update_tick ? (tick - state->update_tick) * portTICK_RATE_MS : 0); - if (state->test) { + printf("\tActive : %s\n", status.active ? "true" : "false"); + printf("\tUpdate : %dms\n", status.update_tick ? (status.tick - status.update_tick) * portTICK_RATE_MS : 0); + if (status.test) { printf("\tTest:\n"); - printf("\t\tMode : %s\n", state->test->mode ? config_enum_to_string(leds_test_mode_enum, state->test->mode) : ""); + printf("\t\tMode : %s\n", status.test_mode ? config_enum_to_string(leds_test_mode_enum, status.test_mode) : ""); } - if (state->artnet) { + if (status.artnet) { printf("\tArt-Net:\n"); - printf("\t\tUpdate : %dms\n", state->artnet->dmx_tick ? (tick - state->artnet->dmx_tick) * portTICK_RATE_MS : 0); + printf("\t\tUpdate : %dms\n", status.artnet_dmx_tick ? (status.tick - status.artnet_dmx_tick) * portTICK_RATE_MS : 0); } printf("\tLimit:\n"); printf("\t\tTotal : count %5d power %5.1f%% limit %5.1f%% util %5.1f%% applied %5.1f%% output %5.1f%%\n", - limit_total_status.count, - leds_limit_status_power(&limit_total_status) * 100.0f, - leds_limit_status_limit(&limit_total_status) * 100.0f, - leds_limit_status_util(&limit_total_status) * 100.0f, - leds_limit_status_applied(&limit_total_status) * 100.0f, - leds_limit_status_output(&limit_total_status) * 100.0f + status.limit_total_status.count, + leds_limit_status_power(&status.limit_total_status) * 100.0f, + leds_limit_status_limit(&status.limit_total_status) * 100.0f, + leds_limit_status_util(&status.limit_total_status) * 100.0f, + leds_limit_status_applied(&status.limit_total_status) * 100.0f, + leds_limit_status_output(&status.limit_total_status) * 100.0f ); - for (unsigned j = 0; j < groups; j++) { + for (unsigned j = 0; j < status.limit_groups_count; j++) { printf("\t\tGroup[%2d]: count %5d power %5.1f%% limit %5.1f%% util %5.1f%% applied %5.1f%% output %5.1f%%\n", j, - limit_groups_status[j].count, - leds_limit_status_power(&limit_groups_status[j]) * 100.0f, - leds_limit_status_limit(&limit_groups_status[j]) * 100.0f, - leds_limit_status_util(&limit_groups_status[j]) * 100.0f, - leds_limit_status_applied(&limit_groups_status[j]) * 100.0f, - leds_limit_status_output(&limit_groups_status[j]) * 100.0f + status.limit_groups_status[j].count, + leds_limit_status_power(&status.limit_groups_status[j]) * 100.0f, + leds_limit_status_limit(&status.limit_groups_status[j]) * 100.0f, + leds_limit_status_util(&status.limit_groups_status[j]) * 100.0f, + leds_limit_status_applied(&status.limit_groups_status[j]) * 100.0f, + leds_limit_status_output(&status.limit_groups_status[j]) * 100.0f ); } diff --git a/main/leds_http.c b/main/leds_http.c index 839f4244..231a80d3 100644 --- a/main/leds_http.c +++ b/main/leds_http.c @@ -1,5 +1,6 @@ #include "leds.h" #include "leds_state.h" +#include "leds_status.h" #include "leds_artnet.h" #include "leds_test.h" #include "leds_config.h" @@ -40,11 +41,11 @@ static int leds_api_write_object_leds_limit_status(struct json_writer *w, const ); } -static int leds_api_write_object_leds_limit_status_groups(struct json_writer *w, const struct leds_limit_status *groups_status, size_t groups) +static int leds_api_write_object_leds_limit_status_groups(struct json_writer *w, const struct leds_limit_status *groups_status, size_t count) { int err; - for (unsigned i = 0; i < groups; i++) { + for (unsigned i = 0; i < count; i++) { if ((err = JSON_WRITE_OBJECT(w, leds_api_write_object_leds_limit_status(w, &groups_status[i])))) { return err; } @@ -55,23 +56,18 @@ static int leds_api_write_object_leds_limit_status_groups(struct json_writer *w, static int leds_api_write_object_status(struct json_writer *w, struct leds_state *state) { - struct leds_limit_status limit_total_status; - struct leds_limit_status limit_groups_status[LEDS_LIMIT_GROUPS_MAX]; - size_t groups = LEDS_LIMIT_GROUPS_MAX; - bool active = leds_is_active(state->leds); - TickType_t tick = xTaskGetTickCount(); + struct leds_status status; - leds_get_limit_total_status(state->leds, &limit_total_status); - leds_get_limit_groups_status(state->leds, limit_groups_status, &groups); + get_leds_status(state, &status); return ( - JSON_WRITE_MEMBER_BOOL(w, "active", active) - || JSON_WRITE_MEMBER_UINT(w, "update_tick", state->update_tick) - || JSON_WRITE_MEMBER_UINT(w, "update_ms", TICK_MS(tick, state->update_tick)) - || JSON_WRITE_MEMBER_UINT(w, "artnet_dmx_ms", state->artnet ? TICK_MS(tick, state->artnet->dmx_tick) : 0) - || JSON_WRITE_MEMBER_STRING(w, "test_mode", (state->test && state->test->mode) ? config_enum_to_string(leds_test_mode_enum, state->test->mode) : "") - || JSON_WRITE_MEMBER_OBJECT(w, "limit_total", leds_api_write_object_leds_limit_status(w, &limit_total_status)) - || JSON_WRITE_MEMBER_ARRAY(w, "limit_groups", leds_api_write_object_leds_limit_status_groups(w, limit_groups_status, groups)) + JSON_WRITE_MEMBER_BOOL(w, "active", status.active) + || 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) + || JSON_WRITE_MEMBER_STRING(w, "test_mode", (status.test && status.test_mode) ? config_enum_to_string(leds_test_mode_enum, status.test_mode) : "") + || JSON_WRITE_MEMBER_OBJECT(w, "limit_total", leds_api_write_object_leds_limit_status(w, &status.limit_total_status)) + || JSON_WRITE_MEMBER_ARRAY(w, "limit_groups", leds_api_write_object_leds_limit_status_groups(w, status.limit_groups_status, status.limit_groups_count)) ); } diff --git a/main/leds_status.c b/main/leds_status.c new file mode 100644 index 00000000..d8a59bdb --- /dev/null +++ b/main/leds_status.c @@ -0,0 +1,28 @@ +#include "leds_status.h" +#include "leds_state.h" +#include "leds_test.h" +#include "leds_artnet.h" +#include "leds.h" + +void get_leds_status(struct leds_state *state, struct leds_status *status) +{ + *status = (struct leds_status) {}; + + status->tick = xTaskGetTickCount(); + status->update_tick = state->update_tick; + status->active = leds_is_active(state->leds); + + if ((status->test = !!state->test)) { + status->test_mode = state->test->mode; + } + + if ((status->artnet = !!state->artnet)) { + status->artnet_dmx_tick = state->artnet->dmx_tick; + } + + // limits + status->limit_groups_count = LEDS_LIMIT_GROUPS_MAX; + + leds_get_limit_total_status(state->leds, &status->limit_total_status); + leds_get_limit_groups_status(state->leds, status->limit_groups_status, &status->limit_groups_count); +} diff --git a/main/leds_status.h b/main/leds_status.h new file mode 100644 index 00000000..fbe22cc2 --- /dev/null +++ b/main/leds_status.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include "leds_config.h" + +struct leds_status { + TickType_t tick; + + bool active; + TickType_t update_tick; + + bool test; + enum leds_test_mode test_mode; + + bool artnet; + TickType_t artnet_dmx_tick; + + struct leds_limit_status limit_total_status; + struct leds_limit_status limit_groups_status[LEDS_LIMIT_GROUPS_MAX]; + size_t limit_groups_count; +}; + +void get_leds_status(struct leds_state *leds, struct leds_status *status); From 6d4c82a22599ea2286ce221732ded4cae7e56292 Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Fri, 5 Jun 2026 23:42:58 +0300 Subject: [PATCH 08/34] web: rate filter --- web/src/filters/rate.filter.js | 3 +++ web/src/index.js | 2 ++ 2 files changed, 5 insertions(+) create mode 100644 web/src/filters/rate.filter.js diff --git a/web/src/filters/rate.filter.js b/web/src/filters/rate.filter.js new file mode 100644 index 00000000..842491de --- /dev/null +++ b/web/src/filters/rate.filter.js @@ -0,0 +1,3 @@ +export default function(value) { + return value.toFixed(1) + '/s'; +} diff --git a/web/src/index.js b/web/src/index.js index bbc96923..dc5aaceb 100644 --- a/web/src/index.js +++ b/web/src/index.js @@ -15,6 +15,7 @@ import fileSizeFilter from "./filters/fileSize.filter" import fileTimeFilter from "./filters/fileTime.filter" import intervalFilter from "./filters/interval.filter"; import percentageFilter from "./filters/percentage.filter" +import rateFilter from "./filters/rate.filter" import timestampFilter from "./filters/timestamp.filter" // global error handlers @@ -38,6 +39,7 @@ Vue.filter('fileSize', fileSizeFilter); Vue.filter('fileTime', fileTimeFilter); Vue.filter('interval', intervalFilter); Vue.filter('percentage', percentageFilter); +Vue.filter('rate', rateFilter); Vue.filter('timestamp', timestampFilter); const router = new VueRouter({ From 5e55896ac9dfe2f0c29a0cb65c293461a9865755 Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Fri, 5 Jun 2026 23:43:36 +0300 Subject: [PATCH 09/34] leds: add task, interface -> rate, util status --- main/leds_cmd.c | 2 ++ main/leds_http.c | 4 ++++ main/leds_status.c | 33 +++++++++++++++++++++++++++++++++ main/leds_status.h | 3 +++ web/src/components/LedsView.vue | 6 ++++++ 5 files changed, 48 insertions(+) diff --git a/main/leds_cmd.c b/main/leds_cmd.c index e46d7d55..eab8fe69 100644 --- a/main/leds_cmd.c +++ b/main/leds_cmd.c @@ -72,6 +72,8 @@ int leds_cmd_status(int argc, char **argv, void *ctx) 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("\tTask : %6.1f/s @ %5.1f%%\n", status.task_rate, status.task_util * 100.0f); + printf("\tInterface : %6.1f/s @ %5.1f%%\n", status.interface_rate, status.interface_util * 100.0f); if (status.test) { printf("\tTest:\n"); printf("\t\tMode : %s\n", status.test_mode ? config_enum_to_string(leds_test_mode_enum, status.test_mode) : ""); diff --git a/main/leds_http.c b/main/leds_http.c index 231a80d3..b3715171 100644 --- a/main/leds_http.c +++ b/main/leds_http.c @@ -65,6 +65,10 @@ static int leds_api_write_object_status(struct json_writer *w, struct leds_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) + || JSON_WRITE_MEMBER_FLOAT(w, "task_rate", status.task_rate) + || JSON_WRITE_MEMBER_FLOAT(w, "task_util", status.task_util) + || JSON_WRITE_MEMBER_FLOAT(w, "interface_rate", status.interface_rate) + || JSON_WRITE_MEMBER_FLOAT(w, "interface_util", status.interface_util) || JSON_WRITE_MEMBER_STRING(w, "test_mode", (status.test && status.test_mode) ? config_enum_to_string(leds_test_mode_enum, status.test_mode) : "") || JSON_WRITE_MEMBER_OBJECT(w, "limit_total", leds_api_write_object_leds_limit_status(w, &status.limit_total_status)) || JSON_WRITE_MEMBER_ARRAY(w, "limit_groups", leds_api_write_object_leds_limit_status_groups(w, status.limit_groups_status, status.limit_groups_count)) diff --git a/main/leds_status.c b/main/leds_status.c index d8a59bdb..b4d8238d 100644 --- a/main/leds_status.c +++ b/main/leds_status.c @@ -1,9 +1,38 @@ #include "leds_status.h" #include "leds_state.h" +#include "leds_stats.h" #include "leds_test.h" #include "leds_artnet.h" #include "leds.h" +void get_leds_stats_status(struct leds_state *state, struct leds_status *status) +{ + const struct leds_stats *stats = &leds_stats[state->index]; + + status->task_rate = stats_timer_average_rate(&stats->loop); + status->task_util = stats_timer_utilization(&stats->loop); +} + +void get_leds_interface_status(struct leds_state *state, struct leds_status *status) +{ + enum leds_interface li = leds_interface(state->leds); + struct i2s_out_stats i2s_out_stats; + + switch (li) { + case LEDS_INTERFACE_I2S0: + case LEDS_INTERFACE_I2S1: + i2s_out_stats = get_leds_i2s_out_stats(leds_interface_i2s_port(li)); + + status->interface_rate = stats_timer_average_rate(&i2s_out_stats.out_timer); + status->interface_util = stats_timer_utilization(&i2s_out_stats.out_timer); + + break; + + default: + break; + } +} + void get_leds_status(struct leds_state *state, struct leds_status *status) { *status = (struct leds_status) {}; @@ -25,4 +54,8 @@ void get_leds_status(struct leds_state *state, struct leds_status *status) leds_get_limit_total_status(state->leds, &status->limit_total_status); leds_get_limit_groups_status(state->leds, status->limit_groups_status, &status->limit_groups_count); + + // stats + get_leds_stats_status(state, status); + get_leds_interface_status(state, status); } diff --git a/main/leds_status.h b/main/leds_status.h index fbe22cc2..26180ceb 100644 --- a/main/leds_status.h +++ b/main/leds_status.h @@ -18,6 +18,9 @@ struct leds_status { struct leds_limit_status limit_total_status; struct leds_limit_status limit_groups_status[LEDS_LIMIT_GROUPS_MAX]; size_t limit_groups_count; + + float task_rate, task_util; + float interface_rate, interface_util; }; 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 eff368f5..1eb854ea 100644 --- a/web/src/components/LedsView.vue +++ b/web/src/components/LedsView.vue @@ -97,6 +97,12 @@
Art-Net Updated
{{ status.artnet_dmx_ms | interval('ms') }}
+ +
Task
+
{{ status.task_rate | rate }} @ {{ status.task_util | percentage }}
+ +
Interface
+
{{ status.interface_rate | rate }} @ {{ status.interface_util | percentage }}
From e0de9214de490d1977c5a8b0b971710cd123ce44 Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Fri, 5 Jun 2026 23:59:56 +0300 Subject: [PATCH 10/34] web: use h1 as header with progress + button --- web/src/components/ArtNetView.vue | 6 ++--- web/src/components/ConfigView.vue | 6 +++-- web/src/components/FilesView.vue | 6 +++-- web/src/components/LedsView.vue | 6 ++--- web/src/components/MainView.vue | 6 ++--- web/src/components/SystemView.vue | 6 +++-- web/src/components/WiFiView.vue | 6 +++-- web/src/styles/main.css | 42 +++++++++++++++---------------- 8 files changed, 46 insertions(+), 38 deletions(-) diff --git a/web/src/components/ArtNetView.vue b/web/src/components/ArtNetView.vue index dac81b8f..aa7b7fd3 100644 --- a/web/src/components/ArtNetView.vue +++ b/web/src/components/ArtNetView.vue @@ -7,11 +7,11 @@ diff --git a/web/src/filters/counterMetrics.filter.js b/web/src/filters/counterMetrics.filter.js index f3c0c5d9..dcc84205 100644 --- a/web/src/filters/counterMetrics.filter.js +++ b/web/src/filters/counterMetrics.filter.js @@ -2,5 +2,5 @@ import rateFilter from "./rate.filter" import intervalFilter from "./interval.filter"; export default function(value) { - return rateFilter(value.rate) + ' / ' + intervalFilter(value.interval); + return rateFilter(value.rate) + ' @ ' + intervalFilter(value.interval); } diff --git a/web/src/filters/rate.filter.js b/web/src/filters/rate.filter.js index 842491de..83485727 100644 --- a/web/src/filters/rate.filter.js +++ b/web/src/filters/rate.filter.js @@ -1,3 +1,3 @@ export default function(value) { - return value.toFixed(1) + '/s'; + return value.toFixed(1) + '\u2215\u209B'; } diff --git a/web/src/filters/timerMetrics.filter.js b/web/src/filters/timerMetrics.filter.js index 30b0ed2a..3aa84d5c 100644 --- a/web/src/filters/timerMetrics.filter.js +++ b/web/src/filters/timerMetrics.filter.js @@ -3,5 +3,5 @@ import percentageFilter from "./percentage.filter" import intervalFilter from "./interval.filter"; export default function(value) { - return rateFilter(value.rate) + ' @ ' + percentageFilter(value.util) + ' / ' + intervalFilter(value.interval); + return percentageFilter(value.util) + ' @ ' + rateFilter(value.rate) + ' @ ' + intervalFilter(value.interval); } From 2da65b0732ce288fc9c2fe2d78959dab58b1b337 Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Sat, 6 Jun 2026 14:19:43 +0300 Subject: [PATCH 28/34] artnet: rename seq_miss counter --- components/artnet/include/artnet_stats.h | 6 +++--- components/artnet/output.c | 9 +++++---- main/artnet_cmd.c | 2 +- main/artnet_http.c | 2 +- main/artnet_status.c | 2 +- main/artnet_status.h | 4 ++-- web/src/components/ArtNetView.vue | 6 +++--- 7 files changed, 16 insertions(+), 15 deletions(-) diff --git a/components/artnet/include/artnet_stats.h b/components/artnet/include/artnet_stats.h index b9945f27..8f202c73 100644 --- a/components/artnet/include/artnet_stats.h +++ b/components/artnet/include/artnet_stats.h @@ -51,10 +51,10 @@ struct artnet_output_stats { /* Received ArtDMX packets in sync mode */ struct stats_counter dmx_sync; - /* Received ArtDMX packets with seq larger than expected */ - struct stats_counter seq_skip; + /* Received ArtDMX packets with seq larger than expected, missed packets */ + struct stats_counter seq_miss; - /* Dropped ArtDMX packets with seq smaller than expected */ + /* Received ArtDMX packets with seq smaller than expected, dropping packet */ struct stats_counter seq_drop; /* Received ArtDMX packets with seq resynced after timeout */ diff --git a/components/artnet/output.c b/components/artnet/output.c index efd0e859..02313dad 100644 --- a/components/artnet/output.c +++ b/components/artnet/output.c @@ -7,7 +7,7 @@ static void init_output_stats(struct artnet_output_stats *stats) stats_counter_init(&stats->sync_recv); stats_counter_init(&stats->dmx_recv); stats_counter_init(&stats->dmx_sync); - stats_counter_init(&stats->seq_skip); + stats_counter_init(&stats->seq_miss); stats_counter_init(&stats->seq_drop); stats_counter_init(&stats->seq_resync); stats_counter_init(&stats->queue_overwrite); @@ -140,7 +140,7 @@ int artnet_get_output_stats(struct artnet *artnet, int index, struct artnet_outp stats->sync_recv = stats_counter_copy(&output->stats.sync_recv); stats->dmx_recv = stats_counter_copy(&output->stats.dmx_recv); stats->dmx_sync = stats_counter_copy(&output->stats.dmx_sync); - stats->seq_skip = stats_counter_copy(&output->stats.seq_skip); + stats->seq_miss = stats_counter_copy(&output->stats.seq_miss); stats->seq_drop = stats_counter_copy(&output->stats.seq_drop); stats->seq_resync = stats_counter_copy(&output->stats.seq_resync); stats->queue_overwrite = stats_counter_copy(&output->stats.queue_overwrite); @@ -180,8 +180,8 @@ void artnet_output_dmx(struct artnet_output *output, struct artnet_dmx *dmx) // in-order } else if (dmx->seq > output->state.seq || output->state.seq - dmx->seq >= 128) { - // skipped - stats_counter_increment(&output->stats.seq_skip); + // missed + stats_counter_increment(&output->stats.seq_miss); } else if (output->state.tick < tick && (tick - output->state.tick) > ARTNET_SEQ_TICKS) { LOG_WARN("resync address=%04x seq=%d < %d on timeout", output->options.address, dmx->seq, output->state.seq); @@ -194,6 +194,7 @@ void artnet_output_dmx(struct artnet_output *output, struct artnet_dmx *dmx) } else { LOG_WARN("drop address=%04x seq=%d < %d", output->options.address, dmx->seq, output->state.seq); + // dropping stats_counter_increment(&output->stats.seq_drop); // do NOT update output->state.tick, in order to resync on timeout diff --git a/main/artnet_cmd.c b/main/artnet_cmd.c index e84463ab..c257c79d 100644 --- a/main/artnet_cmd.c +++ b/main/artnet_cmd.c @@ -184,7 +184,7 @@ int artnet_cmd_stats(int argc, char **argv, void *ctx) print_stats_counter("Sync", "received", &output_stats.sync_recv); print_stats_counter("DMX", "received", &output_stats.dmx_recv); print_stats_counter("DMX", "synced", &output_stats.dmx_sync); - print_stats_counter("Seq", "skipped", &output_stats.seq_skip); + print_stats_counter("Seq", "missed", &output_stats.seq_miss); print_stats_counter("Seq", "dropped", &output_stats.seq_drop); print_stats_counter("Seq", "resynced", &output_stats.seq_resync); print_stats_counter("Queue", "overflowed", &output_stats.queue_overwrite); diff --git a/main/artnet_http.c b/main/artnet_http.c index 1869a30b..7379929a 100644 --- a/main/artnet_http.c +++ b/main/artnet_http.c @@ -89,7 +89,7 @@ static int artnet_api_write_output_object(struct json_writer *w, const struct ar ) || JSON_WRITE_MEMBER_OBJECT(w, "metrics", JSON_WRITE_MEMBER_OBJECT(w, "dmx_counter", artnet_api_write_object_status_counter_metrics(w, &metrics->dmx_counter)) - || JSON_WRITE_MEMBER_OBJECT(w, "seq_skip_counter", artnet_api_write_object_status_counter_metrics(w, &metrics->seq_skip_counter)) + || JSON_WRITE_MEMBER_OBJECT(w, "seq_miss_counter", artnet_api_write_object_status_counter_metrics(w, &metrics->seq_miss_counter)) || JSON_WRITE_MEMBER_OBJECT(w, "seq_drop_counter", artnet_api_write_object_status_counter_metrics(w, &metrics->seq_drop_counter)) || JSON_WRITE_MEMBER_OBJECT(w, "overflow_counter", artnet_api_write_object_status_counter_metrics(w, &metrics->overflow_counter)) ) diff --git a/main/artnet_status.c b/main/artnet_status.c index 25b2cf2e..b3abe5ed 100644 --- a/main/artnet_status.c +++ b/main/artnet_status.c @@ -42,7 +42,7 @@ void update_artnet_status(struct artnet *artnet) artnet_get_output_stats(artnet, i, &artnet_output_stats); update_stats_counter_metrics(&artnet_status_stats.outputs[i].dmx_counter, &artnet_output_stats.dmx_recv, &artnet_status_metrics.outputs[i].dmx_counter); - update_stats_counter_metrics(&artnet_status_stats.outputs[i].seq_skip_counter, &artnet_output_stats.seq_skip, &artnet_status_metrics.outputs[i].seq_skip_counter); + update_stats_counter_metrics(&artnet_status_stats.outputs[i].seq_miss_counter, &artnet_output_stats.seq_miss, &artnet_status_metrics.outputs[i].seq_miss_counter); update_stats_counter_metrics(&artnet_status_stats.outputs[i].seq_drop_counter, &artnet_output_stats.seq_drop, &artnet_status_metrics.outputs[i].seq_drop_counter); update_stats_counter_metrics(&artnet_status_stats.outputs[i].overflow_counter, &artnet_output_stats.queue_overwrite, &artnet_status_metrics.outputs[i].overflow_counter); } diff --git a/main/artnet_status.h b/main/artnet_status.h index e94a0435..13f2c9b2 100644 --- a/main/artnet_status.h +++ b/main/artnet_status.h @@ -13,7 +13,7 @@ struct artnet_status_stats { struct artnet_status_output_stats { struct stats_counter dmx_counter; - struct stats_counter seq_skip_counter; + struct stats_counter seq_miss_counter; struct stats_counter seq_drop_counter; struct stats_counter overflow_counter; } outputs[ARTNET_OUTPUTS_MAX]; @@ -28,7 +28,7 @@ struct artnet_status_metrics { struct artnet_status_output_metrics { struct stats_counter_metrics dmx_counter; - struct stats_counter_metrics seq_skip_counter; + struct stats_counter_metrics seq_miss_counter; struct stats_counter_metrics seq_drop_counter; struct stats_counter_metrics overflow_counter; } outputs[ARTNET_OUTPUTS_MAX]; diff --git a/web/src/components/ArtNetView.vue b/web/src/components/ArtNetView.vue index 03f0b60d..012257a5 100644 --- a/web/src/components/ArtNetView.vue +++ b/web/src/components/ArtNetView.vue @@ -103,8 +103,8 @@ Subnet Universe DMX - Sequence Missing - Sequence Duplicate + Seq Miss + Seq Drop Overflow @@ -115,7 +115,7 @@ {{ output.subnet }} {{ output.universe }} - + From 304f5bf8cf1a9dffc00d950e70c3fdb4f4862638 Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Sat, 6 Jun 2026 14:21:22 +0300 Subject: [PATCH 29/34] artnet: drop per-output sync counter --- components/artnet/include/artnet_stats.h | 3 --- components/artnet/output.c | 4 ---- main/artnet_cmd.c | 1 - 3 files changed, 8 deletions(-) diff --git a/components/artnet/include/artnet_stats.h b/components/artnet/include/artnet_stats.h index 8f202c73..067337de 100644 --- a/components/artnet/include/artnet_stats.h +++ b/components/artnet/include/artnet_stats.h @@ -42,9 +42,6 @@ struct artnet_input_stats { }; struct artnet_output_stats { - /* Received ArtSync packets */ - struct stats_counter sync_recv; - /* Received ArtDMX packets */ struct stats_counter dmx_recv; diff --git a/components/artnet/output.c b/components/artnet/output.c index 02313dad..d2e74eee 100644 --- a/components/artnet/output.c +++ b/components/artnet/output.c @@ -4,7 +4,6 @@ static void init_output_stats(struct artnet_output_stats *stats) { - stats_counter_init(&stats->sync_recv); stats_counter_init(&stats->dmx_recv); stats_counter_init(&stats->dmx_sync); stats_counter_init(&stats->seq_miss); @@ -137,7 +136,6 @@ int artnet_get_output_stats(struct artnet *artnet, int index, struct artnet_outp struct artnet_output *output = &artnet->output_ports[index]; - stats->sync_recv = stats_counter_copy(&output->stats.sync_recv); stats->dmx_recv = stats_counter_copy(&output->stats.dmx_recv); stats->dmx_sync = stats_counter_copy(&output->stats.dmx_sync); stats->seq_miss = stats_counter_copy(&output->stats.seq_miss); @@ -265,8 +263,6 @@ int artnet_sync_outputs(struct artnet *artnet) continue; } - stats_counter_increment(&output->stats.sync_recv); - if (output->options.event_group != event_group) { xEventGroupSetBits(output->options.event_group, output->options.sync_event_bit); diff --git a/main/artnet_cmd.c b/main/artnet_cmd.c index c257c79d..a5be5ed9 100644 --- a/main/artnet_cmd.c +++ b/main/artnet_cmd.c @@ -181,7 +181,6 @@ int artnet_cmd_stats(int argc, char **argv, void *ctx) printf("Output %d: \n", i); - print_stats_counter("Sync", "received", &output_stats.sync_recv); print_stats_counter("DMX", "received", &output_stats.dmx_recv); print_stats_counter("DMX", "synced", &output_stats.dmx_sync); print_stats_counter("Seq", "missed", &output_stats.seq_miss); From 70de811377dd2485a65662cad43e4eff0c232edd Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Sat, 6 Jun 2026 14:25:46 +0300 Subject: [PATCH 30/34] artnet: rename queue_overflow counter --- components/artnet/include/artnet_stats.h | 4 ++-- components/artnet/input.c | 6 +++--- components/artnet/output.c | 6 +++--- main/artnet_cmd.c | 16 ++++++++-------- main/artnet_status.c | 2 +- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/components/artnet/include/artnet_stats.h b/components/artnet/include/artnet_stats.h index 067337de..f9f915b6 100644 --- a/components/artnet/include/artnet_stats.h +++ b/components/artnet/include/artnet_stats.h @@ -38,7 +38,7 @@ struct artnet_input_stats { struct stats_counter dmx_recv; /* Output queue overflowed, previous packet overwritten */ - struct stats_counter queue_overwrite; + struct stats_counter queue_overflow; }; struct artnet_output_stats { @@ -58,7 +58,7 @@ struct artnet_output_stats { struct stats_counter seq_resync; /* Output queue overflowed, previous packet overwritten */ - struct stats_counter queue_overwrite; + struct stats_counter queue_overflow; }; /* diff --git a/components/artnet/input.c b/components/artnet/input.c index 17be847d..0e8d1bfc 100644 --- a/components/artnet/input.c +++ b/components/artnet/input.c @@ -5,7 +5,7 @@ static void init_input_stats(struct artnet_input_stats *stats) { stats_counter_init(&stats->dmx_recv); - stats_counter_init(&stats->queue_overwrite); + stats_counter_init(&stats->queue_overflow); } int artnet_add_input(struct artnet *artnet, struct artnet_input **inputp, struct artnet_input_options options) @@ -59,7 +59,7 @@ void artnet_input_dmx(struct artnet_input *input, const struct artnet_dmx *dmx) // attempt normal send first, before overwriting for overflow stats if (xQueueSend(input->queue, dmx, 0) == errQUEUE_FULL) { - stats_counter_increment(&input->stats.queue_overwrite); + stats_counter_increment(&input->stats.queue_overflow); xQueueOverwrite(input->queue, dmx); } @@ -163,7 +163,7 @@ int artnet_get_input_stats(struct artnet *artnet, int index, struct artnet_input struct artnet_input *input = &artnet->input_ports[index]; stats->dmx_recv = stats_counter_copy(&input->stats.dmx_recv); - stats->queue_overwrite = stats_counter_copy(&input->stats.queue_overwrite); + stats->queue_overflow = stats_counter_copy(&input->stats.queue_overflow); return 0; } diff --git a/components/artnet/output.c b/components/artnet/output.c index d2e74eee..2b481403 100644 --- a/components/artnet/output.c +++ b/components/artnet/output.c @@ -9,7 +9,7 @@ static void init_output_stats(struct artnet_output_stats *stats) stats_counter_init(&stats->seq_miss); stats_counter_init(&stats->seq_drop); stats_counter_init(&stats->seq_resync); - stats_counter_init(&stats->queue_overwrite); + stats_counter_init(&stats->queue_overflow); } int artnet_add_output(struct artnet *artnet, struct artnet_output **outputp, struct artnet_output_options options) @@ -141,7 +141,7 @@ int artnet_get_output_stats(struct artnet *artnet, int index, struct artnet_outp stats->seq_miss = stats_counter_copy(&output->stats.seq_miss); stats->seq_drop = stats_counter_copy(&output->stats.seq_drop); stats->seq_resync = stats_counter_copy(&output->stats.seq_resync); - stats->queue_overwrite = stats_counter_copy(&output->stats.queue_overwrite); + stats->queue_overflow = stats_counter_copy(&output->stats.queue_overflow); return 0; } @@ -210,7 +210,7 @@ void artnet_output_dmx(struct artnet_output *output, struct artnet_dmx *dmx) // attempt normal send first, before overwriting for overflow stats if (xQueueSend(output->queue, dmx, 0) == errQUEUE_FULL) { - stats_counter_increment(&output->stats.queue_overwrite); + stats_counter_increment(&output->stats.queue_overflow); xQueueOverwrite(output->queue, dmx); } diff --git a/main/artnet_cmd.c b/main/artnet_cmd.c index a5be5ed9..cc3e3bd0 100644 --- a/main/artnet_cmd.c +++ b/main/artnet_cmd.c @@ -162,8 +162,8 @@ int artnet_cmd_stats(int argc, char **argv, void *ctx) printf("Input %d: \n", i); - print_stats_counter("DMX", "received", &input_stats.dmx_recv); - print_stats_counter("Queue", "overflowed", &input_stats.queue_overwrite); + print_stats_counter("DMX", "receive", &input_stats.dmx_recv); + print_stats_counter("Queue", "overflow", &input_stats.queue_overflow); printf("\n"); } @@ -181,12 +181,12 @@ int artnet_cmd_stats(int argc, char **argv, void *ctx) printf("Output %d: \n", i); - print_stats_counter("DMX", "received", &output_stats.dmx_recv); - print_stats_counter("DMX", "synced", &output_stats.dmx_sync); - print_stats_counter("Seq", "missed", &output_stats.seq_miss); - print_stats_counter("Seq", "dropped", &output_stats.seq_drop); - print_stats_counter("Seq", "resynced", &output_stats.seq_resync); - print_stats_counter("Queue", "overflowed", &output_stats.queue_overwrite); + print_stats_counter("DMX", "receive", &output_stats.dmx_recv); + print_stats_counter("DMX", "sync", &output_stats.dmx_sync); + print_stats_counter("Seq", "miss", &output_stats.seq_miss); + print_stats_counter("Seq", "drop", &output_stats.seq_drop); + print_stats_counter("Seq", "resync", &output_stats.seq_resync); + print_stats_counter("Queue", "overflow", &output_stats.queue_overflow); printf("\n"); } diff --git a/main/artnet_status.c b/main/artnet_status.c index b3abe5ed..1e830778 100644 --- a/main/artnet_status.c +++ b/main/artnet_status.c @@ -44,7 +44,7 @@ void update_artnet_status(struct artnet *artnet) update_stats_counter_metrics(&artnet_status_stats.outputs[i].dmx_counter, &artnet_output_stats.dmx_recv, &artnet_status_metrics.outputs[i].dmx_counter); update_stats_counter_metrics(&artnet_status_stats.outputs[i].seq_miss_counter, &artnet_output_stats.seq_miss, &artnet_status_metrics.outputs[i].seq_miss_counter); update_stats_counter_metrics(&artnet_status_stats.outputs[i].seq_drop_counter, &artnet_output_stats.seq_drop, &artnet_status_metrics.outputs[i].seq_drop_counter); - update_stats_counter_metrics(&artnet_status_stats.outputs[i].overflow_counter, &artnet_output_stats.queue_overwrite, &artnet_status_metrics.outputs[i].overflow_counter); + update_stats_counter_metrics(&artnet_status_stats.outputs[i].overflow_counter, &artnet_output_stats.queue_overflow, &artnet_status_metrics.outputs[i].overflow_counter); } } From 79f33833495991e4bdc12c19df7803953892f26f Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Sat, 6 Jun 2026 14:37:46 +0300 Subject: [PATCH 31/34] artnet: new dmx output stats, seq state --- components/artnet/include/artnet_stats.h | 9 ++++++++ components/artnet/output.c | 27 ++++++++++++++---------- main/artnet_cmd.c | 4 +++- 3 files changed, 28 insertions(+), 12 deletions(-) diff --git a/components/artnet/include/artnet_stats.h b/components/artnet/include/artnet_stats.h index f9f915b6..2181712f 100644 --- a/components/artnet/include/artnet_stats.h +++ b/components/artnet/include/artnet_stats.h @@ -48,6 +48,12 @@ struct artnet_output_stats { /* Received ArtDMX packets in sync mode */ struct stats_counter dmx_sync; + /* Received ArtDMX packets without seq */ + struct stats_counter seq_zero; + + /* Received ArtDMX packets with expected seq */ + struct stats_counter seq_good; + /* Received ArtDMX packets with seq larger than expected, missed packets */ struct stats_counter seq_miss; @@ -57,6 +63,9 @@ struct artnet_output_stats { /* Received ArtDMX packets with seq resynced after timeout */ struct stats_counter seq_resync; + /* Output queue updated */ + struct stats_counter queue_update; + /* Output queue overflowed, previous packet overwritten */ struct stats_counter queue_overflow; }; diff --git a/components/artnet/output.c b/components/artnet/output.c index 2b481403..0f15ead5 100644 --- a/components/artnet/output.c +++ b/components/artnet/output.c @@ -5,10 +5,12 @@ static void init_output_stats(struct artnet_output_stats *stats) { stats_counter_init(&stats->dmx_recv); - stats_counter_init(&stats->dmx_sync); + stats_counter_init(&stats->seq_zero); + stats_counter_init(&stats->seq_good); stats_counter_init(&stats->seq_miss); stats_counter_init(&stats->seq_drop); stats_counter_init(&stats->seq_resync); + stats_counter_init(&stats->queue_update); stats_counter_init(&stats->queue_overflow); } @@ -137,10 +139,12 @@ int artnet_get_output_stats(struct artnet *artnet, int index, struct artnet_outp struct artnet_output *output = &artnet->output_ports[index]; stats->dmx_recv = stats_counter_copy(&output->stats.dmx_recv); - stats->dmx_sync = stats_counter_copy(&output->stats.dmx_sync); + stats->seq_zero = stats_counter_copy(&output->stats.seq_zero); + stats->seq_good = stats_counter_copy(&output->stats.seq_good); stats->seq_miss = stats_counter_copy(&output->stats.seq_miss); stats->seq_drop = stats_counter_copy(&output->stats.seq_drop); stats->seq_resync = stats_counter_copy(&output->stats.seq_resync); + stats->queue_update = stats_counter_copy(&output->stats.queue_update); stats->queue_overflow = stats_counter_copy(&output->stats.queue_overflow); return 0; @@ -171,11 +175,16 @@ void artnet_output_dmx(struct artnet_output *output, struct artnet_dmx *dmx) stats_counter_increment(&output->stats.dmx_recv); - if (dmx->seq == 0 || output->state.seq == 0) { + if (output->state.seq == 0) { // init or reset + } else if (dmx->seq == 0) { + // disabled + stats_counter_increment(&output->stats.seq_zero); + } else if (dmx->seq == artnet_seq_next(output->state.seq)) { // in-order + stats_counter_increment(&output->stats.seq_good); } else if (dmx->seq > output->state.seq || output->state.seq - dmx->seq >= 128) { // missed @@ -199,13 +208,8 @@ void artnet_output_dmx(struct artnet_output *output, struct artnet_dmx *dmx) return; } - // advance - if (dmx->seq) { - output->state.seq = dmx->seq; - } else { - output->state.seq++; - } - + // update + output->state.seq = dmx->seq; output->state.tick = tick; // attempt normal send first, before overwriting for overflow stats @@ -213,6 +217,8 @@ void artnet_output_dmx(struct artnet_output *output, struct artnet_dmx *dmx) stats_counter_increment(&output->stats.queue_overflow); xQueueOverwrite(output->queue, dmx); + } else { + stats_counter_increment(&output->stats.queue_update); } if (output->options.output_events) { @@ -221,7 +227,6 @@ void artnet_output_dmx(struct artnet_output *output, struct artnet_dmx *dmx) if (artnet_is_sync_state(output->artnet)) { // wait for hard sync - stats_counter_increment(&output->stats.dmx_sync); } else if (output->options.event_group && output->options.dmx_event_bit) { // sync each update xEventGroupSetBits(output->options.event_group, output->options.dmx_event_bit); diff --git a/main/artnet_cmd.c b/main/artnet_cmd.c index cc3e3bd0..f8eb4cac 100644 --- a/main/artnet_cmd.c +++ b/main/artnet_cmd.c @@ -182,10 +182,12 @@ int artnet_cmd_stats(int argc, char **argv, void *ctx) printf("Output %d: \n", i); print_stats_counter("DMX", "receive", &output_stats.dmx_recv); - print_stats_counter("DMX", "sync", &output_stats.dmx_sync); + print_stats_counter("Seq", "zero", &output_stats.seq_zero); + print_stats_counter("Seq", "good", &output_stats.seq_good); print_stats_counter("Seq", "miss", &output_stats.seq_miss); print_stats_counter("Seq", "drop", &output_stats.seq_drop); print_stats_counter("Seq", "resync", &output_stats.seq_resync); + print_stats_counter("Queue", "update", &output_stats.queue_update); print_stats_counter("Queue", "overflow", &output_stats.queue_overflow); printf("\n"); From 2ae1dd151ad500e905e609f18e7d74b82e56ff35 Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Sat, 6 Jun 2026 14:42:30 +0300 Subject: [PATCH 32/34] artnet: add update to status metrics --- main/artnet_http.c | 1 + main/artnet_status.c | 1 + main/artnet_status.h | 2 ++ web/src/components/ArtNetView.vue | 2 ++ 4 files changed, 6 insertions(+) diff --git a/main/artnet_http.c b/main/artnet_http.c index 7379929a..4cd021cc 100644 --- a/main/artnet_http.c +++ b/main/artnet_http.c @@ -91,6 +91,7 @@ static int artnet_api_write_output_object(struct json_writer *w, const struct ar JSON_WRITE_MEMBER_OBJECT(w, "dmx_counter", artnet_api_write_object_status_counter_metrics(w, &metrics->dmx_counter)) || JSON_WRITE_MEMBER_OBJECT(w, "seq_miss_counter", artnet_api_write_object_status_counter_metrics(w, &metrics->seq_miss_counter)) || JSON_WRITE_MEMBER_OBJECT(w, "seq_drop_counter", artnet_api_write_object_status_counter_metrics(w, &metrics->seq_drop_counter)) + || JSON_WRITE_MEMBER_OBJECT(w, "update_counter", artnet_api_write_object_status_counter_metrics(w, &metrics->update_counter)) || JSON_WRITE_MEMBER_OBJECT(w, "overflow_counter", artnet_api_write_object_status_counter_metrics(w, &metrics->overflow_counter)) ) ); diff --git a/main/artnet_status.c b/main/artnet_status.c index 1e830778..32af2615 100644 --- a/main/artnet_status.c +++ b/main/artnet_status.c @@ -44,6 +44,7 @@ void update_artnet_status(struct artnet *artnet) update_stats_counter_metrics(&artnet_status_stats.outputs[i].dmx_counter, &artnet_output_stats.dmx_recv, &artnet_status_metrics.outputs[i].dmx_counter); update_stats_counter_metrics(&artnet_status_stats.outputs[i].seq_miss_counter, &artnet_output_stats.seq_miss, &artnet_status_metrics.outputs[i].seq_miss_counter); update_stats_counter_metrics(&artnet_status_stats.outputs[i].seq_drop_counter, &artnet_output_stats.seq_drop, &artnet_status_metrics.outputs[i].seq_drop_counter); + update_stats_counter_metrics(&artnet_status_stats.outputs[i].update_counter, &artnet_output_stats.queue_update, &artnet_status_metrics.outputs[i].update_counter); update_stats_counter_metrics(&artnet_status_stats.outputs[i].overflow_counter, &artnet_output_stats.queue_overflow, &artnet_status_metrics.outputs[i].overflow_counter); } } diff --git a/main/artnet_status.h b/main/artnet_status.h index 13f2c9b2..8e79a651 100644 --- a/main/artnet_status.h +++ b/main/artnet_status.h @@ -15,6 +15,7 @@ struct artnet_status_stats { struct stats_counter dmx_counter; struct stats_counter seq_miss_counter; struct stats_counter seq_drop_counter; + struct stats_counter update_counter; struct stats_counter overflow_counter; } outputs[ARTNET_OUTPUTS_MAX]; }; @@ -30,6 +31,7 @@ struct artnet_status_metrics { struct stats_counter_metrics dmx_counter; struct stats_counter_metrics seq_miss_counter; struct stats_counter_metrics seq_drop_counter; + struct stats_counter_metrics update_counter; struct stats_counter_metrics overflow_counter; } outputs[ARTNET_OUTPUTS_MAX]; }; diff --git a/web/src/components/ArtNetView.vue b/web/src/components/ArtNetView.vue index 012257a5..2bf8b09a 100644 --- a/web/src/components/ArtNetView.vue +++ b/web/src/components/ArtNetView.vue @@ -105,6 +105,7 @@ DMX Seq Miss Seq Drop + Update Overflow @@ -117,6 +118,7 @@ + From 4df97f6372d62d98ad841aa562fafd7b1bc31887 Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Sat, 6 Jun 2026 14:42:39 +0300 Subject: [PATCH 33/34] artnet: re-add tick_ms to artnet view --- web/src/components/ArtNetView.vue | 2 ++ 1 file changed, 2 insertions(+) diff --git a/web/src/components/ArtNetView.vue b/web/src/components/ArtNetView.vue index 2bf8b09a..d9734204 100644 --- a/web/src/components/ArtNetView.vue +++ b/web/src/components/ArtNetView.vue @@ -102,6 +102,7 @@ Net Subnet Universe + Last DMX Seq Miss Seq Drop @@ -115,6 +116,7 @@ {{ output.net }} {{ output.subnet }} {{ output.universe }} + {{ output.state.tick_ms | interval('ms') }} From 44a88511505b43f14fac1f3269549f1db2aa4146 Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Sat, 6 Jun 2026 16:22:31 +0300 Subject: [PATCH 34/34] i2s_out: drop !dma_done tx rempty to WARN --- components/i2s_out/esp32/intr_iram.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/i2s_out/esp32/intr_iram.c b/components/i2s_out/esp32/intr_iram.c index dd2604da..6340e57b 100644 --- a/components/i2s_out/esp32/intr_iram.c +++ b/components/i2s_out/esp32/intr_iram.c @@ -111,7 +111,7 @@ static void i2s_intr_tx_rempty_handler(struct i2s_out *i2s_out, BaseType_t *pxHi if (!i2s_out->dma_done) { // XXX: ignore if fired before dma_done, will be re-enabled // XXX: may indicate a timing glitch in the output data? - LOG_ISR_ERROR("tx rempty dma_done=%u i2s_done=%u", i2s_out->dma_done, i2s_out->i2s_done); + LOG_ISR_WARN("tx rempty dma_done=%u i2s_done=%u", i2s_out->dma_done, i2s_out->i2s_done); } else { LOG_ISR_DEBUG("tx rempty dma_done=%u i2s_done=%u", i2s_out->dma_done, i2s_out->i2s_done);