Browse Source

garland: new module (#2408)

Port of https://github.com/Vasil-Pahomov/ArWs2812 from Arduino to ESP8266
Implementing garland of WS2812

Co-authored-by: Dmitry Blinov <dblinov@blackberry.com>
mcspr-patch-1
DmitryBlinov 3 years ago
committed by GitHub
parent
commit
5aeb24f263
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 3919 additions and 0 deletions
  1. +2
    -0
      .gitattributes
  2. +6
    -0
      code/espurna/board.cpp
  3. +16
    -0
      code/espurna/config/general.h
  4. +10
    -0
      code/espurna/config/webui.h
  5. BIN
      code/espurna/data/index.garland.html.gz
  6. +484
    -0
      code/espurna/garland.cpp
  7. +16
    -0
      code/espurna/garland.h
  8. +76
    -0
      code/espurna/garland/anim.h
  9. +54
    -0
      code/espurna/garland/animations/anim_assemble.h
  10. +66
    -0
      code/espurna/garland/animations/anim_comets.h
  11. +48
    -0
      code/espurna/garland/animations/anim_fly.h
  12. +68
    -0
      code/espurna/garland/animations/anim_pixiedust.h
  13. +25
    -0
      code/espurna/garland/animations/anim_randcyc.h
  14. +38
    -0
      code/espurna/garland/animations/anim_run.h
  15. +51
    -0
      code/espurna/garland/animations/anim_sparkr.h
  16. +73
    -0
      code/espurna/garland/animations/anim_spread.h
  17. +53
    -0
      code/espurna/garland/animations/anim_stars.h
  18. +41
    -0
      code/espurna/garland/animations/anim_start.h
  19. +87
    -0
      code/espurna/garland/color.h
  20. +95
    -0
      code/espurna/garland/palette.h
  21. +111
    -0
      code/espurna/garland/scene.h
  22. +6
    -0
      code/espurna/main.cpp
  23. +2420
    -0
      code/espurna/static/index.garland.html.gz.h
  24. +2
    -0
      code/espurna/web.cpp
  25. +6
    -0
      code/gulpfile.js
  26. +1
    -0
      code/html/custom.css
  27. +18
    -0
      code/html/custom.js
  28. +39
    -0
      code/html/index.html
  29. +6
    -0
      code/platformio.ini
  30. +1
    -0
      code/test/build/garland.h

+ 2
- 0
.gitattributes View File

@ -1,3 +1,5 @@
*.gz.h -diff -merge
*.gz.h linguist-generated=true
*.ini text eol=lf
*.h text eol=lf
*.cpp text eol=lf

+ 6
- 0
code/espurna/board.cpp View File

@ -114,6 +114,9 @@ PROGMEM const char espurna_modules[] =
#if TERMINAL_SUPPORT
"TERMINAL "
#endif
#if GARLAND_SUPPORT
"GARLAND "
#endif
#if THERMOSTAT_SUPPORT
"THERMOSTAT "
#endif
@ -176,6 +179,9 @@ PROGMEM const char espurna_webui[] =
#if WEBUI_IMAGE == WEBUI_IMAGE_LIGHTFOX
"LIGHTFOX"
#endif
#if WEBUI_IMAGE == WEBUI_IMAGE_GARLAND
"GARLAND"
#endif
#if WEBUI_IMAGE == WEBUI_IMAGE_THERMOSTAT
"THERMOSTAT"
#endif


+ 16
- 0
code/espurna/config/general.h View File

@ -227,6 +227,22 @@
#define SAVE_CRASH_STACK_TRACE_MAX 0x80 // limit at 128 bytes (increment/decrement by 16)
#endif
//------------------------------------------------------------------------------
// GARLAND
//------------------------------------------------------------------------------
#ifndef GARLAND_SUPPORT
#define GARLAND_SUPPORT 0
#endif
#ifndef GARLAND_D_PIN
#define GARLAND_D_PIN D2 // WS2812 pin number
#endif
#ifndef GARLAND_LEDS
#define GARLAND_LEDS 60 // Leds number
#endif
//------------------------------------------------------------------------------
// THERMOSTAT
//------------------------------------------------------------------------------


+ 10
- 0
code/espurna/config/webui.h View File

@ -10,6 +10,7 @@
#define WEBUI_IMAGE_RFBRIDGE 4
#define WEBUI_IMAGE_RFM69 8
#define WEBUI_IMAGE_LIGHTFOX 16
#define WEBUI_IMAGE_GARLAND 31
#define WEBUI_IMAGE_THERMOSTAT 32
#define WEBUI_IMAGE_CURTAIN 64
#define WEBUI_IMAGE_FULL 15
@ -57,6 +58,15 @@
#define WEBUI_IMAGE WEBUI_IMAGE_LIGHTFOX
#endif
#if GARLAND_SUPPORT == 1
#ifndef WEBUI_IMAGE
#define WEBUI_IMAGE WEBUI_IMAGE_GARLAND
#else
#undef WEBUI_IMAGE
#define WEBUI_IMAGE WEBUI_IMAGE_FULL
#endif
#endif
#if THERMOSTAT_SUPPORT == 1
#ifndef WEBUI_IMAGE
#define WEBUI_IMAGE WEBUI_IMAGE_THERMOSTAT


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


+ 484
- 0
code/espurna/garland.cpp View File

@ -0,0 +1,484 @@
/*
GARLAND MODULE
Copyright (C) 2020 by Dmitry Blinov <dblinov76 at gmail dot com>
Inspired by https://github.com/Vasil-Pahomov/ArWs2812 (currently https://github.com/Vasil-Pahomov/Liana)
Tested on 60 led strip.
!!! For more leds can cause WDT rebot. Need to be carefully tested for more than 60 leds !!!
The most time consuming operation is actually showing leds by Adafruit Neopixel. It take about 1870 mcs.
More long strip can take more time to show.
Currently animation calculation, brightness calculation/transition and showing makes in one loop cycle.
Debug output shows timings. Overal timing should be not more that 3000 ms.
For longer strips have sense to divide entire strip (pixels) on parts about 100 pixels and show one part
at a cycle.
*/
#include "garland.h"
#if GARLAND_SUPPORT
#include <Adafruit_NeoPixel.h>
#include <vector>
#include "garland/color.h"
#include "garland/palette.h"
#include "garland/scene.h"
#include "ws.h"
const char* NAME_GARLAND_ENABLED = "garlandEnabled";
const char* NAME_GARLAND_BRIGHTNESS = "garlandBrightness";
const char* NAME_GARLAND_SPEED = "garlandSpeed";
const char* NAME_GARLAND_SWITCH = "garland_switch";
const char* NAME_GARLAND_SET_BRIGHTNESS = "garland_set_brightness";
const char* NAME_GARLAND_SET_SPEED = "garland_set_speed";
const char* NAME_GARLAND_SET_DEFAULT = "garland_set_default";
#define EFFECT_UPDATE_INTERVAL_MIN 5000 // 5 sec
#define EFFECT_UPDATE_INTERVAL_MAX 10000 // 10 sec
bool _garland_enabled = true;
unsigned long _last_update = 0;
unsigned long _interval_effect_update;
// Palette should
Palette 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}),
// 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}),
// 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,
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}),
// Fire:
Palette("Fire", {0x000000, 0x220000, 0x880000, 0xFF0000, 0xFF6600, 0xFFCC00}),
// Blue:
Palette("Blue", {0xffffff, 0x0000ff, 0x00ffff}),
// Sun: Slice Of The Sun
Palette("Sun", {0xfff95b, 0xffe048, 0xffc635, 0xffad22, 0xff930f}),
// Lime: yellow green mix
Palette("Lime", {0x51f000, 0x6fff00, 0x96ff00, 0xc9ff00, 0xf0ff00}),
// Pastel: Pastel Fruity Mixture
Palette("Pastel", {0x75aa68, 0x5960ae, 0xe4be6c, 0xca5959, 0x8366ac}),
// Green: Vibrant greens
Palette("Green", {0x89ff01, 0x42c501, 0x349404, 0x0f6902, 0x004208})};
constexpr size_t palsSize() { return sizeof(pals)/sizeof(pals[0]); }
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(GARLAND_LEDS, GARLAND_D_PIN, NEO_GRB + NEO_KHZ800);
Scene scene(&pixels);
Anim* anims[] = {new AnimStart(), new AnimPixieDust(), new AnimSparkr(), new AnimRun(), new AnimStars(),
new AnimSpread(), new AnimRandCyc(), new AnimFly(), new AnimComets(), new AnimAssemble()};
constexpr size_t animsSize() { return sizeof(anims)/sizeof(anims[0]); }
//------------------------------------------------------------------------------
void garlandDisable() {
pixels.clear();
}
//------------------------------------------------------------------------------
void garlandEnabled(bool enabled) {
_garland_enabled = enabled;
}
//------------------------------------------------------------------------------
bool garlandEnabled() {
return _garland_enabled;
}
//------------------------------------------------------------------------------
// Setup
//------------------------------------------------------------------------------
void _garlandConfigure() {
_garland_enabled = getSetting(NAME_GARLAND_ENABLED, true);
DEBUG_MSG_P(PSTR("[GARLAND] _garland_enabled = %d\n"), _garland_enabled);
byte brightness = getSetting(NAME_GARLAND_BRIGHTNESS, 255);
scene.setBrightness(brightness);
DEBUG_MSG_P(PSTR("[GARLAND] brightness = %d\n"), brightness);
float speed = getSetting(NAME_GARLAND_SPEED, 50);
scene.setSpeed(speed);
}
//------------------------------------------------------------------------------
void _garlandReload() {
_garlandConfigure();
}
#if WEB_SUPPORT
//------------------------------------------------------------------------------
void _garlandWebSocketOnConnected(JsonObject& root) {
root[NAME_GARLAND_ENABLED] = garlandEnabled();
root[NAME_GARLAND_BRIGHTNESS] = scene.getBrightness();
root[NAME_GARLAND_SPEED] = scene.getSpeed();
root["garlandVisible"] = 1;
}
//------------------------------------------------------------------------------
bool _garlandWebSocketOnKeyCheck(const char* key, JsonVariant& value) {
if (strncmp(key, NAME_GARLAND_ENABLED, strlen(NAME_GARLAND_ENABLED)) == 0) return true;
if (strncmp(key, NAME_GARLAND_BRIGHTNESS, strlen(NAME_GARLAND_BRIGHTNESS)) == 0) return true;
if (strncmp(key, NAME_GARLAND_SPEED, strlen(NAME_GARLAND_SPEED)) == 0) return true;
return false;
}
//------------------------------------------------------------------------------
void _garlandWebSocketOnAction(uint32_t client_id, const char* action, JsonObject& data) {
if (strcmp(action, NAME_GARLAND_SWITCH) == 0) {
if (data.containsKey("status") && data.is<int>("status")) {
_garland_enabled = (1 == data["status"].as<int>());
setSetting(NAME_GARLAND_ENABLED, _garland_enabled);
if (!_garland_enabled) {
schedule_function([](){
pixels.clear();
pixels.show();
});
}
}
}
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);
}
}
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);
}
}
if (strcmp(action, NAME_GARLAND_SET_DEFAULT) == 0) {
scene.setDefault();
byte brightness = scene.getBrightness();
setSetting(NAME_GARLAND_BRIGHTNESS, brightness);
byte speed = scene.getSpeed();
setSetting(NAME_GARLAND_SPEED, speed);
char buffer[128];
snprintf_P(buffer, sizeof(buffer), PSTR("{\"garlandBrightness\": %d, \"garlandSpeed\": %d}"), brightness, speed);
wsSend(buffer);
}
}
#endif
//------------------------------------------------------------------------------
// Loop
//------------------------------------------------------------------------------
void garlandLoop(void) {
if (!garlandEnabled())
return;
scene.run();
unsigned long animation_time = millis() - _last_update;
if (animation_time > _interval_effect_update && scene.finishedAnimCycle()) {
_last_update = millis();
_interval_effect_update = secureRandom(EFFECT_UPDATE_INTERVAL_MIN, EFFECT_UPDATE_INTERVAL_MAX);
static int animInd = 0;
int prevAnimInd = animInd;
while (prevAnimInd == animInd) animInd = secureRandom(1, animsSize());
static int paletteInd = 0;
int prevPalInd = paletteInd;
while (prevPalInd == paletteInd) paletteInd = secureRandom(palsSize());
int numShows = scene.getNumShows();
int frameRate = animation_time > 0 ? numShows * 1000 / animation_time : 0;
DEBUG_MSG_P(PSTR("[GARLAND] Anim: %-10s Pal: %-8s timings: calc: %4d pixl: %3d show: %4d frate: %d\n"),
anims[prevAnimInd]->name(), pals[prevPalInd].name(),
scene.getAvgCalcTime(), scene.getAvgPixlTime(), scene.getAvgShowTime(), frameRate);
DEBUG_MSG_P(PSTR("[GARLAND] Anim: %-10s Pal: %-8s Inter: %d\n"),
anims[animInd]->name(), pals[paletteInd].name(), _interval_effect_update);
scene.setAnim(anims[animInd]);
scene.setPalette(&pals[paletteInd]);
scene.setup();
}
}
//------------------------------------------------------------------------------
void garlandSetup() {
_garlandConfigure();
// Websockets
#if WEB_SUPPORT
wsRegister()
.onConnected(_garlandWebSocketOnConnected)
.onKeyCheck(_garlandWebSocketOnKeyCheck)
.onAction(_garlandWebSocketOnAction);
#endif
espurnaRegisterLoop(garlandLoop);
espurnaRegisterReload(_garlandReload);
pixels.begin();
scene.setAnim(anims[0]);
scene.setPalette(&pals[0]);
scene.setup();
_interval_effect_update = secureRandom(EFFECT_UPDATE_INTERVAL_MIN, EFFECT_UPDATE_INTERVAL_MAX);
}
/*#######################################################################
_____
/ ____|
| (___ ___ ___ _ __ ___
\___ \ / __| / _ \ | '_ \ / _ \
____) | | (__ | __/ | | | | | __/
|_____/ \___| \___| |_| |_| \___|
#######################################################################*/
#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
Scene::Scene(Adafruit_NeoPixel* pixels)
: _pixels(pixels),
_numLeds(pixels->numPixels()),
_leds1(_numLeds),
_leds2(_numLeds),
_ledstmp(_numLeds),
_seq(_numLeds) {
}
void Scene::setPalette(Palette* palette) {
_palette = palette;
if (setUpOnPalChange) {
setupImpl();
}
}
void Scene::setBrightness(byte brightness) {
DEBUG_MSG_P(PSTR("[GARLAND] Scene::setBrightness = %d\n"), brightness);
this->brightness = brightness;
}
byte Scene::getBrightness() {
DEBUG_MSG_P(PSTR("[GARLAND] Scene::getBrightness = %d\n"), brightness);
return brightness;
}
// Speed is reverse to cycleFactor and 10x
void Scene::setSpeed(byte speed) {
this->speed = speed;
cycleFactor = (float)(GARLAND_SCENE_SPEED_MAX - speed) / GARLAND_SCENE_SPEED_FACTOR;
DEBUG_MSG_P(PSTR("[GARLAND] Scene::setSpeed %d cycleFactor = %d\n"), speed, (int)(cycleFactor * 1000));
}
byte Scene::getSpeed() {
DEBUG_MSG_P(PSTR("[GARLAND] Scene::getSpeed %d cycleFactor = %d\n"), speed, (int)(cycleFactor * 1000));
return speed;
}
void Scene::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] Scene::setDefault speed = %d cycleFactor = %d brightness = %d\n"), speed, (int)(cycleFactor * 1000), brightness);
}
void Scene::run() {
unsigned long iteration_start_time = micros();
if (state == Calculate || cyclesRemain < 1) {
// Calculate number of cycles for this animation iteration
float cycleSum = cycleFactor * (_anim ? _anim->getCycleFactor() : 1.0) + cycleTail;
cyclesRemain = cycleSum;
if (cyclesRemain < 1) {
cyclesRemain = 1;
cycleSum = 0;
cycleTail = 0;
} else {
cycleTail = cycleSum - cyclesRemain;
}
if (_anim) {
_anim->Run();
}
sum_calc_time += (micros() - iteration_start_time);
iteration_start_time = micros();
++calc_num;
state = Transition;
}
if (state == Transition && cyclesRemain < 3) {
// transition coef, if within 0..1 - transition is active
// changes from 1 to 0 during transition, so we interpolate from current
// color to previous
float transc = (float)((long)transms - (long)millis()) / GARLAND_SCENE_TRANSITION_MS;
Color* leds_prev = (_leds == &_leds1[0]) ? &_leds2[0] : &_leds1[0];
if (transc > 0) {
for (int i = 0; i < _numLeds; i++) {
// transition is in progress
Color c = _leds[i].interpolate(leds_prev[i], transc);
byte r = (int)(bri_lvl[c.r]) * brightness / 256;
byte g = (int)(bri_lvl[c.g]) * brightness / 256;
byte b = (int)(bri_lvl[c.b]) * brightness / 256;
_pixels->setPixelColor(i, _pixels->Color(r, g, b));
}
} else {
for (int i = 0; i < _numLeds; i++) {
// regular operation
byte r = (int)(bri_lvl[_leds[i].r]) * brightness / 256;
byte g = (int)(bri_lvl[_leds[i].g]) * brightness / 256;
byte b = (int)(bri_lvl[_leds[i].b]) * brightness / 256;
_pixels->setPixelColor(i, _pixels->Color(r, g, b));
}
}
sum_pixl_time += (micros() - iteration_start_time);
iteration_start_time = micros();
++pixl_num;
state = Show;
}
if (state == Show && cyclesRemain < 2) {
_pixels->show();
sum_show_time += (micros() - iteration_start_time);
++show_num;
state = Calculate;
++numShows;
}
--cyclesRemain;
}
void Scene::setupImpl() {
transms = millis() + GARLAND_SCENE_TRANSITION_MS;
// switch operation buffers (for transition to operate)
if (_leds == &_leds1[0]) {
_leds = &_leds2[0];
} else {
_leds = &_leds1[0];
}
if (_anim) {
_anim->Setup(_palette, _numLeds, _leds, &_ledstmp[0], &_seq[0]);
}
}
void Scene::setup() {
sum_calc_time = 0;
sum_pixl_time = 0;
sum_show_time = 0;
calc_num = 0;
pixl_num = 0;
show_num = 0;
numShows = 0;
if (!setUpOnPalChange) {
setupImpl();
}
}
unsigned long Scene::getAvgCalcTime() { return sum_calc_time / calc_num; }
unsigned long Scene::getAvgPixlTime() { return sum_pixl_time / pixl_num; }
unsigned long Scene::getAvgShowTime() { return sum_show_time / show_num; }
/*#######################################################################
_ _ _
/\ (_) | | (_)
/ \ _ __ _ _ __ ___ __ _ | |_ _ ___ _ __
/ /\ \ | '_ \ | | | '_ ` _ \ / _` | | __| | | / _ \ | '_ \
/ ____ \ | | | | | | | | | | | | | (_| | | |_ | | | (_) | | | | |
/_/ \_\ |_| |_| |_| |_| |_| |_| \__,_| \__| |_| \___/ |_| |_|
#######################################################################*/
Anim::Anim(const char* name) : _name(name) {}
void Anim::Setup(Palette* palette, uint16_t numLeds, Color* leds, Color* ledstmp, byte* seq) {
this->palette = palette;
this->numLeds = numLeds;
this->leds = leds;
this->ledstmp = ledstmp;
this->seq = seq;
SetupImpl();
}
void Anim::initSeq() {
for (int i = 0; i < numLeds; i++)
seq[i] = i;
}
void Anim::shuffleSeq() {
for (int i = 0; i < numLeds; i++) {
byte ind = (unsigned int)(rngb() * numLeds / 256);
if (ind != i) {
std::swap(seq[ind], seq[i]);
}
}
}
void Anim::glowSetUp() {
braPhaseSpd = secureRandom(4, 13);
if (braPhaseSpd > 8) {
braPhaseSpd = braPhaseSpd - 17;
}
braFreq = secureRandom(20, 60);
}
void Anim::glowForEachLed(int i) {
int8 bra = braPhase + i * braFreq;
bra = BRA_OFFSET + (abs(bra) >> BRA_AMP_SHIFT);
leds[i] = leds[i].brightness(bra);
}
void Anim::glowRun() { braPhase += braPhaseSpd; }
bool operator== (const Color &c1, const Color &c2)
{
return (c1.r == c2.r && c1.g == c2.g && c1.b == c2.b);
}
unsigned int rng() {
static unsigned int y = 0;
y += micros(); // seeded with changing number
y ^= y << 2;
y ^= y >> 7;
y ^= y << 7;
return (y);
}
// Ranom numbers generator in byte range (256) much faster than secureRandom.
// For usage in time-critical places.
byte rngb() { return (byte)rng(); }
#endif // GARLAND_SUPPORT

