Compare commits

...

15 Commits

Author SHA1 Message Date
Fabian Schlenz 205a0df842 Small fixes.
continuous-integration/drone/push Build is failing Details
2019-10-04 15:58:56 +02:00
Fabian Schlenz 8bcee1871f Effect dvd now with subpixel rendering. 2019-10-04 15:58:24 +02:00
Fabian Schlenz ef57c5ea2e Settings can now be changed:
* Via HTTP POST request to /settings, providing key=<key_to_change> and value=<new_value>.
  * Via MQTT at <MQTT_TOPIC>/settings.<key>.
2019-10-04 12:11:05 +02:00
Fabian Schlenz 0f1d4abe04 Settings are now properly global and can be changed. 2019-10-04 12:10:16 +02:00
Fabian Schlenz 2b50691067 Fixed memory leak in random_matrix and rainbow_matrix. 2019-10-04 12:08:01 +02:00
Fabian Schlenz af1314632e sines effect now uses subpixel rendering. 2019-10-04 12:05:54 +02:00
Fabian Schlenz 2b7033b685 cycle effect now tracks the heap leakage of the running effects. Cause somewhere's a memory leak leading to restarts of the ESP every about 2 days... 2019-10-04 12:03:03 +02:00
Fabian Schlenz 97dd6de280 Fixes for recorder and tests. 2019-10-02 06:21:10 +02:00
Fabian Schlenz 54d357e6df Fixes for firework effect. 2019-10-02 06:20:39 +02:00
Fabian Schlenz ac1f758b87 Fixes in blur2d effect. 2019-10-02 06:18:43 +02:00
Fabian Schlenz 85aee53462 More debugging output in Animation.cpp 2019-10-02 06:18:15 +02:00
Fabian Schlenz f42b5e1034 Effect big_clock now show the seconds in a calmer way. Divisible-by-5 seconds are in another color. 2019-10-02 06:17:20 +02:00
Fabian Schlenz 083564caef Reorganized effect selection stuff: No longer a big case clause comparing CRC32. Instead an array of structs. Much nicer. Also, other code can now see which effects there are. 2019-10-02 06:16:07 +02:00
Fabian Schlenz 3f6d4cb0be Moved settings from preprocessor directives to variables. Also added a way to (for now only) display them via HTTP server. 2019-10-02 06:13:55 +02:00
Fabian Schlenz 382631d7d7 Effect#loop now gets the time since the last run of the loop in ms. This enables
the effects to show animations that stay fluid independent of the current frame rate.
2019-10-01 06:29:32 +02:00
58 changed files with 563 additions and 287 deletions

View File

@ -95,7 +95,6 @@ If you enabled `DEBUG`, log messages will be sent to `MQTT_TOPIC/log`.
| FastLED (with small modifications) | Daniel Garcia & Mark Kriegsman | https://fastled.io
| NTPClient (with modifications) | | https://github.com/arduino-libraries/NTPClient
| ESP8266WebServer | | https://github.com/esp8266/Arduino/tree/master/libraries/ESP8266WebServer
| ErriezCRC32 | Erriez | https://github.com/Erriez/ErriezCRC32
| ESPAsyncTCP | me-no-dev | https://github.com/me-no-dev/ESPAsyncTCP
### Inspirations and stuff

View File

@ -9,7 +9,7 @@ protected:
Window* window = Window::getFullWindow(); // Use a full screen window per default.
public:
virtual ~Effect() {};
virtual void loop() = 0;
virtual void loop(uint16_t ms) = 0;
virtual String get_name() = 0;
boolean supports_window = false;
virtual boolean can_be_shown_with_clock() { return false; };

View File

@ -3,6 +3,7 @@
#include <Arduino.h>
#define FASTLED_INTERNAL
#include <FastLED.h>
#include "settings.h"
//#define DEBUG // Uncomment this to enable Debug messages via Serial and, if enabled, MQTT.
//#define CONFIG_USABLE // Uncomment this by removing the // at the beginning!
@ -49,39 +50,35 @@
#define MONITOR_LOOP_TIME_THRESHOLD 500
#define MONITOR_LOOP_TIME_COUNT_MAX 10
#define EFFECT_CYCLE_TIME 300 // Time in seconds between cycling effects.
#define EFFECT_CYCLE_RANDOM true
// settings.effects.cycle.time = 300; // Time in seconds between cycling effects.
// settings.effects.cycle.random = true;
#define EFFECT_MATRIX_LENGTH_MIN 4
#define EFFECT_MATRIX_LENGTH_MAX 20
#define EFFECT_MATRIX_SPEED_MIN 50
#define EFFECT_MATRIX_SPEED_MAX 135
// settings.effects.matrix.length_min = 4;
// settings.effects.matrix.length_max = 20;
// settings.effects.matrix.speed_min = 1;
// settings.effects.matrix.speed_max = 10;
#define EFFECT_SINGLE_DYNAMIC_LOOP_TIME 40
#define EFFECT_MULTI_DYNAMIC_LOOP_TIME 1400
#define EFFECT_BIG_DYNAMIC_LOOP_TIME 50
#define EFFECT_BIG_DYNAMIC_SIZE 3
// .dynamic.single_loop_time = 40;
// .dynamic.multi_loop_time = 1400;
// .dynamic.big_loop_time = 50;
// .dynamic.big_size = 3;
#define EFFECT_CONFETTI_PIXELS_PER_LOOP 2
// .fire.cooldown = 192;
// .fire.spark_chance = 5;
#define EFFECT_SNAKE_DIRECTION_CHANGE 10
#define EFFECT_SNAKE_SLOWDOWN 2
// .firework.drag = 255;
// .firework.bounce = 200;
// .firework.gravity = 10;
// .firework.sparks = 12;
#define EFFECT_FIRE_COOLDOWN 192
#define EFFECT_FIRE_SPARK_CHANCE 5
// .gol.start_percentage = 90;
// .gol.blend_speed = 10;
// .gol.restart_after_steps = 100;
#define EFFECT_FIREWORK_SHOT_CHANCE 200
#define EFFECT_FIREWORK_BLUR 200
#define EFFECT_FIREWORK_FADEOUT_SPEED 5
// .sines.count = 5;
#define EFFECT_GOL_START_PERCENTAGE 90
#define EFFECT_GOL_BLEND_SPEED 10
#define EFFECT_GOL_RESTART_AFTER_STEPS 100
#define EFFECT_DVD_WIDTH 3
#define EFFECT_DVD_HEIGHT 2
#define EFFECT_SINES_COUNT 5
// .snake.direction_change = 5;
// .snake.slowdown = 2;
// Stop editing here

View File

@ -4,6 +4,6 @@
class AnalogClockEffect : public Effect {
public:
void loop();
void loop(uint16_t ms);
String get_name() override { return "analog_clock"; }
};

View File

@ -17,6 +17,6 @@ public:
AnimationEffect(const char* name, uint32_t bg_color, int x, int y);
~AnimationEffect();
AnimationEffect* setFgColor(uint32_t c);
void loop();
void loop(uint16_t ms);
String get_name() override;
};

View File

@ -9,7 +9,7 @@ private:
CRGB color_off = CRGB(0x000000);
boolean invert = false;
public:
void loop();
void loop(uint16_t ms);
String get_name() override { return "bell"; }
};

View File

@ -5,12 +5,13 @@
class BigClockEffect : public Effect {
private:
CRGB _color_font = CRGB(0xAAAAAA);
CRGB _color_seconds = CRGB(0xFF0000);
CRGB _color_seconds_light = CRGB(0xFFFF00);
CRGB _color_seconds_dark = CRGB(0xFF0000);
void _draw_seconds();
void _draw_border_pixel(uint8_t second, CRGB* color);
void _draw_border_pixel(uint8_t second, uint8_t part, CRGB* color);
public:
void loop();
void loop(uint16_t ms);
String get_name() override { return "big_clock"; }
};

View File

@ -11,7 +11,7 @@ public:
~Blur2DEffect();
boolean supports_window = true;
boolean can_be_shown_with_clock();
void loop();
void loop(uint16_t ms);
String get_name() override { return "blur2d"; }
};

