Browse Source

garland: fixes and updates (#2600)

* garland: fix issue with division by zero in first scene setup

* garland: update setters code

* garland: fix web UI

* garland: update scene default settings

* garland: update palettes, add random run animation

* garland: enable and update anim_glow

* garland: update anim_spread

* garland: add anim_crossing

* garland: get rid of redundant variables in main class

* garland: provide anims with all palettes

* garland: unify anim_waves and anim_run to use ColorWave class

* garland: update format for color_wave.h and garland.cpp

* graland: update anim code with clear rands and auto counters

* garland: implement pixel caching for color waves to reduce redundant calculations

* garland: fix anim dolphins out of range issue

* garland: tune anim comets

* garland: combine all wave-based anims; add wave comet anim

* garland: fixes for PR notes
dev
Dmitry 3 months ago
committed by GitHub
parent
commit
238dc130af
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
44 changed files with 16468 additions and 16105 deletions
  1. BIN
      code/espurna/data/index.all.html.gz
  2. BIN
      code/espurna/data/index.curtain.html.gz
  3. BIN
      code/espurna/data/index.garland.html.gz
  4. BIN
      code/espurna/data/index.light.html.gz
  5. BIN
      code/espurna/data/index.lightfox.html.gz
  6. BIN
      code/espurna/data/index.rfbridge.html.gz
  7. BIN
      code/espurna/data/index.rfm69.html.gz
  8. BIN
      code/espurna/data/index.sensor.html.gz
  9. BIN
      code/espurna/data/index.small.html.gz
  10. BIN
      code/espurna/data/index.thermostat.html.gz
  11. +81
    -54
      code/espurna/garland.cpp
  12. +11
    -3
      code/espurna/garland/anim.h
  13. +3
    -6
      code/espurna/garland/animations/anim_assemble.h
  14. +8
    -12
      code/espurna/garland/animations/anim_comets.h
  15. +25
    -19
      code/espurna/garland/animations/anim_dolphins.h
  16. +3
    -6
      code/espurna/garland/animations/anim_fly.h
  17. +5
    -5
      code/espurna/garland/animations/anim_fountain.h
  18. +25
    -10
      code/espurna/garland/animations/anim_glow.h
  19. +2
    -2
      code/espurna/garland/animations/anim_pixiedust.h
  20. +2
    -2
      code/espurna/garland/animations/anim_randcyc.h
  21. +44
    -0
      code/espurna/garland/animations/anim_randrun.h
  22. +0
    -38
      code/espurna/garland/animations/anim_run.h
  23. +6
    -6
      code/espurna/garland/animations/anim_salut.h
  24. +1
    -1
      code/espurna/garland/animations/anim_sparkr.h
  25. +15
    -10
      code/espurna/garland/animations/anim_spread.h
  26. +2
    -2
      code/espurna/garland/animations/anim_stars.h
  27. +2
    -2
      code/espurna/garland/animations/anim_start.h
  28. +89
    -32
      code/espurna/garland/animations/anim_waves.h
  29. +102
    -0
      code/espurna/garland/animations/color_wave.h
  30. +5
    -1
      code/espurna/garland/color.h
  31. +30
    -4
      code/espurna/garland/palette.h
  32. +18
    -8
      code/espurna/garland/scene.h
  33. +2330
    -2312
      code/espurna/static/index.all.html.gz.h
  34. +1462
    -1454
      code/espurna/static/index.curtain.html.gz.h
  35. +1419
    -1412
      code/espurna/static/index.garland.html.gz.h
  36. +2024
    -2009
      code/espurna/static/index.light.html.gz.h
  37. +1422
    -1415
      code/espurna/static/index.lightfox.html.gz.h
  38. +1455
    -1447
      code/espurna/static/index.rfbridge.html.gz.h
  39. +1458
    -1450
      code/espurna/static/index.rfm69.html.gz.h
  40. +1553
    -1546
      code/espurna/static/index.sensor.html.gz.h
  41. +1406
    -1399
      code/espurna/static/index.small.html.gz.h
  42. +1443
    -1434
      code/espurna/static/index.thermostat.html.gz.h
  43. +11
    -0
      code/html/custom.js
  44. +6
    -4
      code/html/index.html

BIN
code/espurna/data/index.all.html.gz View File


BIN
code/espurna/data/index.curtain.html.gz View File


BIN
code/espurna/data/index.garland.html.gz View File


BIN
code/espurna/data/index.light.html.gz View File


BIN
code/espurna/data/index.lightfox.html.gz View File


BIN
code/espurna/data/index.rfbridge.html.gz View File


BIN
code/espurna/data/index.rfm69.html.gz View File


BIN
code/espurna/data/index.sensor.html.gz View File


BIN
code/espurna/data/index.small.html.gz View File


BIN
code/espurna/data/index.thermostat.html.gz View File


+ 81
- 54
code/espurna/garland.cpp View File

@ -89,8 +89,8 @@ alignas(4) static constexpr char MQTT_COMMAND_RESET[] = "reset"; // reset queue
alignas(4) static constexpr char MQTT_COMMAND_QUEUE[] = "queue"; // enqueue command payload
alignas(4) static constexpr char MQTT_COMMAND_SEQUENCE[] = "sequence"; // place command to sequence
#define EFFECT_UPDATE_INTERVAL_MIN 7000 // 5 sec
#define EFFECT_UPDATE_INTERVAL_MAX 12000 // 10 sec
#define EFFECT_UPDATE_INTERVAL_MIN 15000 // 15 sec
#define EFFECT_UPDATE_INTERVAL_MAX 30000 // 30 sec
#define NUMLEDS_CAN_CAUSE_WDT_RESET 100
@ -103,44 +103,52 @@ std::queue<String> _command_queue;
std::vector<String> _command_sequence;
// Palette should
std::array<Palette, 10> pals {
std::array<Palette, 14> pals {
// palettes below are taken from http://www.color-hex.com/color-palettes/ (and modified)
// RGB: Red,Green,Blue sequence
Palette("RGB", {0xFF0000, 0x00FF00, 0x0000FF}),
Palette("RGB", true, {0xFF0000, 0x00FF00, 0x0000FF}),
// Rainbow: Rainbow colors
Palette("Rainbow", {0xFF0000, 0xAB5500, 0xABAB00, 0x00FF00, 0x00AB55, 0x0000FF, 0x5500AB, 0xAB0055}),
// RainbowStripe: Rainbow colors with alternating stripes of black
Palette("Stripe", {0xFF0000, 0x000000, 0xAB5500, 0x000000, 0xABAB00, 0x000000, 0x00FF00, 0x000000,
0x00AB55, 0x000000, 0x0000FF, 0x000000, 0x5500AB, 0x000000, 0xAB0055, 0x000000}),
Palette("Rainbow", true, {0xFF0000, 0xFF8000, 0xFFFF00, 0x00FF00, 0x00FFFF, 0x0000FF, 0x5500AB}),
// Party: Blue purple ping red orange yellow (and back). Basically, everything but the greens.
// This palette is good for lighting at a club or party.
Palette("Party", {0x5500AB, 0x84007C, 0xB5004B, 0xE5001B, 0xE81700, 0xB84700, 0xAB7700, 0xABAB00,
Palette("Party", false, {0x5500AB, 0x84007C, 0xB5004B, 0xE5001B, 0xE81700, 0xB84700, 0xAB7700, 0xABAB00,
0xAB5500, 0xDD2200, 0xF2000E, 0xC2003E, 0x8F0071, 0x5F00A1, 0x2F00D0, 0x0007F9}),
// Heat: Approximate "black body radiation" palette, akin to the FastLED 'HeatColor' function.
// Recommend that you use values 0-240 rather than the usual 0-255, as the last 15 colors will be
// 'wrapping around' from the hot end to the cold end, which looks wrong.
Palette("Heat", {0x700070, 0xFF0000, 0xFFFF00, 0xFFFFCC}),
Palette("Heat", false, {0x700070, 0xFF0000, 0xFFFF00, 0xFFFFCC}),
// Fire:
Palette("Fire", {0x000000, 0x220000, 0x880000, 0xFF0000, 0xFF6600, 0xFFCC00}),
Palette("Fire", false, {0x300000, 0x440000, 0x880000, 0xFF0000, 0xFF6600, 0xFFCC00}),
// Blue:
Palette("Blue", {0xffffff, 0x0000ff, 0x00ffff}),
Palette("Blue", true, {0xffffff, 0x0000ff, 0x00ffff}),
// Sun: Slice Of The Sun
Palette("Sun", {0xfff95b, 0xffe048, 0xffc635, 0xffad22, 0xff930f}),
Palette("Sun", true, {0xfff95b, 0xffe048, 0xffc635, 0xffad22, 0xff930f}),
// Lime: yellow green mix
Palette("Lime", {0x51f000, 0x6fff00, 0x96ff00, 0xc9ff00, 0xf0ff00}),
Palette("Lime", true, {0x51f000, 0x6fff00, 0x96ff00, 0xc9ff00, 0xf0ff00}),
Palette("Greens", false, {0xe5f2e5, 0x91f086, 0x48bf53, 0x11823b, 0x008000, 0x004d25, 0x18392b, 0x02231c}),
// Pastel: Pastel Fruity Mixture
Palette("Pastel", {0x75aa68, 0x5960ae, 0xe4be6c, 0xca5959, 0x8366ac})
Palette("Pastel", true, {0x75aa68, 0x5960ae, 0xe4be6c, 0xca5959, 0x8366ac}),
Palette("Summer", true, {0xb81616, 0xf13057, 0xf68118, 0xf2ab1e, 0xf9ca00, 0xaef133, 0x19ee9f, 0x0ea7b5, 0x0c457d}),
Palette("Autumn", false, {0x8b1509, 0xce7612, 0x11805d, 0x801138, 0x32154b, 0x724c04}),
Palette("Winter", true, {0xca9eb8, 0xfeeacf, 0xe0ecf2, 0x89e1c9, 0x72c3c5, 0x92c1ff, 0x3e6589, 0x052542}),
Palette("Gaang", true, {0xe7a532, 0x46a8ca, 0xaf7440, 0xb4d29d, 0x9f5b72, 0x585c82})
};
auto one_color_palette = std::unique_ptr<Palette>(new Palette("White", true, {0xffffff}));
constexpr uint16_t GarlandLeds { GARLAND_LEDS };
constexpr unsigned char GarlandPin { GARLAND_DATA_PIN };
constexpr neoPixelType GarlandPixelType { NEO_GRB + NEO_KHZ800 };
@ -148,12 +156,11 @@ constexpr neoPixelType GarlandPixelType { NEO_GRB + NEO_KHZ800 };
Adafruit_NeoPixel pixels(GarlandLeds, GarlandPin, GarlandPixelType);
Scene<GarlandLeds> scene(&pixels);
std::array<Anim*, 15> anims {
new AnimGlow(),
std::array<Anim*, 18> anims {
new AnimStart(),
new AnimGlow(),
new AnimPixieDust(),
new AnimSparkr(),
new AnimRun(),
new AnimStars(),
new AnimSpread(),
new AnimRandCyc(),
@ -163,13 +170,14 @@ std::array<Anim*, 15> anims {
new AnimDolphins(),
new AnimSalut(),
new AnimFountain(),
new AnimWaves()
new AnimRandRun(),
new AnimWaves(AnimWaves::Type::LongWaves),
new AnimWaves(AnimWaves::Type::ShortWaves),
new AnimWaves(AnimWaves::Type::Comets),
new AnimWaves(AnimWaves::Type::CrossWaves),
};
#define START_ANIMATION 1
Anim* _currentAnim = anims[1];
Palette* _currentPalette = &pals[0];
auto one_color_palette = std::unique_ptr<Palette>(new Palette("White", {0xffffff}));
#define START_ANIMATION 0
//------------------------------------------------------------------------------
// Setup
@ -236,7 +244,6 @@ void _garlandWebSocketOnAction(uint32_t client_id, const char* action, JsonObjec
if (strcmp(action, NAME_GARLAND_SET_BRIGHTNESS) == 0) {
if (data.containsKey("brightness")) {
byte new_brightness = data.get<byte>("brightness");
DEBUG_MSG_P(PSTR("[GARLAND] new brightness = %d\n"), new_brightness);
setSetting(NAME_GARLAND_BRIGHTNESS, new_brightness);
scene.setBrightness(new_brightness);
}
@ -245,7 +252,6 @@ void _garlandWebSocketOnAction(uint32_t client_id, const char* action, JsonObjec
if (strcmp(action, NAME_GARLAND_SET_SPEED) == 0) {
if (data.containsKey("speed")) {
byte new_speed = data.get<byte>("speed");
DEBUG_MSG_P(PSTR("[GARLAND] new speed = %d\n"), new_speed);
setSetting(NAME_GARLAND_SPEED, new_speed);
scene.setSpeed(new_speed);
}
@ -267,18 +273,16 @@ void setupScene(Anim* new_anim, Palette* new_palette, unsigned long new_duration
static String palette_name = "Start";
DEBUG_MSG_P(PSTR("[GARLAND] Anim: %-10s Pal: %-8s timings: calc: %4d pixl: %3d show: %4d frate: %d\n"),
_currentAnim->name(), palette_name.c_str(),
scene.getAnim()->name(), palette_name.c_str(),
scene.getAvgCalcTime(), scene.getAvgPixlTime(), scene.getAvgShowTime(), frameRate);
_currentDuration = new_duration;
_currentAnim = new_anim;
_currentPalette = new_palette;
palette_name = _currentPalette->name();
palette_name = new_palette->name();
DEBUG_MSG_P(PSTR("[GARLAND] Anim: %-10s Pal: %-8s Inter: %d\n"),
_currentAnim->name(), palette_name.c_str(), _currentDuration);
new_anim->name(), palette_name.c_str(), _currentDuration);
scene.setAnim(_currentAnim);
scene.setPalette(_currentPalette);
scene.setAnim(new_anim);
scene.setPalette(new_palette);
scene.setup();
}
@ -310,7 +314,7 @@ bool executeCommand(const String& command) {
scene.setSpeed(speed);
}
Anim* newAnim = _currentAnim;
Anim* newAnim = anims[0];
if (root.containsKey(MQTT_PAYLOAD_ANIMATION)) {
auto animation = root[MQTT_PAYLOAD_ANIMATION].as<const char*>();
for (size_t i = 0; i < anims.size(); ++i) {
@ -323,10 +327,10 @@ bool executeCommand(const String& command) {
}
}
Palette* newPalette = _currentPalette;
Palette* newPalette = &pals[0];
if (root.containsKey(MQTT_PAYLOAD_PALETTE)) {
if (root.is<int>(MQTT_PAYLOAD_PALETTE)) {
one_color_palette.reset(new Palette("Color", {root[MQTT_PAYLOAD_PALETTE].as<uint32_t>()}));
one_color_palette.reset(new Palette("Color", true, {root[MQTT_PAYLOAD_PALETTE].as<uint32_t>()}));
newPalette = one_color_palette.get();
} else {
auto palette = root[MQTT_PAYLOAD_PALETTE].as<String>();
@ -343,7 +347,7 @@ bool executeCommand(const String& command) {
if (!palette_found) {
const auto result = parseUnsigned(palette);
if (result.ok) {
one_color_palette.reset(new Palette("Color", {result.value}));
one_color_palette.reset(new Palette("Color", true, {result.value}));
newPalette = one_color_palette.get();
}
}
@ -391,13 +395,13 @@ void garlandLoop(void) {
}
if (!scene_setup_done) {
Anim* newAnim = _currentAnim;
while (newAnim == _currentAnim) {
Anim* newAnim = scene.getAnim();
while (newAnim == scene.getAnim()) {
newAnim = anims[secureRandom(START_ANIMATION + 1, anims.size())];
}
Palette* newPalette = _currentPalette;
while (newPalette == _currentPalette) {
Palette* newPalette = scene.getPalette();
while (newPalette == scene.getPalette()) {
newPalette = &pals[secureRandom(pals.size())];
}
@ -459,9 +463,6 @@ void garlandMqttCallback(unsigned int type, espurna::StringView topic, espurna::
#######################################################################*/
#define GARLAND_SCENE_TRANSITION_MS 1000 // transition time between animations, ms
#define GARLAND_SCENE_SPEED_MAX 70
#define GARLAND_SCENE_SPEED_FACTOR 10
#define GARLAND_SCENE_DEFAULT_SPEED 50
#define GARLAND_SCENE_DEFAULT_BRIGHTNESS 255
template<uint16_t Leds>
@ -472,18 +473,25 @@ void Scene<Leds>::setPalette(Palette* palette) {
}
}
template<uint16_t Leds>
void Scene<Leds>::setBrightness(byte value) {
DEBUG_MSG_P(PSTR("[GARLAND] new brightness = %d\n"), value);
brightness = value;
}
// Speed is reverse to cycleFactor and 10x
template<uint16_t Leds>
void Scene<Leds>::setSpeed(byte speed) {
DEBUG_MSG_P(PSTR("[GARLAND] new speed = %d\n"), speed);
this->speed = speed;
cycleFactor = (float)(GARLAND_SCENE_SPEED_MAX - speed) / GARLAND_SCENE_SPEED_FACTOR;
}
template<uint16_t Leds>
void Scene<Leds>::setDefault() {
speed = GARLAND_SCENE_DEFAULT_SPEED;
cycleFactor = (float)(GARLAND_SCENE_SPEED_MAX - speed) / GARLAND_SCENE_SPEED_FACTOR;
brightness = GARLAND_SCENE_DEFAULT_BRIGHTNESS;
DEBUG_MSG_P(PSTR("[GARLAND] set default\n"));
this->setBrightness(GARLAND_SCENE_DEFAULT_BRIGHTNESS);
this->setSpeed(GARLAND_SCENE_DEFAULT_SPEED);
}
template<uint16_t Leds>
@ -579,7 +587,7 @@ void Scene<Leds>::setupImpl() {
}
if (_anim) {
_anim->Setup(_palette, Leds, _leds, _ledstmp.data(), _seq.data());
_anim->Setup(_palette, _pals, _palsNum, Leds, _leds, _ledstmp.data(), _seq.data());
}
}
@ -609,8 +617,10 @@ void Scene<Leds>::setup() {
Anim::Anim(const char* name) : _name(name) {}
void Anim::Setup(Palette* palette, uint16_t numLeds, Color* leds, Color* ledstmp, byte* seq) {
void Anim::Setup(Palette* palette, Palette* pals, size_t palsNum, uint16_t numLeds, Color* leds, Color* ledstmp, byte* seq) {
this->palette = palette;
this->pals = pals;
this->palsNum = palsNum;
this->numLeds = numLeds;
this->leds = leds;
this->ledstmp = ledstmp;
@ -641,7 +651,7 @@ void Anim::glowSetUp() {
braFreq = secureRandom(20, 60);
}
void Anim::glowForEachLed(int i) {
void Anim::glowForEachLed(uint16_t i) {
int8 bra = braPhase + i * braFreq;
bra = BRA_OFFSET + (abs(bra) >> BRA_AMP_SHIFT);
leds[i] = leds[i].brightness(bra);
@ -651,6 +661,14 @@ void Anim::glowRun() {
braPhase += braPhaseSpd;
}
void Anim::flashRandomLeds(uint16_t deciPercent) {
int numLedsToFlash = numLeds * deciPercent / 1000;
for (int i = 0; i < numLedsToFlash; ++i) {
int ind = secureRandom(numLeds);
leds[ind] = sparkleColor;
}
}
unsigned int Anim::rng() {
static unsigned int y = 0;
y += micros(); // seeded with changing number
@ -666,12 +684,20 @@ byte Anim::rngb() {
return static_cast<byte>(rng());
}
bool fiftyFifty() {
return secureRandom(2) == 0;
}
int randDir() {
return secureRandom(2) == 0 ? -1 : 1;
}
} // namespace
//------------------------------------------------------------------------------
void garlandEnabled(bool enabled) {
setSetting(NAME_GARLAND_ENABLED, _garland_enabled);
setSetting(NAME_GARLAND_ENABLED, enabled);
if (_garland_enabled != enabled) {
espurnaRegisterOnceUnique([]() {
pixels.clear();
@ -713,11 +739,12 @@ void garlandSetup() {
espurnaRegisterReload(_garlandReload);
pixels.begin();
scene.setAnim(_currentAnim);
scene.setPalette(_currentPalette);
scene.setAnim(anims[START_ANIMATION]);
scene.setPalette(&pals[0]);
scene.setPals(pals.data(), pals.size());
scene.setup();
_currentDuration = secureRandom(EFFECT_UPDATE_INTERVAL_MIN, EFFECT_UPDATE_INTERVAL_MAX);
_currentDuration = 12000; // Start animation duration
}
#endif // GARLAND_SUPPORT

+ 11
- 3
code/espurna/garland/anim.h View File

@ -18,8 +18,8 @@ class Palette;
class Anim {
public:
Anim(const char* name);
const char* name() { return _name; }
void Setup(Palette* palette, uint16_t numLeds, Color* leds, Color* _ledstmp, byte* seq);
const char* name() const { return _name; }
void Setup(Palette* palette, Palette* pals, size_t pals_size, uint16_t numLeds, Color* leds, Color* _ledstmp, byte* seq);
virtual bool finishedycle() const { return true; };
virtual void Run() = 0;
virtual void setCycleFactor(float new_cycle_factor) { cycleFactor = new_cycle_factor; }
@ -28,6 +28,8 @@ public:
protected:
uint16_t numLeds = 0;
Palette* palette = nullptr;
Palette* pals = nullptr;
size_t palsNum = 0;
Color* leds = nullptr;
Color* ledstmp = nullptr;
byte* seq = nullptr;
@ -63,11 +65,14 @@ protected:
//glow animation - must be called for each LED after it's BASIC color is set
//note this overwrites the LED color, so the glow assumes that color will be stored elsewhere (not in leds[])
//or computed each time regardless previous leds[] value
void glowForEachLed(int i);
void glowForEachLed(uint16_t i);
//glow animation - must be called at the end of each animaton run
void glowRun();
//flash some random LEDs
void flashRandomLeds(uint16_t deciPercent);
//random number helpers for animations
static unsigned int rng();
static byte rngb();
@ -75,3 +80,6 @@ protected:
private:
const char* _name;
};
bool fiftyFifty();
int randDir();

+ 3
- 6
code/espurna/garland/animations/anim_assemble.h View File

@ -20,13 +20,10 @@ class AnimAssemble : public Anim {
cycleFactor = 2;
}
void SetupImpl() override {
inc = 1 + (rngb() >> 5);
if (secureRandom(10) > 5) {
inc = -inc;
}
inc = (1 + (rngb() >> 5)) * randDir();
int p = 0;
for (int i = 0; i < numLeds; ++i) {
for (auto i = 0; i < numLeds; ++i) {
leds[i] = 0;
Color c = palette->getCachedPalColor((byte)p);
ledstmp[i] = c;
@ -56,7 +53,7 @@ class AnimAssemble : public Anim {
}
} else if (phase == Phases::Glow) {
if (pos < numLeds/2) {
for (int i = 0; i < numLeds; ++i) {
for (auto i = 0; i < numLeds; ++i) {
leds[i] = ledstmp[i];
glowForEachLed(i);
}


+ 8
- 12
code/espurna/garland/animations/anim_comets.h View File

@ -18,14 +18,14 @@ class AnimComets : public Anim {
c = {palette, numLeds};
}
} else {
for (int i = 0; i < 4; ++i) {
for (auto i = 0; i < 5; ++i) {
comets.emplace_back(palette, numLeds);
}
}
}
void Run() override {
for (int i = 0; i < numLeds; ++i) leds[i] = 0;
for (auto i = 0; i < numLeds; ++i) leds[i] = 0;
for (auto& c : comets) {
int tail = c.head + c.len * -c.dir;
@ -34,7 +34,7 @@ class AnimComets : public Anim {
c = {palette, numLeds};
}
for (int l = 0; l < c.len; ++l) {
for (auto l = 0; l < c.len; ++l) {
int p = c.head + l * -c.dir;
if (p >= 0 && p < numLeds) {
leds[p] = c.points[l];
@ -46,21 +46,17 @@ class AnimComets : public Anim {
private:
struct Comet {
int dir = randDir();
int len = secureRandom(10, 30);
float speed = ((float)secureRandom(4, 20)) / 10;
float head;
int len = secureRandom(10, 20);
float speed = ((float)secureRandom(4, 10)) / 10;
Color color;
int dir = 1;
std::unique_ptr<Color[]> points;
Comet(Palette* pal, uint16_t numLeds) : head(secureRandom(0, numLeds / 2)), color(pal->getRndInterpColor()) {
Comet(Palette* pal, uint16_t numLeds) : head(dir > 0 ? secureRandom(0, numLeds / 2) : secureRandom(numLeds / 2, numLeds)), color(pal->getRndInterpColor()) {
// DEBUG_MSG_P(PSTR("[GARLAND] Comet created head = %d len = %d speed = %g cr = %d cg = %d cb = %d\n"), head, len, speed, color.r, color.g, color.b);
if (secureRandom(10) > 5) {
head = numLeds - head;
dir = -1;
}
points.reset(new Color[len]);
for (int i = 0; i < len; ++i) {
for (auto i = 0; i < len; ++i) {
points[i] = {
(byte)(color.r * (len - i) / len),
(byte)(color.g * (len - i) / len),


+ 25
- 19
code/espurna/garland/animations/anim_dolphins.h View File

@ -19,19 +19,19 @@ class AnimDolphins : public Anim {
d = {palette, numLeds};
}
} else {
for (int i = 0; i < 4; ++i) {
for (auto i = 0; i < 5; ++i) {
dolphins.emplace_back(palette, numLeds);
}
}
}
void Run() override {
for (int i = 0; i < numLeds; ++i) {
for (auto i = 0; i < numLeds; ++i) {
leds[i] = 0;
seq[i] = 0;
}
// Run dolphins animation. Fill seq (accupied space)
// Run dolphins animation. Fill seq (occupied space)
for (auto& d : dolphins)
d.Run(leds, seq);
@ -51,25 +51,22 @@ class AnimDolphins : public Anim {
private:
struct Dolphin {
uint16_t numLeds;
bool done = false;
int len = secureRandom(10, 20);
int len = secureRandom(10, 30);
int speed = secureRandom(1, 3);
int dir = 1;
int dir = randDir();
int head = 0;
int start;
Color color;
std::unique_ptr<Color[]> points;
Dolphin(Palette* pal, uint16_t numLeds) : start(secureRandom(0, numLeds - len)), color(pal->getRndInterpColor()) {
Dolphin(Palette* pal, uint16_t numLeds) : numLeds(numLeds), start(dir > 0 ? secureRandom(0, numLeds - len) : secureRandom(len, numLeds)), color(pal->getRndInterpColor()) {
// DEBUG_MSG_P(PSTR("[GARLAND] Dolphin created start = %d len = %d dir = %d cr = %d cg = %d cb = %d\n"), start, len, dir, color.r, color.g, color.b);
if (secureRandom(10) > 5) {
start = numLeds - start;
dir = -1;
}
int halflen = len / 2;
points.reset(new Color[len]);
for (int i = 0; i < len; ++i) {
for (auto i = 0; i < len; ++i) {
int nth = (i > halflen) ? (len - i) : i;
points[i] = {
(byte)(color.r * nth / halflen),
@ -83,10 +80,13 @@ class AnimDolphins : public Anim {
return false;
int p = 0;
for (int i = 0; i < len; ++i) {
for (auto i = 0; i < len; ++i) {
p = head - i;
if (p >= 0 && p < len) {
leds[start + p * dir] = points[i];
int j = start + p * dir;
if (j > 0 && j < numLeds) {
leds[j] = points[i];
}
}
}
@ -100,8 +100,11 @@ class AnimDolphins : public Anim {
else {
// dolphin occupy space for future movement
int s = p < 0 ? 0 : p;
for (int i = s; i < len; ++i) {
seq[start + i * dir] = 1;
for (auto i = s; i < len; ++i) {
int j = start + i * dir;
if (j > 0 && j < numLeds) {
seq[j] = 1;
}
}
}
@ -110,10 +113,13 @@ class AnimDolphins : public Anim {
// Decide that dolphin have ehough space if seq of len before it is empty
bool HaveEnoughSpace(byte* seq) {
for (int i = 0; i < len; ++i) {
if (seq[start + i * dir] != 0) {
// DEBUG_MSG_P(PSTR("[GARLAND] Dolphin chaven't enouhg space to move.\n"));
return false;
for (auto i = 0; i < len; ++i) {
int j = start + i * dir;
if (j > 0 && j < numLeds) {
if (seq[j] != 0) {
// DEBUG_MSG_P(PSTR("[GARLAND] Dolphin chaven't enouhg space to move.\n"));
return false;
}
}
}
return true;


+ 3
- 6
code/espurna/garland/animations/anim_fly.h View File

@ -14,10 +14,7 @@ class AnimFly : public Anim {
//length of particle tail
pos = secureRandom(2, 15);
//probability of the tail
inc = secureRandom(5, 15);
if (secureRandom(10) > 5) {
inc = -inc;
}
inc = secureRandom(5, 15) * randDir();
phase = 0;
}
@ -25,12 +22,12 @@ class AnimFly : public Anim {
byte launchpos;
if (inc > 0) {
launchpos = numLeds - 1;
for (int i = 1; i < numLeds; i++) {
for (auto i = 1; i < numLeds; i++) {
leds[i - 1] = leds[i];
}
} else {
launchpos = 0;
for (int i = numLeds - 2; i >= 0; i--) {
for (auto i = numLeds - 2; i >= 0; i--) {
leds[i + 1] = leds[i];
}
}


+ 5
- 5
code/espurna/garland/animations/anim_fountain.h View File

@ -15,12 +15,12 @@ class AnimFountain : public Anim {
void SetupImpl() override {
fountains.clear();
for (int i = 0; i < 3; ++i)
for (auto i = 0; i < 3; ++i)
fountains.emplace_back(palette, numLeds);
}
void Run() override {
for (int i = 0; i < numLeds; ++i) {
for (auto i = 0; i < numLeds; ++i) {
leds[i] = 0;
seq[i] = 0;
}
@ -61,7 +61,7 @@ class AnimFountain : public Anim {
// DEBUG_MSG_P(PSTR("[GARLAND] Fountain created start = %d len = %d dir = %d cr = %d cg = %d cb = %d\n"), start, len, dir, color.r, color.g, color.b);
points.reset(new Color[len]);
for (int i = 0; i < len; ++i) {
for (auto i = 0; i < len; ++i) {
points[i] = {pal->getRndInterpColor()};
// DEBUG_MSG_P(PSTR("[GARLAND] Fountain i=%d cr = %d cg = %d cb = %d\n"), i, points[i].r, points[i].g, points[i].b);
}
@ -72,7 +72,7 @@ class AnimFountain : public Anim {
return false;
int p = 0;
for (int i = 0; i < len; ++i) {
for (auto i = 0; i < len; ++i) {
p = head - i;
if (p >= 0 && p < len) {
if (dir == 1) {
@ -106,7 +106,7 @@ class AnimFountain : public Anim {
// Decide that fountain have ehough space if seq of len before it is empty
bool HaveEnoughSpace(byte* seq) {
for (int i = 0; i < len; ++i) {
for (auto i = 0; i < len; ++i) {
if (seq[start + i] != 0 && seq[start - i] != 0) {
// DEBUG_MSG_P(PSTR("[GARLAND] Fountain chaven't enouhg space to move.\n"));
return false;


+ 25
- 10
code/espurna/garland/animations/anim_glow.h View File

@ -5,29 +5,44 @@
//------------------------------------------------------------------------------
class AnimGlow : public Anim {
private:
float colorInterp;
float colorInterpSpeed;
uint16_t flashDeciPercent;
public:
AnimGlow() : Anim("Glow") {
}
void SetupImpl() override {
prevColor = palette->getRndInterpColor();
curColor = palette->getRndInterpColor();
inc = secureRandom(2) * 2 - 1;
colorInterp = 0;
colorInterpSpeed = ((float)secureRandom(10, 20)) / 1000;
flashDeciPercent = fiftyFifty() ? 0 : secureRandom(5, 20);
glowSetUp();
}
void Run() override {
if (inc > 0) {
for (int i = 0; i < numLeds; ++i) {
leds[i] = curColor;
glowForEachLed(i);
}
} else {
for (int i = numLeds - 1 ; i >= 0; --i) {
leds[i] = curColor;
glowForEachLed(i);
}
Color actualColor = prevColor.interpolate(curColor, colorInterp);
colorInterp += colorInterpSpeed;
if (colorInterp >= 1.0) {
colorInterp = 0;
prevColor = curColor;
curColor = palette->getContrastColor(prevColor);
}
for (auto i = 0; i < numLeds; ++i) {
auto j = inc > 0 ? i : numLeds - i - 1;
leds[j] = actualColor;
glowForEachLed(j);
}
glowRun();
if (flashDeciPercent) {
flashRandomLeds(flashDeciPercent);
}
}
};


+ 2
- 2
code/espurna/garland/animations/anim_pixiedust.h View File

@ -27,7 +27,7 @@ class AnimPixieDust : public Anim {
void Run() override {
if (inc > 0) {
for (int i = 0; i < numLeds; ++i) {
for (auto i = 0; i < numLeds; ++i) {
leds[i] = (i > phase) ? prevColor : curColor;
glowForEachLed(i);
}
@ -38,7 +38,7 @@ class AnimPixieDust : public Anim {
curColor = palette->getRndInterpColor();
}
} else {
for (int i = 0; i < numLeds; ++i) {
for (auto i = 0; i < numLeds; ++i) {
leds[i] = (i < phase) ? prevColor : curColor;
glowForEachLed(i);
}


+ 2
- 2
code/espurna/garland/animations/anim_randcyc.h View File

@ -10,12 +10,12 @@ class AnimRandCyc : public Anim {
}
void SetupImpl() override {
for (int i = 0; i < numLeds; ++i)
for (auto i = 0; i < numLeds; ++i)
seq[i] = rngb();
}
void Run() override {
for (int i = 0; i < numLeds; ++i) {
for (auto i = 0; i < numLeds; ++i) {
leds[i] = palette->getCachedPalColor(seq[i]);
seq[i] += rngb() >> 6;
}


+ 44
- 0
code/espurna/garland/animations/anim_randrun.h View File

@ -0,0 +1,44 @@
#if GARLAND_SUPPORT
#include "../anim.h"
#include "../palette.h"
//------------------------------------------------------------------------------
class AnimRandRun : public Anim {
private:
float speed;
float pos;
int dir;
public:
AnimRandRun() : Anim("RandRun") {
}
void SetupImpl() override {
pos = 0;
speed = ((float)secureRandom(20, 90)) / 100;
DEBUG_MSG_P(PSTR("[GARLAND] speed = %d\n"), (unsigned int)(speed*100));
dir = randDir();
for (auto i = 0; i < numLeds; ++i)
ledstmp[i] = palette->getRndInterpColor();
glowSetUp();
}
void Run() override {
pos += speed * dir;
if (pos >= numLeds) pos -= numLeds;
if (pos < 0) pos += numLeds;
for (auto i = 0; i < numLeds; ++i) {
int j = i + pos;
if (j >= numLeds) j -= numLeds;
leds[i] = ledstmp[j];
glowForEachLed(i);
}
glowRun();
}
};
#endif // GARLAND_SUPPORT

+ 0
- 38
code/espurna/garland/animations/anim_run.h View File

@ -1,38 +0,0 @@
#if GARLAND_SUPPORT
#include "../anim.h"
#include "../color.h"
#include "../palette.h"
//------------------------------------------------------------------------------
class AnimRun : public Anim {
public:
AnimRun() : Anim("Run") {
}
void SetupImpl() override {
pos = 0;
inc = 1 + (rngb() >> 5);
if (secureRandom(10) > 5) {
inc = -inc;
}
}
void Run() override {
int p = pos;
for (int i = 0; i < numLeds; ++i) {
Color c = palette->getCachedPalColor((byte)p);
leds[i] = c;
p = p + inc;
if (p >= 256) {
p = p - 256;
} else if (p < 0) {
p = p + 256;
}
}
pos = pos + 1;
}
};
#endif // GARLAND_SUPPORT

+ 6
- 6
code/espurna/garland/animations/anim_salut.h View File

@ -25,7 +25,7 @@ class AnimSalut : public Anim {
}
void Run() override {
for (int i = 0; i < numLeds; ++i) leds[i] = 0;
for (auto i = 0; i < numLeds; ++i) leds[i] = 0;
for (auto& c : shots) {
if (!c.Run(leds)) {
@ -42,11 +42,11 @@ class AnimSalut : public Anim {
float speed = ((float)secureRandom(1, 25)) / 10;
float speed_dec = ((float)secureRandom(1, 3)) / 10;
float pos;
int dir;
int dir = randDir();
Color color;
uint16_t numLeds;
Spark() = default;
Spark(int pos, Palette* pal, uint16_t numLeds) : pos(pos), dir(secureRandom(10) > 5 ? -1 : 1), color(pal->getRndInterpColor()), numLeds(numLeds) {}
Spark(int pos, Palette* pal, uint16_t numLeds) : pos(pos), color(pal->getRndInterpColor()), numLeds(numLeds) {}
void Run(Color* leds) {
if (pos >= 0 && pos < numLeds) {
leds[(int)pos] = color;
@ -55,7 +55,7 @@ class AnimSalut : public Anim {
speed -= speed_dec;
} else {
color.fade(5);
if (color.empty()) {
if (color.is_empty()) {
if (secureRandom(10) > 8)
leds[(int)pos] = 0xFFFFFF;
done = true;
@ -74,13 +74,13 @@ class AnimSalut : public Anim {
// DEBUG_MSG_P(PSTR("[GARLAND] Shot created center = %d spark_num = %d\n"), center, spark_num);
int center = secureRandom(15, numLeds - 15);
sparks.reset(new Spark[spark_num]);
for (int i = 0; i < spark_num; ++i) {
for (auto i = 0; i < spark_num; ++i) {
sparks[i] = {center, pal, numLeds};
}
}
bool Run(Color* leds) {
bool done = true;
for (int i = 0; i < spark_num; ++i) {
for (auto i = 0; i < spark_num; ++i) {
if (!sparks[i].done) {
done = false;
sparks[i].Run(leds);


+ 1
- 1
code/espurna/garland/animations/anim_sparkr.h View File

@ -21,7 +21,7 @@ class AnimSparkr : public Anim {
}
void Run() override {
for (int i = 0; i < numLeds; ++i) {
for (auto i = 0; i < numLeds; ++i) {
byte pos = seq[i];
leds[pos] = (i > phase) ? prevColor
: (i == phase) ? sparkleColor


+ 15
- 10
code/espurna/garland/animations/anim_spread.h View File

@ -9,11 +9,11 @@ class AnimSpread : public Anim {
const byte minWidth = 10;
const byte maxWidth = 15;
const byte numTries = 5;
const byte fade_ind = 255 / minWidth;
byte fade_ind = 10;
int GeneratePos() {
int pos = -1;
for (int i = 0; i < numTries; ++i) {
for (auto i = 0; i < numTries; ++i) {
pos = secureRandom(0, numLeds);
for (int j = pos - maxWidth; j < pos + maxWidth; ++j) {
if (j >= 0 && j < numLeds && seq[j] > 0) {
@ -32,29 +32,34 @@ class AnimSpread : public Anim {
}
void SetupImpl() override {
fade_ind = secureRandom(1, 20);
inc = secureRandom(2, 4);
// DEBUG_MSG_P(PSTR("[GARLAND] AnimSpread inc = %d\n"), inc);
for (int i = 0; i < numLeds; ++i)
// DEBUG_MSG_P(PSTR("[GARLAND] AnimSpread inc = %d, fade_inc = %d\n"), inc, fade_ind);
for (auto i = 0; i < numLeds; ++i)
seq[i] = 0;
}
void Run() override {
for (int i = 0; i < numLeds; ++i)
for (auto i = 0; i < numLeds; ++i)
leds[i] = 0;
for (int i = 0; i < numLeds; ++i) {
if (seq[i] > 0) {
for (auto i = 0; i < numLeds; ++i) {
if (!ledstmp[i].is_empty()) {
byte width = maxWidth - seq[i];
for (int j = i - width; j <= (i + width); j++) {
Color c = ledstmp[i];
if (j >= 0 && j < numLeds) {
leds[j].r += c.r;
leds[j].g += c.g;
leds[j].b += c.b;
if (leds[j].is_empty()) {
leds[j] = c;
} else {
leds[j] = leds[j].interpolate(c, 0.5);
}
}
}
ledstmp[i].fade(fade_ind);
seq[i]--;
} else {
seq[i] = 0;
}
}


+ 2
- 2
code/espurna/garland/animations/anim_stars.h View File

@ -18,12 +18,12 @@ class AnimStars : public Anim {
inc = secureRandom(2, 5);
//reset all phases
for (int i = 0; i < numLeds; ++i)
for (auto i = 0; i < numLeds; ++i)
seq[i] = 255;
}
void Run() override {
for (int i = 0; i < numLeds; i++) {
for (auto i = 0; i < numLeds; i++) {
byte phi = seq[i];
if (phi < 254) {
Color col = ledstmp[i];


+ 2
- 2
code/espurna/garland/animations/anim_start.h View File

@ -17,11 +17,11 @@ class AnimStart : public Anim {
leds[phase].r = 255;
leds[phase].g = 255;
leds[phase].b = 255;
for (int i = 0; i < numLeds; ++i) {
for (auto i = 0; i < numLeds; ++i) {
leds[i].fade(50);
}
} else if (phase >= numLeds) {
for (int i = 0; i < numLeds; ++i) {
for (auto i = 0; i < numLeds; ++i) {
short r = numLeds + 255 - phase + rngb();
r = min(r, (short)255);
leds[i].r = (byte)max(r, (short)0);


+ 89
- 32
code/espurna/garland/animations/anim_waves.h View File

@ -2,54 +2,111 @@
#include "../anim.h"
#include "../palette.h"
#include "color_wave.h"
//------------------------------------------------------------------------------
class AnimWaves : public Anim {
private:
int bra_phase;
int bra_phase_speed;
byte bra_speed;
byte bra_min;
int sign;
public:
AnimWaves() : Anim("Waves") {
enum Type {
LongWaves,
ShortWaves,
Comets,
CrossWaves,
};
AnimWaves(Type _type) : Anim("Waves"), type(_type) {
}
void SetupImpl() override {
curColor = palette->getRndInterpColor().max_bright();
switch (type) {
case LongWaves:
wave1 = generateLongWave();
break;
case ShortWaves:
wave1 = generateShortWave();
break;
case Comets:
wave1 = generateCometWave();
break;
case CrossWaves:
WaveType wave1Type = secureRandom(100) > 70 ? WaveType::Comet : WaveType::Wave;
wave1 = generateCrossingWave(1, wave1Type, ledstmp);
WaveType wave2Type = secureRandom(100) > 60 ? WaveType::Comet : WaveType::Wave;
// if first wave is comet - inverse second wave probability to be comer to 60%
if (wave1Type == WaveType::Comet) {
wave2Type = wave2Type == WaveType::Comet ? WaveType::Wave : WaveType::Comet;
}
// we have only one pixel array for cache
wave2 = generateCrossingWave(-1, wave2Type, nullptr);
break;
}
glowSetUp();
sign = braPhaseSpd > 128 ? -1 : 1;
bra_phase = secureRandom(255);
bra_phase_speed = secureRandom(2, 5);
bra_speed = secureRandom(4, 12);
bra_min = secureRandom(20, 30);
}
void Run() override {
int bra = bra_phase;
int8_t bra_inc = -sign * bra_speed;
for (int i = 0; i < numLeds; ++i) {
leds[i] = curColor;
glowForEachLed(i);
leds[i] = leds[i].brightness((byte)bra);
bra += bra_inc;
if (bra > 255 || bra < bra_min) {
bra_inc = -bra_inc;
bra = bra > 255 ? 255 : bra_min;
}
for (auto i = 0; i < numLeds; ++i) {
leds[i] = wave1.getLedColor(i);
if (type == LongWaves) {
glowForEachLed(i);
}
}
glowRun();
wave1.move();
bra_phase += bra_phase_speed;
if (bra_phase > 255 || bra_phase < bra_min) {
bra_phase_speed = -bra_phase_speed;
sign = -sign;
bra_phase = bra_phase > 255 ? 255 : bra_min;
if (type == LongWaves) {
glowRun();
} else if (type == CrossWaves) {
for (auto i = 0; i < numLeds; ++i) {
leds[i] = leds[i].interpolate(wave2.getLedColor(i), 0.5);
}
wave2.move();
}
}
private:
ColorWave generateLongWave() {
uint16_t waveLen = secureRandom(50, 100);
bool cleanColors = secureRandom(10) > 7;
float speed = secureRandom(5, 15) / 10.0;
int dir = randDir();
return ColorWave(ColorWave::Params{numLeds, palette, waveLen, cleanColors, 0, ledstmp, WaveType::Wave, speed, dir, false});
}
ColorWave generateShortWave() {
uint16_t waveLen = secureRandom(10, 30);
bool cleanColors = secureRandom(10) > 7;
byte fade = palette->bright() ? secureRandom(180, 220) : 0;
float speed = secureRandom(5, 15) / 10.0;
int dir = randDir();
return ColorWave(ColorWave::Params{numLeds, palette, waveLen, cleanColors, fade, ledstmp, WaveType::Wave, speed, dir, false});
}
ColorWave generateCometWave() {
uint16_t waveLen = secureRandom(10, 40);
bool cleanColors = secureRandom(10) > 5;
float speed = secureRandom(5, 15) / 10.0;
int dir = randDir();
return ColorWave(ColorWave::Params{numLeds, palette, waveLen, cleanColors, 0, ledstmp, WaveType::Comet, speed, dir, false});
}
ColorWave generateCrossingWave(int dir, WaveType waveType, Color* pixelCache) {
Palette* wavePal = &pals[secureRandom(palsNum)];
uint16_t waveLen = secureRandom(10, 50);
bool cleanColors = fiftyFifty();
byte fade = fiftyFifty() ? 0 : palette->bright() ? secureRandom(180, 220) : 120;
float speed = secureRandom(5, 20) / 10.0;
bool startEmpty = fiftyFifty();
return ColorWave(ColorWave::Params{numLeds, wavePal, waveLen, cleanColors, fade, pixelCache, waveType, speed, dir, startEmpty});
}
Type type;
ColorWave wave1;
ColorWave wave2;
};
#endif // GARLAND_SUPPORT

+ 102
- 0
code/espurna/garland/animations/color_wave.h View File

@ -0,0 +1,102 @@
#pragma once
enum WaveType {
Wave,
Comet
};
class ColorWave {
public:
struct Params {
uint16_t numLeds; // number of leds in garland
Palette *palette; // palette for wave colors
uint16_t waveLen; // wave length in leds (for clean colors - length of one color), also fade length
bool cleanColors; // if true - use clean colors from palette, else - use interpolated colors
byte fade; // wave fade deep: 0 - no fade, 255 - fade to black
Color* pixelCache; // pointer to array of Color with numLeds size, if not null - use it for caching colors
WaveType type; // Wave or Comet
float speed; // wave moving speed (0.5 - 2.0)
int dir; // moving direction (1 or -1)
bool startEmpty; // if true - start wave from empty leds, else - start from full leds
};
ColorWave() = default;
ColorWave(Params params) : params(params) {
if (params.speed < 0) {
params.speed = -params.speed;
params.dir = -params.dir;
}
head = params.startEmpty ? 0 : params.numLeds - 1;
if (params.type == WaveType::Wave) {
fade_step = params.fade * 2 / params.waveLen;
} else {
fade_step = 255 / params.waveLen;
}
DEBUG_MSG_P(PSTR("[GARLAND] Wave: waveLen = %d Pal: %-8s fade = %d cleanColors = %d speed = %d, type = %d\n"),
params.waveLen, params.palette->name(), params.fade, params.cleanColors, (int)(params.speed * 10.0), params.type);
if (params.pixelCache) {
for (auto i = 0; i < params.numLeds; ++i) {
params.pixelCache[i] = Color::empty();
}
}
}
Color getLedColor(uint16_t ledNum) {
uint16_t real_led_num = params.dir > 0 ? ledNum : params.numLeds - ledNum - 1;
if (real_led_num > head) {
return Color::empty();
}
if (params.pixelCache && !(params.pixelCache[real_led_num].is_empty())) {
return params.pixelCache[real_led_num];
}
uint16_t dist_to_head = head - real_led_num;
Color c = params.cleanColors ? params.palette->getCleanColor(dist_to_head / params.waveLen)
: params.palette->getCachedPalColor(dist_to_head * 256 * 20 / params.waveLen / params.numLeds);
if (fade_step > 0) {
byte bright = 255;
if (params.type == WaveType::Wave) {
bright -= fade_step * abs(params.waveLen / 2 - (dist_to_head % params.waveLen));
} else {
bright -= fade_step * (dist_to_head % params.waveLen);
}
c = c.brightness(bright);
}
if (params.pixelCache) {
params.pixelCache[real_led_num] = c;
}
return c;
}
void move() {
uint16_t prevHead = head;
head += params.speed;
uint16_t headDelta = head - prevHead;
if (params.pixelCache && headDelta > 0) {
for (auto i = params.numLeds - 1; i >= 0; --i) {
if (i >= headDelta) {
params.pixelCache[i] = params.pixelCache[i - headDelta];
} else {
params.pixelCache[i] = Color::empty();
}
}
}
}
private:
Params params;
float head;
byte fade_step;
};

+ 5
- 1
code/espurna/garland/color.h View File

@ -34,6 +34,10 @@ public:
: r((colorcode >> 16) & 0xFF), g((colorcode >> 8) & 0xFF), b((colorcode >> 0) & 0xFF) {
}
static Color empty() {
return Color();
}
//interpolates between this color and provided.
//x is from 0 to 1, 0 gives this color, 1 gives provided color, values between give interpolation
Color interpolate(Color color, float x) const {
@ -92,7 +96,7 @@ public:
return abs(r - c.r) + abs(g - c.g) + abs(b - c.b);
}
bool empty() const {
bool is_empty() const {
return r == 0 && g == 0 && b == 0;
}


+ 30
- 4
code/espurna/garland/palette.h View File

@ -11,10 +11,11 @@ Inspired by https://github.com/Vasil-Pahomov/ArWs2812 (currently https://github.
class Palette {
public:
Palette(const char* name, std::vector<Color>&& colors) : _name(name), _numColors(colors.size()), _colors(std::move(colors)), _cache(256) {
Palette(const char* name, bool bright, std::vector<Color>&& colors) : _name(name), _bright(bright), _numColors(colors.size()), _colors(std::move(colors)), _cache(256) {
}
const char* name() const { return _name; }
const bool bright() const { return _bright; }
/**
* Get the interpolated color from the palette.
@ -31,11 +32,11 @@ class Palette {
}
Color getCachedPalColor(byte i) {
if (!_cache[i].empty())
if (!_cache[i].is_empty())
return _cache[i];
Color col = getPalColor((float)i / 256);
if (col.empty())
if (col.is_empty())
col = 1;
_cache[i] = col;
@ -81,9 +82,34 @@ class Palette {
return bestColor;
}
Color getCleanColor(uint16_t i) const {
return _colors[i % _numColors];
}
Color getCloseCleanColor(const Color& refColor) const {
int bestDiff = 0;
Color bestColor;
for (auto i = 0; i < _numColors; ++i) {
Color newColor = _colors[i];
int diff = refColor.howCloseTo(newColor);
if (bestDiff < diff) {
bestDiff = diff;
bestColor = newColor;
}
}
return bestColor;
}
Color getRndCleanColor() const {
return _colors[secureRandom(_numColors)];
}
private:
const char* _name;
const int _numColors;
const bool _bright;
const uint16_t _numColors;
std::vector<Color> _colors;
std::vector<Color> _cache;
};

+ 18
- 8
code/espurna/garland/scene.h View File

@ -15,8 +15,8 @@ Inspired by https://github.com/Vasil-Pahomov/ArWs2812 (currently https://github.
#include "animations/anim_fly.h"
#include "animations/anim_glow.h"
#include "animations/anim_pixiedust.h"
#include "animations/anim_randrun.h"
#include "animations/anim_randcyc.h"
#include "animations/anim_run.h"
#include "animations/anim_salut.h"
#include "animations/anim_sparkr.h"
#include "animations/anim_spread.h"
@ -24,23 +24,31 @@ Inspired by https://github.com/Vasil-Pahomov/ArWs2812 (currently https://github.
#include "animations/anim_start.h"
#include "animations/anim_waves.h"
#define GARLAND_SCENE_SPEED_MAX 70
#define GARLAND_SCENE_SPEED_FACTOR 10
#define GARLAND_SCENE_DEFAULT_SPEED 40
template <uint16_t Leds>
class Scene {
public:
Scene(Adafruit_NeoPixel* pixels) : _pixels(pixels) {}
constexpr uint16_t getLeds() const { return Leds; }
void setAnim(Anim* anim) { _anim = anim; }
bool finishedAnimCycle() { return _anim ? _anim->finishedycle() : true; }
unsigned long getAvgCalcTime() { return sum_calc_time / calc_num; }
unsigned long getAvgPixlTime() { return sum_pixl_time / pixl_num; }
unsigned long getAvgShowTime() { return sum_show_time / show_num; }
unsigned long getAvgCalcTime() { return calc_num > 0 ? sum_calc_time / calc_num : 0; }
unsigned long getAvgPixlTime() { return pixl_num > 0 ? sum_pixl_time / pixl_num : 0; }
unsigned long getAvgShowTime() { return show_num > 0 ? sum_show_time / show_num : 0; }
int getNumShows() { return numShows; }
byte getBrightness() { return brightness; }
void setBrightness(byte value) { brightness = value; }
byte getSpeed() { return speed; }
float getCycleFactor(byte speed) { return (float)(GARLAND_SCENE_SPEED_MAX - speed) / GARLAND_SCENE_SPEED_FACTOR; }
Palette* getPalette() const { return _palette; }
Anim* getAnim() const { return _anim; }
void setAnim(Anim* anim) { _anim = anim; }
void setPalette(Palette* palette);
void setPals(Palette* palettes, size_t palsNum) { _pals = palettes; _palsNum = palsNum; }
void setBrightness(byte value);
void setSpeed(byte speed);
void setDefault();
void run();
@ -60,6 +68,8 @@ private:
std::array<byte, Leds> _seq;
Palette* _palette = nullptr;
Palette* _pals = nullptr;
size_t _palsNum = 0;
// millis to transition end
unsigned long transms;
@ -70,13 +80,13 @@ private:
// if cycleFactor is 2 or more, than calculation and drawing made in different cycles
// cycleFactor is float. For example cycleFactor=2.5 gives one step 2 than next 3 cycles per anim step
// Recommended values: 1 < cycleFactor < 4
float cycleFactor = 2.0;
float cycleFactor = getCycleFactor(GARLAND_SCENE_DEFAULT_SPEED);
// speed is reverse to cycleFactor. For forward direction control of animation speed.
// Recommended values: 30 < speed < 60.
// Correspondence:
// speed=60, cycleFactor=1
// speed=30, cycleFactor=4
byte speed = 50;
byte speed = GARLAND_SCENE_DEFAULT_SPEED;
float cycleTail = 0;
int cyclesRemain = 0;


+ 2330
- 2312
code/espurna/static/index.all.html.gz.h
File diff suppressed because it is too large
View File


+ 1462
- 1454
code/espurna/static/index.curtain.html.gz.h
File diff suppressed because it is too large
View File


+ 1419
- 1412
code/espurna/static/index.garland.html.gz.h
File diff suppressed because it is too large
View File


+ 2024
- 2009
code/espurna/static/index.light.html.gz.h
File diff suppressed because it is too large
View File


+ 1422
- 1415
code/espurna/static/index.lightfox.html.gz.h
File diff suppressed because it is too large
View File


+ 1455
- 1447
code/espurna/static/index.rfbridge.html.gz.h
File diff suppressed because it is too large
View File


+ 1458
- 1450
code/espurna/static/index.rfm69.html.gz.h
File diff suppressed because it is too large
View File


+ 1553
- 1546
code/espurna/static/index.sensor.html.gz.h
File diff suppressed because it is too large
View File


+ 1406
- 1399
code/espurna/static/index.small.html.gz.h
File diff suppressed because it is too large
View File


+ 1443
- 1434
code/espurna/static/index.thermostat.html.gz.h
File diff suppressed because it is too large
View File


+ 11
- 0
code/html/custom.js View File

@ -2896,6 +2896,17 @@ function processData(data) {
return;
}
if ("garlandBrightness" === key) {
const brightnessSlider = document.getElementById("garlandBrightness");
brightnessSlider.value = value;
}
if ("garlandSpeed" === key) {
const speedSlider = document.getElementById("garlandSpeed");
speedSlider.value = value;
}
initGenericKeyValueElement(key, value);
});
}


+ 6
- 4
code/html/index.html View File

@ -1310,7 +1310,7 @@
</form>
<!-- removeIf(!garland) -->
<form id="form-garland" class="pure-form form-settings">
<form id="form-garland" class="pure-form pure-form-aligned form-settings">
<div class="panel" id="panel-garland">
<div class="header">
@ -1322,17 +1322,19 @@
<div class="pure-control-group">
<label>Enable Garland</label>
<div><input class="checkbox-garland-enable" type="checkbox" name="garlandEnabled"></div>
<div class="pure-input-2-3">
<input class="checkbox-garland-enable" type="checkbox" name="garlandEnabled">
</div>
</div>
<div class="pure-control-group">
<label>Brightness</label>
<input class="slider slider-garland-brightness pure-input-1" type="range" min="12" max="254" step="22" name="garlandBrightness">
<input class="slider slider-garland-brightness pure-input-3-4" type="range" min="12" max="254" step="22" id="garlandBrightness">
</div>
<div class="pure-control-group">
<label>Speed</label>
<input class="slider slider-garland-speed" type="range" min="10" max="70" name="garlandSpeed">
<input class="slider slider-garland-speed pure-input-3-4" type="range" min="10" max="70" id="garlandSpeed">
</div>
<button type="button" class="pure-button button-garland-set-default">Set default</button>


Loading…
Cancel
Save