+ 16
- 0
code/espurna/garland.h View File

@ -0,0 +1,16 @@
/*
GARLAND MODULE
Copyright (C) 2020 by Dmitry Blinov <dblinov76 at gmail dot com>
Inspired by https://github.com/Vasil-Pahomov/ArWs2812 (currently https://github.com/Vasil-Pahomov/Liana)
*/
#pragma once
#include "espurna.h"
#if GARLAND_SUPPORT
void garlandSetup();
#endif

+ 76
- 0
code/espurna/garland/anim.h View File

@ -0,0 +1,76 @@
/*
Part of the GARLAND MODULE
Copyright (C) 2020 by Dmitry Blinov <dblinov76 at gmail dot com>
Inspired by https://github.com/Vasil-Pahomov/ArWs2812 (currently https://github.com/Vasil-Pahomov/Liana)
*/
#pragma once
#include "color.h"
#define BRA_AMP_SHIFT 1 // brigthness animation amplitude shift. true BrA amplitude is calculated
// as (0..127) value shifted right by this amount
#define BRA_OFFSET 127 //(222-64) // brigthness animation amplitude offset
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);
virtual bool finishedycle() const { return true; };
virtual void Run() = 0;
virtual void setCycleFactor(float new_cycle_factor) { cycleFactor = new_cycle_factor; }
virtual float getCycleFactor() { return cycleFactor; }
protected:
uint16_t numLeds = 0;
Palette* palette = nullptr;
Color* leds = nullptr;
Color* ledstmp = nullptr;
byte* seq = nullptr;
int phase;
int pos;
int inc;
//brigthness animation (BrA) current initial phase
byte braPhase;
//braPhase change speed
byte braPhaseSpd = 5;
//BrA frequency (spatial)
byte braFreq = 150;
Color curColor = Color(0);
Color prevColor = Color(0);
const Color sparkleColor = Color(0xFFFFFF);
// Reversed analog of speed. To control speed for particular animation (fine tuning for one).
// Lower - faster. Set cycleFactor < 1 speed up animation, while cycleFactor > 1 slow it down.
float cycleFactor = 1.0;
virtual void SetupImpl() = 0;
// helper functions for animations
void initSeq();
void shuffleSeq();
//glow animation setup
void glowSetUp();
//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);
//glow animation - must be called at the end of each animaton run
void glowRun();
private:
const char* _name;
};
unsigned int rng();
byte rngb();

