Преглед изворни кода

Add random and sparkle animation patterns

Includes slight refactor of pattern factory
master
jrhoffa пре 3 година
родитељ
комит
94797ebda9
10 измењених фајлова са 442 додато и 141 уклоњено
  1. +4
    -5
      main/CMakeLists.txt
  2. +0
    -56
      main/leds.cpp
  3. +1
    -47
      main/leds.hpp
  4. +6
    -5
      main/main.cpp
  5. +138
    -0
      main/patterns/gradient.cpp
  6. +118
    -0
      main/patterns/random.cpp
  7. +132
    -0
      main/patterns/sparkle.cpp
  8. +30
    -28
      main/presets.cpp
  9. +11
    -0
      main/presets.hpp
  10. +2
    -0
      main/presets.json

+ 4
- 5
main/CMakeLists.txt Прегледај датотеку

@@ -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)

+ 0
- 56
main/leds.cpp Прегледај датотеку

@@ -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]),


+ 1
- 47
main/leds.hpp Прегледај датотеку

@@ -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:


+ 6
- 5
main/main.cpp Прегледај датотеку

@@ -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) {


+ 138
- 0
main/patterns/gradient.cpp Прегледај датотеку

@@ -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);
}

+ 118
- 0
main/patterns/random.cpp Прегледај датотеку

@@ -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);
}

+ 132
- 0
main/patterns/sparkle.cpp Прегледај датотеку

@@ -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);
}

+ 30
- 28
main/presets.cpp Прегледај датотеку

@@ -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!");


+ 11
- 0
main/presets.hpp Прегледај датотеку

@@ -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

+ 2
- 0
main/presets.json Прегледај датотеку

@@ -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"]}},


Loading…
Откажи
Сачувај