Includes slight refactor of pattern factorymaster
| @@ -1,4 +1,5 @@ | |||
| idf_component_register( | |||
| SRCS | |||
| "main.cpp" | |||
| "wifi.cpp" | |||
| @@ -10,6 +11,9 @@ idf_component_register( | |||
| "leds.cpp" | |||
| "presets.cpp" | |||
| "spi_leds.cpp" | |||
| "patterns/gradient.cpp" | |||
| "patterns/random.cpp" | |||
| "patterns/sparkle.cpp" | |||
| INCLUDE_DIRS "." | |||
| ) | |||
| @@ -27,8 +31,3 @@ endfunction() | |||
| function(export) | |||
| 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" | |||
| 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) : | |||
| length(_length), pattern(NULL), | |||
| pixels(new Color[_length]), | |||
| @@ -8,26 +8,12 @@ struct Color { | |||
| uint8_t r, g, b; | |||
| } __attribute__ ((packed)); | |||
| typedef uint16_t Fixed; | |||
| constexpr int factor = 65536; | |||
| 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 { | |||
| public: | |||
| virtual ~Pattern() {}; | |||
| @@ -39,38 +25,6 @@ public: | |||
| 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 { | |||
| public: | |||
| @@ -149,16 +149,17 @@ extern "C" void app_main(void) { | |||
| if (pattern) { | |||
| ESP_LOGI(TAG, "Setting pattern '%s'", effect.c_str()); | |||
| LEDs->setPattern(pattern); | |||
| char *config = cJSON_PrintUnformatted(json); | |||
| write_file(config_path, config); | |||
| ESP_LOGI(TAG, "Saved config"); | |||
| cJSON_free(config); | |||
| } else { | |||
| ESP_LOGW(TAG, "Could not find pattern '%s'", effect.c_str()); | |||
| pattern = LEDs->getPattern(); | |||
| } | |||
| char *config = cJSON_PrintUnformatted(json); | |||
| write_file(config_path, config); | |||
| ESP_LOGI(TAG, "Saved config"); | |||
| cJSON_free(config); | |||
| cJSON_Delete(json); | |||
| 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" | |||
| /* 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 { | |||
| /* 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 */ | |||
| 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_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; | |||
| } | |||
| 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) { | |||
| ESP_LOGE(TAG, "Not an object: %s", json->string); | |||
| return NULL; | |||
| @@ -74,38 +92,22 @@ static Pattern* json_pattern(cJSON *json, const std::map <std::string, Color> &c | |||
| cJSON *pattern = json->child; | |||
| 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()); | |||
| return NULL; | |||
| } | |||
| return NULL; | |||
| return iter->second(pattern, colors); | |||
| } | |||
| 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"); | |||
| if (!jcolors) { | |||
| ESP_LOGW(TAG, "No preset colors!"); | |||
| @@ -1,7 +1,10 @@ | |||
| #pragma once | |||
| #include <string> | |||
| #include <map> | |||
| #include <cJSON.h> | |||
| #include "leds.hpp" | |||
| @@ -14,4 +17,12 @@ std::map<std::string, const Pattern*>::const_iterator map_end(); | |||
| void reload(); | |||
| // Helpers | |||
| typedef std::map <std::string, Color> ColorMap; | |||
| Color json_color(cJSON *json, const ColorMap &colors); | |||
| } // Presets | |||
| @@ -5,6 +5,8 @@ | |||
| "peacock": {"gradient": {"colors": ["purple", "blue", "teal", "black"]}}, | |||
| "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"]}}, | |||
| "irish": {"gradient": {"colors": ["dark_green", "black", "green", "black"]}}, | |||