+ 54
- 0
code/espurna/garland/animations/anim_assemble.h View File

@ -0,0 +1,54 @@
#if GARLAND_SUPPORT
#include <list>
#include "../anim.h"
#include "../color.h"
#include "../palette.h"
//------------------------------------------------------------------------------
class AnimAssemble : public Anim {
public:
AnimAssemble() : Anim("Assemble") {
cycleFactor = 2;
}
void SetupImpl() override {
inc = 1 + (rngb() >> 5);
if (secureRandom(10) > 5) {
inc = -inc;
}
int p = 0;
for (int i = 0; i < numLeds; i++) {
leds[i] = 0;
Color c = palette->getCachedPalColor((byte)p);
ledstmp[i] = c;
p = p + inc;
if (p >= 256) {
p = p - 256;
} else if (p < 0) {
p = p + 256;
}
}
initSeq();
shuffleSeq();
pos = 0;
}
void Run() override {
if (pos < numLeds) {
byte cur_point = seq[pos];
leds[cur_point] = ledstmp[cur_point];
++pos;
} else {
int del_pos = pos - numLeds;
byte cur_point = seq[del_pos];
leds[cur_point] = 0;
if (++pos >= numLeds * 2)
pos = 0;
}
}
};
#endif // GARLAND_SUPPORT