View File

@ -11,7 +11,7 @@ protected:
public:
~ClockEffect();
virtual void loop();
virtual void loop(uint16_t ms);
String get_name() override { return "clock"; }
void loop_with_invert(bool invert);
void loop(boolean invert, CRGB fg_color, CRGB bg_color, uint8_t y);
@ -20,5 +20,5 @@ public:
class NightClockEffect : public ClockEffect {
public:
NightClockEffect();
void loop() override;
void loop(uint16_t ms) override;
};

View File

@ -7,7 +7,7 @@ class ConfettiEffect : public Effect {
protected:
virtual CRGB _getColor();
public:
void loop();
void loop(uint16_t ms);
boolean can_be_shown_with_clock();
String get_name() override { return "confetti"; }
};

View File

@ -8,6 +8,8 @@ private:
Effect* effect = NULL;
uint16_t effect_id = -1;
unsigned long effectSince = 0;
uint16_t _heap_free = 0;
uint8_t _effects_count;
public:
CycleEffect();
~CycleEffect();
@ -17,5 +19,5 @@ public:
boolean clock_as_mask();
String get_name() override;
void loop();
void loop(uint16_t ms);
};

View File

@ -4,15 +4,15 @@
class DvdEffect : public Effect {
private:
Window* window = new Window(0, 0, LED_WIDTH, LED_HEIGHT-6);
uint8_t _x = 0;
uint8_t _y = 0;
saccum78 _x = 0;
saccum78 _y = 0;
int8_t _x_dir = 1;
int8_t _y_dir = 1;
CRGB _color;
public:
DvdEffect();
~DvdEffect();
void loop() override;
void loop(uint16_t ms) override;
bool can_be_shown_with_clock() override;
String get_name() override { return "dvd"; }
};

View File

@ -13,14 +13,14 @@ public:
SingleDynamicEffect();
void init();
boolean can_be_shown_with_clock();
virtual void loop();
virtual void loop(uint16_t ms);
void draw();
String get_name() override { return "single_dynamic"; }
};
class MultiDynamicEffect : public SingleDynamicEffect {
public:
void loop();
void loop(uint16_t ms);
String get_name() override { return "multi_dynamic"; }
};
@ -28,7 +28,7 @@ class BigDynamicEffect : public Effect {
private:
Window* window = new Window(0, 0, LED_WIDTH, LED_HEIGHT-6);
public:
void loop();
void loop(uint16_t ms);
~BigDynamicEffect();
boolean can_be_shown_with_clock() override;
String get_name() override { return "big_dynamic"; }

View File

@ -15,6 +15,6 @@ private:
public:
FireEffect();
~FireEffect();
void loop();
void loop(uint16_t ms);
String get_name() override { return "fire"; }
};

View File

@ -6,11 +6,6 @@
enum FireworkDotType { FIREWORK_DOT_NONE, FIREWORK_DOT_SHELL, FIREWORK_DOT_SPARK };
#define EFFECT_FIREWORK_DRAG 255
#define EFFECT_FIREWORK_BOUNCE 200
#define EFFECT_FIREWORK_GRAVITY 10
#define EFFECT_FIREWORK_SPARKS 12
class FireworkEffect;
class FireworkEffectDot {
@ -49,14 +44,14 @@ private:
CRGB _burst_color;
FireworkEffectDot* _dot;
FireworkEffectDot* _sparks[EFFECT_FIREWORK_SPARKS];
FireworkEffectDot** _sparks;
public:
FireworkEffect();
~FireworkEffect();
void skyburst(accum88 x, accum88 y, saccum78 xv, saccum78 yv, CRGB c);
boolean supports_window = true;
boolean can_be_shown_with_clock();
void loop();
void loop(uint16_t ms);
String get_name() override { return "firework"; }
};

View File

@ -17,7 +17,7 @@ private:
public:
GolEffect();
~GolEffect();
void loop();
void loop(uint16_t ms);
bool can_be_shown_with_clock();
String get_name() override { return "gol"; }
};

View File

@ -12,7 +12,7 @@ private:
public:
boolean supports_window = true;
boolean can_be_shown_with_clock();
void loop();
void loop(uint16_t ms);
void apply_option(String key, String value) override;
String get_name() override { return "marquee"; }
};

View File

@ -27,9 +27,9 @@ public:
MatrixEffectColumn(Window* win, uint8_t direction=0, bool random_direction=false);
virtual ~MatrixEffectColumn() {};
void advance();
void advance(uint16_t ms);
void draw();
void loop();
void loop(uint16_t ms);
};
class RainbowMatrixEffectColumn : public MatrixEffectColumn {
@ -55,7 +55,7 @@ public:
boolean can_be_shown_with_clock();
MatrixEffect();
virtual ~MatrixEffect();
void loop();
void loop(uint16_t ms);
String get_name() override { return "matrix"; }
};

View File

@ -10,7 +10,7 @@ private:
public:
PixelClockEffect();
~PixelClockEffect();
void loop();
void loop(uint16_t ms);
bool can_be_shown_with_clock();
String get_name() override { return "pixel_clock"; }
};

View File

@ -30,7 +30,7 @@ public:
boolean supports_window = true;
boolean can_be_shown_with_clock();
boolean clock_as_mask();
void loop();
void loop(uint16_t ms);
String get_name() override { return "sinematrix3"; }
};

View File

@ -6,28 +6,29 @@
class SinesEffectSinus {
private:
uint8_t _value;
uint8_t _frequency;
uint8_t _amplitude;
uint8_t _x;
uint8_t _step;
uint16_t _frequency;
uint16_t _color_frequency;
uint16_t _amplitude;
uint16_t _x;
uint16_t _offset;
Window* _window;
CRGB _color;
public:
SinesEffectSinus(Window* w);
void loop();
void loop(uint16_t ms);
};
class SinesEffect : public Effect {
private:
SinesEffectSinus* _sinus[EFFECT_SINES_COUNT];
uint8_t _step = 0;
SinesEffectSinus** _sinus;
uint8_t _count;
void _init();
void _delete();
public:
SinesEffect();
~SinesEffect();
boolean supports_window = true;
boolean can_be_shown_with_clock();
void loop();
void loop(uint16_t ms);
String get_name() override { return "sines"; }
};

View File

@ -17,7 +17,7 @@ private:
public:
SnakeEffect();
~SnakeEffect();
void loop();
void loop(uint16_t ms);
boolean valid_position(Coords c);
Coords update_position(Coords c, uint8_t direction);
boolean can_be_shown_with_clock();

View File

@ -9,7 +9,7 @@ private:
public:
StaticEffect(CRGB col);
boolean supports_window = true;
void loop();
void loop(uint16_t ms);
String get_name() override { return "static"; }
};

View File

@ -10,7 +10,7 @@ private:
double _real_center_x = LED_WIDTH / 2;
double _real_center_y = LED_HEIGHT / 2;
public:
void loop();
void loop(uint16_t ms);
boolean can_be_shown_with_clock() override;
boolean clock_as_mask() override;
String get_name() override { return "twirl"; }

View File

@ -1,17 +1,20 @@
#ifndef effects_H
#define effects_H
#pragma once
#include "Effect.h"
#include "effect_clock.h"
extern const char* cycle_effects[];
extern uint8_t cycle_effects_count;
struct EffectEntry {
const char* name;
bool use_in_cycle;
std::function<Effect*()> create;
};
extern const EffectEntry effects[];
extern const uint8_t effects_size;
extern Effect* current_effect;
extern ClockEffect effect_clock;
Effect* select_effect(uint32_t c);
Effect* select_effect(char* name);
Effect* select_effect(uint8_t id);
bool change_current_effect(String s);
void setup_effects();
#endif

View File

