diff --git a/components/config/cmd.c b/components/config/cmd.c index aa286f03..3f8c3859 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/color.c b/components/config/color.c new file mode 100644 index 00000000..adbcb107 --- /dev/null +++ b/components/config/color.c @@ -0,0 +1,57 @@ +#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; +} + +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 7a9c2e98..339df493 100644 --- a/components/config/get.c +++ b/components/config/get.c @@ -71,6 +71,13 @@ int config_get(const struct config_path path, unsigned index, char *buf, size_t break; } + case CONFIG_TYPE_COLOR: + if (config_color_str(buf, size, tab->color_type.value[index])) { + 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..a969c73b 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; }; }; @@ -167,6 +177,12 @@ 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 */ +#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/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..32b0c36e 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,13 @@ 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; + + return config_color_parse(&tab->color_type.value[index], str); +} + int config_set(const struct config_path path, const char *value) { const struct configtab *tab = path.tab; @@ -201,6 +212,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; diff --git a/components/leds/include/leds.h b/components/leds/include/leds.h index 72bf06b5..9fa7ff1a 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); @@ -401,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 @@ -414,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 9e629c6d..0783bf2e 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) @@ -129,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); @@ -138,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 60d521c3..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. */ @@ -260,9 +242,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/config_http_get.c b/main/config_http_get.c index 8ccb8b0a..b078a8f6 100644 --- a/main/config_http_get.c +++ b/main/config_http_get.c @@ -30,6 +30,17 @@ 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) +{ + char buf[CONFIG_COLOR_BUF_SIZE]; + + 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) { @@ -49,6 +60,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 +180,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 +202,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; } 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 eb46b4f8..aa910dbd 100644 --- a/main/http_routes.h +++ b/main/http_routes.h @@ -42,9 +42,13 @@ 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); +/* 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.c b/main/leds.c index 5ab11e71..4b29975d 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" @@ -75,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; @@ -105,6 +111,13 @@ int init_leds() return err; } } + + if (config->static_enabled) { + if ((err = config_leds_static(state, config))) { + LOG_ERROR("leds%d: init_leds_static", i + 1); + return err; + } + } } return 0; @@ -243,7 +256,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; @@ -253,92 +266,10 @@ 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; - } - - 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; -} - -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_api.c b/main/leds_api.c new file mode 100644 index 00000000..c51af09f --- /dev/null +++ b/main/leds_api.c @@ -0,0 +1,70 @@ +#include "leds_api.h" + +#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) +{ + 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..d12c0676 --- /dev/null +++ b/main/leds_api.h @@ -0,0 +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_artnet.c b/main/leds_artnet.c index 7df50c7c..05cbfe6b 100644 --- a/main/leds_artnet.c +++ b/main/leds_artnet.c @@ -152,7 +152,14 @@ static void leds_artnet_sync_reset(struct leds_state *state) } } -void leds_artnet_timeout_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; @@ -163,6 +170,26 @@ 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? + // TODO: fallback to static, sequence? + + leds_clear_all(state->leds); +} + TickType_t leds_artnet_wait(struct leds_state *state) { if (state->artnet->sync_missed) { @@ -174,7 +201,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; } @@ -223,25 +250,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)) { @@ -271,6 +279,25 @@ 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 + leds_clear_all(state->leds); + + 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]; @@ -284,13 +311,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); @@ -303,6 +323,8 @@ 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) { + leds_artnet_clean(state); + // set output from artnet universe for (unsigned index = 0; index < state->artnet->universe_count; index++) { if (!(data_bits & (1 << index))) { @@ -336,6 +358,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); @@ -346,27 +369,27 @@ 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"); - } + } else if (leds_artnet_timeout_expired(state)) { + stats_counter_increment(&stats->artnet_timeout); - // repeat - leds_artnet_timeout_reset(state); - } - } + timeout = true; - if (update) { - leds_artnet_sync_reset(state); + leds_artnet_timeout(state); + leds_artnet_timeout_clear(state); + leds_artnet_sync_clear(state); } - + + // return if (timeout) { return LEDS_ARTNET_UPDATE_TIMEOUT; } else if (update) { @@ -416,8 +439,12 @@ 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); + // 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 c95e3660..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_reset(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_cmd.c b/main/leds_cmd.c index 51b75626..8a265fe2 100644 --- a/main/leds_cmd.c +++ b/main/leds_cmd.c @@ -1,7 +1,9 @@ #include "leds.h" #include "leds_state.h" +#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" @@ -14,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++) { @@ -71,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) { @@ -80,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"); @@ -143,13 +149,61 @@ 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 ((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; +} + +int leds_cmd_static(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; + + // TODO: use leds_api_color_parse()? + 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 = 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; } } @@ -169,6 +223,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, @@ -196,15 +251,14 @@ 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 = start_leds_update(state, LEDS_UPDATE_CMD))) { + LOG_ERROR("start_leds_update"); + continue; } - if ((err = update_leds(state, USER_ACTIVITY_LEDS_CMD))) { - LOG_ERROR("update_leds"); - return err; - } + leds_set_all(state->leds, leds_color); + + end_leds_update(state); } return 0; @@ -233,6 +287,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, @@ -252,52 +307,54 @@ 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) { - 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 = set_leds_test_next(state))) { + LOG_ERROR("set_leds_test_next"); + return err; + } } } @@ -319,10 +376,14 @@ 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; } + + // no-op update + + end_leds_update(state); } else { for (int i = 0; i < LEDS_COUNT; i++) { const struct leds_config *config = &leds_configs[i]; @@ -332,10 +393,14 @@ 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; } + + // no-op update + + end_leds_update(state); } } @@ -406,7 +471,10 @@ 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", "update", &stats->update); + 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); @@ -432,11 +500,12 @@ 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" }, { "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 = "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_config.c b/main/leds_config.c index 02353d47..0124dbfb 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 }, {} }; @@ -350,3 +349,37 @@ int config_leds(struct leds_state *state, const struct leds_config *config) return 0; } + +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); + } +} diff --git a/main/leds_config.h b/main/leds_config.h index 25733088..4d7eafa5 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]; @@ -192,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_configtab.i b/main/leds_configtab.i index 66cae892..a6ada7e0 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 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", + .description = "Static color to output at boot", + .color_type = { .value = &LEDS_CONFIG.static_color }, + }, + {} }; diff --git a/main/leds_http.c b/main/leds_http.c index eb00e0de..82a41e84 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" @@ -71,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) @@ -105,6 +107,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_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) ); } @@ -161,22 +166,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); } } @@ -186,7 +180,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; } @@ -223,41 +217,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 26a7a5ef..6d723bfd 100644 --- a/main/leds_http_post.c +++ b/main/leds_http_post.c @@ -1,6 +1,8 @@ #include "leds.h" +#include "leds_api.h" #include "leds_config.h" #include "leds_state.h" +#include "leds_task.h" #include "http_routes.h" #include "http_handlers.h" @@ -9,36 +11,11 @@ #include -struct leds_api_req { +struct leds_api_params { 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) +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; @@ -46,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) { + if (strcmp(key, "leds") == 0) { + if (params->state) { + LOG_WARN("duplicate leds="); return HTTP_UNPROCESSABLE_ENTITY; - } else if (index <= 0 || index > LEDS_COUNT) { - 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) { @@ -75,33 +54,41 @@ 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))) { 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_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) { - LOG_ERROR("leds_api_state_parse"); - return err; + 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", key, value ? value : ""); - return err; + LOG_WARN("leds_api_params_parse: %s=%s -> %d", key, value ? value : "", err); + goto error; } } @@ -110,94 +97,19 @@ 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; - } - } - - return HTTP_NO_CONTENT; -} - -int leds_api_post(struct http_request *request, struct http_response *response, void *ctx) -{ - const struct http_request_headers *headers; - int err; + err = HTTP_NO_CONTENT; - if ((err = http_request_headers(request, &headers))) { - LOG_WARN("http_request_headers"); - return err; +error: + if (params.state) { + end_leds_update(params.state); } - switch (headers->content_type) { - case HTTP_CONTENT_TYPE_APPLICATION_X_WWW_FORM_URLENCODED: - return leds_api_form(request, response); - - default: - LOG_WARN("Unknown Content-Type"); - - return HTTP_UNSUPPORTED_MEDIA_TYPE; - } -} - -/* POST /api/leds/test */ -struct leds_api_test_params { - int index; - enum leds_test_mode 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) { - return HTTP_UNPROCESSABLE_ENTITY; - } - } 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 { - return HTTP_UNPROCESSABLE_ENTITY; - } - - return 0; + return err; } -/* 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) +int leds_api_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))) { @@ -207,37 +119,11 @@ int leds_api_test_post(struct http_request *request, struct http_response *respo 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; + return leds_api_post_form(request, response); default: LOG_WARN("Unknown Content-Type"); 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); - 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"); - return HTTP_INTERNAL_SERVER_ERROR; - } else if (err) { - LOG_WARN("test_leds"); - return HTTP_CONFLICT; - } - - return HTTP_NO_CONTENT; } diff --git a/main/leds_state.h b/main/leds_state.h index 84c27494..c0e4c1bf 100644 --- a/main/leds_state.h +++ b/main/leds_state.h @@ -1,13 +1,14 @@ #pragma once +#include #include #include "leds.h" -#include "leds_status.h" #include "user.h" #include #include #include +#include struct leds_config; @@ -15,12 +16,35 @@ 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, + LEDS_UPDATE_CMD, + 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; - + SemaphoreHandle_t mutex; + 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; @@ -28,6 +52,9 @@ struct leds_state { struct leds_test_state *test; struct leds_artnet_state *artnet; struct leds_sequence_state *sequence; + struct leds_static_state { + struct leds_color color; + } static_; struct leds_status_timers status_timers; struct leds_status_timer_metrics status_timer_metrics; @@ -66,14 +93,12 @@ 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); -/* - * Variant of update_leds() that turns off all LEDs, and does NOT flash the status leds. - */ -int clear_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); -int test_leds_mode(struct leds_state *state, enum leds_test_mode mode); -int test_leds(struct leds_state *state); +/* Release LEDS and trigger task */ +void end_leds_update(struct leds_state *state); diff --git a/main/leds_static.c b/main/leds_static.c new file mode 100644 index 00000000..7bb6442a --- /dev/null +++ b/main/leds_static.c @@ -0,0 +1,55 @@ +#include "leds_static.h" +#include "leds_config.h" +#include "leds_task.h" +#include "leds_test.h" +#include "leds_artnet.h" + +#include + +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))); + + 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; +} + +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 +} + +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); + + leds_set_all(state->leds, state->static_.color); + + return 1; +} diff --git a/main/leds_static.h b/main/leds_static.h new file mode 100644 index 00000000..7d7d5421 --- /dev/null +++ b/main/leds_static.h @@ -0,0 +1,16 @@ +#pragma once + +#include "leds_state.h" + +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); + +/* 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_static_http.c b/main/leds_static_http.c new file mode 100644 index 00000000..1a88393a --- /dev/null +++ b/main/leds_static_http.c @@ -0,0 +1,97 @@ +#include "leds.h" +#include "leds_api.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) +{ + 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; + } else if (leds_api_color_parse(¶ms->color, leds_parameter_type_for_protocol(leds_protocol(params->state->leds)), value)) { + return HTTP_UNPROCESSABLE_ENTITY; + } + } 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 leds="); + 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; + } +} diff --git a/main/leds_stats.c b/main/leds_stats.c index 610f3d70..991a772e 100644 --- a/main/leds_stats.c +++ b/main/leds_stats.c @@ -21,7 +21,10 @@ void init_leds_stats() stats_timer_init(&stats->test); stats_timer_init(&stats->artnet); stats_timer_init(&stats->sequence); - stats_timer_init(&stats->update); + 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 faf35118..9a80856e 100644 --- a/main/leds_stats.h +++ b/main/leds_stats.h @@ -26,8 +26,14 @@ struct leds_stats { struct stats_timer sequence; - struct stats_timer update; + struct stats_timer static_; + + 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_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/main/leds_task.c b/main/leds_task.c index b4147368..f7f1895b 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" @@ -10,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) { @@ -21,7 +24,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) { @@ -68,6 +80,12 @@ static EventBits_t leds_task_wait(struct leds_state *state) } } + if ((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(); @@ -83,25 +101,56 @@ 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); + 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; 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 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_SEQUENCE_BIT)), + !!(event_bits & (1 << LEDS_EVENT_STATIC_BIT)), + !!(event_bits & (1 << LEDS_EVENT_UPDATE_BIT)) ); + if (!xSemaphoreTakeRecursive(state->mutex, LEDS_MUTEX_TIMEOUT)) { + LOG_FATAL("xSemaphoreTakeRecursive: timeout"); + } + 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; 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; @@ -109,22 +158,55 @@ 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); + if (leds_static_active(state, event_bits)) { + leds_update_state(state, LEDS_UPDATE_STATIC); + + LOG_DEBUG("static"); + + WITH_STATS_TIMER(&stats->static_) { + if (leds_static_update(state, event_bits)) { + user_activity(USER_ACTIVITY_LEDS_STATIC); + + update = true; + } + } + } + + if (state->test && leds_test_active(state, event_bits)) { + leds_update_state(state, LEDS_UPDATE_TEST); + + LOG_DEBUG("test"); + + WITH_STATS_TIMER(&stats->test) { + if (leds_test_update(state, event_bits)) { + user_activity(USER_ACTIVITY_LEDS_TEST); + + update = true; + } + } + } + if (state->sequence && leds_sequence_active(state, event_bits)) { + leds_update_state(state, LEDS_UPDATE_SEQUENCE); + LOG_DEBUG("sequence"); 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; } } } if (state->artnet && leds_artnet_active(state, event_bits)) { + leds_update_state(state, LEDS_UPDATE_ARTNET); + LOG_DEBUG("artnet"); WITH_STATS_TIMER(&stats->artnet) { @@ -133,11 +215,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: @@ -146,33 +232,30 @@ static void leds_main(void *ctx) } } - if (state->test && leds_test_active(state, event_bits)) { - LOG_DEBUG("test"); + if (leds_update_active(state, event_bits)) { + LOG_DEBUG("update"); - WITH_STATS_TIMER(&stats->test) { - if (leds_test_update(state, event_bits)) { - update_activity = USER_ACTIVITY_LEDS_TEST; - } - } - } + // external + update = true; - if (leds_update_active(state)) { + } 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"); - + + // TODO: rename to output_tick, output_timeout? 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); } @@ -181,6 +264,10 @@ static void leds_main(void *ctx) } error: + 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); state->task = NULL; @@ -243,3 +330,67 @@ void notify_leds_tasks(EventBits_t bits) notify_leds_task(state, bits); } } + +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; + } + + 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; + + default: + LOG_ERROR("update_state=%d", update_state); + return -1; + } + + return 0; +} + +void end_leds_update(struct leds_state *state) +{ + struct leds_stats *stats = &leds_stats[state->index]; + + assert(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; + } + + if (!xSemaphoreGiveRecursive(state->mutex)) { + LOG_FATAL("xSemaphoreGiveRecursive: mutex not locked by task"); + } + + xEventGroupSetBits(state->event_group, 1 << LEDS_EVENT_UPDATE_BIT); +} diff --git a/main/leds_task.h b/main/leds_task.h index 681fd90d..e3c59d22 100644 --- a/main/leds_task.h +++ b/main/leds_task.h @@ -5,13 +5,15 @@ #include -#define LEDS_EVENT_BITS 0x0f +#define LEDS_EVENT_BITS 0x003f 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, + LEDS_EVENT_UPDATE_BIT = 5, }; int init_leds_task(struct leds_state *state, const struct leds_config *config); diff --git a/main/leds_test.c b/main/leds_test.c index 6d8553b5..64fa0b33 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,35 +63,113 @@ 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"); + } } } -static void leds_test_reset(struct leds_state *state) +static void leds_test_next_mode(struct leds_state *state) { - state->test->mode = 0; + 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; } -void leds_test_clear(struct leds_state *state) +int set_leds_test(struct leds_state *state, enum leds_test_mode mode, bool auto_mode) { - if (!state->test->mode) { - return; + 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 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->frame_tick) { + 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); + + return 0; +} + +int set_leds_test_next(struct leds_state *state) +{ + if (!state->test) { + LOG_ERROR("disabled"); + return -1; } - // incoming artnet data overrides test mode - if (leds_clear_all(state->leds)) { - LOG_WARN("leds_clear_all"); + 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); + + return 0; +} + +int clear_leds_test(struct leds_state *state) +{ + if (!state->test) { + LOG_ERROR("disabled"); + return -1; } - leds_test_reset(state); + // will be reset to 0 by leds_test_update() + 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); + + notify_leds_task(state, 1 << LEDS_EVENT_TEST_BIT); + + return 0; +} + +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) @@ -114,10 +178,14 @@ 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) { @@ -134,50 +202,59 @@ 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(); + 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); - LOG_INFO("test mode=%d", state->test->mode); - } else if (state->test->frame_tick) { + leds_clear_all(state->leds); - } 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); + leds_test_clear(state); + + return 1; + + } else if (state->test->frame == 0) { + state->test->frame_tick = 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); + + 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; } + // 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; + } 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 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; } - if (state->artnet) { - leds_artnet_timeout_reset(state); - } - return 1; } diff --git a/main/leds_test.h b/main/leds_test.h index af839b40..0ed921c0 100644 --- a/main/leds_test.h +++ b/main/leds_test.h @@ -27,8 +27,14 @@ void auto_leds_test(); /* Clear test modes */ void reset_leds_test(); -/* Clear test mode */ -void leds_test_clear(struct leds_state *state); +/* Set test mode */ +int set_leds_test(struct leds_state *state, enum leds_test_mode mode, bool auto_mode); +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); + +/* 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); diff --git a/main/leds_test_http.c b/main/leds_test_http.c new file mode 100644 index 00000000..09ea2b30 --- /dev/null +++ b/main/leds_test_http.c @@ -0,0 +1,150 @@ +#include "leds.h" +#include "leds_api.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, "leds") == 0) { + return leds_api_leds_parse(¶ms->state, value); + } 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; + } + + if (!params->state) { + LOG_WARN("missing leds="); + return HTTP_UNPROCESSABLE_ENTITY; + } + + 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 ((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/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, 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; } } diff --git a/web/src/components/ConfigView.vue b/web/src/components/ConfigView.vue index 10491d85..b7a21d06 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/components/LedsView.vue b/web/src/components/LedsView.vue index eee6b9a6..43be0b1f 100644 --- a/web/src/components/LedsView.vue +++ b/web/src/components/LedsView.vue @@ -13,150 +13,163 @@ {{ id }} -
-

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