+ 66
- 0
code/espurna/garland/animations/anim_comets.h View File

@ -0,0 +1,66 @@
#if GARLAND_SUPPORT
#include <list>
#include "../anim.h"
#include "../color.h"
#include "../palette.h"
//------------------------------------------------------------------------------
class AnimComets : public Anim {
public:
AnimComets() : Anim("Comets") {
}
void SetupImpl() override {
comets.clear();
for (int i = 0; i < 4; ++i)
comets.emplace_back(palette, numLeds);
}
void Run() override {
for (int i = 0; i < numLeds; i++) leds[i] = 0;
for (auto& c : comets) {
int tail = c.head + c.len * -c.dir;
// Check if Comet out of range and generate it again
if ((c.head < 0 && tail < 0) || (c.head >= numLeds && tail >= numLeds)) {
Comet new_comet(palette, numLeds);
std::swap(c, new_comet);
}
for (int l = 0; l < c.len; ++l) {
int p = c.head + l * -c.dir;
if (p >= 0 && p < numLeds) {
leds[p] = c.points[l];
}
}
c.head = c.head + c.speed * c.dir;
}
}
private:
struct Comet {
float head;
int len = secureRandom(10, 20);
float speed = ((float)secureRandom(4, 10)) / 10;
Color color;
int dir = 1;
std::vector<Color> points;
Comet(Palette* pal, uint16_t numLeds) : head(secureRandom(0, numLeds / 2)), color(pal->getRndInterpColor()), points(len) {
// 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;
}
for (int i = 0; i < len; ++i) {
points[i] = Color((byte)(color.r * (len - i) / len), (byte)(color.g * (len - i) / len), (byte)(color.b * (len - i) / len));
}
}
};
std::list<Comet> comets;
};
#endif // GARLAND_SUPPORT

