| @@ -1,4 +1,5 @@ | |||||
| idf_component_register( | idf_component_register( | ||||
| SRCS | SRCS | ||||
| "main.cpp" | "main.cpp" | ||||
| "wifi.cpp" | "wifi.cpp" | ||||
| @@ -10,6 +11,9 @@ idf_component_register( | |||||
| "leds.cpp" | "leds.cpp" | ||||
| "presets.cpp" | "presets.cpp" | ||||
| "spi_leds.cpp" | "spi_leds.cpp" | ||||
| "patterns/gradient.cpp" | |||||
| "patterns/random.cpp" | |||||
| "patterns/sparkle.cpp" | |||||
| INCLUDE_DIRS "." | INCLUDE_DIRS "." | ||||
| ) | ) | ||||
| @@ -27,8 +31,3 @@ endfunction() | |||||
| function(export) | function(export) | ||||
| endfunction() | endfunction() | ||||
| # Import tinyxml2 targets | |||||
| #add_subdirectory(lib/tinyxml2) | |||||
| # Link tinyxml2 to main component | |||||
| #target_link_libraries(${COMPONENT_LIB} PUBLIC tinyxml2) | |||||
| @@ -5,62 +5,6 @@ static const char *TAG = "leds"; | |||||
| #include "utils.hpp" | #include "utils.hpp" | ||||
| static inline uint8_t mix(int l, int r, Fixed x) { | |||||
| return ((l << shift) + (x * (r - l))) >> shift; | |||||
| } | |||||
| Color Gradient::at(Fixed x) const { | |||||
| int i_left = (x * n_colors) >> shift; | |||||
| int i_right = i_left < n_colors - 1 ? i_left + 1 : 0; | |||||
| Fixed ratio = (x * n_colors) - (i_left << shift); | |||||
| //ESP_LOGI(TAG, "at(%d) = %d between %d and %d", x, ratio, i_left, i_right); | |||||
| Color cl = colors[i_left]; | |||||
| Color cr = colors[i_right]; | |||||
| Color color = { | |||||
| mix(cl.r, cr.r, ratio), | |||||
| mix(cl.g, cr.g, ratio), | |||||
| mix(cl.b, cr.b, ratio) | |||||
| }; | |||||
| /*ESP_LOGI(TAG, "(%d, %d, %d) %f (%d, %d, %d) -> (%d, %d, %d)", | |||||
| cl.r, cl.g, cl.b, | |||||
| ratio / (float)factor, | |||||
| cr.r, cr.g, cr.b, | |||||
| color.r, color.g, color.b | |||||
| );*/ | |||||
| return color; | |||||
| } | |||||
| void GradientPattern::step(Color pixels[], int len, Pattern::State *_state) const { | |||||
| GradientPattern::State *state = (GradientPattern::State*)_state; | |||||
| int64_t now_us = time_us(); | |||||
| int64_t duration_us = state->last_us == 0 ? 0 : now_us - state->last_us; | |||||
| state->last_us = now_us; | |||||
| //ESP_LOGI(TAG, "duration %d", duration_us); | |||||
| /* | |||||
| int period_us = 1000000 / 60; | |||||
| int n_skipped = (duration_us - period_us / 2) / period_us; | |||||
| if (n_skipped) { | |||||
| ESP_LOGW(TAG, "Skipped %d frames", n_skipped); | |||||
| } | |||||
| */ | |||||
| // Don't make a major animation jump if it's been terribly long | |||||
| if (duration_us > 100000) duration_us = 100000; | |||||
| int offset_delta = (duration_us << shift) / (cycle_time_ms * 1000); | |||||
| if (reverse) state->offset += offset_delta; else state->offset -= offset_delta; | |||||
| Fixed off = march ? | |||||
| (((int)state->offset * cycle_length) & ~((1 << shift) - 1)) / cycle_length : | |||||
| state->offset; | |||||
| //ESP_LOGI(TAG, "cycle time %d, delta %d, offset %d, off %d", cycle_time_ms, offset_delta, offset, off); | |||||
| for (int i = 0; i < len; ++i) { | |||||
| pixels[i] = gradient->at(off + ((i << shift) / cycle_length)); | |||||
| } | |||||
| } | |||||
| LEDStrip::LEDStrip(int _length) : | LEDStrip::LEDStrip(int _length) : | ||||
| length(_length), pattern(NULL), | length(_length), pattern(NULL), | ||||
| pixels(new Color[_length]), | pixels(new Color[_length]), | ||||
| @@ -8,26 +8,12 @@ struct Color { | |||||
| uint8_t r, g, b; | uint8_t r, g, b; | ||||
| } __attribute__ ((packed)); | } __attribute__ ((packed)); | ||||
| typedef uint16_t Fixed; | typedef uint16_t Fixed; | ||||
| constexpr int factor = 65536; | constexpr int factor = 65536; | ||||
| constexpr int shift = 16; | constexpr int shift = 16; | ||||
| struct Gradient { | |||||
| Color at(Fixed x) const; | |||||
| static Gradient* make(int n) { | |||||
| Gradient *gradient = (Gradient*)malloc( | |||||
| sizeof(Gradient) + n * sizeof(*colors) | |||||
| ); | |||||
| gradient->n_colors = n; | |||||
| return gradient; | |||||
| } | |||||
| unsigned int n_colors; | |||||
| Color colors[]; | |||||
| }; | |||||
| class Pattern { | class Pattern { | ||||
| public: | public: | ||||
| virtual ~Pattern() {}; | virtual ~Pattern() {}; | ||||
| @@ -39,38 +25,6 @@ public: | |||||
| virtual void step(Color[], int, State*) const = 0; | virtual void step(Color[], int, State*) const = 0; | ||||
| }; | }; | ||||
| class GradientPattern : public Pattern { | |||||
| public: | |||||
| GradientPattern( | |||||
| Gradient *_gradient, | |||||
| int _cycle_length=20, int _cycle_time_ms=-1, | |||||
| bool _reverse=false, bool _march=false | |||||
| ) : cycle_length(_cycle_length), | |||||
| cycle_time_ms( _cycle_time_ms < 0 ? | |||||
| _cycle_length * 500 : _cycle_time_ms | |||||
| ), | |||||
| reverse(_reverse), march(_march), gradient(_gradient) | |||||
| {} | |||||
| ~GradientPattern() { free(gradient); } | |||||
| State* start(int) const { return new State(); } | |||||
| void step(Color[], int, State*) const; | |||||
| private: | |||||
| struct State : Pattern::State { | |||||
| State() : last_us(0), offset(0) {} | |||||
| int64_t last_us; | |||||
| Fixed offset; | |||||
| }; | |||||
| int cycle_length; | |||||
| int cycle_time_ms; | |||||
| bool reverse; | |||||
| bool march; | |||||
| Gradient *gradient; | |||||
| }; | |||||
| class LEDStrip { | class LEDStrip { | ||||
| public: | public: | ||||
| @@ -149,16 +149,17 @@ extern "C" void app_main(void) { | |||||
| if (pattern) { | if (pattern) { | ||||
| ESP_LOGI(TAG, "Setting pattern '%s'", effect.c_str()); | ESP_LOGI(TAG, "Setting pattern '%s'", effect.c_str()); | ||||
| LEDs->setPattern(pattern); | LEDs->setPattern(pattern); | ||||
| char *config = cJSON_PrintUnformatted(json); | |||||
| write_file(config_path, config); | |||||
| ESP_LOGI(TAG, "Saved config"); | |||||
| cJSON_free(config); | |||||
| } else { | } else { | ||||
| ESP_LOGW(TAG, "Could not find pattern '%s'", effect.c_str()); | ESP_LOGW(TAG, "Could not find pattern '%s'", effect.c_str()); | ||||
| pattern = LEDs->getPattern(); | pattern = LEDs->getPattern(); | ||||
| } | } | ||||
| char *config = cJSON_PrintUnformatted(json); | |||||
| write_file(config_path, config); | |||||
| ESP_LOGI(TAG, "Saved config"); | |||||
| cJSON_free(config); | |||||
| cJSON_Delete(json); | cJSON_Delete(json); | ||||
| if (length <= 0 || !pattern || !is_on) { | if (length <= 0 || !pattern || !is_on) { | ||||
| @@ -0,0 +1,138 @@ | |||||
| static const char *TAG = "gradient"; | |||||
| #include <esp_log.h> | |||||
| #include "presets.hpp" | |||||
| #include "utils.hpp" | |||||
| struct Gradient { | |||||
| Color at(Fixed x) const; | |||||
| static Gradient* make(int n) { | |||||
| Gradient *gradient = (Gradient*)malloc( | |||||
| sizeof(Gradient) + n * sizeof(*colors) | |||||
| ); | |||||
| gradient->n_colors = n; | |||||
| return gradient; | |||||
| } | |||||
| unsigned int n_colors; | |||||
| Color colors[]; | |||||
| }; | |||||
| class GradientPattern : public Pattern { | |||||
| public: | |||||
| GradientPattern( | |||||
| Gradient *_gradient, | |||||
| int _cycle_length=20, int _cycle_time_ms=-1, | |||||
| bool _reverse=false, bool _march=false | |||||
| ) : cycle_length(_cycle_length), | |||||
| cycle_time_ms( _cycle_time_ms < 0 ? | |||||
| _cycle_length * 500 : _cycle_time_ms | |||||
| ), | |||||
| reverse(_reverse), march(_march), gradient(_gradient) | |||||
| {} | |||||
| ~GradientPattern() { free(gradient); } | |||||
| State* start(int) const { return new State(); } | |||||
| void step(Color[], int, State*) const; | |||||
| private: | |||||
| struct State : Pattern::State { | |||||
| State() : last_us(0), offset(0) {} | |||||
| int64_t last_us; | |||||
| Fixed offset; | |||||
| }; | |||||
| int cycle_length; | |||||
| int cycle_time_ms; | |||||
| bool reverse; | |||||
| bool march; | |||||
| Gradient *gradient; | |||||
| }; | |||||
| static inline uint8_t mix(int l, int r, Fixed x) { | |||||
| return ((l << shift) + (x * (r - l))) >> shift; | |||||
| } | |||||
| Color Gradient::at(Fixed x) const { | |||||
| int i_left = (x * n_colors) >> shift; | |||||
| int i_right = i_left < n_colors - 1 ? i_left + 1 : 0; | |||||
| Fixed ratio = (x * n_colors) - (i_left << shift); | |||||
| //ESP_LOGI(TAG, "at(%d) = %d between %d and %d", x, ratio, i_left, i_right); | |||||
| Color cl = colors[i_left]; | |||||
| Color cr = colors[i_right]; | |||||
| Color color = { | |||||
| mix(cl.r, cr.r, ratio), | |||||
| mix(cl.g, cr.g, ratio), | |||||
| mix(cl.b, cr.b, ratio) | |||||
| }; | |||||
| /*ESP_LOGI(TAG, "(%d, %d, %d) %f (%d, %d, %d) -> (%d, %d, %d)", | |||||
| cl.r, cl.g, cl.b, | |||||
| ratio / (float)factor, | |||||
| cr.r, cr.g, cr.b, | |||||
| color.r, color.g, color.b | |||||
| );*/ | |||||
| return color; | |||||
| } | |||||
| void GradientPattern::step(Color pixels[], int len, Pattern::State *_state) const { | |||||
| GradientPattern::State *state = (GradientPattern::State*)_state; | |||||
| int64_t now_us = time_us(); | |||||
| int64_t duration_us = state->last_us == 0 ? 0 : now_us - state->last_us; | |||||
| state->last_us = now_us; | |||||
| //ESP_LOGI(TAG, "duration %d", duration_us); | |||||
| /* | |||||
| int period_us = 1000000 / 60; | |||||
| int n_skipped = (duration_us - period_us / 2) / period_us; | |||||
| if (n_skipped) { | |||||
| ESP_LOGW(TAG, "Skipped %d frames", n_skipped); | |||||
| } | |||||
| */ | |||||
| // Don't make a major animation jump if it's been terribly long | |||||
| if (duration_us > 100000) duration_us = 100000; | |||||
| int offset_delta = (duration_us << shift) / (cycle_time_ms * 1000); | |||||
| if (reverse) state->offset += offset_delta; else state->offset -= offset_delta; | |||||
| Fixed off = march ? | |||||
| (((int)state->offset * cycle_length) & ~((1 << shift) - 1)) / cycle_length : | |||||
| state->offset; | |||||
| //ESP_LOGI(TAG, "cycle time %d, delta %d, offset %d, off %d", cycle_time_ms, offset_delta, offset, off); | |||||
| for (int i = 0; i < len; ++i) { | |||||
| pixels[i] = gradient->at(off + ((i << shift) / cycle_length)); | |||||
| } | |||||
| } | |||||
| Pattern* json_gradient_pattern(cJSON *pattern, const Presets::ColorMap &colors) { | |||||
| if (pattern->type != cJSON_Object) { | |||||
| ESP_LOGE(TAG, "Not an object: %s", pattern->type); | |||||
| return NULL; | |||||
| } | |||||
| cJSON *jcolors = cJSON_GetObjectItem(pattern, "colors"); | |||||
| if (!jcolors) { | |||||
| ESP_LOGE(TAG, "No colors in %s", pattern->string); | |||||
| return NULL; | |||||
| } | |||||
| if (jcolors->type != cJSON_Array) { | |||||
| ESP_LOGE(TAG, "Not an array: %s", jcolors->string); | |||||
| return NULL; | |||||
| } | |||||
| Gradient *gradient = Gradient::make(cJSON_GetArraySize(jcolors)); | |||||
| int i = 0; | |||||
| cJSON *jcolor; | |||||
| cJSON_ArrayForEach(jcolor, jcolors) { | |||||
| Color color = Presets::json_color(jcolor, colors); | |||||
| gradient->colors[i++] = color; | |||||
| } | |||||
| return new GradientPattern(gradient); | |||||
| } | |||||
| @@ -0,0 +1,118 @@ | |||||
| static const char *TAG = "random"; | |||||
| #include <esp_log.h> | |||||
| #include <esp_random.h> | |||||
| #include "presets.hpp" | |||||
| #include "utils.hpp" | |||||
| static Color rand_color() { | |||||
| static const Color colors[] = { | |||||
| {255, 0, 0}, | |||||
| { 78, 25, 0}, | |||||
| {255, 160, 0}, | |||||
| { 0, 255, 0}, | |||||
| { 0, 0, 255}, | |||||
| { 38, 10, 42}, | |||||
| }; | |||||
| return colors[esp_random() % 6]; | |||||
| } | |||||
| class RandomPattern : public Pattern { | |||||
| private: | |||||
| struct State : Pattern::State { | |||||
| State(int _len, int gap) : | |||||
| last_us(0), step(0), offset(0), | |||||
| n_colors((_len + gap) / (gap + 1)), colors(new Color[n_colors]) | |||||
| { | |||||
| for (Color *color = colors, *end = colors + n_colors; color < end; ++color) { | |||||
| *color = rand_color(); | |||||
| } | |||||
| } | |||||
| ~State() { delete[] colors; } | |||||
| int64_t last_us; | |||||
| int step; | |||||
| int offset; | |||||
| int n_colors; | |||||
| Color *colors; | |||||
| }; | |||||
| public: | |||||
| RandomPattern(int _delay_ms, int _gap) : delay_us(_delay_ms * 1000), gap(_gap) {} | |||||
| State* start(int len) const { return new State(len, gap); } | |||||
| void step(Color colors[], int len, Pattern::State *_state) const { | |||||
| State *state = (State*)_state; | |||||
| int64_t now_us = time_us(); | |||||
| int64_t elapsed_us = now_us - state->last_us; | |||||
| bool should_step = (state->last_us == 0) || (elapsed_us >= delay_us && delay_us > 0); | |||||
| if (should_step) { | |||||
| state->last_us = (elapsed_us >= delay_us + 100000 ? now_us : state->last_us + delay_us); | |||||
| int step = state->step; | |||||
| int offset = state->offset; | |||||
| Color *inc = state->colors + offset; | |||||
| Color *outc = colors; | |||||
| if ((gap + 1 - step) % (gap + 1) == 0) *inc = rand_color(); | |||||
| for (int i = 0; i < len; i++) { | |||||
| if ((i - step + gap + 1) % (gap + 1) == 0) { | |||||
| *outc = *inc--; | |||||
| if (inc < state->colors) inc += state->n_colors; | |||||
| } else { | |||||
| *outc = {0, 0, 0}; | |||||
| } | |||||
| ++outc; | |||||
| } | |||||
| step++; | |||||
| if (step > gap) { | |||||
| step -= gap + 1; | |||||
| ++offset; | |||||
| if (offset >= state->n_colors) offset -= state->n_colors; | |||||
| state->offset = offset; | |||||
| } | |||||
| state->step = step; | |||||
| } | |||||
| } | |||||
| private: | |||||
| int delay_us; | |||||
| int gap; | |||||
| }; | |||||
| Pattern* json_random_pattern(cJSON *pattern, const Presets::ColorMap &colors) { | |||||
| int delay_ms = 500; | |||||
| int gap = 1; | |||||
| if (pattern->type != cJSON_Object) { | |||||
| ESP_LOGE(TAG, "Not an object: %s", pattern->type); | |||||
| return NULL; | |||||
| } | |||||
| cJSON *jdelay = cJSON_GetObjectItem(pattern, "delay"); | |||||
| if (jdelay) { | |||||
| if (jdelay->type != cJSON_Number) { | |||||
| ESP_LOGE(TAG, "Not a number: %s", jdelay->string); | |||||
| } else { | |||||
| delay_ms = jdelay->valuedouble * 1000.; | |||||
| } | |||||
| } | |||||
| cJSON *jgap = cJSON_GetObjectItem(pattern, "gap"); | |||||
| if (jgap) { | |||||
| if (jgap->type != cJSON_Number) { | |||||
| ESP_LOGE(TAG, "Not a number: %s", jgap->string); | |||||
| } else { | |||||
| gap = jgap->valueint; | |||||
| } | |||||
| } | |||||
| return new RandomPattern(delay_ms, gap); | |||||
| } | |||||
| @@ -0,0 +1,132 @@ | |||||
| static const char *TAG = "random"; | |||||
| #include <esp_log.h> | |||||
| #include <list> | |||||
| #include <cstring> | |||||
| #include <esp_random.h> | |||||
| #include "presets.hpp" | |||||
| #include "utils.hpp" | |||||
| static const int8_t ndist[64] = {-127, -104, -92, -84, -77, -72, -67, -62, -58, -55, -51, -48, -45, -42, -39, -37, -34, -32, -29, -27, -25, -22, -20, -18, -16, -14, -11, -9, -7, -5, -3, -1, 1, 3, 5, 7, 9, 11, 14, 16, 18, 20, 22, 25, 27, 29, 32, 34, 37, 39, 42, 45, 48, 51, 55, 58, 62, 67, 72, 77, 84, 92, 104, 127}; | |||||
| int norm(int mean) { | |||||
| return (mean * 128) / ndist[esp_random() % 64]; | |||||
| } | |||||
| struct Sparkle { | |||||
| Sparkle(int64_t time_us, int mean_dur_us, int len): | |||||
| start_us(time_us), end_us(time_us + (mean_dur_us)), | |||||
| pos(esp_random() % len) {} | |||||
| bool expired(int64_t time_us) { return time_us >= end_us; } | |||||
| uint8_t intensity_at(int64_t time_us) { | |||||
| if (time_us < start_us || time_us >= end_us) return 0; | |||||
| int64_t midpoint_us = (start_us + end_us) / 2; | |||||
| if (time_us >= midpoint_us) time_us = start_us + (end_us - time_us); | |||||
| return (((time_us - start_us) * 255) * 2) / (end_us - start_us); | |||||
| } | |||||
| int64_t start_us; | |||||
| int end_us; | |||||
| int pos; | |||||
| }; | |||||
| Color fade(Color c, unsigned int i) { | |||||
| return { | |||||
| (uint8_t)((c.r * i) / 255), | |||||
| (uint8_t)((c.g * i) / 255), | |||||
| (uint8_t)((c.b * i) / 255) | |||||
| }; | |||||
| } | |||||
| class SparklePattern : public Pattern { | |||||
| private: | |||||
| struct State : Pattern::State { | |||||
| std::list <Sparkle> sparkles; | |||||
| }; | |||||
| public: | |||||
| SparklePattern(Color _color, int _duration_ms, int _delay_ms, int _density) : | |||||
| color(_color), duration_us(_duration_ms * 1000), | |||||
| delay_us(_delay_ms * 1000), density(_density) | |||||
| {} | |||||
| State* start(int len) const { return new State(); } | |||||
| void step(Color colors[], int len, Pattern::State *_state) const { | |||||
| State *state = (State*)_state; | |||||
| int64_t now_us = time_us(); | |||||
| int64_t start_us = now_us; | |||||
| for ( int open = (len / density) + 1 - state->sparkles.size(); | |||||
| open > 0; | |||||
| --open, start_us += delay_us | |||||
| ) { | |||||
| state->sparkles.push_front(Sparkle(start_us, duration_us, len)); | |||||
| } | |||||
| memset(colors, 0, sizeof(*colors) * len); | |||||
| for (auto iter = state->sparkles.begin(); iter != state->sparkles.end(); ) { | |||||
| if (iter->expired(now_us)) { | |||||
| state->sparkles.erase(iter++); | |||||
| } else { | |||||
| colors[iter->pos] = fade(color, iter->intensity_at(now_us)); | |||||
| ++iter; | |||||
| } | |||||
| } | |||||
| } | |||||
| private: | |||||
| Color color; | |||||
| int duration_us; | |||||
| int delay_us; | |||||
| int density; | |||||
| }; | |||||
| Pattern* json_sparkle_pattern(cJSON *pattern, const Presets::ColorMap &colors) { | |||||
| int duration_ms = 250; | |||||
| int delay_ms = 3000; | |||||
| int density = 10; | |||||
| Color color = {255, 255, 255}; | |||||
| if (pattern->type != cJSON_Object) { | |||||
| ESP_LOGE(TAG, "Not an object: %s", pattern->type); | |||||
| return NULL; | |||||
| } | |||||
| cJSON *jnum = cJSON_GetObjectItem(pattern, "delay"); | |||||
| if (jnum) { | |||||
| if (jnum->type != cJSON_Number) { | |||||
| ESP_LOGE(TAG, "Not a number: %s", jnum->string); | |||||
| } else { | |||||
| delay_ms = jnum->valuedouble * 1000.; | |||||
| } | |||||
| } | |||||
| jnum = cJSON_GetObjectItem(pattern, "duration"); | |||||
| if (jnum) { | |||||
| if (jnum->type != cJSON_Number) { | |||||
| ESP_LOGE(TAG, "Not a number: %s", jnum->string); | |||||
| } else { | |||||
| duration_ms = jnum->valuedouble * 1000.; | |||||
| } | |||||
| } | |||||
| jnum = cJSON_GetObjectItem(pattern, "density"); | |||||
| if (jnum) { | |||||
| if (jnum->type != cJSON_Number) { | |||||
| ESP_LOGE(TAG, "Not a number: %s", jnum->string); | |||||
| } else { | |||||
| density = jnum->valuedouble; | |||||
| } | |||||
| } | |||||
| return new SparklePattern(color, duration_ms, delay_ms, density); | |||||
| } | |||||
| @@ -7,8 +7,25 @@ static const char *TAG = "presets"; | |||||
| #include "utils.hpp" | #include "utils.hpp" | ||||
| /* Sadly, need to define these here */ | |||||
| extern Pattern* json_gradient_pattern(cJSON *pattern, const Presets::ColorMap &colors); | |||||
| extern Pattern* json_random_pattern(cJSON *pattern, const Presets::ColorMap &colors); | |||||
| extern Pattern* json_sparkle_pattern(cJSON *pattern, const Presets::ColorMap &colors); | |||||
| namespace Presets { | namespace Presets { | ||||
| /* Factory */ | |||||
| typedef Pattern* (*PatternGenerator)(cJSON*, const Presets::ColorMap&); | |||||
| static const std::map <std::string, PatternGenerator> generators { | |||||
| {"gradient", json_gradient_pattern}, | |||||
| {"random", json_random_pattern}, | |||||
| {"sparkle", json_sparkle_pattern}, | |||||
| }; | |||||
| /* Preset Map */ | /* Preset Map */ | ||||
| std::map <std::string, const Pattern*> map; | std::map <std::string, const Pattern*> map; | ||||
| @@ -51,7 +68,7 @@ static Color json_raw_color(cJSON *json) { | |||||
| }; | }; | ||||
| } | } | ||||
| static Color json_color(cJSON *json, const std::map <std::string, Color> &colors) { | |||||
| Color json_color(cJSON *json, const ColorMap &colors) { | |||||
| if (json->type == cJSON_Array) return json_raw_color(json); | if (json->type == cJSON_Array) return json_raw_color(json); | ||||
| if (json->type != cJSON_String) { | if (json->type != cJSON_String) { | ||||
| ESP_LOGE(TAG, "Not a string: %s", json->string); | ESP_LOGE(TAG, "Not a string: %s", json->string); | ||||
| @@ -65,7 +82,8 @@ static Color json_color(cJSON *json, const std::map <std::string, Color> &colors | |||||
| return iter->second; | return iter->second; | ||||
| } | } | ||||
| static Pattern* json_pattern(cJSON *json, const std::map <std::string, Color> &colors) { | |||||
| static Pattern* json_pattern(cJSON *json, const ColorMap &colors) { | |||||
| if (json->type != cJSON_Object) { | if (json->type != cJSON_Object) { | ||||
| ESP_LOGE(TAG, "Not an object: %s", json->string); | ESP_LOGE(TAG, "Not an object: %s", json->string); | ||||
| return NULL; | return NULL; | ||||
| @@ -74,38 +92,22 @@ static Pattern* json_pattern(cJSON *json, const std::map <std::string, Color> &c | |||||
| cJSON *pattern = json->child; | cJSON *pattern = json->child; | ||||
| std::string type(pattern->string); | std::string type(pattern->string); | ||||
| if (type == "gradient") { | |||||
| if (pattern->type != cJSON_Object) { | |||||
| ESP_LOGE(TAG, "Not an object: %s", pattern->type); | |||||
| return NULL; | |||||
| } | |||||
| cJSON *jcolors = cJSON_GetObjectItem(pattern, "colors"); | |||||
| if (!jcolors) { | |||||
| ESP_LOGE(TAG, "No colors for %s", json->string); | |||||
| return NULL; | |||||
| } | |||||
| if (jcolors->type != cJSON_Array) { | |||||
| ESP_LOGE(TAG, "Not an array: %s", jcolors->string); | |||||
| return NULL; | |||||
| } | |||||
| Gradient *gradient = Gradient::make(cJSON_GetArraySize(jcolors)); | |||||
| int i = 0; | |||||
| cJSON *jcolor; | |||||
| cJSON_ArrayForEach(jcolor, jcolors) { | |||||
| Color color = json_color(jcolor, colors); | |||||
| gradient->colors[i++] = color; | |||||
| } | |||||
| return new GradientPattern(gradient); | |||||
| } else { | |||||
| auto iter = generators.find(type); | |||||
| if (iter == generators.end()) { | |||||
| ESP_LOGE(TAG, "Unknown pattern type: %s", type.c_str()); | ESP_LOGE(TAG, "Unknown pattern type: %s", type.c_str()); | ||||
| return NULL; | |||||
| } | } | ||||
| return NULL; | |||||
| return iter->second(pattern, colors); | |||||
| } | } | ||||
| void reload() { | void reload() { | ||||
| std::map <std::string, Color> colors; | |||||
| if (generators.empty()) { | |||||
| ESP_LOGE(TAG, "No pattern generators defined!"); | |||||
| return; | |||||
| } | |||||
| ColorMap colors; | |||||
| cJSON *jcolors = load_json("/spiffs/colors.json"); | cJSON *jcolors = load_json("/spiffs/colors.json"); | ||||
| if (!jcolors) { | if (!jcolors) { | ||||
| ESP_LOGW(TAG, "No preset colors!"); | ESP_LOGW(TAG, "No preset colors!"); | ||||
| @@ -1,7 +1,10 @@ | |||||
| #pragma once | #pragma once | ||||
| #include <string> | #include <string> | ||||
| #include <map> | #include <map> | ||||
| #include <cJSON.h> | |||||
| #include "leds.hpp" | #include "leds.hpp" | ||||
| @@ -14,4 +17,12 @@ std::map<std::string, const Pattern*>::const_iterator map_end(); | |||||
| void reload(); | void reload(); | ||||
| // Helpers | |||||
| typedef std::map <std::string, Color> ColorMap; | |||||
| Color json_color(cJSON *json, const ColorMap &colors); | |||||
| } // Presets | } // Presets | ||||
| @@ -5,6 +5,8 @@ | |||||
| "peacock": {"gradient": {"colors": ["purple", "blue", "teal", "black"]}}, | "peacock": {"gradient": {"colors": ["purple", "blue", "teal", "black"]}}, | ||||
| "pusheen": {"gradient": {"colors": ["teal", "pink"]}}, | "pusheen": {"gradient": {"colors": ["teal", "pink"]}}, | ||||
| "random": {"random": {"gap": 1, "delay": 0.5}}, | |||||
| "snow": {"sparkle": {"color": "white", "duration": 0.25, "delay": 3, "density": 10}}, | |||||
| "love": {"gradient": {"colors": ["pink", "black", "red", "black"]}}, | "love": {"gradient": {"colors": ["pink", "black", "red", "black"]}}, | ||||
| "irish": {"gradient": {"colors": ["dark_green", "black", "green", "black"]}}, | "irish": {"gradient": {"colors": ["dark_green", "black", "green", "black"]}}, | ||||