+
+
+

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

- - - - + +
+
+

Static

+
+ + + Applying... + + +
+ +
@@ -170,7 +183,10 @@ export default { data: () => ({ loading: true, loadingStatus: false, - activeID: "leds1", + selectedID: null, + applyingStatic: false, + savingStatic: false, + staticColor: null, }), created() { this.load(); @@ -181,8 +197,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); } }, @@ -201,6 +226,11 @@ export default { return this.activeLeds.status; } }, + static() { + if (this.activeLeds) { + return this.activeLeds.static; + } + }, }, filters: { limitPower: function(limit) { @@ -248,6 +278,8 @@ export default { } finally { this.loading = false; } + + this.staticColor = this.static.color; }, async loadStatus() { this.loadingStatus = true; @@ -258,8 +290,38 @@ export default { this.loadingStatus = false; } }, - switchActive(id) { - this.activeID = id; + 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; + } + }, + async saveStaticColor() { + this.savingStatic = true; + + try { + 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 c796745c..10f657a1 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,20 @@ export default class ConfigService { return response.data } + async set(entries) { + let data = new URLSearchParams(); + + 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); + + return response.data; + } + async post(config, formdata) { let data = new URLSearchParams(); @@ -23,19 +60,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); } 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..92b21133 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 }, entries) { + try { + const configState = await configService.set(entries); + + commit('updateConfigState', configState); + } catch (error) { + if (error.name == "APIError" && error.data) { + commit('updateConfigState', error.data); + } + throw error; + } + }, /* system */ async loadSystem({ commit }) { @@ -156,6 +168,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 8c880e5b..000076e5 100644 --- a/web/src/styles/form.css +++ b/web/src/styles/form.css @@ -160,6 +160,11 @@ fieldset input[type="file"] { margin: auto 0; } +fieldset input[type="color"] { + background: none; + padding: 0; +} + fieldset div.actions button { width: 1.8em; height: 1.8em; @@ -223,6 +228,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; }