+ 48
- 0
code/espurna/garland/animations/anim_fly.h View File

@ -0,0 +1,48 @@
#if GARLAND_SUPPORT
#include "../anim.h"
#include "../color.h"
#include "../palette.h"
//------------------------------------------------------------------------------
class AnimFly : public Anim {
public:
AnimFly() : Anim("Fly") {
}
void SetupImpl() override {
//length of particle tail
pos = secureRandom(2, 15);
//probability of the tail
inc = secureRandom(5, 15);
if (secureRandom(10) > 5) {
inc = -inc;
}
phase = 0;
}
void Run() override {
byte launchpos;
if (inc > 0) {
launchpos = numLeds - 1;
for (int i = 1; i < numLeds; i++) {
leds[i - 1] = leds[i];
}
} else {
launchpos = 0;
for (int i = numLeds - 2; i >= 0; i--) {
leds[i + 1] = leds[i];
}
}
if (secureRandom(abs(inc)) == 0) {
curColor = palette->getRndInterpColor();
phase = pos;
}
leds[launchpos] = Color((int)curColor.r * phase / pos, (int)curColor.g * phase / pos, (int)curColor.b * phase / pos);
if (phase > 0) phase--;
}
};
#endif // GARLAND_SUPPORT

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

@ -0,0 +1,68 @@
#if GARLAND_SUPPORT
#include "../anim.h"
#include "../color.h"
#include "../palette.h"
#define DUST_LENGTH 20
//------------------------------------------------------------------------------
class AnimPixieDust : public Anim {
public:
AnimPixieDust() : Anim("PixieDust") {
}
void SetupImpl() override {
phase = 0;
curColor = palette->getRndInterpColor();
prevColor = palette->getRndInterpColor();
inc = secureRandom(2) * 2 - 1;
if (inc > 0) {
phase = -DUST_LENGTH / 2;
} else {
phase = numLeds + DUST_LENGTH / 2;
}
glowSetUp();
}
void Run() override {
if (inc > 0) {
for (int i = 0; i < numLeds; i++) {
leds[i] = (i > phase) ? prevColor : curColor;
glowForEachLed(i);
}
phase++;
if (phase >= 4 * numLeds) {
phase = -DUST_LENGTH / 2;
prevColor = curColor;
curColor = palette->getRndInterpColor();
}
} else {
for (int i = 0; i < numLeds; i++) {
leds[i] = (i < phase) ? prevColor : curColor;
glowForEachLed(i);
}
phase--;
if (phase <= -3 * numLeds) {
phase = numLeds + DUST_LENGTH / 2;
prevColor = curColor;
curColor = palette->getContrastColor(prevColor);
}
}
glowRun();
for (int k = phase - DUST_LENGTH / 2; k < (phase + DUST_LENGTH / 2); k++) {
if (k >= 0 && k < numLeds) {
int mix = abs(k - phase) * 255 / DUST_LENGTH + ((int)rngb() - 125);
if (mix < 0) {
mix = 0;
} else if (mix > 255) {
mix = 255;
}
leds[k] = sparkleColor.interpolate(leds[k], (float)mix / 255);
}
}
}
};
#endif // GARLAND_SUPPORT

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

@ -0,0 +1,25 @@
#if GARLAND_SUPPORT
#include "../anim.h"
#include "../palette.h"
//------------------------------------------------------------------------------
class AnimRandCyc : public Anim {
public:
AnimRandCyc() : Anim("RandCyc") {
}
void SetupImpl() override {
for (int i = 0; i < numLeds; i++)
seq[i] = rngb();
}
void Run() override {
for (int i = 0; i < numLeds; i++) {
leds[i] = palette->getCachedPalColor(seq[i]);
seq[i] += rngb() >> 6;
}
}
};
#endif // GARLAND_SUPPORT

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

@ -0,0 +1,38 @@
#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

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

@ -0,0 +1,51 @@
#if GARLAND_SUPPORT
#include "../anim.h"
#include "../color.h"
#include "../palette.h"
#define SPARK_PROB 3 //probability of spark when in idle plase
//------------------------------------------------------------------------------
class AnimSparkr : public Anim {
public:
AnimSparkr() : Anim("Sparkr") {
}
void SetupImpl() override {
glowSetUp();
phase = 0;
curColor = palette->getRndInterpColor();
prevColor = palette->getRndInterpColor();
initSeq();
shuffleSeq();
}
void Run() override {
for (int i = 0; i < numLeds; i++) {
byte pos = seq[i];
leds[pos] = (i > phase) ? prevColor
: (i == phase) ? sparkleColor
: curColor;
glowForEachLed(i);
}
glowRun();
if (phase > numLeds) {
if (secureRandom(SPARK_PROB) == 0) {
int i = (int)rngb() * numLeds / 256;
leds[i] = sparkleColor;
}
}
phase++;
if (phase > 2 * numLeds) {
phase = 0;
prevColor = curColor;
curColor = palette->getContrastColor(prevColor);
shuffleSeq();
}
}
};
#endif // GARLAND_SUPPORT

+ 73
- 0
code/espurna/garland/animations/anim_spread.h View File