@ -2,6 +2,9 @@
#include <Arduino.h>
extern uint8_t baseHue;
extern char hostname[30];
typedef struct {
uint8_t width;
uint8_t height;
@ -26,6 +29,3 @@ typedef struct {
uint16_t x;
uint16_t y;
} Coords;
extern uint8_t baseHue;
extern char hostname[30];

83
include/settings.h Normal file
View File

@ -0,0 +1,83 @@
#pragma once
#include <Arduino.h>
enum SettingType {
TYPE_UINT8,
TYPE_UINT16,
TYPE_BOOL
};
typedef struct {
const char* name;
uint16_t* value;
SettingType type;
} Setting;
struct Settings {
uint16_t fps = 50;
struct /* effects */ {
struct /* cycle */ {
uint16_t time = 300;
uint16_t random = 1;
} cycle ;
struct /* matrix */ {
uint16_t length_min = 4;
uint16_t length_max = 20;
uint16_t speed_min = 1;
uint16_t speed_max = 10;
} matrix;
struct /* confetti */ {
uint16_t pixels_per_loop = 2;
} confetti;
struct /* dvd */ {
uint16_t width = 3;
uint16_t height = 2;
uint16_t speed = 50;
} dvd;
struct /* dynamic */ {
uint16_t single_loop_time = 40;
uint16_t multi_loop_time = 1400;
uint16_t big_loop_time = 50;
uint16_t big_size = 3;
} dynamic;
struct /* fire */ {
uint16_t cooldown = 192;
uint16_t spark_chance = 5;
} fire;
struct /* firework */ {
uint16_t drag = 255;
uint16_t bounce = 200;
uint16_t gravity = 10;
uint16_t sparks = 12;
} firework;
struct /* gol */ {
uint16_t start_percentage = 90;
uint16_t blend_speed = 10;
uint16_t restart_after_steps = 100;
} gol;
struct /* sines */ {
uint16_t count = 5;
} sines;
struct /* snake */ {
uint16_t direction_change = 5;
uint16_t slowdown = 2;
} snake;
} effects;
};
extern Settings settings;
extern Setting all_settings[];
extern const uint8_t all_settings_size;
bool change_setting(const char* key, uint16_t new_value);

View File

@ -18,7 +18,6 @@ lib_deps =
https://github.com/fabianonline/FastLED.git
https://github.com/fabianonline/NTPClient.git
ESP8266WebServer
ErriezCRC32
ESPAsyncTCP
[env:ota]

View File

@ -75,7 +75,7 @@ bool Animation::_load_from_file(const char* filename) {
return false;
}
if (file.available() != size - 6) {
if (file.available() < 0 || file.available() + 6 != size) {
LOGln("Animation * Expected file to have %d bytes available, but found %d bytes available.", size - 6, file.available());
file.close();
return false;
@ -207,27 +207,29 @@ void Animation::setSingleFrame(uint8_t frame) {
Animation::~Animation() {
for (int i=0; i<_color_count; i++) delete _colors[i];
LOGln("Deleting _colors...");
LOGln("Animation * Deleting _colors...");
if (_colors) delete [] _colors;
LOGln("Deleting fgColor...");
LOGln("Animation * Deleting fgColor...");
if (fgColor != NULL) delete fgColor;
LOGln("Deleting bgColor...");
LOGln("Animation * Deleting bgColor...");
if (bgColor != NULL) delete bgColor;
LOGln("Deleting _frame_data_lengths...");
LOGln("Animation * Deleting _frame_data_lengths...");
if (_frame_data_lengths) delete [] _frame_data_lengths;
LOGln("Deleting _frame_times...");
LOGln("Animation * Deleting _frame_times...");
if (_frame_times) delete [] _frame_times;
for (int i=0; i<_frame_count; i++) {
delete [] _frame_data[i];
}
LOGln("Deleting _frame_data...");
LOGln("Animation * Deleting _frame_data...");
if (_frame_data) delete [] _frame_data;
LOGln("Deleteion done.");
LOGln("Animation * Deletion done.");
}
void Animation::draw() {

View File

@ -42,13 +42,13 @@ void Window::clear(CRGB* color) {
}
void Window::drawText(Font* font, uint16_t x, uint16_t y, String text, CRGB* color) {
for (int i=0; i<text.length(); i++) {
drawChar(font, (x+(i*(font->width + 1))<<8), (y<<8), text[i], color);
for (uint16_t i=0; i<text.length(); i++) {
drawChar(font, (x+((i*(font->width + 1))<<8)), (y<<8), text[i], color);
}
}
void Window::drawSubText(Font* font, accum88 x, accum88 y, String text, CRGB* color) {
for (int i=0; i<text.length(); i++) {
for (uint16_t i=0; i<text.length(); i++) {
drawChar(font, x+(i*((font->width + 1)<<8)), y, text[i], color);
}
}

View File

@ -2,7 +2,7 @@
#include "my_fastled.h"
#include "ntp.h"
void AnalogClockEffect::loop() {
void AnalogClockEffect::loop(uint16_t ms) {
window->clear();
CRGB white(0xFFFFFF);
CRGB red(0xFF0000);

View File

@ -20,7 +20,7 @@ AnimationEffect::~AnimationEffect() {
delete this->animation;
}
void AnimationEffect::loop() {
void AnimationEffect::loop(uint16_t ms) {
this->animation->drawFrame();
this->animation->advance();
}

View File

@ -3,7 +3,7 @@
#include "effect_bell.h"
#include "sprites.h"
void BellEffect::loop() {
void BellEffect::loop(uint16_t ms) {
Serial.println("This is Bell.loop()");
for (int y = 0; y < 16; y++) {
for (int x = 0; x < 2; x++) {

View File

@ -3,7 +3,7 @@
#include "fonts.h"
#include "ntp.h"
void BigClockEffect::loop() {
void BigClockEffect::loop(uint16_t ms) {
window->clear();
uint8_t h = ntpClient.getHours();
window->drawChar(&font_numbers3x5_blocky, 6<<8, 2<<8, '0' + (h / 10), &_color_font);
@ -25,17 +25,28 @@ void BigClockEffect::loop() {
void BigClockEffect::_draw_seconds() {
uint8_t seconds = ntpClient.getSeconds();
for (int i=1; i<=seconds; i++) {
_draw_border_pixel(i, &_color_seconds);
_draw_border_pixel(i, 0, (i%5==0) ? &_color_seconds_light : &_color_seconds_dark);
}
uint16_t millis = ntpClient.getEpochMillis() % 1000;
/*
// Enable this to have the next pixel move smoothly to its position
if (millis > 0) {
uint8_t part = 60 - ((60 - seconds) * millis / 1000);
_draw_border_pixel(part, &_color_seconds);
}
*/
uint8_t offset = 5 - ((millis % 1000) / 200);
uint8_t part = scale8(millis % 200, 200);
uint8_t number_to_show = (60 - seconds - offset) / 5 + 1;
for(uint8_t i = 0; i<number_to_show; i++) {
uint8_t pos = seconds + offset + i*5;
_draw_border_pixel(pos, part, (seconds + i + 1)%5==0 ? &_color_seconds_light : &_color_seconds_dark);
}
}
void BigClockEffect::_draw_border_pixel(uint8_t i, CRGB* color) {
uint8_t x, y;
void BigClockEffect::_draw_border_pixel(uint8_t i, uint8_t part, CRGB* color) {
/*uint8_t x, y;
if (i<=8) {
x = 7 + i;
y = 0;
@ -48,9 +59,31 @@ void BigClockEffect::_draw_border_pixel(uint8_t i, CRGB* color) {
} else if (i <= 53) {
x = 0;
y = 15 - i + 38;
} else {
} else if (i <= 60) {
x = i - 53;
y = 0;
} else {
return;
}
window->setPixel(x, y, color);
window->setPixel(x, y, color);*/
accum88 x, y;
if (i<=8) {
x = ((7+i)<<8) + part;
y = 0;
} else if (i<=23) {
x = 15<<8;
y = ((i-8)<<8) + part;
} else if (i<=38) {
x = ((38-i)<<8) - part;
y = 15<<8;
} else if (i<=53) {
x = 0;
y = ((53-i)<<8) - part;
} else if (i<=60) {
x = ((i-53)<<8) + part;
y = 0;
} else {
return;
}
window->setSubPixel(x, y, color);
}

View File

@ -4,7 +4,7 @@ boolean Blur2DEffect::can_be_shown_with_clock() {
return true;
}
void Blur2DEffect::loop() {
void Blur2DEffect::loop(uint16_t ms) {
uint8_t blur_amount = dim8_raw(beatsin8(3, 128, 224));
window->blur(blur_amount);
@ -17,10 +17,10 @@ void Blur2DEffect::loop() {
uint8_t x3 = beatsin8(11, 0, window->width-1);
uint8_t y3 = beatsin8(13, 0, window->height-1);
uint16_t ms = millis();
CRGB c1 = CHSV(ms / 29, 200, 255);
CRGB c2 = CHSV(ms / 41, 200, 255);
CRGB c3 = CHSV(ms / 73, 200, 255);
uint16_t time = millis();
CRGB c1 = CHSV(time / 29, 200, 255);
CRGB c2 = CHSV(time / 41, 200, 255);
CRGB c3 = CHSV(time / 73, 200, 255);
window->addPixelColor(x1, y1, &c1);
window->addPixelColor(x2, y2, &c2);
window->addPixelColor(x3, y3, &c3);

View File

@ -8,7 +8,7 @@ NightClockEffect::NightClockEffect() {
window = Window::getFullWindow();
}
void NightClockEffect::loop() {
void NightClockEffect::loop(uint16_t ms) {
uint16_t minutes = minutes16();
//uint8_t y = minutes % ((window->height - 5) * 2 - 2);
//if (y > window->height - 5) y = 2*window->height - 2*y;
@ -16,7 +16,7 @@ void NightClockEffect::loop() {
ClockEffect::loop(false, CRGB(0x200000), CRGB(0x000000), y);
}
void ClockEffect::loop() {
void ClockEffect::loop(uint16_t ms) {
loop_with_invert(false);
}

View File

@ -3,9 +3,9 @@
#include "functions.h"
#include "prototypes.h"
void ConfettiEffect::loop() {
void ConfettiEffect::loop(uint16_t ms) {
window->fadeToBlackBy(3);
for (int i=0; i<EFFECT_CONFETTI_PIXELS_PER_LOOP; i++) {
for (int i=0; i<settings.effects.confetti.pixels_per_loop; i++) {
CRGB color = _getColor();
window->addPixelColor(random16(LED_COUNT), &color);
}

View File

@ -3,6 +3,11 @@
#include <ErriezCRC32.h>
CycleEffect::CycleEffect() {
_effects_count = 0;
for (uint8_t i=0; i<effects_size; i++) {
if (effects[i].use_in_cycle) _effects_count++;
}
LOGln("Cycle * Found %d effects to use in cycle.", _effects_count);
changeEffect();
}
@ -11,20 +16,41 @@ CycleEffect::~CycleEffect() {
}
void CycleEffect::changeEffect() {
int new_id;
if (EFFECT_CYCLE_RANDOM) {
uint8_t new_id;
if (settings.effects.cycle.random && _effects_count>1) {
do {
new_id = random8(cycle_effects_count);
new_id = random8(_effects_count);
} while (new_id == effect_id);
} else {
new_id = (effect_id + 1) % cycle_effects_count;
new_id = (effect_id + 1) % _effects_count;
}
LOGln("CycleEffect * Changing effect from #%d to #%d", effect_id, new_id);
delay(25);
if (effect) delete effect;
LOGln("CycleEffect * Searching for new effect '%s'", cycle_effects[new_id]);
int16_t diff;
uint16_t old_heap = _heap_free;
_heap_free = ESP.getFreeHeap();
if (old_heap) {
// diff positive = More heap used (baad)
// diff negative = Less heap used (good-ish)
diff = old_heap - _heap_free;
LOGln("CycleEffect * Heap usage: #%d,%d,%+d", effect_id, _heap_free, diff);
}
delay(25);
effect = select_effect( crc32String(cycle_effects[new_id]) );
LOGln("CycleEffect * Searching for new effect #%d", new_id);
uint8_t count = 0;
for (uint8_t i=0; i<effects_size; i++) {
if (effects[i].use_in_cycle) {
if (count == new_id) {
effect = effects[i].create();
break;
}
count++;
}
}
if (effect) {
effect_id = new_id;
effectSince = millis();
@ -41,13 +67,13 @@ boolean CycleEffect::clock_as_mask() {
return effect->clock_as_mask();
};
void CycleEffect::loop() {
void CycleEffect::loop(uint16_t ms) {
if (!effect) changeEffect(); // If this is the first run, we have to select an effect first!
effect->loop();
effect->loop(ms);
// Don't use EVERY_N_SECONDS(config_effect_cycle_time) here because that function isn't relly made
// to be used with changing values.
EVERY_N_SECONDS(1) {
if (effectSince + EFFECT_CYCLE_TIME*1000 < millis()) {
if (effectSince + settings.effects.cycle.time*1000 < millis()) {
changeEffect();
}
}

View File

@ -1,29 +1,43 @@
#include "effect_dvd.h"
#include "my_fastled.h"
void DvdEffect::loop() {
void DvdEffect::loop(uint16_t ms) {
bool dir_changed = false;
EVERY_N_MILLISECONDS( 250 ) {
_x += _x_dir;
_y += _y_dir;
_x += _x_dir * settings.effects.dvd.speed;
_y += _y_dir * settings.effects.dvd.speed;
if (_x == 0 || _x + EFFECT_DVD_WIDTH >= window->width) {
_x_dir = -_x_dir;
dir_changed = true;
}
if (_y == 0 || _y + EFFECT_DVD_HEIGHT >= window->height) {
_y_dir = -_y_dir;
dir_changed = true;
}
if (_x <= 0) {
_x = -_x;
_x_dir = -_x_dir;
dir_changed = true;
//LOGln("speed: %d", settings.effects.dvd.speed);
} else if (_x + (settings.effects.dvd.width << 8) >= (window->width << 8)) {
_x -= 2*settings.effects.dvd.speed;
_x_dir = -_x_dir;
dir_changed = true;
}
if (_y <= 0) {
_y = -_y;
_y_dir = -_y_dir;
dir_changed = true;
} else if (_y + (settings.effects.dvd.height << 8) >= (window->height << 8)) {
_y -= 2*settings.effects.dvd.speed;
_y_dir = -_y_dir;
dir_changed = true;
}
window->clear();
for (int x=0; x<EFFECT_DVD_WIDTH; x++) for (int y=0; y<EFFECT_DVD_HEIGHT; y++) {
window->setPixel(_x + x, _y + y, (CRGB*)&_color);
}
if (dir_changed) _color = (CRGB)CHSV(random8(), 255, 255);
for (int x=0; x<settings.effects.dvd.width; x++) for (int y=0; y<settings.effects.dvd.height; y++) {
window->setSubPixel(_x + (x<<8), _y + (y<<8), (CRGB*)&_color);
}
for (int x=1; x<settings.effects.dvd.width; x++) for (int y=1; y<settings.effects.dvd.height; y++) {
window->setPixel((_x>>8) + x, (_y>>8) + y, (CRGB*)&_color);
}
}
bool DvdEffect::can_be_shown_with_clock() { return true; }

View File

@ -10,8 +10,8 @@ void SingleDynamicEffect::init() {
for (int i=0; i<tile_count; i++) tiles[i] = CHSV(baseHue + random8(64), 180, 255);
}
void SingleDynamicEffect::loop() {
EVERY_N_MILLISECONDS( EFFECT_SINGLE_DYNAMIC_LOOP_TIME ) {
void SingleDynamicEffect::loop(uint16_t ms) {
EVERY_N_MILLISECONDS( settings.effects.dynamic.single_loop_time ) {
tiles[random8(tile_count)] = CHSV(baseHue + random8(64), 180, 255);
}
this->draw();
@ -28,8 +28,8 @@ boolean SingleDynamicEffect::can_be_shown_with_clock() {
return true;
}
void MultiDynamicEffect::loop() {
EVERY_N_MILLISECONDS( EFFECT_MULTI_DYNAMIC_LOOP_TIME ) {
void MultiDynamicEffect::loop(uint16_t ms) {
EVERY_N_MILLISECONDS( settings.effects.dynamic.multi_loop_time ) {
for (int i=0; i<tile_count; i++) tiles[i] = CHSV(baseHue + random8(64), 180, 255);
}
this->draw();
@ -39,24 +39,16 @@ BigDynamicEffect::~BigDynamicEffect() {
delete window;
}
void BigDynamicEffect::loop() {
EVERY_N_MILLISECONDS( EFFECT_BIG_DYNAMIC_LOOP_TIME ) {
uint8_t x = random8(0, window->width - EFFECT_BIG_DYNAMIC_SIZE + 1);
uint8_t y = random8(0, window->height - EFFECT_BIG_DYNAMIC_SIZE + 1);
void BigDynamicEffect::loop(uint16_t ms) {
EVERY_N_MILLISECONDS( settings.effects.dynamic.big_loop_time ) {
uint8_t x = random8(0, window->width - settings.effects.dynamic.big_size + 1);
uint8_t y = random8(0, window->height - settings.effects.dynamic.big_size + 1);
CRGB color = CHSV(random8(), 255, 255);
CRGB black(0x000000);
for (uint8_t ix=0; ix<EFFECT_BIG_DYNAMIC_SIZE; ix++) for (uint8_t iy=0; iy<EFFECT_BIG_DYNAMIC_SIZE; iy++) {
for (uint8_t ix=0; ix<settings.effects.dynamic.big_size; ix++) for (uint8_t iy=0; iy<settings.effects.dynamic.big_size; iy++) {
window->setPixel(x+ix, y+iy, &color);
}
/*for (uint8_t ix=0; ix<EFFECT_BIG_DYNAMIC_SIZE+2; ix++) {
window->setPixel(x-1+ix, y-1, &black);
window->setPixel(x-1+ix, y+EFFECT_BIG_DYNAMIC_SIZE+1, &black);
}
for (uint8_t iy=0; iy<EFFECT_BIG_DYNAMIC_SIZE+2; iy++) {
window->setPixel(x-1, y-1+iy, &black);
window->setPixel(x+EFFECT_BIG_DYNAMIC_SIZE+1, y-1+iy, &black);
}*/
}
}

View File

@ -14,7 +14,7 @@ FireEffect::~FireEffect() {
delete [] this->data;
}
void FireEffect::loop() {
void FireEffect::loop(uint16_t ms) {
cooldown();
spark();
propagate();
@ -22,11 +22,11 @@ void FireEffect::loop() {
}
void FireEffect::cooldown() {
for(int i=0; i<(this->window->width * this->window->height); i++) this->data[i] = scale8(this->data[i], EFFECT_FIRE_COOLDOWN); // 240 or something
for(int i=0; i<(this->window->width * this->window->height); i++) this->data[i] = scale8(this->data[i], settings.effects.fire.cooldown);
}
void FireEffect::spark() {
for(int x=0; x<this->window->width; x++) if (random8(EFFECT_FIRE_SPARK_CHANCE)==0) this->data[x] = this->spark_temp();
for(int x=0; x<this->window->width; x++) if (random8(settings.effects.fire.spark_chance)==0) this->data[x] = this->spark_temp();
}
inline uint8_t FireEffect::spark_temp() {

View File

@ -49,20 +49,20 @@ void FireworkEffectDot::draw() {
dim8_video( scale8( scale8( _color.g, ye), xe)),
dim8_video( scale8( scale8( _color.b, ye), xe)));
_window->addPixelColor(ix, iy, &c00);
_window->addPixelColor(ix, iy+1, &c01);
_window->addPixelColor(ix, iy-1, &c01);
_window->addPixelColor(ix+1, iy, &c10);
_window->addPixelColor(ix+1, iy+1, &c11);
_window->addPixelColor(ix+1, iy-1, &c11);
}
void FireworkEffectDot::move() {
if (!show) return;
_yv -= EFFECT_FIREWORK_GRAVITY;
_xv = _scale15by8_local(_xv, EFFECT_FIREWORK_DRAG);
_yv = _scale15by8_local(_yv, EFFECT_FIREWORK_DRAG);
_yv -= settings.effects.firework.gravity;
_xv = _scale15by8_local(_xv, settings.effects.firework.drag);
_yv = _scale15by8_local(_yv, settings.effects.firework.drag);
if (type == FIREWORK_DOT_SPARK) {
_xv = _scale15by8_local(_xv, EFFECT_FIREWORK_DRAG);
_yv = _scale15by8_local(_yv, EFFECT_FIREWORK_DRAG);
_xv = _scale15by8_local(_xv, settings.effects.firework.drag);
_yv = _scale15by8_local(_yv, settings.effects.firework.drag);
_color.nscale8(255);
if (!_color) {
show = 0;
@ -70,12 +70,12 @@ void FireworkEffectDot::move() {
}
// Bounce if we hit the ground
if (_xv < 0 && _y - _window->height < (-_yv)) {
if (_yv < 0 && _y - _window->height < (-_yv)) {
if (type == FIREWORK_DOT_SPARK) {
show = 0;
} else {
_yv = -_yv;
_yv = _scale15by8_local(_yv, EFFECT_FIREWORK_BOUNCE);
_yv = _scale15by8_local(_yv, settings.effects.firework.bounce);
if (_yv < 500) {
show = 0;
}
@ -139,11 +139,11 @@ boolean FireworkEffect::can_be_shown_with_clock() {
return true;
}
void FireworkEffect::loop() {
void FireworkEffect::loop(uint16_t ms) {
window->clear();
_dot->move();
_dot->draw();
for (int i=0; i<EFFECT_FIREWORK_SPARKS; i++) {
for (int i=0; i<settings.effects.firework.sparks; i++) {
_sparks[i]->move();
_sparks[i]->draw();
}
@ -159,7 +159,7 @@ void FireworkEffect::loop() {
}
if (_skyburst) {
int nsparks = random8(EFFECT_FIREWORK_SPARKS / 2, EFFECT_FIREWORK_SPARKS + 1);
int nsparks = random8(settings.effects.firework.sparks / 2, settings.effects.firework.sparks + 1);
for (int i=0; i<nsparks; i++) {
_sparks[i]->sky_burst(_burst_x, _burst_y, _burst_yv, _burst_color);
_skyburst = 0;
@ -169,14 +169,15 @@ void FireworkEffect::loop() {
FireworkEffect::FireworkEffect() {
_dot = new FireworkEffectDot(window, this);
for (int i=0; i<EFFECT_FIREWORK_SPARKS; i++) {
_sparks = new FireworkEffectDot*[settings.effects.firework.sparks];
for (int i=0; i<settings.effects.firework.sparks; i++) {
_sparks[i] = new FireworkEffectDot(window, this);
}
}
FireworkEffect::~FireworkEffect() {
delete window;
for (int i=0; i<EFFECT_FIREWORK_SPARKS; i++) {
for (int i=0; i<settings.effects.firework.sparks; i++) {
delete _sparks[i];
}
delete _dot;

View File

@ -16,7 +16,7 @@ bool GolEffect::can_be_shown_with_clock() { return true; }
void GolEffect::_initialize() {
for(uint16_t i=0; i<this->window->count; i++) {
_data[i] = random8() < EFFECT_GOL_START_PERCENTAGE ? 1 : 0;
_data[i] = random8() < settings.effects.gol.start_percentage ? 1 : 0;
}
_old_hue = _hue;
_hue = random8();
@ -29,12 +29,12 @@ GolEffect::~GolEffect() {
delete window;
}
void GolEffect::loop() {
if (EFFECT_GOL_BLEND_SPEED + _blend > 255) {
void GolEffect::loop(uint16_t ms) {
if (settings.effects.gol.blend_speed + _blend > 255) {
_blend = 0;
_advance();
} else {
_blend += EFFECT_GOL_BLEND_SPEED;
_blend += settings.effects.gol.blend_speed;
}
_draw();
@ -43,7 +43,7 @@ void GolEffect::loop() {
void GolEffect::_advance() {
_step++;
_old_hue = _hue;
if (_step >= EFFECT_GOL_RESTART_AFTER_STEPS) {
if (_step >= settings.effects.gol.restart_after_steps) {
_initialize();
} else {
for(uint16_t i=0; i<this->window->count; i++) {

View File

@ -5,8 +5,7 @@ boolean MarqueeEffect::can_be_shown_with_clock() {
return true;
}
void MarqueeEffect::loop() {
static int loop_counter = 0;
void MarqueeEffect::loop(uint16_t ms) {
window->clear();
CRGB color = CHSV(0, 255, 255);
uint16_t width = _text.length() * 6;

View File

@ -38,27 +38,27 @@ void MatrixEffectColumn::restart(bool completely_random) {
}
}
length = random8(EFFECT_MATRIX_LENGTH_MIN, EFFECT_MATRIX_LENGTH_MAX);
length = random8(settings.effects.matrix.length_min, settings.effects.matrix.length_max);
running = true;
speed = random8(EFFECT_MATRIX_SPEED_MIN, EFFECT_MATRIX_SPEED_MAX);
speed = random8(settings.effects.matrix.speed_min, settings.effects.matrix.speed_max);
}
void MatrixEffectColumn::advance() {
void MatrixEffectColumn::advance(uint16_t ms) {
switch(_direction) {
case DIR_NORTH:
y-=speed;
y-=speed * ms;
if ((y>>8) > window->height && (y>>8) + length > window->height) running=false;
break;
case DIR_EAST:
x+=speed;
x+=speed * ms;
if ((x>>8) - length > window->width) running=false;
break;
case DIR_SOUTH:
y+=speed;
y+=speed * ms;
if ((y>>8) - length > window->height) running=false;
break;
case DIR_WEST:
x-=speed;
x-=speed * ms;
if ((x>>8) > window->width && (y>>8) + length > window->width) running=false;
break;
}
@ -79,14 +79,14 @@ void MatrixEffectColumn::draw() {
}
}
void MatrixEffectColumn::loop() {
void MatrixEffectColumn::loop(uint16_t ms) {
if (!running) {
if (random8() < 20) {
// Start the column again.
restart(false);
}
} else {
advance();
advance(ms);
draw();
}
}
@ -144,13 +144,19 @@ MatrixEffect::MatrixEffect() {
}
RandomMatrixEffect::RandomMatrixEffect() {
_columns = new MatrixEffectColumn* [window->width];
for (int i=0; i<window->width; i++) _columns[i] = new RandomMatrixEffectColumn(window, random8(4), true);
// No need to initialize _columns, because that will have been done by ctor of MatrixEffect.
for (int i=0; i<window->width; i++) {
delete _columns[i];
_columns[i] = new RandomMatrixEffectColumn(window, random8(4), true);
}
}
RainbowMatrixEffect::RainbowMatrixEffect() {
_columns = new MatrixEffectColumn* [window->width];
for (int i=0; i<window->width; i++) _columns[i] = new RainbowMatrixEffectColumn(window, MatrixEffectColumn::DIR_SOUTH);
// No need to initialize _columns, because that will have been done by ctor of MatrixEffect.
for (int i=0; i<window->width; i++) {
delete _columns[i];
_columns[i] = new RainbowMatrixEffectColumn(window, MatrixEffectColumn::DIR_SOUTH);
}
}
MatrixEffect::~MatrixEffect() {
@ -160,7 +166,7 @@ MatrixEffect::~MatrixEffect() {
delete[] _columns;
}
void MatrixEffect::loop() {
void MatrixEffect::loop(uint16_t ms) {
window->clear();
for (int i=0; i<window->width; i++) _columns[i]->loop();
for (int i=0; i<window->width; i++) _columns[i]->loop(ms);
}

View File

@ -13,7 +13,7 @@ PixelClockEffect::~PixelClockEffect() {
delete window;
}
void PixelClockEffect::loop() {
void PixelClockEffect::loop(uint16_t ms) {
uint8_t x, y; // Temporary variables for calculating positions
window->clear();
// Seconds

View File

@ -5,7 +5,7 @@
boolean Sinematrix3Effect::can_be_shown_with_clock() { return true; };
boolean Sinematrix3Effect::clock_as_mask() { return true; };
void Sinematrix3Effect::loop() {
void Sinematrix3Effect::loop(uint16_t ms) {
pangle = addmodpi( pangle, 0.0133 + (angle / 256) );
angle = cos(pangle) * PI;
sx = addmodpi( sx, 0.00673 );

View File

@ -2,47 +2,56 @@
SinesEffectSinus::SinesEffectSinus(Window* w) {
_window = w;
_frequency = random8(40)+8;
_amplitude = random(5)+2;
_x = random8(_window->width);
_step = 0;
_color = CHSV(random8(), 255, 255);
_frequency = random16(6<<8, 30<<8);
_color_frequency = random16(128, 2<<8);
_x = random16(1<<8, (_window->width-2)<<8);
accum88 diff = (_window->width<<8) - _x;
if (_x > diff) diff=_x;
_amplitude = random16(1<<8, diff);
_offset = random16();
}
void SinesEffectSinus::loop() {
_value += _frequency;
if ((_value == 0 || _value==128) && random8(16)==0) {
int8_t sign = _value == 0 ? -1 : +1;
if (_x > 200) sign = -1;
else if (_x >= _window->width) sign = 1;
_amplitude = random(3)+2;
_frequency = random8(40)+8;
_color = CHSV(random8(), 255, 255);
_x = _x - sign*_amplitude;
}
uint8_t x = _x + ((sin8(_value) - 128) * _amplitude / 128);
_window->setPixel(x, 0, &_color);
void SinesEffectSinus::loop(uint16_t ms) {
accum88 x = beatsin88(_frequency, _x-_amplitude, _x+_amplitude, _offset);
CRGB color = CHSV(beat88(_color_frequency, _offset)>>8, 255, 255);
_window->setSubPixel(x, 0, &color);
}
SinesEffect::SinesEffect() {
for (int i=0; i<EFFECT_SINES_COUNT; i++) {
_init();
}
void SinesEffect::_init() {
_count = settings.effects.sines.count;
_sinus = new SinesEffectSinus*[_count];
for (int i=0; i<_count; i++) {
_sinus[i] = new SinesEffectSinus(window);
}
}
SinesEffect::~SinesEffect() {
for (int i=0; i<EFFECT_SINES_COUNT; i++) { delete _sinus[i]; }
_delete();
}
void SinesEffect::_delete() {
for (int i=0; i<_count; i++) {
delete _sinus[i];
}
delete [] _sinus;
}
boolean SinesEffect::can_be_shown_with_clock() {
return true;
}
void SinesEffect::loop() {
void SinesEffect::loop(uint16_t ms) {
if (settings.effects.sines.count != _count) {
_delete();
_init();
}
// do stuff
if (_step++ % 4) return; // Skip 3 out of 4 steps.
window->shift_down_and_blur();
for (int i=0; i<EFFECT_SINES_COUNT; i++) {
_sinus[i]->loop();
for (int i=0; i<_count; i++) {
_sinus[i]->loop(ms);
}
}

View File

@ -10,9 +10,9 @@ SnakeEffect::~SnakeEffect() {
delete window;
}
void SnakeEffect::loop() {
if (run++ % EFFECT_SNAKE_SLOWDOWN == 0) { // Change the coordinates only on every n-th run.
if (random8(EFFECT_SNAKE_DIRECTION_CHANGE)==0 || is_turn_needed()) turn_random();
void SnakeEffect::loop(uint16_t ms) {
if (run++ % settings.effects.snake.slowdown == 0) { // Change the coordinates only on every n-th run.
if (random8(settings.effects.snake.direction_change)==0 || is_turn_needed()) turn_random();
this->coords = update_position(this->coords, this->direction);
}

View File

@ -6,7 +6,7 @@ StaticEffect::StaticEffect(CRGB col) {
color = col;
}
void StaticEffect::loop() {
void StaticEffect::loop(uint16_t ms) {
EVERY_N_SECONDS(1) {
window->clear(&color);
}

View File

@ -4,7 +4,7 @@
boolean TwirlEffect::can_be_shown_with_clock() { return true; };
boolean TwirlEffect::clock_as_mask() { return true; };
void TwirlEffect::loop() {
void TwirlEffect::loop(uint16_t ms) {
double center_x = _real_center_x; // - (cos8(_center_offset_angle)>>6);
double center_y = _real_center_y; // + (sin8(_center_offset_angle)>>6);
for (int x=0; x<window->width; x++) for (int y=0; y<window->height; y++) {

View File

@ -1,6 +1,6 @@
#include "effects.h"
#include "config.h"
#include "my_fastled.h"
#include <ErriezCRC32.h>
#include "effect_bell.h"
#include "effect_sinematrix3.h"
#include "effect_big_clock.h"
@ -27,41 +27,50 @@ Effect* current_effect;
ClockEffect effect_clock;
Effect* select_effect(uint32_t code) {
switch (code) {
// use e.g. https://crccalc.com/ for the conversion of name to crc.
case 0: case 0xD682E3C8 /* sinematrix3 */ : return new Sinematrix3Effect();
case 1: case 0x90A887DA /* big_clock */ : return new BigClockEffect();
case 2: case 0xBE7BBE92 /* clock */ : return new ClockEffect();
case 3: case 0x733BE087 /* bell */ : return new BellEffect(); //(new AnimationEffect("/bell.pia", 0x000000, 0, 0))->setFgColor(0xFFFF00);
case 4: case 0x2BBC5D43 /* off */ : return new StaticEffect(0x000000);
case 5: case 0x1D84F231 /* koopa */ : return new AnimationEffect("/koopa.pia", CRGB(0x000000), 0, 0);
case 6: case 0xAC43BCF1 /* couple_rain */ : return new AnimationEffect("/couple_rain.pia", CRGB(0x000000), -8, -16);
case 7: case 0xF1B117F7 /* single_dynamic */ : return new SingleDynamicEffect();
case 8: case 0xF52F2804 /* multi_dynamic */ : return new MultiDynamicEffect();
case 9: case 0xF83341CF /* matrix */ : return new MatrixEffect();
case 10: case 0xD2B79DD0 /* rainbow_matrix */ : return new RainbowMatrixEffect();
case 11: case 0xE8DD3433 /* random_matrix */ : return new RandomMatrixEffect();
case 12: case 0xB086D193 /* cycle */ : return new CycleEffect();
case 13: case 0x2293EF9F /* twirl */ : return new TwirlEffect();
case 14: case 0x60ECC3E6 /* heart */ : return new AnimationEffect("/heart.pia", CRGB(0x000000), 0, 0);
case 15: case 0x42090A49 /* confetti */ : return new ConfettiEffect();
case 16: case 0x516D6B9E /* snake */ : return new SnakeEffect();
case 17: case 0x58DE09CF /* fire */ : return new FireEffect();
case 18: case 0x08BA9C08 /* firework */ : return new FireworkEffect();
case 19: case 0x14B85EAC /* gol */ : return new GolEffect();
case 20: case 0xFA13015D /* cake */ : return new AnimationEffect("/cake.pia", CRGB(0x000000), 0, 0);
case 21: case 0xA2B0D68B /* pixel_clock */ : return new PixelClockEffect();
case 22: case 0x2C0E6962 /* big_dynamic */ : return new BigDynamicEffect();
case 23: case 0xDA6F31A5 /* random_confetti */ : return new RandomConfettiEffect();
case 24: case 0x8325C1DF /* dvd */ : return new DvdEffect();
case 25: case 0x8CA97519 /* analog_clock */ : return new AnalogClockEffect();
case 26: case 0xADB18CC5 /* sines */ : return new SinesEffect();
case 27: case 0x0407881E /* blur2d */ : return new Blur2DEffect();
case 28: case 0x935CFA7C /* marquee */ : return new MarqueeEffect();
case 29: case 0xE27D739E /* night_clock */ : return new NightClockEffect();
default : return NULL;
};
const EffectEntry effects[] = {
/* 0 */ {"sinematrix3", true, [](){ return new Sinematrix3Effect(); }},
/* 1 */ {"big_clock", true, [](){ return new BigClockEffect(); }},
/* 2 */ {"clock", false, [](){ return new ClockEffect(); }},
/* 3 */ {"bell", false, [](){ return new BellEffect(); }},
/* 4 */ {"off", false, [](){ return new StaticEffect(0x000000); }},
/* 5 */ {"single_dynamic", true, [](){ return new SingleDynamicEffect(); }},
/* 6 */ {"multi_dynamic", true, [](){ return new MultiDynamicEffect(); }},
/* 7 */ {"big_dynamic", true, [](){ return new BigDynamicEffect(); }},
/* 8 */ {"matrix", true, [](){ return new MatrixEffect(); }},
/* 9 */ {"random_matrix", true, [](){ return new RandomMatrixEffect(); }},
/* 10 */ {"rainbow_matrix", true, [](){ return new RainbowMatrixEffect(); }},
/* 11 */ {"cycle", false, [](){ return new CycleEffect(); }},
/* 12 */ {"twirl", true, [](){ return new TwirlEffect(); }},
/* 13 */ {"confetti", true, [](){ return new ConfettiEffect(); }},
/* 14 */ {"random_confetti", true, [](){ return new RandomConfettiEffect(); }},
/* 15 */ {"snake", true, [](){ return new SnakeEffect(); }},
/* 16 */ {"firework", true, [](){ return new FireworkEffect(); }},
/* 17 */ {"gol", true, [](){ return new GolEffect(); }},
/* 18 */ {"pixel_clock", false, [](){ return new PixelClockEffect(); }},
/* 19 */ {"dvd", false, [](){ return new DvdEffect(); }},
/* 20 */ {"analog_clock", false, [](){ return new AnalogClockEffect(); }},
/* 21 */ {"sines", true, [](){ return new SinesEffect(); }},
/* 22 */ {"blur2d", true, [](){ return new Blur2DEffect(); }},
/* 23 */ {"marquee", 0, [](){ return new MarqueeEffect(); }},
/* 24 */ {"night_clock", false, [](){ return new NightClockEffect(); }}
};
const uint8_t effects_size = 25;
Effect* select_effect(const char* name) {
for(int i=0; i<effects_size; i++) {
if (strcmp(effects[i].name, name)==0) {
return effects[i].create();
}
}
return NULL;
}
Effect* select_effect(uint8_t id) {
if (id < effects_size) {
return effects[id].create();
}
return NULL;
}
bool change_current_effect(String payload) {
@ -74,7 +83,7 @@ bool change_current_effect(String payload) {
LOGln("Effects * Cleaned effect name: %s", payload.c_str());
}
Effect* new_effect = select_effect( crc32String(payload.c_str()) );
Effect* new_effect = select_effect( payload.c_str() );
if (new_effect == NULL) {
LOGln("Effects * Could not find effect with name %s", payload.c_str());
return false;
@ -101,21 +110,6 @@ bool change_current_effect(String payload) {
return true;
}
const char* cycle_effects[] = {
"sinematrix3",
"single_dynamic", "multi_dynamic", "big_dynamic",
"matrix", "rainbow_matrix", "random_matrix",
"confetti", "random_confetti",
"snake",
"gol",
"twirl",
"sines",
"blur2d",
"firework",
"big_clock",
"dvd"};
uint8_t cycle_effects_count = 17;
void setup_effects() {
current_effect = new CycleEffect();
}

View File

@ -42,7 +42,7 @@ void http_server_setup() {
PGM_P text_plain = PSTR("text/plain");
http_server.on("/", HTTP_GET, [&](){
LOGln("HTTP * GET /");
String message = "<html><head><title>Pitrix</title></head><body><h1>Pitrix</h1><p>Known animations:</p>";
String message = "<html><head><title>Pitrix</title></head><body><h1>Pitrix</h1><a href='/settings'>Settings</a><p>Known animations:</p>";
if (!SPIFFS.begin()) {
message += "<strong>No SPIFFS file system found.</strong>";
} else {
@ -57,6 +57,32 @@ void http_server_setup() {
message += "</body></html>";
http_server.send(200, "text/html", message);
});
http_server.on("/settings", HTTP_GET, [&]() {
String message = "<html><head><title>Pitrix settings</title></head><body><h1>Pitrix settings</h1><a href='/'>Back to main page</a><table>";
for (int i=0; i<all_settings_size; i++) {
message += "<tr><td>";
message += all_settings[i].name;
message += "</td><td>";
message += *all_settings[i].value;
message += "</td></tr>";
}
message += "</table></body></html>";
http_server.send(200, "text/html", message);
});
http_server.on("/settings", HTTP_POST, [&]() {
if (!http_server.hasArg("key") || !http_server.hasArg("value")) {
http_server.send(400, "text/plain", "Missing argument.");
return;
}
String name = http_server.arg("key");
uint16_t value = http_server.arg("value").toInt();
if (change_setting(name.c_str(), value)) {
http_server.send(200, "text/plain", "OK");
} else {
http_server.send(400, "text/plain", "Could not change setting.");
}
});
http_server.on("/delete", HTTP_GET, [&]() {
LOGln("HTTP * GET /delete");
if (http_server.args()==0) {

View File

@ -71,10 +71,15 @@ void mqtt_callback(char* original_topic, byte* pl, unsigned int length) {
tests::run();
return;
}
long value = payload.toInt();
LOGln("MQTT * Payload as number: %d", value);
if (topic.compareTo("brightness")==0) {
if (topic.startsWith("settings.")) {
topic.remove(0, 9);
change_setting(topic.c_str(), value);
return;
} else if (topic.compareTo("brightness")==0) {
if (value > 0 && value <= 255) {
LOGln("MQTT * Changing brightness...");
FastLED.setBrightness(value);

View File

@ -16,6 +16,7 @@ int loop_timeouts = 0;
long loop_started_at = 0;
uint8_t baseHue = 0; // defined as extern in prototypes.h
char hostname[30]; // defined as extern in prototypes.h
unsigned long _last_effect_loop_finished_at = 0;
#ifdef RECORDER_ENABLE
Recorder* recorder;
#endif
@ -87,9 +88,20 @@ void loop() {
}
EVERY_N_MILLISECONDS(1000 / FPS) {
//LOGln("Core * loop running");
current_effect->loop();
//LOGln("Core * loop ran");
// Calculate the delay since the last time loop() was called.
// This way, the effect can handle varying frame rates.
uint16_t last_loop_ago;
unsigned long now = millis();
if (now > _last_effect_loop_finished_at && _last_effect_loop_finished_at) {
last_loop_ago = now - _last_effect_loop_finished_at;
} else {
last_loop_ago = 0;
}
current_effect->loop(last_loop_ago);
// Save the time for the next run.
_last_effect_loop_finished_at = now;
if (current_effect->can_be_shown_with_clock()) {
effect_clock.loop_with_invert(current_effect->clock_as_mask());

View File

@ -36,7 +36,7 @@ Recorder::Recorder() {
LOGln("Recorder * Sending data to port %d", _client_port);
}
} else if (*(char*)data == 'E') {
String effect = String(((char*)(data+1)));
String effect = String((char*)data+1);
LOGln("Recorder * Setting effect %s", effect.c_str());
Window::getFullWindow()->clear();
change_current_effect(effect);

74
src/settings.cpp Normal file
View File

@ -0,0 +1,74 @@
#include "settings.h"
#include "config.h"
Settings settings;
Setting all_settings[] = {
{"fps", &settings.fps, TYPE_UINT8},
{"effects.confetti.pixels_per_loop", &settings.effects.confetti.pixels_per_loop, TYPE_UINT8},
{"effects.cycle.random", &settings.effects.cycle.random, TYPE_BOOL},
{"effects.cycle.time", &settings.effects.cycle.time, TYPE_UINT16},
{"effects.dvd.width", &settings.effects.dvd.width, TYPE_UINT8},
{"effects.dvd.height", &settings.effects.dvd.height, TYPE_UINT8},
{"effects.dvd.speed", &settings.effects.dvd.speed, TYPE_UINT8},
{"effects.dynamic.single_loop_time", &settings.effects.dynamic.single_loop_time, TYPE_UINT16},
{"effects.dynamic.multi_loop_time", &settings.effects.dynamic.multi_loop_time, TYPE_UINT16},
{"effects.dynamic.big_loop_time", &settings.effects.dynamic.big_loop_time, TYPE_UINT16},
{"effects.dynamic.big_size", &settings.effects.dynamic.big_size, TYPE_UINT8},
{"effects.fire.cooldown", &settings.effects.fire.cooldown, TYPE_UINT8},
{"effects.fire.spark_chance", &settings.effects.fire.spark_chance, TYPE_UINT8},
{"effects.firework.drag", &settings.effects.firework.drag, TYPE_UINT8},
{"effects.firework.bounce", &settings.effects.firework.bounce, TYPE_UINT8},
{"effects.firework.gravity", &settings.effects.firework.gravity, TYPE_UINT8},
{"effects.firework.sparks", &settings.effects.firework.sparks, TYPE_UINT8},
{"effects.gol.start_percentage", &settings.effects.gol.start_percentage, TYPE_UINT8},
{"effects.gol.blend_speed", &settings.effects.gol.blend_speed, TYPE_UINT8},
{"effects.gol.restart_after_steps", &settings.effects.gol.restart_after_steps, TYPE_UINT8},
{"effects.matrix.length_min", &settings.effects.matrix.length_min, TYPE_UINT8},
{"effects.matrix.length_max", &settings.effects.matrix.length_max, TYPE_UINT8},
{"effects.matrix.speed_min", &settings.effects.matrix.speed_min, TYPE_UINT8},
{"effects.matrix.speed_max", &settings.effects.matrix.speed_max, TYPE_UINT8},
{"effects.sines.count", &settings.effects.sines.count, TYPE_UINT8},
{"effects.snake.direction_change", &settings.effects.snake.direction_change, TYPE_UINT8}
};
const uint8_t all_settings_size = 25;
bool change_setting(const char* key, uint16_t new_value) {
LOGln("Settings * Trying to set setting %s to new value %d...", key, new_value);
Setting* s = NULL;
for (uint8_t i=0; i<all_settings_size; i++) {
if (strcmp(key, all_settings[i].name)==0) {
s = &all_settings[i];
break;
}
}
if (s==NULL) {
LOGln("Settings * No setting matching the name %s found.", key);
return false;
}
// Check data size
if (s->type == TYPE_BOOL && new_value > 1) {
LOGln("Settings * Data type of %s is boolean, but new value is > 1.", key);
return false;
}
if (s->type == TYPE_UINT8 && new_value>0xFF) {
LOGln("Settings * Data type of %s is uint8_t, but new value is > 0xFF.", key);
return false;
}
*(s->value) = new_value;
LOGln("Settings * Success. New value for %s is %d.", key, new_value);
return true;
}

View File

@ -11,15 +11,18 @@ namespace tests {
int i=0;
Effect* effect;
int32_t diffs[3] = {0, 0, 0};
String effect_name;
while (1) {
for (int j=0; j<3; j++) {
int free_at_start = ESP.getFreeHeap();
effect = select_effect(i);
effect->loop(1);
if (effect == NULL) return;
effect_name = effect->get_name();
delete effect;
diffs[i] = ESP.getFreeHeap() - free_at_start;
}
LOGln("Tests * Memory leakage of effect #%d: %d, %d, %d", i, diffs[0], diffs[1], diffs[2]);
LOGln("Tests * Memory leakage of effect %s: %d, %d, %d", effect_name.c_str(), diffs[0], diffs[1], diffs[2]);
i++;
}
}