@ -0,0 +1,73 @@
#if GARLAND_SUPPORT
#include "../anim.h"
#include "../color.h"
#include "../palette.h"
#include "debug.h"
//------------------------------------------------------------------------------
class AnimSpread : public Anim {
const byte minWidth = 10;
const byte maxWidth = 15;
const byte numTries = 5;
const byte fade_ind = 255 / minWidth;
int GeneratePos() {
int pos = -1;
for (int 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) {
pos = -1;
break;
}
}
if (pos >= 0) break;
}
return pos;
}
public:
AnimSpread() : Anim("Spread") {
cycleFactor = 2;
}
void SetupImpl() override {
inc = secureRandom(2, 4);
// DEBUG_MSG_P(PSTR("[GARLAND] AnimSpread inc = %d\n"), inc);
for (int i = 0; i < numLeds; i++)
seq[i] = 0;
}
void Run() override {
for (int i = 0; i < numLeds; i++)
leds[i] = 0;
for (int i = 0; i < numLeds; i++) {
if (seq[i] > 0) {
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;
}
}
ledstmp[i].fade(fade_ind);
seq[i]--;
}
}
if (secureRandom(inc) == 0) {
int pos = GeneratePos();
if (pos == -1)
return;
ledstmp[pos] = palette->getRndInterpColor();
seq[pos] = secureRandom(minWidth, maxWidth);
}
}
};
#endif // GARLAND_SUPPORT

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

@ -0,0 +1,53 @@
#if GARLAND_SUPPORT
#include "../anim.h"
#include "../color.h"
#include "../palette.h"
//seq keeps phases: 0..127 increasing, 128..255 decreasing, ends at 255 (steady off)
//ledstmp keeps color of stars
//------------------------------------------------------------------------------
class AnimStars : public Anim {
public:
AnimStars() : Anim("Stars") {
}
void SetupImpl() override {
//inc is (average) interval between appearance of new stars
inc = secureRandom(2, 5);
//reset all phases
for (int i = 0; i < numLeds; i++)
seq[i] = 255;
}
void Run() override {
for (byte i = 0; i < numLeds; i++) {
byte phi = seq[i];
if (phi < 254) {
Color col = ledstmp[i];
if (phi <= 127) {
leds[i] = col.brightness(phi << 1);
} else {
leds[i] = col.brightness((255 - phi) << 1);
}
seq[i] += 2;
} else {
leds[i].r = 0;
leds[i].g = 0;
leds[i].b = 0;
}
}
if (secureRandom(inc) == 0) {
byte pos = secureRandom(numLeds);
if (seq[pos] > 250) {
seq[pos] = 0;
ledstmp[pos] = palette->getRndInterpColor();
}
}
}
};
#endif // GARLAND_SUPPORT

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

@ -0,0 +1,41 @@
#if GARLAND_SUPPORT
#include "../anim.h"
//------------------------------------------------------------------------------
class AnimStart : public Anim {
public:
AnimStart() : Anim("Start") {
}
void SetupImpl() override {
phase = 0;
}
void Run() override {
if (phase < numLeds) {
leds[phase].r = 255;
leds[phase].g = 255;
leds[phase].b = 255;
for (int i = 0; i < numLeds; i++) {
leds[i].fade(50);
}
} else if (phase >= numLeds) {
for (int i = 0; i < numLeds; i++) {
short r = numLeds + 255 - phase + rngb();
r = min(r, (short)255);
leds[i].r = (byte)max(r, (short)0);
short g = numLeds + 255 - phase + rngb();
g = min(g, (short)255);
leds[i].g = (byte)max(g, (short)0);
short b = numLeds + 255 - phase + rngb();
b = min(b, (short)255);
leds[i].b = (byte)max(b, (short)0);
}
phase++;
}
phase++;
}
};
#endif // GARLAND_SUPPORT

+ 87
- 0
code/espurna/garland/color.h View File

@ -0,0 +1,87 @@
/*
Part of the GARLAND MODULE
Copyright (C) 2020 by Dmitry Blinov <dblinov76 at gmail dot com>
Inspired by https://github.com/Vasil-Pahomov/ArWs2812 (currently https://github.com/Vasil-Pahomov/Liana)
*/
#pragma once
#if GARLAND_SUPPORT
#include <Arduino.h>
struct Color
{
byte r;
byte g;
byte b;
inline Color() : r(0), g(0), b(0) {}
// allow construction from R, G, B
inline Color(uint8_t ir, uint8_t ig, uint8_t ib)
: r(ir), g(ig), b(ib) {
}
// allow construction from 32-bit (really 24-bit) bit 0xRRGGBB color code
inline Color(uint32_t colorcode)
: r((colorcode >> 16) & 0xFF), g((colorcode >> 8) & 0xFF), b((colorcode >> 0) & 0xFF) {
}
//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 {
int r0 = x * (color.r - r) + r;
int g0 = x * (color.g - g) + g;
int b0 = x * (color.b - b) + b;
return Color(r0, g0, b0);
}
//creates color with decreased brightness
Color brightness(byte k) const {
int r0 = r * (int)k / 255;
int g0 = g * (int)k / 255;
int b0 = b * (int)k / 255;
return Color(r0, g0, b0);
}
//fades (decreases all RGB channels brightness) this color by k
void fade(byte k) {
if (r>=k) { r=r-k; } else { r=0; }
if (g>=k) { g=g-k; } else { g=0; }
if (b>=k) { b=b-k; } else { b=0; }
}
//fades color separately for each channel
void fade3(byte dr, byte dg, byte db) {
if (r>=dr) { r=r-dr; } else { r=0; }
if (g>=dg) { g=g-dg; } else { g=0; }
if (b>=db) { b=b-db; } else { b=0; }
}
//checks whether this color is visually close to given one
bool isCloseTo(Color c) const {
int diff = abs(r - c.r) + abs(g - c.g) + abs(b - c.b);
return diff <= 220; //220 is magic number. Low values give "true" on closer colors, while higher can cause infinite loop while trying to find different color
}
//return value, that can be used to define how close one color to another
int howCloseTo(Color c) const {
return abs(r - c.r) + abs(g - c.g) + abs(b - c.b);
}
bool empty() const {
return r == 0 && g == 0 && b == 0;
}
void println() const {
Serial.print(("r="));Serial.print(r);Serial.print((" "));
Serial.print(("g="));Serial.print(g);Serial.print( (" "));
Serial.print(("b="));Serial.println(b);
}
friend bool operator== (const Color &c1, const Color &c2);
};
#endif // GARLAND_SUPPORT

+ 95
- 0
code/espurna/garland/palette.h View File

@ -0,0 +1,95 @@
/*
Part of the GARLAND MODULE
Copyright (C) 2020 by Dmitry Blinov <dblinov76 at gmail dot com>
Inspired by https://github.com/Vasil-Pahomov/ArWs2812 (currently https://github.com/Vasil-Pahomov/Liana)
*/
#pragma once
#if GARLAND_SUPPORT
#include <vector>
#include "color.h"
class Palette {
public:
Palette(const char* name, std::vector<Color>&& colors) : _name(name), _numColors(colors.size()), _colors(std::move(colors)), _cache(256) {
}
const char* name() const { return _name; }
/**
* Get the interpolated color from the palette.
* The argument is a floating number between 0 and 1
* Used to smoothly traverse through palette.
*/
Color getPalColor(float i) const {
int i0 = (int)(i * _numColors) % (_numColors);
int i1 = (int)(i * _numColors + 1) % (_numColors);
// decimal part is used to interpolate between the two colors
float t0 = i * _numColors - trunc(i * _numColors);
return _colors[i0].interpolate(_colors[i1], t0);
}
Color getCachedPalColor(byte i) {
if (!_cache[i].empty())
return _cache[i];
Color col = getPalColor((float)i / 256);
if (col.empty())
col = 1;
_cache[i] = col;
return col;
}
/**
* Get the interpolated color between two random neighbour colors.
*/
Color getRndNeighborInterpColor() const {
int i0 = secureRandom(_numColors);
int i1 = (i0 + 1) % (_numColors);
float t0 = (float)(secureRandom(256)) / 256;
return _colors[i0].interpolate(_colors[i1], t0);
}
/**
* Get the interpolated color between two random colors.
*/
Color getRndInterpColor() const {
int i0 = secureRandom(_numColors);
int i1 = secureRandom(_numColors);
float t0 = (float)(secureRandom(256)) / 256;
return _colors[i0].interpolate(_colors[i1], t0);
}
Color getContrastColor(const Color& prevColor) const {
int tries = 0;
int bestDiff = 0;
Color bestColor;
// 220 is magic number. Low values give "true" on closer colors, while higher can cause infinite loop while trying to find different color
// let's try to find contras enough color but no more than 10 tries
while (bestDiff <= 220 && tries++ < 10) {
Color newColor = getRndInterpColor();
int diff = prevColor.howCloseTo(newColor);
if (bestDiff < diff) {
bestDiff = diff;
bestColor = newColor;
}
}
return bestColor;
}
private:
const char* _name;
const int _numColors;
std::vector<Color> _colors;
std::vector<Color> _cache;
};
#endif // GARLAND_SUPPORT

+ 111
- 0
code/espurna/garland/scene.h View File

@ -0,0 +1,111 @@
/*
Part of the GARLAND MODULE
Copyright (C) 2020 by Dmitry Blinov <dblinov76 at gmail dot com>
Inspired by https://github.com/Vasil-Pahomov/ArWs2812 (currently https://github.com/Vasil-Pahomov/Liana)
*/
#pragma once
#if GARLAND_SUPPORT
#include <array>
#include <vector>
#include "anim.h"
#include "animations/anim_assemble.h"
#include "animations/anim_comets.h"
#include "animations/anim_fly.h"
#include "animations/anim_pixiedust.h"
#include "animations/anim_randcyc.h"
#include "animations/anim_run.h"
#include "animations/anim_sparkr.h"
#include "animations/anim_spread.h"
#include "animations/anim_stars.h"
#include "animations/anim_start.h"
class Adafruit_NeoPixel;
class Palette;
class Scene {
public:
Scene(Adafruit_NeoPixel* pixels);
void setPalette(Palette* palette);
void setBrightness(byte brightness);
byte getBrightness();
void setSpeed(byte speed);
byte getSpeed();
void setDefault();
void setAnim(Anim* anim) { _anim = anim; }
void run();
void setup();
bool finishedAnimCycle() { return _anim ? _anim->finishedycle() : true; }
unsigned long getAvgCalcTime();
unsigned long getAvgPixlTime();
unsigned long getAvgShowTime();
int getNumShows() { return numShows; }
private:
Adafruit_NeoPixel* _pixels = nullptr;
uint16_t _numLeds;
//Color arrays - two for making transition
std::vector<Color> _leds1;
std::vector<Color> _leds2;
// array of Colorfor anim to currently work with
Color* _leds = nullptr;
Anim* _anim = nullptr;
//auxiliary colors array for mutual usage of anims
std::vector<Color> _ledstmp;
std::vector<byte> _seq;
Palette* _palette = nullptr;
// millis to transition end
unsigned long transms;
byte brightness = 0;
// Reverse to speed. If more convenient to calculate in this way.
// 1 < cycleFactor < 4
byte speed = 50;
float cycleFactor = 2.0;
float cycleTail = 0;
int cyclesRemain = 0;
enum State {
Calculate,
Transition,
Show
} state = Calculate;
int numShows = 0;
//whether to call SetUp on palette change
//(some animations require full transition with fade, otherwise the colors would change in a step, some not)
bool setUpOnPalChange = true;
unsigned long sum_calc_time = 0;
unsigned long sum_pixl_time = 0;
unsigned long sum_show_time = 0;
unsigned int calc_num = 0;
unsigned int show_num = 0;
unsigned int pixl_num = 0;
std::array<byte, 256> bri_lvl = {{0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3,
4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 9, 9, 9, 9, 10,
10, 10, 10, 10, 11, 11, 11, 11, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 15, 15, 15, 15, 16, 16, 16, 17, 17,
17, 18, 18, 19, 19, 19, 20, 20, 20, 21, 21, 22, 22, 22, 23, 23, 24, 24, 25, 25, 26, 26, 27, 27, 28, 28, 29,
29, 30, 30, 31, 31, 32, 32, 33, 34, 34, 35, 35, 36, 37, 37, 38, 39, 39, 40, 41, 42, 42, 43, 44, 45, 45, 46,
47, 48, 49, 49, 50, 51, 52, 53, 54, 55, 56, 57, 57, 58, 59, 60, 61, 63, 64, 65, 66, 67, 68, 69, 70, 71, 73,
74, 75, 76, 78, 79, 80, 82, 83, 84, 86, 87, 89, 90, 91, 93, 94, 96, 98, 99, 101, 102, 104, 106, 108, 109,
111, 113, 115, 117, 119, 121, 122, 124, 126, 129, 131, 133, 135, 137, 139, 142, 144, 146, 149, 151, 153, 156,
158, 161, 163, 166, 169, 171, 174, 177, 180, 183, 186, 189, 192, 195, 198, 201, 204, 208, 211, 214, 218, 221,
225, 228, 232, 236, 239, 243, 247, 251, 255}};
void setupImpl();
};
#endif // GARLAND_SUPPORT

+ 6
- 0
code/espurna/main.cpp View File

@ -31,6 +31,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "domoticz.h"
#include "encoder.h"
#include "homeassistant.h"
#include "garland.h"
#include "i2c.h"
#include "influxdb.h"
#include "ir.h"
@ -309,6 +310,11 @@ void setup() {
extraSetup();
#endif
#if GARLAND_SUPPORT
garlandSetup();
#endif
// Prepare configuration for version 2.0
migrate();


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


+ 2
- 0
code/espurna/web.cpp View File

@ -41,6 +41,8 @@ Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
#include "static/index.rfm69.html.gz.h"
#elif WEBUI_IMAGE == WEBUI_IMAGE_LIGHTFOX
#include "static/index.lightfox.html.gz.h"
#elif WEBUI_IMAGE == WEBUI_IMAGE_GARLAND
#include "static/index.garland.html.gz.h"
#elif WEBUI_IMAGE == WEBUI_IMAGE_THERMOSTAT
#include "static/index.thermostat.html.gz.h"
#elif WEBUI_IMAGE == WEBUI_IMAGE_CURTAIN


+ 6
- 0
code/gulpfile.js View File

@ -117,6 +117,7 @@ var buildWebUI = function(module) {
'sensor': false,
'rfbridge': false,
'rfm69': false,
'garland': false,
'thermostat': false,
'lightfox': false,
'curtain': false
@ -215,6 +216,10 @@ gulp.task('webui_lightfox', function() {
return buildWebUI('lightfox');
});
gulp.task('webui_garland', function() {
return buildWebUI('garland');
});
gulp.task('webui_thermostat', function() {
return buildWebUI('thermostat');
});
@ -235,6 +240,7 @@ gulp.task('webui',
'webui_rfbridge',
'webui_rfm69',
'webui_lightfox',
'webui_garland',
'webui_thermostat',
'webui_curtain',
'webui_all'


+ 1
- 0
code/html/custom.css View File

@ -213,6 +213,7 @@ div.state {
.button-ha-config,
.button-settings-backup,
.button-settings-restore,
.button-garland-set-default,
.button-dbgcmd,
.button-apikey {
background: rgb(0, 192, 0); /* green */


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

@ -2320,6 +2320,24 @@ $(function() {
$("#uploader").on("change", onFileUpload);
$(".button-upgrade").on("click", doUpgrade);
<!-- removeIf(!garland)-->
$(".checkbox-garland-enable").on("change", function() {
sendAction("garland_switch", {status: $(this).prop("checked") ? 1 : 0});
});
$(".slider-garland-brightness").on("change", function() {
sendAction("garland_set_brightness", {brightness: $(this)[0].value});
});
$(".slider-garland-speed").on("change", function() {
sendAction("garland_set_speed", {speed: $(this)[0].value});
});
$(".button-garland-set-default").on("click", function() {
sendAction("garland_set_default", {});
});
<!-- endRemoveIf(!garland)-->
<!-- removeIf(!thermostat)-->
$(".button-thermostat-reset-counters").on('click', doResetThermostatCounters);
<!-- endRemoveIf(!thermostat)-->


+ 39
- 0
code/html/index.html View File

@ -102,6 +102,12 @@
</li>
<!-- endRemoveIf(!curtain) -->
<!-- removeIf(!garland) -->
<li class="pure-menu-item module module-garland">
<a href="#" class="pure-menu-link" data="panel-garland">GARLAND</a>
</li>
<!-- endRemoveIf(!garland) -->
<!-- removeIf(!thermostat) -->
<li class="pure-menu-item module module-thermostat">
<a href="#" class="pure-menu-link" data="panel-thermostat">THERMOSTAT</a>
@ -1134,6 +1140,39 @@
</div>
</form>
<!-- removeIf(!garland) -->
<form id="form-garland" class="pure-form form-settings">
<div class="panel" id="panel-garland">
<div class="header">
<h1>GARLAND</h1>
<h2>Garland configuration</h2>
</div>
<div class="page">
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Enable Garland</label>
<div class="pure-u-1 pure-u-lg-1-4"><input class="checkbox-garland-enable" type="checkbox" name="garlandEnabled" tabindex="30" /></div>
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Brightness</label>
<input class="pure-u-1 pure-u-lg-3-4 slider slider-garland-brightness" type="range" min="12" max="254" step="22" name="garlandBrightness" />
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Speed</label>
<input class="pure-u-1 pure-u-lg-3-4 slider slider-garland-speed" type="range" min="10" max="70" name="garlandSpeed" />
</div>
<button type="button" class="pure-button button-garland-set-default">Set default</button>
</div>
</div>
</form>
<!-- endRemoveIf(!garland) -->
<!-- removeIf(!thermostat) -->
<form id="form-thermostat" class="pure-form form-settings">
<div class="panel" id="panel-thermostat">


+ 6
- 0
code/platformio.ini View File

@ -154,6 +154,7 @@ lib_deps =
https://github.com/ThingPulse/esp8266-oled-ssd1306#3398c97
Adafruit SI1145 Library@~1.1.1
https://github.com/BoschSensortec/BSEC-Arduino-library.git#c5503e0
adafruit/Adafruit NeoPixel@^1.7.0
# ------------------------------------------------------------------------------
# COMMON ENVIRONMENT SETTINGS:
@ -1065,3 +1066,8 @@ src_build_flags = -DBENEXMART_GU53_RGBWW
[env:lsc-e27-10w-white]
extends = env:esp8266-1m-base
src_build_flags = -DLSC_E27_10W_WHITE
[env:garland-wemos]
extends = env:esp8266-4m-base
board = d1_mini
src_build_flags = -DWEMOS_D1_MINI -DGARLAND_SUPPORT=1

+ 1
- 0
code/test/build/garland.h View File

@ -0,0 +1 @@
#define GARLAND_SUPPORT 1

Loading…
Cancel
Save