Compare commits
80 Commits
205a0df842
...
master
Author | SHA1 | Date | |
---|---|---|---|
d76e088c37 | |||
c9825c8f9b | |||
ae997ef802 | |||
a6af2829ee | |||
0a4a62d7c8 | |||
7747e38253 | |||
b9cfc6568b | |||
1d66f9c541 | |||
f9e6a5ebd6 | |||
a96f6c79e3 | |||
ecf5998510 | |||
0ac4f9b181 | |||
fd44b217a7 | |||
209140cfb7 | |||
bcb5fdc9be | |||
b5bb0feccf | |||
0863380648 | |||
075823220a | |||
caa86551a0 | |||
10be8ef7cc | |||
9de77349e8 | |||
599bcd87bc | |||
70ddba2cbc | |||
3f09d9adbf | |||
cfb25d6030 | |||
4762a852d8 | |||
8e2d2225cb | |||
65dd09ca0d | |||
f014fd7cae | |||
1707084299 | |||
402d7f5d75 | |||
521e5f735d | |||
0da161ccd1 | |||
f8b6f5cc02 | |||
c6b2a8a1d0 | |||
6ba916282b | |||
79c13e760f | |||
47812de405 | |||
d28dca0a4d | |||
e2a56d7c29 | |||
439e2de17f | |||
994f4894dd | |||
b5343b59e5 | |||
66c0124072 | |||
2a6f68cc45 | |||
f5d47fe7da | |||
029c93166d | |||
141210a370 | |||
a902addf94 | |||
b644006036 | |||
dfe99408c9 | |||
3c0e4af325 | |||
aa72196a07 | |||
f76354a4d5 | |||
01c364795f | |||
38aa654c54 | |||
1418d519d5 | |||
efe9b924ec | |||
7b8dabee43 | |||
783cfdae3f | |||
54925dfc0e | |||
306f72d838 | |||
230a1f1a91 | |||
bd0041619a | |||
a3caaa1fef | |||
ec379c009e | |||
8568436fc1 | |||
6eeb7488b1 | |||
efa9a73cae | |||
bbdb46c04d | |||
4c611da6d1 | |||
377ccc477f | |||
efebe9adb4 | |||
359b7a7826 | |||
b5c1f350d2 | |||
5eba691429 | |||
d8fe055e3d | |||
6b4f75b8bc | |||
416ab46f9b | |||
b4aa711940 |
2
.gitignore
vendored
2
.gitignore
vendored
@ -9,3 +9,5 @@ include/config.h
|
|||||||
.piolibdeps
|
.piolibdeps
|
||||||
.pioenvs
|
.pioenvs
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
.vscode
|
||||||
|
src/tools/snakenet/data_set.*
|
||||||
|
BIN
data/child.pia
Normal file
BIN
data/child.pia
Normal file
Binary file not shown.
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
class Effect {
|
class Effect {
|
||||||
protected:
|
protected:
|
||||||
Window* window = Window::getFullWindow(); // Use a full screen window per default.
|
Window* window = &Window::window_full; // Use a full screen window per default.
|
||||||
public:
|
public:
|
||||||
virtual ~Effect() {};
|
virtual ~Effect() {};
|
||||||
virtual void loop(uint16_t ms) = 0;
|
virtual void loop(uint16_t ms) = 0;
|
||||||
|
32
include/SimpleEffect.h
Normal file
32
include/SimpleEffect.h
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "Effect.h"
|
||||||
|
#include "prototypes.h"
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
#define SE_CYCLE_COLORS 1 // Slowly cycle through the rainbow.
|
||||||
|
#define SE_RANDOM_PIXEL_COLORS 2 // Every pixel gets a random color every frame.
|
||||||
|
#define SE_ONLY_POSITIVE 4 // Only use colors, not white. This is equivalent to running your output through abs()
|
||||||
|
#define SE_FADEOUT 8 // Fades the old image out. Returning 0 doesn't change the pixel's value.
|
||||||
|
#define SE_RANDOM_STATIC_COLOR 16 // Sets a random static color at start of the effect.
|
||||||
|
#define SE_DEBUG 32 // Prints debug messages.
|
||||||
|
|
||||||
|
class SimpleEffect : public Effect {
|
||||||
|
protected:
|
||||||
|
Window* window = &Window::window_full; // Use a full screen window per default.
|
||||||
|
uint8_t _color = 0;
|
||||||
|
uint16_t _flags;
|
||||||
|
String _name;
|
||||||
|
simple_effect_t _method;
|
||||||
|
public:
|
||||||
|
SimpleEffect(String name, uint16_t flags, simple_effect_t method): _name { name }, _method { method } {
|
||||||
|
_flags = flags;
|
||||||
|
if (_flags & SE_RANDOM_STATIC_COLOR) {
|
||||||
|
_color = random8();
|
||||||
|
_flags &= ~SE_CYCLE_COLORS & ~SE_RANDOM_PIXEL_COLORS;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
void loop(uint16_t ms) override;
|
||||||
|
String get_name() { return _name; };
|
||||||
|
boolean can_be_shown_with_clock() { return true; }
|
||||||
|
|
||||||
|
};
|
@ -14,10 +14,13 @@ private:
|
|||||||
void _circle_point(int x0, int y0, int x1, int y1, CRGB* color);
|
void _circle_point(int x0, int y0, int x1, int y1, CRGB* color);
|
||||||
void _subpixel_render(uint8_t x, uint8_t y, CRGB* color, SubpixelRenderingMode m);
|
void _subpixel_render(uint8_t x, uint8_t y, CRGB* color, SubpixelRenderingMode m);
|
||||||
public:
|
public:
|
||||||
|
static Window window_full;
|
||||||
|
static Window window_with_clock;
|
||||||
|
static Window window_clock;
|
||||||
|
|
||||||
const uint8_t x, y;
|
const uint8_t x, y;
|
||||||
const uint8_t width, height;
|
const uint8_t width, height;
|
||||||
uint16_t count;
|
uint16_t count;
|
||||||
static Window* getFullWindow();
|
|
||||||
|
|
||||||
Window(): Window(0, 0, LED_WIDTH, LED_HEIGHT) {};
|
Window(): Window(0, 0, LED_WIDTH, LED_HEIGHT) {};
|
||||||
Window(uint8_t x, uint8_t y) : Window(x, y, LED_WIDTH-x, LED_HEIGHT-y) {};
|
Window(uint8_t x, uint8_t y) : Window(x, y, LED_WIDTH-x, LED_HEIGHT-y) {};
|
||||||
@ -29,9 +32,9 @@ public:
|
|||||||
void setSubPixel(accum88 x, accum88 y, CRGB* color, SubpixelRenderingMode m = SUBPIXEL_RENDERING_ADD);
|
void setSubPixel(accum88 x, accum88 y, CRGB* color, SubpixelRenderingMode m = SUBPIXEL_RENDERING_ADD);
|
||||||
void setPixelByIndex(uint16_t index, CRGB* color);
|
void setPixelByIndex(uint16_t index, CRGB* color);
|
||||||
void raisePixel(uint8_t x, uint8_t y, CRGB* color);
|
void raisePixel(uint8_t x, uint8_t y, CRGB* color);
|
||||||
void line(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, CRGB* color);
|
void line(saccum78 x1, saccum78 y1, saccum78 x2, saccum78 y2, CRGB* color);
|
||||||
void lineWithAngle(uint8_t x, uint8_t y, uint8_t angle, uint8_t length, CRGB* color);
|
void lineWithAngle(uint8_t x, uint8_t y, uint16_t angle, uint8_t length, CRGB* color);
|
||||||
void lineWithAngle(uint8_t x, uint8_t y, uint8_t angle, uint8_t startdist, uint8_t length, CRGB* color);
|
void lineWithAngle(uint8_t x, uint8_t y, uint16_t angle, uint8_t startdist, uint8_t length, CRGB* color);
|
||||||
void circle(uint8_t x, uint8_t y, uint8_t r, CRGB* color);
|
void circle(uint8_t x, uint8_t y, uint8_t r, CRGB* color);
|
||||||
uint16_t coordsToGlobalIndex(uint8_t x, uint8_t y);
|
uint16_t coordsToGlobalIndex(uint8_t x, uint8_t y);
|
||||||
uint16_t localToGlobalIndex(uint16_t);
|
uint16_t localToGlobalIndex(uint16_t);
|
||||||
@ -50,4 +53,5 @@ public:
|
|||||||
void blur_row(uint8_t y, fract8 intensity);
|
void blur_row(uint8_t y, fract8 intensity);
|
||||||
void blur_columns(fract8 intensity);
|
void blur_columns(fract8 intensity);
|
||||||
void blur_column(uint8_t x, fract8 intensity);
|
void blur_column(uint8_t x, fract8 intensity);
|
||||||
|
void fill_with_checkerboard();
|
||||||
};
|
};
|
||||||
|
@ -36,6 +36,8 @@
|
|||||||
#define MQTT_TOPIC "pitrix/" // MQTT topic to listen to. Must not start with a slash, but must end with one.
|
#define MQTT_TOPIC "pitrix/" // MQTT topic to listen to. Must not start with a slash, but must end with one.
|
||||||
#define MQTT_REPORT_METRICS // Whether to report metrics via MQTT. Disable if unwanted.
|
#define MQTT_REPORT_METRICS // Whether to report metrics via MQTT. Disable if unwanted.
|
||||||
#define MQTT_TOPIC_WEATHER "accuweather/pitrix/" // MQTT topic to listen for weather data. Must not start with a slash, but must end with one.
|
#define MQTT_TOPIC_WEATHER "accuweather/pitrix/" // MQTT topic to listen for weather data. Must not start with a slash, but must end with one.
|
||||||
|
#define MQTT_TOPIC_TIMER "alexa/timer"
|
||||||
|
#define MQTT_TOPIC_HOMEASSISTANT "homeassistant"
|
||||||
|
|
||||||
#define HOSTNAME "pitrix-%08X" // Hostname of the ESP to use for OTA and MQTT client id. %08X will be replaced by the chip id.
|
#define HOSTNAME "pitrix-%08X" // Hostname of the ESP to use for OTA and MQTT client id. %08X will be replaced by the chip id.
|
||||||
#define OTA_STARTUP_DELAY 10 // How many seconds to wait at startup. This is useful to prevent being unable to flash OTA by a bug in the code. Set to 0 to disable.
|
#define OTA_STARTUP_DELAY 10 // How many seconds to wait at startup. This is useful to prevent being unable to flash OTA by a bug in the code. Set to 0 to disable.
|
||||||
@ -43,9 +45,6 @@
|
|||||||
#define FPS 50
|
#define FPS 50
|
||||||
#define SHOW_TEXT_DELAY 100
|
#define SHOW_TEXT_DELAY 100
|
||||||
|
|
||||||
#define RECORDER_ENABLE
|
|
||||||
#define RECORDER_PORT 2122
|
|
||||||
|
|
||||||
#define MONITOR_LOOP_TIMES false
|
#define MONITOR_LOOP_TIMES false
|
||||||
#define MONITOR_LOOP_TIME_THRESHOLD 500
|
#define MONITOR_LOOP_TIME_THRESHOLD 500
|
||||||
#define MONITOR_LOOP_TIME_COUNT_MAX 10
|
#define MONITOR_LOOP_TIME_COUNT_MAX 10
|
||||||
@ -98,22 +97,18 @@
|
|||||||
Serial.println(buffer);\
|
Serial.println(buffer);\
|
||||||
} while (0);
|
} while (0);
|
||||||
#else
|
#else
|
||||||
#define LOG(msg, ...) do { \
|
#define LOG(x, ...) Serial.printf(x, ##__VA_ARGS__);
|
||||||
char buffer[128]; \
|
#define LOGln(x, ...) { Serial.printf(x, ##__VA_ARGS__); Serial.println(); }
|
||||||
snprintf_P(buffer, 128, PSTR(msg), ##__VA_ARGS__);\
|
|
||||||
Serial.print(buffer);\
|
|
||||||
} while (0);
|
|
||||||
#define LOGln(msg, ...) do { \
|
|
||||||
char buffer[128]; \
|
|
||||||
snprintf_P(buffer, 128, PSTR(msg), ##__VA_ARGS__);\
|
|
||||||
Serial.println(buffer);\
|
|
||||||
} while (0);
|
|
||||||
#endif
|
#endif
|
||||||
|
#define DBG(msg, ...) { Serial.printf(msg, ##__VA_ARGS__); Serial.println(); }
|
||||||
#else
|
#else
|
||||||
#define LOG(msg, ...) do {} while(0);
|
#define LOG(x) do {} while(0);
|
||||||
#define LOGln(msg, ...) do {} while(0);
|
#define LOGln(x) do {} while(0);
|
||||||
|
#define DBG(msg, ...) do {} while(0);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#if !defined( ESP8266 ) && !defined( ESP32 )
|
#if !defined( ESP8266 ) && !defined( ESP32 )
|
||||||
#error "Neither ESP8266 nor ESP32 are set. Maybe you are compiling this for another platform...?"
|
#error "Neither ESP8266 nor ESP32 are set. Maybe you are compiling this for another platform...?"
|
||||||
#endif
|
#endif
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "Effect.h"
|
|
||||||
|
|
||||||
class BigClockEffect : public Effect {
|
|
||||||
private:
|
|
||||||
CRGB _color_font = CRGB(0xAAAAAA);
|
|
||||||
CRGB _color_seconds_light = CRGB(0xFFFF00);
|
|
||||||
CRGB _color_seconds_dark = CRGB(0xFF0000);
|
|
||||||
|
|
||||||
void _draw_seconds();
|
|
||||||
void _draw_border_pixel(uint8_t second, uint8_t part, CRGB* color);
|
|
||||||
|
|
||||||
public:
|
|
||||||
void loop(uint16_t ms);
|
|
||||||
String get_name() override { return "big_clock"; }
|
|
||||||
};
|
|
@ -1,20 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "Effect.h"
|
|
||||||
#include "my_fastled.h"
|
|
||||||
|
|
||||||
class ConfettiEffect : public Effect {
|
|
||||||
protected:
|
|
||||||
virtual CRGB _getColor();
|
|
||||||
public:
|
|
||||||
void loop(uint16_t ms);
|
|
||||||
boolean can_be_shown_with_clock();
|
|
||||||
String get_name() override { return "confetti"; }
|
|
||||||
};
|
|
||||||
|
|
||||||
class RandomConfettiEffect : public ConfettiEffect {
|
|
||||||
protected:
|
|
||||||
CRGB _getColor() override;
|
|
||||||
String get_name() override { return "random_confetti"; }
|
|
||||||
};
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "Effect.h"
|
|
||||||
#include "prototypes.h"
|
|
||||||
|
|
||||||
class SnakeEffect : public Effect {
|
|
||||||
private:
|
|
||||||
Coords coords;
|
|
||||||
uint8_t direction = 1;
|
|
||||||
uint8_t hue = 0;
|
|
||||||
uint8_t run = 0;
|
|
||||||
bool is_turn_needed();
|
|
||||||
void turn_random();
|
|
||||||
bool turn_right();
|
|
||||||
bool turn_left();
|
|
||||||
bool is_direction_okay(uint8_t direction);
|
|
||||||
public:
|
|
||||||
SnakeEffect();
|
|
||||||
~SnakeEffect();
|
|
||||||
void loop(uint16_t ms);
|
|
||||||
boolean valid_position(Coords c);
|
|
||||||
Coords update_position(Coords c, uint8_t direction);
|
|
||||||
boolean can_be_shown_with_clock();
|
|
||||||
String get_name() override { return "snake"; }
|
|
||||||
};
|
|
@ -1,18 +1,26 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Effect.h"
|
#include "Effect.h"
|
||||||
#include "effect_clock.h"
|
#include "effects/clock.h"
|
||||||
|
#include "effects/timer.h"
|
||||||
|
|
||||||
|
#define SIMPLE_EFFECT(name, use_in_cycle, flags, ...) {name, use_in_cycle, [](){ return new SimpleEffect(name, flags, [](double t, uint16_t i, uint8_t x, uint8_t y)->double __VA_ARGS__ ); }}
|
||||||
|
|
||||||
struct EffectEntry {
|
struct EffectEntry {
|
||||||
const char* name;
|
const char* name;
|
||||||
bool use_in_cycle;
|
bool use_in_cycle;
|
||||||
std::function<Effect*()> create;
|
std::function<Effect*()> create;
|
||||||
|
#ifdef MQTT_REPORT_METRICS
|
||||||
|
int16_t heap_change_sum;
|
||||||
|
uint16_t run_count;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
extern const EffectEntry effects[];
|
extern EffectEntry effects[];
|
||||||
extern const uint8_t effects_size;
|
extern const uint8_t effects_size;
|
||||||
|
|
||||||
extern Effect* current_effect;
|
extern Effect* current_effect;
|
||||||
extern ClockEffect effect_clock;
|
extern ClockEffect effect_clock;
|
||||||
|
extern TimerEffect effect_timer;
|
||||||
|
|
||||||
Effect* select_effect(char* name);
|
Effect* select_effect(char* name);
|
||||||
Effect* select_effect(uint8_t id);
|
Effect* select_effect(uint8_t id);
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
#include "Effect.h"
|
#include "Effect.h"
|
||||||
#include "prototypes.h"
|
#include "prototypes.h"
|
||||||
#include "my_fastled.h"
|
#include "my_fastled.h"
|
||||||
#include "Animation.h"
|
#include "../Animation.h"
|
||||||
|
|
||||||
class AnimationEffect : public Effect {
|
class AnimationEffect : public Effect {
|
||||||
private:
|
private:
|
||||||
@ -11,12 +11,14 @@ private:
|
|||||||
const char* name;
|
const char* name;
|
||||||
uint16_t xOffset;
|
uint16_t xOffset;
|
||||||
uint16_t yOffset;
|
uint16_t yOffset;
|
||||||
|
unsigned long _last_blink_at;
|
||||||
|
uint16_t _blink_freq;
|
||||||
public:
|
public:
|
||||||
AnimationEffect(const char* name) : AnimationEffect(name, 0x000000, 0, 0) {}
|
AnimationEffect(const char* name, uint32_t bg_color=0x000000, int x=0, int y=0);
|
||||||
AnimationEffect(const char* name, uint32_t bg_color) : AnimationEffect(name, bg_color, 0, 0) {}
|
static AnimationEffect* Blinker(const char* name, uint16_t interval, uint32_t color, uint32_t background_color=0x000000);
|
||||||
AnimationEffect(const char* name, uint32_t bg_color, int x, int y);
|
|
||||||
~AnimationEffect();
|
~AnimationEffect();
|
||||||
AnimationEffect* setFgColor(uint32_t c);
|
AnimationEffect* setFgColor(uint32_t c);
|
||||||
|
AnimationEffect* setBlinkFrequency(uint16_t);
|
||||||
void loop(uint16_t ms);
|
void loop(uint16_t ms);
|
||||||
String get_name() override;
|
String get_name() override;
|
||||||
};
|
};
|
21
include/effects/big_clock.h
Normal file
21
include/effects/big_clock.h
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Effect.h"
|
||||||
|
|
||||||
|
class BigClockEffect : public Effect {
|
||||||
|
protected:
|
||||||
|
CRGB _color_font = CRGB(0xAAAAAA);
|
||||||
|
CRGB _color_seconds_light = CRGB(0xFFFF00);
|
||||||
|
CRGB _color_seconds_dark = CRGB(0xAA0000);
|
||||||
|
CRGB _color_seconds_moving_light = CRGB(0x666600);
|
||||||
|
CRGB _color_seconds_moving_dark = CRGB(0x660000);
|
||||||
|
|
||||||
|
virtual CRGB _get_color_font() { return CRGB(0xAAAAAA); }
|
||||||
|
|
||||||
|
void _draw_seconds(uint8_t seconds);
|
||||||
|
virtual void _draw_border_pixel(accum88 pos, CRGB* color);
|
||||||
|
void _draw_colon(bool odd);
|
||||||
|
public:
|
||||||
|
virtual void loop(uint16_t ms);
|
||||||
|
String get_name() override { return "big_clock"; }
|
||||||
|
};
|
@ -4,11 +4,26 @@
|
|||||||
#include "functions.h"
|
#include "functions.h"
|
||||||
#include "Effect.h"
|
#include "Effect.h"
|
||||||
|
|
||||||
|
class Blur2DBlob {
|
||||||
|
private:
|
||||||
|
accum88 _x_freq;
|
||||||
|
accum88 _y_freq;
|
||||||
|
uint8_t _color_freq;
|
||||||
|
public:
|
||||||
|
Blur2DBlob();
|
||||||
|
void render(Window* win);
|
||||||
|
};
|
||||||
|
|
||||||
class Blur2DEffect : public Effect {
|
class Blur2DEffect : public Effect {
|
||||||
private:
|
private:
|
||||||
Window* window = new Window(0, 0, LED_WIDTH, LED_HEIGHT-6);
|
Window* window = &Window::window_with_clock;
|
||||||
|
uint8_t _count;
|
||||||
|
Blur2DBlob* _blobs;
|
||||||
public:
|
public:
|
||||||
|
Blur2DEffect();
|
||||||
~Blur2DEffect();
|
~Blur2DEffect();
|
||||||
|
void _init();
|
||||||
|
void _delete();
|
||||||
boolean supports_window = true;
|
boolean supports_window = true;
|
||||||
boolean can_be_shown_with_clock();
|
boolean can_be_shown_with_clock();
|
||||||
void loop(uint16_t ms);
|
void loop(uint16_t ms);
|
@ -7,18 +7,12 @@
|
|||||||
|
|
||||||
class ClockEffect : public Effect {
|
class ClockEffect : public Effect {
|
||||||
protected:
|
protected:
|
||||||
Window* window = new Window(0, LED_HEIGHT - 6, LED_WIDTH, 6);
|
Window* window = &Window::window_clock;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
~ClockEffect();
|
virtual ~ClockEffect();
|
||||||
virtual void loop(uint16_t ms);
|
virtual void loop(uint16_t ms);
|
||||||
String get_name() override { return "clock"; }
|
String get_name() override { return "clock"; }
|
||||||
void loop_with_invert(bool invert);
|
void loop_with_invert(bool invert);
|
||||||
void loop(boolean invert, CRGB fg_color, CRGB bg_color, uint8_t y);
|
void loop(boolean invert, CRGB fg_color, CRGB bg_color, uint8_t y);
|
||||||
};
|
};
|
||||||
|
|
||||||
class NightClockEffect : public ClockEffect {
|
|
||||||
public:
|
|
||||||
NightClockEffect();
|
|
||||||
void loop(uint16_t ms) override;
|
|
||||||
};
|
|
11
include/effects/diamond.h
Normal file
11
include/effects/diamond.h
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "Effect.h"
|
||||||
|
|
||||||
|
class DiamondEffect : public Effect {
|
||||||
|
private:
|
||||||
|
Window* window = &Window::window_with_clock;
|
||||||
|
public:
|
||||||
|
void loop(uint16_t ms) override;
|
||||||
|
bool can_be_shown_with_clock() override;
|
||||||
|
String get_name() override { return "diamond"; }
|
||||||
|
};
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
class DvdEffect : public Effect {
|
class DvdEffect : public Effect {
|
||||||
private:
|
private:
|
||||||
Window* window = new Window(0, 0, LED_WIDTH, LED_HEIGHT-6);
|
Window* window = &Window::window_with_clock;
|
||||||
saccum78 _x = 0;
|
saccum78 _x = 0;
|
||||||
saccum78 _y = 0;
|
saccum78 _y = 0;
|
||||||
int8_t _x_dir = 1;
|
int8_t _x_dir = 1;
|
@ -26,7 +26,7 @@ public:
|
|||||||
|
|
||||||
class BigDynamicEffect : public Effect {
|
class BigDynamicEffect : public Effect {
|
||||||
private:
|
private:
|
||||||
Window* window = new Window(0, 0, LED_WIDTH, LED_HEIGHT-6);
|
Window* window = &Window::window_with_clock;
|
||||||
public:
|
public:
|
||||||
void loop(uint16_t ms);
|
void loop(uint16_t ms);
|
||||||
~BigDynamicEffect();
|
~BigDynamicEffect();
|
@ -34,7 +34,7 @@ public:
|
|||||||
|
|
||||||
class FireworkEffect : public Effect {
|
class FireworkEffect : public Effect {
|
||||||
private:
|
private:
|
||||||
Window* window = new Window(0, 0, LED_WIDTH, LED_HEIGHT-6);
|
Window* window = &Window::window_with_clock;
|
||||||
bool _skyburst = 0;
|
bool _skyburst = 0;
|
||||||
|
|
||||||
accum88 _burst_x;
|
accum88 _burst_x;
|
32
include/effects/lightspeed.h
Normal file
32
include/effects/lightspeed.h
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Effect.h"
|
||||||
|
#include "my_fastled.h"
|
||||||
|
|
||||||
|
class LightspeedEffectStar {
|
||||||
|
private:
|
||||||
|
uint16_t _angle;
|
||||||
|
accum88 _distance;
|
||||||
|
uint16_t _speed;
|
||||||
|
uint16_t _target;
|
||||||
|
uint8_t _saturation;
|
||||||
|
void _init();
|
||||||
|
public:
|
||||||
|
LightspeedEffectStar();
|
||||||
|
void loop(Window* win);
|
||||||
|
};
|
||||||
|
|
||||||
|
class LightspeedEffect : public Effect {
|
||||||
|
private:
|
||||||
|
LightspeedEffectStar* _stars;
|
||||||
|
uint8_t _count;
|
||||||
|
void _init();
|
||||||
|
void _delete();
|
||||||
|
public:
|
||||||
|
LightspeedEffect();
|
||||||
|
~LightspeedEffect();
|
||||||
|
void loop(uint16_t ms);
|
||||||
|
boolean can_be_shown_with_clock();
|
||||||
|
String get_name() override { return "lightspeed"; }
|
||||||
|
};
|
||||||
|
|
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
class MarqueeEffect : public Effect {
|
class MarqueeEffect : public Effect {
|
||||||
private:
|
private:
|
||||||
Window* window = new Window(0, 0, LED_WIDTH, LED_HEIGHT-6);
|
Window* window = &Window::window_with_clock;
|
||||||
String _text = String("No text set +++ ");
|
String _text = String("No text set +++ ");
|
||||||
saccum78 _position = (window->width<<8);
|
saccum78 _position = (window->width<<8);
|
||||||
public:
|
public:
|
@ -9,7 +9,7 @@
|
|||||||
class MatrixEffectColumn {
|
class MatrixEffectColumn {
|
||||||
protected:
|
protected:
|
||||||
Window* window;
|
Window* window;
|
||||||
accum88 x, y;
|
saccum78 x, y;
|
||||||
uint8_t length = 1;
|
uint8_t length = 1;
|
||||||
uint8_t _direction = 2;
|
uint8_t _direction = 2;
|
||||||
bool _random_direction = false;
|
bool _random_direction = false;
|
||||||
@ -48,25 +48,58 @@ public:
|
|||||||
RandomMatrixEffectColumn(Window* win, uint8_t dir, bool rnd=false) : MatrixEffectColumn(win, dir, rnd) {};
|
RandomMatrixEffectColumn(Window* win, uint8_t dir, bool rnd=false) : MatrixEffectColumn(win, dir, rnd) {};
|
||||||
};
|
};
|
||||||
|
|
||||||
class MatrixEffect : public Effect {
|
class ColumnMatrixEffectColumn : public MatrixEffectColumn {
|
||||||
protected:
|
protected:
|
||||||
MatrixEffectColumn** _columns;
|
uint8_t _hue;
|
||||||
|
CRGB _getColor(uint8_t height) override;
|
||||||
|
void restart(bool completely_random) override;
|
||||||
public:
|
public:
|
||||||
boolean can_be_shown_with_clock();
|
ColumnMatrixEffectColumn(Window* win, uint8_t dir, bool rnd=false) : MatrixEffectColumn(win, dir, rnd) {};
|
||||||
MatrixEffect();
|
|
||||||
virtual ~MatrixEffect();
|
|
||||||
void loop(uint16_t ms);
|
|
||||||
String get_name() override { return "matrix"; }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class RainbowMatrixEffect : public MatrixEffect {
|
class MatrixEffectBase : public Effect {
|
||||||
|
protected:
|
||||||
|
MatrixEffectColumn** _columns;
|
||||||
|
uint8_t _count;
|
||||||
|
virtual uint8_t _get_count();
|
||||||
|
virtual void _delete();
|
||||||
|
void _init();
|
||||||
|
virtual void _create() = 0;
|
||||||
|
public:
|
||||||
|
boolean can_be_shown_with_clock();
|
||||||
|
virtual ~MatrixEffectBase();
|
||||||
|
void loop(uint16_t ms);
|
||||||
|
};
|
||||||
|
|
||||||
|
class MatrixEffect : public MatrixEffectBase {
|
||||||
|
protected:
|
||||||
|
void _create() override;
|
||||||
|
public:
|
||||||
|
MatrixEffect();
|
||||||
|
String get_name() override { return "matrix"; }
|
||||||
|
};
|
||||||
|
|
||||||
|
class RainbowMatrixEffect : public MatrixEffectBase {
|
||||||
|
protected:
|
||||||
|
void _create() override;
|
||||||
public:
|
public:
|
||||||
RainbowMatrixEffect();
|
RainbowMatrixEffect();
|
||||||
String get_name() override { return "rainbow_matrix"; }
|
String get_name() override { return "rainbow_matrix"; }
|
||||||
};
|
};
|
||||||
|
|
||||||
class RandomMatrixEffect : public MatrixEffect {
|
class RandomMatrixEffect : public MatrixEffectBase {
|
||||||
|
protected:
|
||||||
|
uint8_t _get_count() override;
|
||||||
|
void _create() override;
|
||||||
public:
|
public:
|
||||||
RandomMatrixEffect();
|
RandomMatrixEffect();
|
||||||
String get_name() override { return "random_matrix"; }
|
String get_name() override { return "random_matrix"; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class ColumnMatrixEffect : public MatrixEffectBase {
|
||||||
|
protected:
|
||||||
|
void _create() override;
|
||||||
|
public:
|
||||||
|
ColumnMatrixEffect();
|
||||||
|
String get_name() override { return "column_matrix"; }
|
||||||
|
};
|
8
include/effects/night_clock.h
Normal file
8
include/effects/night_clock.h
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "Effect.h"
|
||||||
|
|
||||||
|
class NightClockEffect : public Effect {
|
||||||
|
public:
|
||||||
|
virtual void loop(uint16_t ms);
|
||||||
|
String get_name() override { return "night_clock"; }
|
||||||
|
};
|
@ -4,6 +4,12 @@
|
|||||||
#include "functions.h"
|
#include "functions.h"
|
||||||
#include "Effect.h"
|
#include "Effect.h"
|
||||||
|
|
||||||
|
enum SinematrixColorScheme {
|
||||||
|
SINEMATRIX_COLOR_PASTEL_RAINBOW,
|
||||||
|
SINEMATRIX_COLOR_RAINBOW,
|
||||||
|
SINEMATRIX_COLOR_PURPLEFLY,
|
||||||
|
};
|
||||||
|
|
||||||
class Sinematrix3Effect : public Effect {
|
class Sinematrix3Effect : public Effect {
|
||||||
private:
|
private:
|
||||||
double pangle = 0;
|
double pangle = 0;
|
||||||
@ -25,12 +31,14 @@ private:
|
|||||||
double fx = 1.0 / (LED_WIDTH / PI);
|
double fx = 1.0 / (LED_WIDTH / PI);
|
||||||
double fy = 1.0 / (LED_HEIGHT / PI);
|
double fy = 1.0 / (LED_HEIGHT / PI);
|
||||||
Matrix rotate;
|
Matrix rotate;
|
||||||
|
SinematrixColorScheme _color_scheme;
|
||||||
|
CRGB _get_color(int value);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
Sinematrix3Effect(SinematrixColorScheme s = SINEMATRIX_COLOR_PASTEL_RAINBOW): _color_scheme(s) {};
|
||||||
boolean supports_window = true;
|
boolean supports_window = true;
|
||||||
boolean can_be_shown_with_clock();
|
boolean can_be_shown_with_clock();
|
||||||
boolean clock_as_mask();
|
boolean clock_as_mask();
|
||||||
void loop(uint16_t ms);
|
void loop(uint16_t ms);
|
||||||
String get_name() override { return "sinematrix3"; }
|
String get_name() override { return "sinematrix3"; }
|
||||||
};
|
};
|
||||||
|
|
68
include/effects/snake.h
Normal file
68
include/effects/snake.h
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Effect.h"
|
||||||
|
#include "prototypes.h"
|
||||||
|
|
||||||
|
#define SNAKE_DIR_NORTH 0
|
||||||
|
#define SNAKE_DIR_EAST 1
|
||||||
|
#define SNAKE_DIR_SOUTH 2
|
||||||
|
#define SNAKE_DIR_WEST 3
|
||||||
|
|
||||||
|
#define SNAKE_DEBUG false
|
||||||
|
|
||||||
|
class SnakeEffect : public Effect {
|
||||||
|
private:
|
||||||
|
Coords _pos;
|
||||||
|
Coords _apple;
|
||||||
|
Coords _tail;
|
||||||
|
int8_t _dir = SNAKE_DIR_NORTH;
|
||||||
|
uint8_t* _map;
|
||||||
|
uint16_t _pixels;
|
||||||
|
uint8_t _length;
|
||||||
|
unsigned long _last_apple_at;
|
||||||
|
unsigned long _last_move_at;
|
||||||
|
uint16_t _round;
|
||||||
|
|
||||||
|
// Neural net config
|
||||||
|
// These are actually float values. But in order to prevent rounding errors and stuff, they are provided
|
||||||
|
// in form of the raw binary data of the IEE754 floating point numbers.
|
||||||
|
// In _decide() there's code to memcpy()-convert them to a float.
|
||||||
|
// Round 340, 223.4 points, length 39, 36% stopped, 64% died
|
||||||
|
// const uint32_t _weights[36] = {0xbd8e626e, 0xbee2cd2c, 0x3e4d5cab, 0x3eceb8c3, 0xbed0a514, 0x3ec62438, 0x3e947ed4, 0xbe4b8bf2, 0xbf301113, 0xbf3f0a75, 0x3f1868f7, 0xbf0253ca, 0xbedca2f2, 0xbd547c6d, 0x3edd6a8a, 0xbd4b97b6, 0x3f64ec26, 0xbe5323c1, 0x3eccf87d, 0xbf2d4796, 0xbf62b6e8, 0xbf71daf6, 0xbf03f08e, 0xbf222609, 0x3e26c03c, 0xbf497837, 0xbee4d175, 0x3ec601de, 0x3e4e0695, 0x3eef2619, 0xbe849370, 0xbf18fb2b, 0x3f25bbd1, 0xbf3dcd78, 0x3f37a58d, 0x3ef4a25b};
|
||||||
|
// Round 630, 221.0 points, length 38, 36% stopped, 64% died
|
||||||
|
const uint32_t _weights[36] = {0xbd25943f, 0xbf279d81, 0x3e25d128, 0x3ec62438, 0x3f0e719c, 0x3eefbea9, 0x3e947ed4, 0xbe5323c1, 0xbf2d4796, 0xbf3f0a75, 0x3f0e45d9, 0xbf0253ca, 0xbedca2f2, 0xbd79073c, 0x3ede80ec, 0xbd4b97b6, 0x3f69a6be, 0xbe4b8bf2, 0x3eccf87d, 0xbf301113, 0xbf62b6e8, 0xbf71daf6, 0xbf204130, 0xbf222609, 0x3e26c03c, 0xbf497837, 0xbee4d175, 0x3ec601de, 0x3e4954eb, 0x3eef2619, 0xbe849370, 0xbf18fb2b, 0x3f25bbd1, 0xbf3b4e44, 0x3f484d59, 0x3edd6a8a};
|
||||||
|
// Round 193, 164.8 points, length 36, 6% stopped, 94% died
|
||||||
|
//const uint32_t _weights[36] = {0x3e872ffb, 0xbea57262, 0xbee269bf, 0x3ed790a3, 0xbf54014f, 0x3ecde0a6, 0xbf240a93, 0xbe9e4782, 0x3f205106, 0xbf4465c2, 0xbf79579a, 0xbf07f122, 0x3ed0e1bc, 0xbf7a5a09, 0xbf0fc70b, 0xbf6d1971, 0xbe0f5585, 0xbec94b12, 0x3f51f7a9, 0x3eaac42b, 0xbe6aafa6, 0x3d3e3ce3, 0xbf7c4232, 0xbe634103, 0x3f800000, 0x3eff886c, 0x3deae1e8, 0x3eea6988, 0xbf800000, 0xbf426a20, 0x3e3a0a45, 0xbe848803, 0x3e84e8c9, 0x3ef9fabc, 0xbe7733e6, 0xbecda633};
|
||||||
|
// Round 13650, 244.8 points, length 42, 34% stopped, 66% died
|
||||||
|
//const uint32_t _weights[36] = {0xbeb99de3, 0x3e6ca488, 0xbe3e9dad, 0xbed38d4e, 0x3f279fc1, 0xbd367111, 0xbf473843, 0xbf800000, 0x3f614edc, 0xbecc734f, 0xbe59b29d, 0x3d479078, 0x3efa7ca6, 0xbedc6ce6, 0x3f4626a1, 0x3e9d8c2c, 0x3f29e25c, 0x3ebde05d, 0x3e8f3e29, 0xbe8ad92c, 0xbe148f2d, 0x3d4a3ca7, 0xbf800000, 0x3d9cd634, 0x3f29802e, 0xbf2cc57e, 0xbcbfafff, 0x3e280b8a, 0x3f5ff9a3, 0xbf4e29c9, 0x3e8936d2, 0xbf49dda9, 0xbe9bf4c7, 0x3e203ea8, 0xbd4edf4d, 0xbf4e3c05};
|
||||||
|
|
||||||
|
const uint8_t _net_layout[3] = {6, 4, 3};
|
||||||
|
const uint8_t _net_layers = 3;
|
||||||
|
const uint8_t _net_total_size = 36;
|
||||||
|
|
||||||
|
uint8_t _head_rounds = 0;
|
||||||
|
uint8_t _tail_rounds = 0;
|
||||||
|
|
||||||
|
|
||||||
|
uint16_t _xy2i(uint8_t x, uint8_t y);
|
||||||
|
uint16_t _xy2i(Coords c);
|
||||||
|
Coords _i2xy(uint16_t i);
|
||||||
|
Coords _new_pos(uint8_t dir);
|
||||||
|
uint8_t _dying = 0;
|
||||||
|
bool _is_free(uint8_t dir);
|
||||||
|
uint8_t _free_spaces(uint8_t dir);
|
||||||
|
uint8_t _to_apple(uint8_t dir);
|
||||||
|
void _place_apple();
|
||||||
|
void _init();
|
||||||
|
void _decide();
|
||||||
|
uint8_t _coords_to_field_id(Coords);
|
||||||
|
int8_t _manual_decision();
|
||||||
|
void _move();
|
||||||
|
void _draw();
|
||||||
|
public:
|
||||||
|
SnakeEffect();
|
||||||
|
~SnakeEffect();
|
||||||
|
void loop(uint16_t ms);
|
||||||
|
boolean can_be_shown_with_clock();
|
||||||
|
String get_name() override { return "snake"; }
|
||||||
|
};
|
16
include/effects/timer.h
Normal file
16
include/effects/timer.h
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Effect.h"
|
||||||
|
#include "prototypes.h"
|
||||||
|
#include "my_fastled.h"
|
||||||
|
#include "Window.h"
|
||||||
|
|
||||||
|
class TimerEffect : public Effect {
|
||||||
|
protected:
|
||||||
|
Window* window = new Window(0, 0, LED_WIDTH, 6);
|
||||||
|
|
||||||
|
public:
|
||||||
|
~TimerEffect();
|
||||||
|
void loop(uint16_t ms);
|
||||||
|
String get_name() override { return "timer"; }
|
||||||
|
};
|
30
include/effects/tpm2_net.h
Normal file
30
include/effects/tpm2_net.h
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Effect.h"
|
||||||
|
#include "prototypes.h"
|
||||||
|
#include "my_fastled.h"
|
||||||
|
#include "Window.h"
|
||||||
|
#include "config.h"
|
||||||
|
#include <WiFiUdp.h>
|
||||||
|
|
||||||
|
class Tpm2NetEffect : public Effect {
|
||||||
|
protected:
|
||||||
|
Window* window = &Window::window_full;
|
||||||
|
WiFiUDP _udp;
|
||||||
|
uint16_t _pixel_index = 0;
|
||||||
|
|
||||||
|
void _parse_command(uint16_t size, uint8_t packet_number);
|
||||||
|
void _parse_data(uint16_t size, uint8_t packet_number);
|
||||||
|
void _respond(uint8_t* data, uint8_t len);
|
||||||
|
void _respond_ack();
|
||||||
|
void _respond_with_data(uint8_t* data, uint8_t len);
|
||||||
|
void _respond_unknown_command();
|
||||||
|
unsigned long _last_packet_at = 0;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Tpm2NetEffect();
|
||||||
|
virtual ~Tpm2NetEffect();
|
||||||
|
virtual void loop(uint16_t ms);
|
||||||
|
bool can_be_shown_with_clock();
|
||||||
|
String get_name() override { return "tpm2.net"; }
|
||||||
|
};
|
12
include/effects/tv_static.h
Normal file
12
include/effects/tv_static.h
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "Effect.h"
|
||||||
|
|
||||||
|
class TvStaticEffect : public Effect {
|
||||||
|
private:
|
||||||
|
Window* _window = &Window::window_with_clock;
|
||||||
|
public:
|
||||||
|
~TvStaticEffect();
|
||||||
|
void loop(uint16_t ms) override;
|
||||||
|
bool can_be_shown_with_clock() override;
|
||||||
|
String get_name() override { return "tv_static"; }
|
||||||
|
};
|
@ -2,6 +2,7 @@
|
|||||||
#include "prototypes.h"
|
#include "prototypes.h"
|
||||||
|
|
||||||
extern Font font_numbers3x5;
|
extern Font font_numbers3x5;
|
||||||
|
extern Font font_numbers3x3;
|
||||||
extern Font font_numbers3x5_blocky;
|
extern Font font_numbers3x5_blocky;
|
||||||
extern Font font_numbers4x7;
|
extern Font font_numbers4x7;
|
||||||
extern Font font5x7;
|
extern Font font5x7;
|
||||||
|
@ -5,15 +5,13 @@
|
|||||||
|
|
||||||
#include "my_wifi.h"
|
#include "my_wifi.h"
|
||||||
#include <FS.h>
|
#include <FS.h>
|
||||||
|
#include <ESPAsyncWebServer.h>
|
||||||
|
|
||||||
#if defined ( ESP8266 )
|
extern AsyncWebServer http_server;
|
||||||
extern ESP8266WebServer http_server;
|
|
||||||
#elif defined ( ESP32 )
|
|
||||||
extern ESP32WebServer http_server;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
extern File upload_file;
|
extern File upload_file;
|
||||||
|
extern uint32_t monitor_client;
|
||||||
|
|
||||||
void http_server_setup();
|
void http_server_setup();
|
||||||
void http_server_loop();
|
void http_server_send_framedata();
|
||||||
#endif
|
#endif
|
||||||
|
@ -6,3 +6,4 @@
|
|||||||
|
|
||||||
extern const TProgmemRGBGradientPalette_byte palette_fire[] FL_PROGMEM;
|
extern const TProgmemRGBGradientPalette_byte palette_fire[] FL_PROGMEM;
|
||||||
extern const TProgmemRGBGradientPalette_byte palette_matrix[] FL_PROGMEM;
|
extern const TProgmemRGBGradientPalette_byte palette_matrix[] FL_PROGMEM;
|
||||||
|
extern const TProgmemRGBGradientPalette_byte palette_purplefly_gp[] FL_PROGMEM;
|
||||||
|
@ -17,8 +17,8 @@ void mqtt_setup();
|
|||||||
|
|
||||||
void mqtt_loop();
|
void mqtt_loop();
|
||||||
|
|
||||||
void mqtt_publish(const char* topic, int number);
|
void mqtt_publish(const char* topic, int number, bool retain=false);
|
||||||
void mqtt_publish(const char* topic, const char* message);
|
void mqtt_publish(const char* topic, const char* message, bool retain=false);
|
||||||
|
|
||||||
void mqtt_log(const char* message);
|
void mqtt_log(const char* message);
|
||||||
void mqtt_log(int number);
|
void mqtt_log(int number);
|
||||||
|
@ -5,13 +5,11 @@
|
|||||||
#if defined( ESP8266 )
|
#if defined( ESP8266 )
|
||||||
#include <ESP8266WiFi.h>
|
#include <ESP8266WiFi.h>
|
||||||
#include <ESP8266mDNS.h>
|
#include <ESP8266mDNS.h>
|
||||||
#include <ESP8266WebServer.h>
|
|
||||||
#elif defined( ESP32 )
|
#elif defined( ESP32 )
|
||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
#include <ESPmDNS.h>
|
#include <ESPmDNS.h>
|
||||||
#include <WiFiClient.h>
|
#include <WiFiClient.h>
|
||||||
#include <WiFiServer.h>
|
#include <WiFiServer.h>
|
||||||
#include <ESP32WebServer.h>
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <WiFiUdp.h>
|
#include <WiFiUdp.h>
|
||||||
|
@ -1,9 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <NTPClient.h>
|
|
||||||
#include "my_wifi.h"
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
void time_changed();
|
||||||
extern NTPClient ntpClient;
|
|
||||||
void updateCallback(NTPClient* c);
|
|
||||||
void ntp_setup();
|
void ntp_setup();
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
#include "my_fastled.h"
|
||||||
|
|
||||||
extern uint8_t baseHue;
|
extern uint8_t baseHue;
|
||||||
extern char hostname[30];
|
extern char hostname[30];
|
||||||
|
extern uint16_t frame;
|
||||||
|
extern unsigned long timer;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint8_t width;
|
uint8_t width;
|
||||||
@ -29,3 +32,5 @@ typedef struct {
|
|||||||
uint16_t x;
|
uint16_t x;
|
||||||
uint16_t y;
|
uint16_t y;
|
||||||
} Coords;
|
} Coords;
|
||||||
|
|
||||||
|
typedef std::function<double(double, uint16_t, uint8_t, uint8_t)> simple_effect_t;
|
@ -1,22 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include "my_wifi.h"
|
|
||||||
#include "config.h"
|
|
||||||
#include <ESPAsyncTCP.h>
|
|
||||||
#include <WiFiUdp.h>
|
|
||||||
|
|
||||||
#ifdef RECORDER_ENABLE
|
|
||||||
class Recorder {
|
|
||||||
private:
|
|
||||||
WiFiUDP _udp;
|
|
||||||
AsyncServer* _server;
|
|
||||||
AsyncClient* _client = NULL;
|
|
||||||
uint16_t _client_port = 0;
|
|
||||||
size_t _buffer_len;
|
|
||||||
char* _buffer;
|
|
||||||
uint16_t _msgid;
|
|
||||||
boolean _skip_next_frame = false;
|
|
||||||
public:
|
|
||||||
Recorder();
|
|
||||||
void loop();
|
|
||||||
};
|
|
||||||
#endif
|
|
@ -22,57 +22,75 @@ struct Settings {
|
|||||||
uint16_t time = 300;
|
uint16_t time = 300;
|
||||||
uint16_t random = 1;
|
uint16_t random = 1;
|
||||||
} cycle ;
|
} cycle ;
|
||||||
|
|
||||||
struct /* matrix */ {
|
struct /* matrix */ {
|
||||||
uint16_t length_min = 4;
|
uint16_t length_min = 4;
|
||||||
uint16_t length_max = 20;
|
uint16_t length_max = 20;
|
||||||
uint16_t speed_min = 1;
|
uint16_t speed_min = 3;
|
||||||
uint16_t speed_max = 10;
|
uint16_t speed_max = 7;
|
||||||
|
uint16_t count = 16;
|
||||||
|
uint16_t random_count = 32;
|
||||||
} matrix;
|
} matrix;
|
||||||
|
|
||||||
|
struct /* big_clock */ {
|
||||||
|
uint16_t spacing = 5;
|
||||||
|
} big_clock;
|
||||||
|
|
||||||
|
struct /* blur2d */ {
|
||||||
|
uint16_t count = 5;
|
||||||
|
} blur2d;
|
||||||
|
|
||||||
struct /* confetti */ {
|
struct /* confetti */ {
|
||||||
uint16_t pixels_per_loop = 2;
|
uint16_t pixels_per_loop = 2;
|
||||||
} confetti;
|
} confetti;
|
||||||
|
|
||||||
struct /* dvd */ {
|
struct /* dvd */ {
|
||||||
uint16_t width = 3;
|
uint16_t width = 3;
|
||||||
uint16_t height = 2;
|
uint16_t height = 2;
|
||||||
uint16_t speed = 50;
|
uint16_t speed = 50;
|
||||||
} dvd;
|
} dvd;
|
||||||
|
|
||||||
struct /* dynamic */ {
|
struct /* dynamic */ {
|
||||||
uint16_t single_loop_time = 40;
|
uint16_t single_loop_time = 40;
|
||||||
uint16_t multi_loop_time = 1400;
|
uint16_t multi_loop_time = 1400;
|
||||||
uint16_t big_loop_time = 50;
|
uint16_t big_loop_time = 50;
|
||||||
uint16_t big_size = 3;
|
uint16_t big_size = 3;
|
||||||
} dynamic;
|
} dynamic;
|
||||||
|
|
||||||
struct /* fire */ {
|
struct /* fire */ {
|
||||||
uint16_t cooldown = 192;
|
uint16_t cooldown = 192;
|
||||||
uint16_t spark_chance = 5;
|
uint16_t spark_chance = 5;
|
||||||
} fire;
|
} fire;
|
||||||
|
|
||||||
struct /* firework */ {
|
struct /* firework */ {
|
||||||
uint16_t drag = 255;
|
uint16_t drag = 255;
|
||||||
uint16_t bounce = 200;
|
uint16_t bounce = 200;
|
||||||
uint16_t gravity = 10;
|
uint16_t gravity = 10;
|
||||||
uint16_t sparks = 12;
|
uint16_t sparks = 12;
|
||||||
} firework;
|
} firework;
|
||||||
|
|
||||||
struct /* gol */ {
|
struct /* gol */ {
|
||||||
uint16_t start_percentage = 90;
|
uint16_t start_percentage = 90;
|
||||||
uint16_t blend_speed = 10;
|
uint16_t blend_speed = 10;
|
||||||
uint16_t restart_after_steps = 100;
|
uint16_t restart_after_steps = 100;
|
||||||
} gol;
|
} gol;
|
||||||
|
|
||||||
|
struct /* lightspeed */ {
|
||||||
|
uint16_t count = 25;
|
||||||
|
} lightspeed;
|
||||||
|
|
||||||
struct /* sines */ {
|
struct /* sines */ {
|
||||||
uint16_t count = 5;
|
uint16_t count = 5;
|
||||||
} sines;
|
} sines;
|
||||||
|
|
||||||
struct /* snake */ {
|
struct /* snake */ {
|
||||||
uint16_t direction_change = 5;
|
uint16_t direction_change = 5;
|
||||||
uint16_t slowdown = 2;
|
uint16_t slowdown = 2;
|
||||||
} snake;
|
} snake;
|
||||||
|
|
||||||
|
struct /* tv_static */ {
|
||||||
|
uint16_t black_bar_speed = 12;
|
||||||
|
} tv_static;
|
||||||
} effects;
|
} effects;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -81,3 +99,6 @@ extern Setting all_settings[];
|
|||||||
extern const uint8_t all_settings_size;
|
extern const uint8_t all_settings_size;
|
||||||
|
|
||||||
bool change_setting(const char* key, uint16_t new_value);
|
bool change_setting(const char* key, uint16_t new_value);
|
||||||
|
uint16_t setting_default(Setting* s);
|
||||||
|
bool save_settings();
|
||||||
|
bool load_settings();
|
||||||
|
@ -10,31 +10,31 @@
|
|||||||
|
|
||||||
[platformio]
|
[platformio]
|
||||||
lib_dir = lib
|
lib_dir = lib
|
||||||
env_default = ota
|
default_envs = ota
|
||||||
|
|
||||||
[extra]
|
[extra]
|
||||||
lib_deps =
|
lib_deps =
|
||||||
PubSubClient
|
PubSubClient
|
||||||
https://github.com/fabianonline/FastLED.git
|
https://github.com/fabianonline/FastLED.git
|
||||||
https://github.com/fabianonline/NTPClient.git
|
https://github.com/me-no-dev/ESPAsyncWebServer.git
|
||||||
ESP8266WebServer
|
|
||||||
ESPAsyncTCP
|
|
||||||
|
|
||||||
[env:ota]
|
[env:ota]
|
||||||
upload_port = 10.10.2.78
|
upload_port = 10.10.2.78 ; .78=prod, .80=dev
|
||||||
upload_protocol = espota
|
upload_protocol = espota
|
||||||
platform = espressif8266
|
platform = espressif8266
|
||||||
board = esp07
|
board = esp07
|
||||||
framework = arduino
|
framework = arduino
|
||||||
lib_deps = ${extra.lib_deps}
|
lib_deps = ${extra.lib_deps}
|
||||||
lib_ldf_mode = chain+
|
lib_ldf_mode = deep
|
||||||
build_flags = -Wl,-Teagle.flash.2m512.ld
|
build_flags = -Wl,-Teagle.flash.2m512.ld
|
||||||
|
|
||||||
[env:local]
|
[env:local]
|
||||||
upload_port = /dev/cu.wchusbserial1420
|
upload_port = /dev/serial/by-id/usb-1a86_USB2.0-Serial-if00-port0
|
||||||
platform = espressif8266
|
platform = espressif8266
|
||||||
board = esp07
|
board = esp07
|
||||||
framework = arduino
|
framework = arduino
|
||||||
lib_deps = ${extra.lib_deps}
|
lib_deps = ${extra.lib_deps}
|
||||||
lib_ldf_mode = chain+
|
lib_ldf_mode = deep
|
||||||
build_flags = -Wl,-Teagle.flash.2m512.ld
|
build_flags = -Wl,-Teagle.flash.2m512.ld
|
||||||
|
monitor_filters = time, esp8266_exception_decoder
|
||||||
|
build_type = debug
|
||||||
|
@ -84,16 +84,16 @@ bool Animation::_load_from_file(const char* filename) {
|
|||||||
// Now we can be sure to have the right amount of bytes available for reading
|
// Now we can be sure to have the right amount of bytes available for reading
|
||||||
|
|
||||||
_width = file.read();
|
_width = file.read();
|
||||||
LOGln("Animation * width: %d", _width);
|
DBG("Animation * width: %d", _width);
|
||||||
_height = file.read();
|
_height = file.read();
|
||||||
LOGln("Animation * height: %d", _height);
|
DBG("Animation * height: %d", _height);
|
||||||
|
|
||||||
_frame_count = file.read();
|
_frame_count = file.read();
|
||||||
LOGln("Animation * frame_count: %d", _frame_count);
|
DBG("Animation * frame_count: %d", _frame_count);
|
||||||
_color_count = file.read();
|
_color_count = file.read();
|
||||||
LOGln("Animation * color_count: %d", _color_count);
|
DBG("Animation * color_count: %d", _color_count);
|
||||||
|
|
||||||
LOGln("Animation * Loading colors...");
|
DBG("Animation * Loading colors...");
|
||||||
_colors = new CRGB*[_color_count];
|
_colors = new CRGB*[_color_count];
|
||||||
char* temp = new char[_color_count*3];
|
char* temp = new char[_color_count*3];
|
||||||
int bytes_read = file.readBytes(temp, _color_count*3);
|
int bytes_read = file.readBytes(temp, _color_count*3);
|
||||||
@ -102,36 +102,33 @@ bool Animation::_load_from_file(const char* filename) {
|
|||||||
_colors[i] = new CRGB(temp[i*3], temp[i*3+1], temp[i*3+2]);
|
_colors[i] = new CRGB(temp[i*3], temp[i*3+1], temp[i*3+2]);
|
||||||
}
|
}
|
||||||
delete [] temp;
|
delete [] temp;
|
||||||
LOGln("Animation * Color loading done.");
|
DBG("Animation * Color loading done.");
|
||||||
|
|
||||||
LOG("Animation * Loading frame times...");
|
DBG("Animation * Loading frame times...");
|
||||||
_frame_times = new uint16_t[_frame_count];
|
_frame_times = new uint16_t[_frame_count];
|
||||||
for (int i=0; i<_frame_count; i++) {
|
for (int i=0; i<_frame_count; i++) {
|
||||||
_frame_times[i] = (file.read() << 8) | file.read();
|
_frame_times[i] = (file.read() << 8) | file.read();
|
||||||
}
|
}
|
||||||
LOGln(" done.");
|
DBG(" done.");
|
||||||
|
|
||||||
LOGln("Animation * Loading frame lengths...");
|
DBG("Animation * Loading frame lengths...");
|
||||||
_frame_data_lengths = new uint16_t[_frame_count];
|
_frame_data_lengths = new uint16_t[_frame_count];
|
||||||
temp = new char[_frame_count*2];
|
temp = new char[_frame_count*2];
|
||||||
bytes_read = file.readBytes(temp, _frame_count*2);
|
bytes_read = file.readBytes(temp, _frame_count*2);
|
||||||
LOGln("Animation * Read %d bytes.", bytes_read);
|
DBG("Animation * Read %d bytes.", bytes_read);
|
||||||
for (int i=0; i<_frame_count; i++) {
|
for (int i=0; i<_frame_count; i++) {
|
||||||
//LOGln("Animation * Raw frame lengths: %d, %d", temp[i*2], temp[i*2+1]);
|
//LOGln("Animation * Raw frame lengths: %d, %d", temp[i*2], temp[i*2+1]);
|
||||||
_frame_data_lengths[i] = (temp[i*2]<<8) | temp[i*2+1];
|
_frame_data_lengths[i] = (temp[i*2]<<8) | temp[i*2+1];
|
||||||
}
|
}
|
||||||
delete [] temp;
|
delete [] temp;
|
||||||
LOGln("Animation * Frame length loading done.");
|
DBG("Animation * Frame length loading done.");
|
||||||
|
|
||||||
LOGln("Animation * Loading frame data...");
|
DBG("Animation * Loading frame data...");
|
||||||
_frame_data = new uint8_t*[_frame_count];
|
_frame_data = new uint8_t*[_frame_count];
|
||||||
for (int i=0; i<_frame_count; i++) {
|
for (int i=0; i<_frame_count; i++) {
|
||||||
uint16_t fl = _frame_data_lengths[i];
|
uint16_t fl = _frame_data_lengths[i];
|
||||||
LOGln("Animation * Loading frame %d with %d bytes...", i, fl);
|
DBG("Animation * Loading frame %d/%d with %d bytes...", i, _frame_count, fl);
|
||||||
_frame_data[i] = new uint8_t[fl];
|
_frame_data[i] = new uint8_t[fl];
|
||||||
/*for (int b=0; b<fl; b++) {
|
|
||||||
_frame_data[i][b] = file.read();
|
|
||||||
}*/
|
|
||||||
file.readBytes((char*)_frame_data[i], fl);
|
file.readBytes((char*)_frame_data[i], fl);
|
||||||
}
|
}
|
||||||
LOGln("Animation * Frame data loaded.");
|
LOGln("Animation * Frame data loaded.");
|
||||||
@ -208,27 +205,25 @@ void Animation::setSingleFrame(uint8_t frame) {
|
|||||||
Animation::~Animation() {
|
Animation::~Animation() {
|
||||||
for (int i=0; i<_color_count; i++) delete _colors[i];
|
for (int i=0; i<_color_count; i++) delete _colors[i];
|
||||||
|
|
||||||
LOGln("Animation * Deleting _colors...");
|
DBG("Animation * Deleting _colors...");
|
||||||
if (_colors) delete [] _colors;
|
if (_colors) delete[] _colors;
|
||||||
|
|
||||||
LOGln("Animation * Deleting fgColor...");
|
DBG("Animation * Deleting fgColor...");
|
||||||
if (fgColor != NULL) delete fgColor;
|
if (fgColor != NULL) delete fgColor;
|
||||||
|
|
||||||
LOGln("Animation * Deleting bgColor...");
|
DBG("Animation * Deleting bgColor...");
|
||||||
if (bgColor != NULL) delete bgColor;
|
if (bgColor != NULL) delete bgColor;
|
||||||
|
|
||||||
LOGln("Animation * Deleting _frame_data_lengths...");
|
DBG("Animation * Deleting _frame_data_lengths...");
|
||||||
if (_frame_data_lengths) delete [] _frame_data_lengths;
|
if (_frame_data_lengths) delete[] _frame_data_lengths;
|
||||||
|
|
||||||
LOGln("Animation * Deleting _frame_times...");
|
DBG("Animation * Deleting _frame_times...");
|
||||||
if (_frame_times) delete [] _frame_times;
|
if (_frame_times) delete[] _frame_times;
|
||||||
for (int i=0; i<_frame_count; i++) {
|
for (int i=0; i<_frame_count; i++) {
|
||||||
delete [] _frame_data[i];
|
delete[] _frame_data[i];
|
||||||
}
|
}
|
||||||
|
DBG("Animation * Deleting _frame_data...");
|
||||||
LOGln("Animation * Deleting _frame_data...");
|
if (_frame_data) delete[] _frame_data;
|
||||||
if (_frame_data) delete [] _frame_data;
|
|
||||||
|
|
||||||
LOGln("Animation * Deletion done.");
|
LOGln("Animation * Deletion done.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
35
src/SimpleEffect.cpp
Normal file
35
src/SimpleEffect.cpp
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
#include "SimpleEffect.h"
|
||||||
|
|
||||||
|
void SimpleEffect::loop(uint16_t ms) {
|
||||||
|
if (_flags & SE_FADEOUT) window->fadeToBlackBy(3);
|
||||||
|
double t = 0.001 * millis();
|
||||||
|
for(uint8_t x=0; x<window->width; x++) for(uint8_t y=0; y<window->height; y++) {
|
||||||
|
uint16_t i = y*window->width + x;
|
||||||
|
double r = _method(t, i, x, y);
|
||||||
|
//if (i==0) Serial.printf("t=%f i=%d x=%d y=%d => r=%f, abs(r)=%d\n", t, i, x, y, r, abs(r)*255);
|
||||||
|
if ((_flags & SE_DEBUG) && i==17) Serial.printf("t=%f i=%d x=%d y=%d => r=%f, abs(r*255)=%d\n", t, i, x, y, r, (int)abs(r*255));
|
||||||
|
if ((_flags & SE_FADEOUT) && r==0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clamp r between -1.0 and +1.0
|
||||||
|
if (r<-1.0) {
|
||||||
|
r = -1.0;
|
||||||
|
} else if (r>1.0) {
|
||||||
|
r = 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_flags & SE_ONLY_POSITIVE) {
|
||||||
|
r = abs(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
CRGB color;
|
||||||
|
if (_flags & SE_RANDOM_PIXEL_COLORS) {
|
||||||
|
color = CHSV(random8(), 255, abs(r*255));
|
||||||
|
} else {
|
||||||
|
color = CHSV(_flags & SE_CYCLE_COLORS ? baseHue : _color, r<0?0:255, abs(r*255));
|
||||||
|
}
|
||||||
|
|
||||||
|
window->setPixel(x, y, &color);
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,9 @@
|
|||||||
#include "Window.h"
|
#include "Window.h"
|
||||||
#include "functions.h"
|
#include "functions.h"
|
||||||
|
|
||||||
Window* Window::getFullWindow() {
|
Window Window::window_full = Window();
|
||||||
static Window win;
|
Window Window::window_with_clock = Window(0, 0, LED_WIDTH, LED_HEIGHT-6);
|
||||||
return &win;
|
Window Window::window_clock = Window(0, LED_HEIGHT-6, LED_WIDTH, 6);
|
||||||
}
|
|
||||||
|
|
||||||
void Window::setPixel(uint8_t x, uint8_t y, CRGB* color) {
|
void Window::setPixel(uint8_t x, uint8_t y, CRGB* color) {
|
||||||
if (x>=this->width || y>=this->height) return;
|
if (x>=this->width || y>=this->height) return;
|
||||||
@ -150,26 +149,29 @@ void Window::fadeToBlackBy(fract8 speed) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Window::line(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, CRGB* color) {
|
void Window::line(saccum78 x1, saccum78 y1, saccum78 x2, saccum78 y2, CRGB* color) {
|
||||||
// Bresenham algorithm
|
// Bresenham algorithm
|
||||||
int dx = x2-x1;
|
const uint8_t stepsize = 64;
|
||||||
int dy = y2-y1;
|
saccum78 dx = abs(x2 - x1);
|
||||||
|
saccum78 dy = -abs(y2 - y1);
|
||||||
int x = x1;
|
int8_t sx = x1<x2 ? 1 : -1;
|
||||||
int y = y1;
|
int8_t sy = y1<y2 ? 1 : -1;
|
||||||
|
saccum78 err = dx + dy;
|
||||||
int p = 2*dy - dx;
|
saccum78 e2;
|
||||||
|
uint8_t step = 0;
|
||||||
while (x < x2) {
|
while (1) {
|
||||||
if (p >= 0) {
|
if (step == 0) setSubPixel(x1, y1, color, SUBPIXEL_RENDERING_RAISE);
|
||||||
setPixel(x, y, color);
|
if (++step >= stepsize) step=0;
|
||||||
y++;
|
if (x1>>8==x2>>8 && y1>>8==y2>>8) break;
|
||||||
p = p + 2*dy - 2*dx;
|
e2 = 2*err;
|
||||||
} else {
|
if (e2 > dy) {
|
||||||
setPixel(x, y, color);
|
err += dy;
|
||||||
p = p + 2*dy;
|
x1 += sx;
|
||||||
|
}
|
||||||
|
if (e2 < dx) {
|
||||||
|
err += dx;
|
||||||
|
y1 += sy;
|
||||||
}
|
}
|
||||||
x++;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -201,22 +203,28 @@ void Window::circle(uint8_t x0, uint8_t y0, uint8_t radius, CRGB* color) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Window::lineWithAngle(uint8_t x, uint8_t y, uint8_t angle, uint8_t length, CRGB* color) {
|
void Window::lineWithAngle(uint8_t x, uint8_t y, uint16_t angle, uint8_t length, CRGB* color) {
|
||||||
lineWithAngle(x, y, angle, 0, length, color);
|
lineWithAngle(x, y, angle, 0, length, color);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Window::lineWithAngle(uint8_t x, uint8_t y, uint8_t angle, uint8_t startdist, uint8_t length, CRGB* color) {
|
void Window::lineWithAngle(uint8_t x, uint8_t y, uint16_t angle, uint8_t startdist, uint8_t length, CRGB* color) {
|
||||||
int16_t x1 = x;
|
//LOGln("lineWithAngle called. x: %d.%03d, y: %d.%03d, angle: %d", x>>8, x&0xFF, y>>8, y&0xFF, angle);
|
||||||
int16_t y1 = y;
|
saccum78 x1 = x<<8;
|
||||||
|
saccum78 y1 = y<<8;
|
||||||
|
|
||||||
if (startdist > 0) {
|
if (startdist > 0) {
|
||||||
x1 = x + scale8(startdist, cos8(angle));
|
x1 = (x<<8) + (startdist<<8) * cos16(angle) / 0x10000;
|
||||||
y1 = y + scale8(startdist, sin8(angle));
|
y1 = (y<<8) + (startdist<<8) * sin16(angle) / 0x10000;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (length==0) {
|
||||||
|
setSubPixel(x1, y1, color);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int16_t x2 = x + scale8(startdist + length, cos8(angle));
|
saccum78 x2 = (x<<8) + ((startdist + length)<<8) * cos16(angle) / 0x10000;
|
||||||
int16_t y2 = y + scale8(startdist + length, sin8(angle));
|
saccum78 y2 = (y<<8) + ((startdist + length)<<8) * sin16(angle) / 0x10000;
|
||||||
|
//LOGln("x1: %d.%03d, y1: %d.%03d, x2: %d.%03d, y2: %d.%03d", x1>>8, x1&0xFF, y1>>8, y1&0xFF, x2>>8, x2&0xFF, y2>>8, y2&0xFF);
|
||||||
line(x1, y1, x2, y2, color);
|
line(x1, y1, x2, y2, color);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -264,3 +272,14 @@ void Window::_subpixel_render(uint8_t x, uint8_t y, CRGB* color, SubpixelRenderi
|
|||||||
case SUBPIXEL_RENDERING_SET: setPixel(x, y, color); break;
|
case SUBPIXEL_RENDERING_SET: setPixel(x, y, color); break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Window::fill_with_checkerboard() {
|
||||||
|
CRGB pink(0xFF00FF);
|
||||||
|
CRGB black(0x000000);
|
||||||
|
uint8_t s = (uint8_t)(millis() / 1000);
|
||||||
|
for(int x=0; x<this->width; x++) {
|
||||||
|
for(int y=0; y<this->height; y++) {
|
||||||
|
this->setPixel(x, y, ((x+y+s) % 2)?&pink:&black);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -9,3 +9,14 @@ __attribute__ ((aligned(4))) extern const TProgmemRGBGradientPalette_byte palett
|
|||||||
__attribute__ ((aligned(4))) extern const TProgmemRGBGradientPalette_byte palette_matrix[] FL_PROGMEM = {
|
__attribute__ ((aligned(4))) extern const TProgmemRGBGradientPalette_byte palette_matrix[] FL_PROGMEM = {
|
||||||
0, 0, 0, 0, // black
|
0, 0, 0, 0, // black
|
||||||
255, 0,255, 0 }; // green
|
255, 0,255, 0 }; // green
|
||||||
|
|
||||||
|
// Gradient palette "purplefly_gp", originally from
|
||||||
|
// http://soliton.vm.bytemark.co.uk/pub/cpt-city/rc/tn/purplefly.png.index.html
|
||||||
|
// converted for FastLED with gammas (2.6, 2.2, 2.5)
|
||||||
|
// Size: 16 bytes of program space.
|
||||||
|
|
||||||
|
__attribute__ ((aligned(4))) extern const TProgmemRGBGradientPalette_byte palette_purplefly_gp[] FL_PROGMEM = {
|
||||||
|
0, 0, 0, 0,
|
||||||
|
63, 239, 0,122,
|
||||||
|
191, 252,255, 78,
|
||||||
|
255, 0, 0, 0};
|
||||||
|
@ -1,89 +0,0 @@
|
|||||||
#include "Effect.h"
|
|
||||||
#include "effect_big_clock.h"
|
|
||||||
#include "fonts.h"
|
|
||||||
#include "ntp.h"
|
|
||||||
|
|
||||||
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);
|
|
||||||
window->drawChar(&font_numbers3x5_blocky, 11<<8, 2<<8, '0' + (h % 10), &_color_font);
|
|
||||||
|
|
||||||
uint8_t m = ntpClient.getMinutes();
|
|
||||||
window->drawChar(&font_numbers3x5_blocky, 6<<8, 9<<8, '0' + (m / 10), &_color_font);
|
|
||||||
window->drawChar(&font_numbers3x5_blocky, 11<<8, 9<<8, '0' + (m % 10), &_color_font);
|
|
||||||
|
|
||||||
uint8_t s = ntpClient.getSeconds();
|
|
||||||
if (s & 1) {
|
|
||||||
window->setPixel(3, 10, &_color_font);
|
|
||||||
window->setPixel(3, 12, &_color_font);
|
|
||||||
}
|
|
||||||
|
|
||||||
_draw_seconds();
|
|
||||||
}
|
|
||||||
|
|
||||||
void BigClockEffect::_draw_seconds() {
|
|
||||||
uint8_t seconds = ntpClient.getSeconds();
|
|
||||||
for (int i=1; i<=seconds; i++) {
|
|
||||||
_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, uint8_t part, CRGB* color) {
|
|
||||||
/*uint8_t x, y;
|
|
||||||
if (i<=8) {
|
|
||||||
x = 7 + i;
|
|
||||||
y = 0;
|
|
||||||
} else if (i<=23) {
|
|
||||||
x = 15;
|
|
||||||
y = i - 8;
|
|
||||||
} else if (i<= 38) {
|
|
||||||
x = 15 - i + 23;
|
|
||||||
y = 15;
|
|
||||||
} else if (i <= 53) {
|
|
||||||
x = 0;
|
|
||||||
y = 15 - i + 38;
|
|
||||||
} else if (i <= 60) {
|
|
||||||
x = i - 53;
|
|
||||||
y = 0;
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
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);
|
|
||||||
}
|
|
@ -1,31 +0,0 @@
|
|||||||
#include "effect_blur2d.h"
|
|
||||||
|
|
||||||
boolean Blur2DEffect::can_be_shown_with_clock() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Blur2DEffect::loop(uint16_t ms) {
|
|
||||||
uint8_t blur_amount = dim8_raw(beatsin8(3, 128, 224));
|
|
||||||
window->blur(blur_amount);
|
|
||||||
|
|
||||||
uint8_t x1 = beatsin8(7, 0, window->width-1);
|
|
||||||
uint8_t y1 = beatsin8(11, 0, window->height-1);
|
|
||||||
|
|
||||||
uint8_t x2 = beatsin8(13, 0, window->width-1);
|
|
||||||
uint8_t y2 = beatsin8(8, 0, window->height-1);
|
|
||||||
|
|
||||||
uint8_t x3 = beatsin8(11, 0, window->width-1);
|
|
||||||
uint8_t y3 = beatsin8(13, 0, window->height-1);
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
Blur2DEffect::~Blur2DEffect() {
|
|
||||||
delete window;
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
#include "effect_confetti.h"
|
|
||||||
#include "config.h"
|
|
||||||
#include "functions.h"
|
|
||||||
#include "prototypes.h"
|
|
||||||
|
|
||||||
void ConfettiEffect::loop(uint16_t ms) {
|
|
||||||
window->fadeToBlackBy(3);
|
|
||||||
for (int i=0; i<settings.effects.confetti.pixels_per_loop; i++) {
|
|
||||||
CRGB color = _getColor();
|
|
||||||
window->addPixelColor(random16(LED_COUNT), &color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CRGB ConfettiEffect::_getColor() {
|
|
||||||
return CHSV(baseHue + random8(64), 255, 255);
|
|
||||||
}
|
|
||||||
|
|
||||||
CRGB RandomConfettiEffect::_getColor() {
|
|
||||||
return CHSV(random8(), 255, 255);
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean ConfettiEffect::can_be_shown_with_clock() { return true; };
|
|
@ -1,64 +0,0 @@
|
|||||||
#include "effect_snake.h"
|
|
||||||
#include "functions.h"
|
|
||||||
|
|
||||||
SnakeEffect::SnakeEffect() {
|
|
||||||
this->coords = {0, 0};
|
|
||||||
this->window = new Window(0, 0, LED_WIDTH, LED_HEIGHT-6);
|
|
||||||
}
|
|
||||||
|
|
||||||
SnakeEffect::~SnakeEffect() {
|
|
||||||
delete window;
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
window->fadeToBlackBy(2);
|
|
||||||
CRGB color(CHSV(hue, 200, 255));
|
|
||||||
window->setPixel(this->coords.x, this->coords.y, &color);
|
|
||||||
hue++;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SnakeEffect::turn_random() {
|
|
||||||
if ((random8() & 1) == 0) {
|
|
||||||
turn_right() || turn_left();
|
|
||||||
} else {
|
|
||||||
turn_left() || turn_right();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SnakeEffect::turn_left() {
|
|
||||||
if (!is_direction_okay(this->direction - 1)) return false;
|
|
||||||
this->direction--;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SnakeEffect::turn_right() {
|
|
||||||
if (!is_direction_okay(this->direction + 1)) return false;
|
|
||||||
this->direction++;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SnakeEffect::is_turn_needed() {
|
|
||||||
return !is_direction_okay(this->direction);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SnakeEffect::is_direction_okay(uint8_t dir) {
|
|
||||||
Coords c = update_position(this->coords, dir);
|
|
||||||
return c.x<window->width && c.y<window->height;
|
|
||||||
}
|
|
||||||
|
|
||||||
Coords SnakeEffect::update_position(Coords original, uint8_t direction) {
|
|
||||||
direction = direction % 4;
|
|
||||||
if (direction == 0) original.y--;
|
|
||||||
else if (direction == 1) original.x++;
|
|
||||||
else if (direction == 2) original.y++;
|
|
||||||
else if (direction == 3) original.x--;
|
|
||||||
return original;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean SnakeEffect::can_be_shown_with_clock() { return true; }
|
|
118
src/effects.cpp
118
src/effects.cpp
@ -1,60 +1,82 @@
|
|||||||
#include "effects.h"
|
#include "effects.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "my_fastled.h"
|
#include "my_fastled.h"
|
||||||
#include "effect_bell.h"
|
#include "effects/bell.h"
|
||||||
#include "effect_sinematrix3.h"
|
#include "effects/sinematrix3.h"
|
||||||
#include "effect_big_clock.h"
|
#include "effects/big_clock.h"
|
||||||
#include "effect_clock.h"
|
#include "effects/clock.h"
|
||||||
#include "effect_static.h"
|
#include "effects/static.h"
|
||||||
#include "effect_animation.h"
|
#include "effects/animation.h"
|
||||||
#include "effect_dynamic.h"
|
#include "effects/dynamic.h"
|
||||||
#include "effect_matrix.h"
|
#include "effects/matrix.h"
|
||||||
#include "effect_twirl.h"
|
#include "effects/twirl.h"
|
||||||
#include "effect_cycle.h"
|
#include "effects/cycle.h"
|
||||||
#include "effect_confetti.h"
|
#include "effects/snake.h"
|
||||||
#include "effect_snake.h"
|
#include "effects/fire.h"
|
||||||
#include "effect_fire.h"
|
#include "effects/firework.h"
|
||||||
#include "effect_firework.h"
|
#include "effects/gol.h"
|
||||||
#include "effect_gol.h"
|
#include "effects/pixelclock.h"
|
||||||
#include "effect_pixelclock.h"
|
#include "effects/dvd.h"
|
||||||
#include "effect_dvd.h"
|
#include "effects/analogclock.h"
|
||||||
#include "effect_analogclock.h"
|
#include "effects/sines.h"
|
||||||
#include "effect_sines.h"
|
#include "effects/marquee.h"
|
||||||
#include "effect_marquee.h"
|
#include "effects/blur2d.h"
|
||||||
#include "effect_blur2d.h"
|
#include "effects/tv_static.h"
|
||||||
|
#include "effects/lightspeed.h"
|
||||||
|
#include "effects/diamond.h"
|
||||||
|
#include "effects/tpm2_net.h"
|
||||||
|
#include "SimpleEffect.h"
|
||||||
|
#include "effects/night_clock.h"
|
||||||
|
|
||||||
Effect* current_effect;
|
Effect* current_effect;
|
||||||
|
|
||||||
ClockEffect effect_clock;
|
ClockEffect effect_clock;
|
||||||
|
TimerEffect effect_timer;
|
||||||
|
|
||||||
const EffectEntry effects[] = {
|
// We're using 0 instead of false to get a better visual difference between true and false.
|
||||||
/* 0 */ {"sinematrix3", true, [](){ return new Sinematrix3Effect(); }},
|
EffectEntry effects[] = {
|
||||||
/* 1 */ {"big_clock", true, [](){ return new BigClockEffect(); }},
|
/* 0 */ {"sinematrix3", true, [](){ return new Sinematrix3Effect(); }},
|
||||||
/* 2 */ {"clock", false, [](){ return new ClockEffect(); }},
|
/* 1 */ {"big_clock", true, [](){ return new BigClockEffect(); }},
|
||||||
/* 3 */ {"bell", false, [](){ return new BellEffect(); }},
|
/* 2 */ {"clock", 0, [](){ return new ClockEffect(); }},
|
||||||
/* 4 */ {"off", false, [](){ return new StaticEffect(0x000000); }},
|
/* 3 */ {"bell", 0, [](){ return AnimationEffect::Blinker("/bell.pia", 300, 0xFFFF00); }},
|
||||||
/* 5 */ {"single_dynamic", true, [](){ return new SingleDynamicEffect(); }},
|
/* 4 */ {"off", 0, [](){ return new StaticEffect(0x000000); }},
|
||||||
/* 6 */ {"multi_dynamic", true, [](){ return new MultiDynamicEffect(); }},
|
/* 5 */ {"single_dynamic", true, [](){ return new SingleDynamicEffect(); }},
|
||||||
/* 7 */ {"big_dynamic", true, [](){ return new BigDynamicEffect(); }},
|
/* 6 */ {"multi_dynamic", true, [](){ return new MultiDynamicEffect(); }},
|
||||||
/* 8 */ {"matrix", true, [](){ return new MatrixEffect(); }},
|
/* 7 */ {"big_dynamic", true, [](){ return new BigDynamicEffect(); }},
|
||||||
/* 9 */ {"random_matrix", true, [](){ return new RandomMatrixEffect(); }},
|
/* 8 */ {"matrix", true, [](){ return new MatrixEffect(); }},
|
||||||
/* 10 */ {"rainbow_matrix", true, [](){ return new RainbowMatrixEffect(); }},
|
/* 9 */ {"random_matrix", true, [](){ return new RandomMatrixEffect(); }},
|
||||||
/* 11 */ {"cycle", false, [](){ return new CycleEffect(); }},
|
/* 10 */ {"rainbow_matrix", true, [](){ return new RainbowMatrixEffect(); }},
|
||||||
/* 12 */ {"twirl", true, [](){ return new TwirlEffect(); }},
|
/* 11 */ {"cycle", 0, [](){ return new CycleEffect(); }},
|
||||||
/* 13 */ {"confetti", true, [](){ return new ConfettiEffect(); }},
|
/* 12 */ {"twirl", true, [](){ return new TwirlEffect(); }},
|
||||||
/* 14 */ {"random_confetti", true, [](){ return new RandomConfettiEffect(); }},
|
/* 13 */ SIMPLE_EFFECT("confetti", true, SE_CYCLE_COLORS | SE_FADEOUT, {return random8()>252?1:0;}),
|
||||||
/* 15 */ {"snake", true, [](){ return new SnakeEffect(); }},
|
/* 14 */ SIMPLE_EFFECT("rainbow_confetti", true, SE_RANDOM_PIXEL_COLORS | SE_FADEOUT, {return random8()>252?1:0;}),
|
||||||
/* 16 */ {"firework", true, [](){ return new FireworkEffect(); }},
|
/* 15 */ {"snake", true, [](){ return new SnakeEffect(); }},
|
||||||
/* 17 */ {"gol", true, [](){ return new GolEffect(); }},
|
/* 16 */ {"firework", true, [](){ return new FireworkEffect(); }},
|
||||||
/* 18 */ {"pixel_clock", false, [](){ return new PixelClockEffect(); }},
|
/* 17 */ {"gol", true, [](){ return new GolEffect(); }},
|
||||||
/* 19 */ {"dvd", false, [](){ return new DvdEffect(); }},
|
/* 18 */ {"pixel_clock", 0, [](){ return new PixelClockEffect(); }},
|
||||||
/* 20 */ {"analog_clock", false, [](){ return new AnalogClockEffect(); }},
|
/* 19 */ {"dvd", true, [](){ return new DvdEffect(); }},
|
||||||
/* 21 */ {"sines", true, [](){ return new SinesEffect(); }},
|
/* 20 */ {"analog_clock", 0, [](){ return new AnalogClockEffect(); }},
|
||||||
/* 22 */ {"blur2d", true, [](){ return new Blur2DEffect(); }},
|
/* 21 */ {"sines", true, [](){ return new SinesEffect(); }},
|
||||||
/* 23 */ {"marquee", 0, [](){ return new MarqueeEffect(); }},
|
/* 22 */ {"blur2d", true, [](){ return new Blur2DEffect(); }},
|
||||||
/* 24 */ {"night_clock", false, [](){ return new NightClockEffect(); }}
|
/* 23 */ {"marquee", 0, [](){ return new MarqueeEffect(); }},
|
||||||
|
/* 24 */ {"night_clock", 0, [](){ return new NightClockEffect(); }},
|
||||||
|
/* 25 */ {"tv_static", 0, [](){ return new TvStaticEffect(); }},
|
||||||
|
/* 26 */ {"sinematrix3_rainbow", true, [](){ return new Sinematrix3Effect(SINEMATRIX_COLOR_RAINBOW); }},
|
||||||
|
/* 27 */ {"sinematrix3_purplefly", true, [](){ return new Sinematrix3Effect(SINEMATRIX_COLOR_PURPLEFLY); }},
|
||||||
|
/* 28 */ {"lightspeed", true, [](){ return new LightspeedEffect(); }},
|
||||||
|
/* 29 */ {"koopa", 0, [](){ return new AnimationEffect("/koopa.pia"); }},
|
||||||
|
/* 30 */ {"cake", 0, [](){ return new AnimationEffect("/cake.pia"); }},
|
||||||
|
/* 31 */ {"child", 0, [](){ return AnimationEffect::Blinker("/child.pia", 300, 0xFFFF00); }},
|
||||||
|
/* 32 */ {"diamond", true, [](){ return new DiamondEffect(); }},
|
||||||
|
/* 33 */ {"tpm2.net", 0, [](){ return new Tpm2NetEffect(); }},
|
||||||
|
/* 34 */ SIMPLE_EFFECT("slow_blinking", true, SE_CYCLE_COLORS, {return sin(t + (x+1)*(y+1)*i);} ),
|
||||||
|
/* 35 */ SIMPLE_EFFECT("upwave", true, SE_CYCLE_COLORS, {return (cos(t+y/2));} ),
|
||||||
|
/* 36 */ SIMPLE_EFFECT("centerwave", true, SE_CYCLE_COLORS, {return sin(t*2 - sqrt((x-4)*(x-4) + (y-7)*(y-7)));} ),
|
||||||
|
/* 37 */ SIMPLE_EFFECT("sineline", true, SE_RANDOM_STATIC_COLOR, {return sin(x/2)-sin(x-t)-y+6;} ),
|
||||||
|
/* 38 */ SIMPLE_EFFECT("barbershop", true, SE_RANDOM_STATIC_COLOR, {return 1*cos(0.8*i-t*5);} ),
|
||||||
|
/* 39 */ SIMPLE_EFFECT("zigzag", true, SE_CYCLE_COLORS, { return cos(cos(x+y)-y*cos(t/8+x/16));} ),
|
||||||
};
|
};
|
||||||
const uint8_t effects_size = 25;
|
const uint8_t effects_size = 40;
|
||||||
|
|
||||||
|
|
||||||
Effect* select_effect(const char* name) {
|
Effect* select_effect(const char* name) {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
#include "effect_analogclock.h"
|
#include "effects/analogclock.h"
|
||||||
#include "my_fastled.h"
|
#include "my_fastled.h"
|
||||||
#include "ntp.h"
|
#include "ntp.h"
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
void AnalogClockEffect::loop(uint16_t ms) {
|
void AnalogClockEffect::loop(uint16_t ms) {
|
||||||
window->clear();
|
window->clear();
|
||||||
@ -8,9 +9,18 @@ void AnalogClockEffect::loop(uint16_t ms) {
|
|||||||
CRGB red(0xFF0000);
|
CRGB red(0xFF0000);
|
||||||
window->circle(8, 8, 7, &white);
|
window->circle(8, 8, 7, &white);
|
||||||
|
|
||||||
uint8_t seconds = ntpClient.getSeconds();
|
time_t now;
|
||||||
uint8_t angle = 6 * seconds;
|
tm timeinfo;
|
||||||
window->lineWithAngle(8, 8, angle, 0, 10, &red);
|
time(&now);
|
||||||
|
localtime_r(&now, &timeinfo);
|
||||||
|
uint16_t seconds = timeinfo.tm_sec * 1000 + (millis()%1000);
|
||||||
|
uint16_t angle = seconds * 0x10000 / 60000;
|
||||||
|
window->lineWithAngle(8, 8, angle, 12, &red);
|
||||||
|
//window->line(0<<8, 0<<8, 7<<8, 7<<8, &white);
|
||||||
|
//window->line(15<<8, 0<<8, 8<<8, 7<<8, &red);
|
||||||
|
//window->line(0<<8, 15<<8, 7<<8, 8<<8, &blue);
|
||||||
|
//window->line(15<<8, 15<<8, 8<<8, 8<<8, &green);
|
||||||
|
|
||||||
/*for (uint8_t i=0; i<=12; i++) {
|
/*for (uint8_t i=0; i<=12; i++) {
|
||||||
window->lineWithAngle(8, 8, 255/12*i, 5, 2, &white);
|
window->lineWithAngle(8, 8, 255/12*i, 5, 2, &white);
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
#include "effect_animation.h"
|
#include "effects/animation.h"
|
||||||
#include "functions.h"
|
#include "functions.h"
|
||||||
|
|
||||||
AnimationEffect::AnimationEffect(const char* name, uint32_t bg, int x, int y) {
|
AnimationEffect::AnimationEffect(const char* name, uint32_t bg, int x, int y) {
|
||||||
@ -9,6 +9,8 @@ AnimationEffect::AnimationEffect(const char* name, uint32_t bg, int x, int y) {
|
|||||||
this->animation = new Animation(name, window);
|
this->animation = new Animation(name, window);
|
||||||
this->animation->setBgColor(bg);
|
this->animation->setBgColor(bg);
|
||||||
this->animation->setOffsets(this->xOffset, this->yOffset);
|
this->animation->setOffsets(this->xOffset, this->yOffset);
|
||||||
|
|
||||||
|
_last_blink_at = millis();
|
||||||
}
|
}
|
||||||
|
|
||||||
AnimationEffect* AnimationEffect::setFgColor(uint32_t c) {
|
AnimationEffect* AnimationEffect::setFgColor(uint32_t c) {
|
||||||
@ -16,11 +18,23 @@ AnimationEffect* AnimationEffect::setFgColor(uint32_t c) {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AnimationEffect* AnimationEffect::setBlinkFrequency(uint16_t ms) {
|
||||||
|
_blink_freq = ms;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
AnimationEffect::~AnimationEffect() {
|
AnimationEffect::~AnimationEffect() {
|
||||||
delete this->animation;
|
delete this->animation;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AnimationEffect::loop(uint16_t ms) {
|
void AnimationEffect::loop(uint16_t ms) {
|
||||||
|
if (_blink_freq > 0) {
|
||||||
|
unsigned long mil = millis();
|
||||||
|
if (mil < _last_blink_at || _last_blink_at + _blink_freq <= mil) {
|
||||||
|
this->animation->invert();
|
||||||
|
_last_blink_at = mil;
|
||||||
|
}
|
||||||
|
}
|
||||||
this->animation->drawFrame();
|
this->animation->drawFrame();
|
||||||
this->animation->advance();
|
this->animation->advance();
|
||||||
}
|
}
|
||||||
@ -30,3 +44,10 @@ String AnimationEffect::get_name() {
|
|||||||
s += this->name;
|
s += this->name;
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AnimationEffect* AnimationEffect::Blinker(const char* name, uint16_t interval, uint32_t color, uint32_t background_color) {
|
||||||
|
AnimationEffect* anim = new AnimationEffect(name, background_color);
|
||||||
|
anim->setFgColor(color);
|
||||||
|
anim->setBlinkFrequency(interval);
|
||||||
|
return anim;
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
#include "Effect.h"
|
#include "Effect.h"
|
||||||
#include "functions.h"
|
#include "functions.h"
|
||||||
#include "effect_bell.h"
|
#include "effects/bell.h"
|
||||||
#include "sprites.h"
|
#include "sprites.h"
|
||||||
|
|
||||||
void BellEffect::loop(uint16_t ms) {
|
void BellEffect::loop(uint16_t ms) {
|
74
src/effects/big_clock.cpp
Normal file
74
src/effects/big_clock.cpp
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
#include "Effect.h"
|
||||||
|
#include "effects/big_clock.h"
|
||||||
|
#include "fonts.h"
|
||||||
|
#include <time.h>
|
||||||
|
#include "settings.h"
|
||||||
|
|
||||||
|
void BigClockEffect::loop(uint16_t ms) {
|
||||||
|
window->clear();
|
||||||
|
time_t now;
|
||||||
|
tm timeinfo;
|
||||||
|
time(&now);
|
||||||
|
localtime_r(&now, &timeinfo);
|
||||||
|
uint8_t h = timeinfo.tm_hour;
|
||||||
|
CRGB color = _get_color_font();
|
||||||
|
window->drawChar(&font_numbers3x5_blocky, 6<<8, 2<<8, '0' + (h / 10), &color);
|
||||||
|
window->drawChar(&font_numbers3x5_blocky, 11<<8, 2<<8, '0' + (h % 10), &color);
|
||||||
|
|
||||||
|
uint8_t m = timeinfo.tm_min;
|
||||||
|
window->drawChar(&font_numbers3x5_blocky, 6<<8, 9<<8, '0' + (m / 10), &color);
|
||||||
|
window->drawChar(&font_numbers3x5_blocky, 11<<8, 9<<8, '0' + (m % 10), &color);
|
||||||
|
|
||||||
|
uint8_t s = timeinfo.tm_sec;
|
||||||
|
_draw_colon(s & 1);
|
||||||
|
|
||||||
|
_draw_seconds(timeinfo.tm_sec);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BigClockEffect::_draw_colon(bool odd) {
|
||||||
|
if (odd) {
|
||||||
|
CRGB color = _get_color_font();
|
||||||
|
window->setPixel(3, 10, &color);
|
||||||
|
window->setPixel(3, 12, &color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BigClockEffect::_draw_seconds(uint8_t seconds) {
|
||||||
|
for (int i=1; i<=seconds; i++) {
|
||||||
|
_draw_border_pixel(i<<8, (i%5==0) ? &_color_seconds_light : &_color_seconds_dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*timeval tv;
|
||||||
|
gettimeofday(&tv, nullptr);
|
||||||
|
uint16_t mil = (tv.tv_usec / 1000) % 1000;
|
||||||
|
accum88 pos = (seconds<<8) + ((settings.effects.big_clock.spacing-1)<<8) * (1000 - mil) / 1000 + (1<<8);
|
||||||
|
uint8_t sec = seconds + 1;
|
||||||
|
while (pos < (60<<8)) {
|
||||||
|
_draw_border_pixel(pos, sec%5==0 ? &_color_seconds_moving_light : &_color_seconds_moving_dark);
|
||||||
|
pos += settings.effects.big_clock.spacing<<8;
|
||||||
|
sec++;
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
|
||||||
|
void BigClockEffect::_draw_border_pixel(accum88 i, CRGB* color) {
|
||||||
|
accum88 x, y;
|
||||||
|
if (i<(8<<8)) {
|
||||||
|
x = i + (7<<8);
|
||||||
|
y = 0;
|
||||||
|
} else if (i<(23<<8)) {
|
||||||
|
x = 15<<8;
|
||||||
|
y = i - (8<<8);
|
||||||
|
} else if (i<(38<<8)) {
|
||||||
|
x = (38<<8) - i;
|
||||||
|
y = 15<<8;
|
||||||
|
} else if (i<(53<<8)) {
|
||||||
|
x = 0;
|
||||||
|
y = (53<<8) - i;
|
||||||
|
} else if (i<=(60<<8)) {
|
||||||
|
x = i - (53<<8);
|
||||||
|
y = 0;
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window->setSubPixel(x, y, color);
|
||||||
|
}
|
48
src/effects/blur2d.cpp
Normal file
48
src/effects/blur2d.cpp
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
#include "effects/blur2d.h"
|
||||||
|
Blur2DBlob::Blur2DBlob() {
|
||||||
|
_x_freq = random16(6<<8, 15<<8);
|
||||||
|
_y_freq = random16(6<<8, 15<<8);
|
||||||
|
_color_freq = random8(25, 80);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Blur2DBlob::render(Window* window) {
|
||||||
|
uint8_t x = beatsin16(_x_freq, 0, window->width-1);
|
||||||
|
uint8_t y = beatsin16(_y_freq, 0, window->height-1);
|
||||||
|
|
||||||
|
CRGB c = CHSV(millis() / _color_freq, 200, 255);
|
||||||
|
window->addPixelColor(x, y, &c);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
boolean Blur2DEffect::can_be_shown_with_clock() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Blur2DEffect::loop(uint16_t ms) {
|
||||||
|
if (_count != settings.effects.blur2d.count) {
|
||||||
|
_delete();
|
||||||
|
_init();
|
||||||
|
}
|
||||||
|
uint8_t blur_amount = dim8_raw(beatsin8(3, 128, 224));
|
||||||
|
window->blur(blur_amount);
|
||||||
|
for (int i=0; i<_count; i++) {
|
||||||
|
_blobs[i].render(window);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Blur2DEffect::Blur2DEffect() {
|
||||||
|
_init();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Blur2DEffect::_init() {
|
||||||
|
_count = settings.effects.blur2d.count;
|
||||||
|
_blobs = new Blur2DBlob[_count];
|
||||||
|
}
|
||||||
|
|
||||||
|
void Blur2DEffect::_delete() {
|
||||||
|
delete[] _blobs;
|
||||||
|
}
|
||||||
|
|
||||||
|
Blur2DEffect::~Blur2DEffect() {
|
||||||
|
_delete();
|
||||||
|
}
|
@ -1,21 +1,9 @@
|
|||||||
#include "effect_clock.h"
|
#include "effects/clock.h"
|
||||||
#include <FastLED.h>
|
#include <FastLED.h>
|
||||||
#include "functions.h"
|
#include "functions.h"
|
||||||
#include "fonts.h"
|
#include "fonts.h"
|
||||||
#include "ntp.h"
|
#include "ntp.h"
|
||||||
|
|
||||||
NightClockEffect::NightClockEffect() {
|
|
||||||
window = Window::getFullWindow();
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
uint8_t y = minutes % 10;
|
|
||||||
ClockEffect::loop(false, CRGB(0x200000), CRGB(0x000000), y);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ClockEffect::loop(uint16_t ms) {
|
void ClockEffect::loop(uint16_t ms) {
|
||||||
loop_with_invert(false);
|
loop_with_invert(false);
|
||||||
}
|
}
|
||||||
@ -42,23 +30,26 @@ void ClockEffect::loop(boolean invert, CRGB fg_color, CRGB bg_color, uint8_t yPo
|
|||||||
}
|
}
|
||||||
fg_color = bg_color;
|
fg_color = bg_color;
|
||||||
}
|
}
|
||||||
if (ntpClient.isTimeSet()==false && (ntpClient.getSeconds() & 1)==0) {
|
/*if (ntpClient.isTimeSet()==false && (ntpClient.getSeconds() & 1)==0) {
|
||||||
window->clear(&bg_color);
|
window->clear(&bg_color);
|
||||||
return;
|
return;
|
||||||
}
|
}*/
|
||||||
int h = ntpClient.getHours();
|
time_t now;
|
||||||
|
tm timeinfo;
|
||||||
|
time(&now);
|
||||||
|
localtime_r(&now, &timeinfo);
|
||||||
|
int h = timeinfo.tm_hour;
|
||||||
//void drawChar(Font f, uint8_t x, uint8_t y, const char c, CRGB* color, bool mask=false);
|
//void drawChar(Font f, uint8_t x, uint8_t y, const char c, CRGB* color, bool mask=false);
|
||||||
window->drawChar(&font_numbers3x5, 0<<8, yPos<<8, '0' + (h / 10), &fg_color, invert);
|
window->drawChar(&font_numbers3x5, 0<<8, yPos<<8, '0' + (h / 10), &fg_color, invert);
|
||||||
window->drawChar(&font_numbers3x5, 4<<8, yPos<<8, '0' + (h % 10), &fg_color, invert);
|
window->drawChar(&font_numbers3x5, 4<<8, yPos<<8, '0' + (h % 10), &fg_color, invert);
|
||||||
int m = ntpClient.getMinutes();
|
int m = timeinfo.tm_min;
|
||||||
window->drawChar(&font_numbers3x5, 9<<8, yPos<<8, '0' + (m / 10), &fg_color, invert);
|
window->drawChar(&font_numbers3x5, 9<<8, yPos<<8, '0' + (m / 10), &fg_color, invert);
|
||||||
window->drawChar(&font_numbers3x5, 13<<8, yPos<<8, '0' + (m % 10), &fg_color, invert);
|
window->drawChar(&font_numbers3x5, 13<<8, yPos<<8, '0' + (m % 10), &fg_color, invert);
|
||||||
if (ntpClient.getSeconds() & 1) {
|
if (timeinfo.tm_sec & 1) {
|
||||||
window->setPixel(7, yPos+1, &fg_color);
|
window->setPixel(7, yPos+1, &fg_color);
|
||||||
window->setPixel(7, yPos+3, &fg_color);
|
window->setPixel(7, yPos+3, &fg_color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ClockEffect::~ClockEffect() {
|
ClockEffect::~ClockEffect() {
|
||||||
delete window;
|
|
||||||
}
|
}
|
@ -1,6 +1,5 @@
|
|||||||
#include "effect_cycle.h"
|
#include "effects/cycle.h"
|
||||||
#include "effects.h"
|
#include "effects.h"
|
||||||
#include <ErriezCRC32.h>
|
|
||||||
|
|
||||||
CycleEffect::CycleEffect() {
|
CycleEffect::CycleEffect() {
|
||||||
_effects_count = 0;
|
_effects_count = 0;
|
||||||
@ -27,31 +26,46 @@ void CycleEffect::changeEffect() {
|
|||||||
LOGln("CycleEffect * Changing effect from #%d to #%d", effect_id, new_id);
|
LOGln("CycleEffect * Changing effect from #%d to #%d", effect_id, new_id);
|
||||||
delay(25);
|
delay(25);
|
||||||
|
|
||||||
if (effect) delete effect;
|
String old_effect_name = String("UNKNOWN");
|
||||||
|
if (effect) {
|
||||||
|
old_effect_name = effect->get_name();
|
||||||
|
delete effect;
|
||||||
|
}
|
||||||
|
|
||||||
int16_t diff;
|
int16_t diff = 0;
|
||||||
uint16_t old_heap = _heap_free;
|
uint16_t old_heap = _heap_free;
|
||||||
_heap_free = ESP.getFreeHeap();
|
_heap_free = ESP.getFreeHeap();
|
||||||
if (old_heap) {
|
if (old_heap) {
|
||||||
// diff positive = More heap used (baad)
|
// diff positive = More heap used (baad)
|
||||||
// diff negative = Less heap used (good-ish)
|
// diff negative = Less heap used (good-ish)
|
||||||
diff = old_heap - _heap_free;
|
diff = old_heap - _heap_free;
|
||||||
LOGln("CycleEffect * Heap usage: #%d,%d,%+d", effect_id, _heap_free, diff);
|
LOGln("CycleEffect * Heap usage: #%d,%s,%d,%+d", effect_id, old_effect_name.c_str(), _heap_free, diff);
|
||||||
}
|
}
|
||||||
|
|
||||||
delay(25);
|
delay(25);
|
||||||
LOGln("CycleEffect * Searching for new effect #%d", new_id);
|
LOGln("CycleEffect * Searching for new effect #%d", new_id);
|
||||||
uint8_t count = 0;
|
uint8_t count = 0;
|
||||||
|
EffectEntry* e = nullptr;
|
||||||
for (uint8_t i=0; i<effects_size; i++) {
|
for (uint8_t i=0; i<effects_size; i++) {
|
||||||
if (effects[i].use_in_cycle) {
|
if (effects[i].use_in_cycle) {
|
||||||
if (count == new_id) {
|
if (count == new_id) {
|
||||||
effect = effects[i].create();
|
e = &effects[i];
|
||||||
|
effect = e->create();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (effect) {
|
if (e) {
|
||||||
|
#ifdef MQTT_REPORT_METRICS
|
||||||
|
e->heap_change_sum += diff;
|
||||||
|
e->run_count++;
|
||||||
|
LOGln("CycleEffect * Last effect stats: name:%s, runs:%d, total_change:%d", old_effect_name.c_str(), e->run_count, e->heap_change_sum);
|
||||||
|
String topic = "metrics/effects/";
|
||||||
|
topic.concat(old_effect_name);
|
||||||
|
String message = String("runs:") + e->run_count + ", total_heap_change:" + e->heap_change_sum;
|
||||||
|
mqtt_publish(topic.c_str(), message.c_str(), true);
|
||||||
|
#endif
|
||||||
effect_id = new_id;
|
effect_id = new_id;
|
||||||
effectSince = millis();
|
effectSince = millis();
|
||||||
LOGln("CycleEffect * Effect %s found", effect->get_name().c_str());
|
LOGln("CycleEffect * Effect %s found", effect->get_name().c_str());
|
14
src/effects/diamond.cpp
Normal file
14
src/effects/diamond.cpp
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#include "effects/diamond.h"
|
||||||
|
#include "my_fastled.h"
|
||||||
|
|
||||||
|
void DiamondEffect::loop(uint16_t ms) {
|
||||||
|
for (int x=0; x<window->width; x++) {
|
||||||
|
for (int y=0; y<window->height; y++) {
|
||||||
|
uint8_t distance = abs(window->height/2 - y) + abs(window->width/2 - x);
|
||||||
|
CRGB col = CHSV(distance*8 - (millis()>>5)%255, 255, 255);
|
||||||
|
window->setPixel(x, y, &col);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DiamondEffect::can_be_shown_with_clock() { return true; }
|
@ -1,4 +1,4 @@
|
|||||||
#include "effect_dvd.h"
|
#include "effects/dvd.h"
|
||||||
#include "my_fastled.h"
|
#include "my_fastled.h"
|
||||||
|
|
||||||
void DvdEffect::loop(uint16_t ms) {
|
void DvdEffect::loop(uint16_t ms) {
|
||||||
@ -47,5 +47,4 @@ DvdEffect::DvdEffect() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
DvdEffect::~DvdEffect() {
|
DvdEffect::~DvdEffect() {
|
||||||
delete window;
|
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
#include "effect_dynamic.h"
|
#include "effects/dynamic.h"
|
||||||
#include "functions.h"
|
#include "functions.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
@ -36,7 +36,6 @@ void MultiDynamicEffect::loop(uint16_t ms) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
BigDynamicEffect::~BigDynamicEffect() {
|
BigDynamicEffect::~BigDynamicEffect() {
|
||||||
delete window;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void BigDynamicEffect::loop(uint16_t ms) {
|
void BigDynamicEffect::loop(uint16_t ms) {
|
@ -1,4 +1,4 @@
|
|||||||
#include "effect_fire.h"
|
#include "effects/fire.h"
|
||||||
#include "my_color_palettes.h"
|
#include "my_color_palettes.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "my_fastled.h"
|
#include "my_fastled.h"
|
@ -1,5 +1,5 @@
|
|||||||
// Based on https://gist.github.com/kriegsman/68929cbd1d6de4535b20
|
// Based on https://gist.github.com/kriegsman/68929cbd1d6de4535b20
|
||||||
#include "effect_firework.h"
|
#include "effects/firework.h"
|
||||||
|
|
||||||
FireworkEffectDot::FireworkEffectDot(Window* w, FireworkEffect* e) {
|
FireworkEffectDot::FireworkEffectDot(Window* w, FireworkEffect* e) {
|
||||||
_window = w;
|
_window = w;
|
||||||
@ -176,7 +176,6 @@ FireworkEffect::FireworkEffect() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
FireworkEffect::~FireworkEffect() {
|
FireworkEffect::~FireworkEffect() {
|
||||||
delete window;
|
|
||||||
for (int i=0; i<settings.effects.firework.sparks; i++) {
|
for (int i=0; i<settings.effects.firework.sparks; i++) {
|
||||||
delete _sparks[i];
|
delete _sparks[i];
|
||||||
}
|
}
|
@ -1,8 +1,8 @@
|
|||||||
#include "effect_gol.h"
|
#include "effects/gol.h"
|
||||||
#include "my_fastled.h"
|
#include "my_fastled.h"
|
||||||
|
|
||||||
GolEffect::GolEffect() {
|
GolEffect::GolEffect() {
|
||||||
this->window = new Window(0, 0, LED_WIDTH, LED_HEIGHT-6);
|
this->window = &Window::window_with_clock;
|
||||||
|
|
||||||
_data = new uint8_t[this->window->count];
|
_data = new uint8_t[this->window->count];
|
||||||
_old = new uint8_t[this->window->count];
|
_old = new uint8_t[this->window->count];
|
||||||
@ -26,7 +26,6 @@ void GolEffect::_initialize() {
|
|||||||
GolEffect::~GolEffect() {
|
GolEffect::~GolEffect() {
|
||||||
delete[] _data;
|
delete[] _data;
|
||||||
delete[] _old;
|
delete[] _old;
|
||||||
delete window;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GolEffect::loop(uint16_t ms) {
|
void GolEffect::loop(uint16_t ms) {
|
65
src/effects/lightspeed.cpp
Normal file
65
src/effects/lightspeed.cpp
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
#include "effects/lightspeed.h"
|
||||||
|
#include "config.h"
|
||||||
|
#include "functions.h"
|
||||||
|
#include "prototypes.h"
|
||||||
|
|
||||||
|
LightspeedEffect::LightspeedEffect() {
|
||||||
|
window = &Window::window_with_clock;
|
||||||
|
_init();
|
||||||
|
}
|
||||||
|
|
||||||
|
LightspeedEffect::~LightspeedEffect() {
|
||||||
|
_delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LightspeedEffect::_init() {
|
||||||
|
_count = settings.effects.lightspeed.count;
|
||||||
|
_stars = new LightspeedEffectStar[_count];
|
||||||
|
for (int i=0; i<_count; i++) {
|
||||||
|
_stars[i] = LightspeedEffectStar();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LightspeedEffect::_delete() {
|
||||||
|
delete[] _stars;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LightspeedEffect::loop(uint16_t ms) {
|
||||||
|
if (settings.effects.lightspeed.count != _count) {
|
||||||
|
_delete();
|
||||||
|
_init();
|
||||||
|
}
|
||||||
|
window->clear();
|
||||||
|
for (int i=0; i<_count; i++) {
|
||||||
|
_stars[i].loop(window);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean LightspeedEffect::can_be_shown_with_clock() { return true; };
|
||||||
|
|
||||||
|
LightspeedEffectStar::LightspeedEffectStar() {
|
||||||
|
_init();
|
||||||
|
_distance = random16(10<<8);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LightspeedEffectStar::_init() {
|
||||||
|
_angle = random16();
|
||||||
|
_distance = 0;
|
||||||
|
_speed = random16(128, 2<<8);
|
||||||
|
_target = random16(25<<8, 35<<8);
|
||||||
|
_saturation = random8(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LightspeedEffectStar::loop(Window* win) {
|
||||||
|
CRGB col = CHSV(192, _saturation, 255);
|
||||||
|
accum88 current_speed = _speed * beatsin16(0x100, 0, 65535) / 65535;
|
||||||
|
uint8_t length = (current_speed>>6);
|
||||||
|
if (_distance < (length<<8)) {
|
||||||
|
win->lineWithAngle(win->width/2, win->height/2, _angle, 0, _distance>>8, &col);
|
||||||
|
} else {
|
||||||
|
win->lineWithAngle(win->width/2, win->height/2, _angle, (_distance>>8) - length, length, &col);
|
||||||
|
}
|
||||||
|
_distance += current_speed;
|
||||||
|
//_angle+=8<<8;
|
||||||
|
if (_distance > _target) _init();
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
#include "effect_marquee.h"
|
#include "effects/marquee.h"
|
||||||
#include "fonts.h"
|
#include "fonts.h"
|
||||||
|
|
||||||
boolean MarqueeEffect::can_be_shown_with_clock() {
|
boolean MarqueeEffect::can_be_shown_with_clock() {
|
@ -1,4 +1,4 @@
|
|||||||
#include "effect_matrix.h"
|
#include "effects/matrix.h"
|
||||||
#include "my_color_palettes.h"
|
#include "my_color_palettes.h"
|
||||||
#include "functions.h"
|
#include "functions.h"
|
||||||
|
|
||||||
@ -47,7 +47,7 @@ void MatrixEffectColumn::advance(uint16_t ms) {
|
|||||||
switch(_direction) {
|
switch(_direction) {
|
||||||
case DIR_NORTH:
|
case DIR_NORTH:
|
||||||
y-=speed * ms;
|
y-=speed * ms;
|
||||||
if ((y>>8) > window->height && (y>>8) + length > window->height) running=false;
|
if ((y>>8) + length < 0) running=false;
|
||||||
break;
|
break;
|
||||||
case DIR_EAST:
|
case DIR_EAST:
|
||||||
x+=speed * ms;
|
x+=speed * ms;
|
||||||
@ -59,7 +59,7 @@ void MatrixEffectColumn::advance(uint16_t ms) {
|
|||||||
break;
|
break;
|
||||||
case DIR_WEST:
|
case DIR_WEST:
|
||||||
x-=speed * ms;
|
x-=speed * ms;
|
||||||
if ((x>>8) > window->width && (y>>8) + length > window->width) running=false;
|
if ((x>>8) + length < 0) running=false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -127,6 +127,17 @@ void RandomMatrixEffectColumn::restart(bool completely_random) {
|
|||||||
_hue = random8();
|
_hue = random8();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CRGB ColumnMatrixEffectColumn::_getColor(uint8_t i) {
|
||||||
|
CRGB color;
|
||||||
|
uint8_t dist = abs(length / 2 - i);
|
||||||
|
color = CHSV(_hue, 255, 255 - dist * 5);
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ColumnMatrixEffectColumn::restart(bool completely_random) {
|
||||||
|
MatrixEffectColumn::restart(completely_random);
|
||||||
|
_hue = random8();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -136,37 +147,71 @@ void RandomMatrixEffectColumn::restart(bool completely_random) {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
boolean MatrixEffect::can_be_shown_with_clock() { return true; };
|
|
||||||
|
boolean MatrixEffectBase::can_be_shown_with_clock() { return true; };
|
||||||
|
|
||||||
|
void MatrixEffectBase::_init() {
|
||||||
|
_count = _get_count();
|
||||||
|
_columns = new MatrixEffectColumn* [_count];
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t MatrixEffectBase::_get_count() { return settings.effects.matrix.count; }
|
||||||
|
|
||||||
MatrixEffect::MatrixEffect() {
|
MatrixEffect::MatrixEffect() {
|
||||||
_columns = new MatrixEffectColumn* [window->width];
|
_init();
|
||||||
for (int i=0; i<window->width; i++) _columns[i] = new MatrixEffectColumn(window, MatrixEffectColumn::DIR_SOUTH);
|
_create();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MatrixEffect::_create() {
|
||||||
|
for (int i=0; i<_count; i++) _columns[i] = new MatrixEffectColumn(window, MatrixEffectColumn::DIR_SOUTH);
|
||||||
}
|
}
|
||||||
|
|
||||||
RandomMatrixEffect::RandomMatrixEffect() {
|
RandomMatrixEffect::RandomMatrixEffect() {
|
||||||
// No need to initialize _columns, because that will have been done by ctor of MatrixEffect.
|
_init();
|
||||||
for (int i=0; i<window->width; i++) {
|
_create();
|
||||||
delete _columns[i];
|
|
||||||
_columns[i] = new RandomMatrixEffectColumn(window, random8(4), true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RandomMatrixEffect::_create() {
|
||||||
|
for (int i=0; i<_count; i++) _columns[i] = new RandomMatrixEffectColumn(window, random8(4), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t RandomMatrixEffect::_get_count() { return settings.effects.matrix.random_count; }
|
||||||
|
|
||||||
RainbowMatrixEffect::RainbowMatrixEffect() {
|
RainbowMatrixEffect::RainbowMatrixEffect() {
|
||||||
// No need to initialize _columns, because that will have been done by ctor of MatrixEffect.
|
_init();
|
||||||
for (int i=0; i<window->width; i++) {
|
_create();
|
||||||
delete _columns[i];
|
|
||||||
_columns[i] = new RainbowMatrixEffectColumn(window, MatrixEffectColumn::DIR_SOUTH);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MatrixEffect::~MatrixEffect() {
|
void RainbowMatrixEffect::_create() {
|
||||||
for (int i=0; i<window->width; i++) {
|
for (int i=0; i<_count; i++) _columns[i] = new RainbowMatrixEffectColumn(window, MatrixEffectColumn::DIR_SOUTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnMatrixEffect::ColumnMatrixEffect() {
|
||||||
|
_init();
|
||||||
|
_create();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ColumnMatrixEffect::_create() {
|
||||||
|
for (int i=0; i<_count; i++) _columns[i] = new ColumnMatrixEffectColumn(window, MatrixEffectColumn::DIR_NORTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
MatrixEffectBase::~MatrixEffectBase() {
|
||||||
|
_delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MatrixEffectBase::_delete() {
|
||||||
|
for (int i=0; i<_count; i++) {
|
||||||
delete _columns[i];
|
delete _columns[i];
|
||||||
}
|
}
|
||||||
delete[] _columns;
|
delete[] _columns;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MatrixEffect::loop(uint16_t ms) {
|
void MatrixEffectBase::loop(uint16_t ms) {
|
||||||
|
if (_count != _get_count()) {
|
||||||
|
_delete();
|
||||||
|
_init();
|
||||||
|
_create();
|
||||||
|
}
|
||||||
window->clear();
|
window->clear();
|
||||||
for (int i=0; i<window->width; i++) _columns[i]->loop(ms);
|
for (int i=0; i<_count; i++) _columns[i]->loop(ms);
|
||||||
}
|
}
|
26
src/effects/night_clock.cpp
Normal file
26
src/effects/night_clock.cpp
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
#include "Effect.h"
|
||||||
|
#include "effects/night_clock.h"
|
||||||
|
#include "fonts.h"
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
void NightClockEffect::loop(uint16_t ms) {
|
||||||
|
window->clear();
|
||||||
|
time_t now;
|
||||||
|
tm timeinfo;
|
||||||
|
time(&now);
|
||||||
|
localtime_r(&now, &timeinfo);
|
||||||
|
uint8_t h = timeinfo.tm_hour;
|
||||||
|
CRGB color = CRGB(0x440000);
|
||||||
|
window->drawChar(&font5x7, 4<<8, 0<<8, '0' + (h / 10), &color);
|
||||||
|
window->drawChar(&font5x7, 10<<8, 0<<8, '0' + (h % 10), &color);
|
||||||
|
|
||||||
|
uint8_t m = timeinfo.tm_min;
|
||||||
|
window->drawChar(&font5x7, 4<<8, 9<<8, '0' + (m / 10), &color);
|
||||||
|
window->drawChar(&font5x7, 10<<8, 9<<8, '0' + (m % 10), &color);
|
||||||
|
|
||||||
|
uint8_t s = timeinfo.tm_sec;
|
||||||
|
if(s & 1) {
|
||||||
|
window->setPixel(2, 11, &color);
|
||||||
|
window->setPixel(2, 13, &color);
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +1,8 @@
|
|||||||
#include "effect_pixelclock.h"
|
#include "effects/pixelclock.h"
|
||||||
#include "ntp.h"
|
#include "ntp.h"
|
||||||
|
|
||||||
PixelClockEffect::PixelClockEffect() {
|
PixelClockEffect::PixelClockEffect() {
|
||||||
window = new Window(0, 0, LED_WIDTH, LED_HEIGHT-6);
|
window = &Window::window_with_clock;
|
||||||
_color_seconds = new CRGB(0x00FF00);
|
_color_seconds = new CRGB(0x00FF00);
|
||||||
_color_minutes = new CRGB(0xFFFF00);
|
_color_minutes = new CRGB(0xFFFF00);
|
||||||
}
|
}
|
||||||
@ -10,21 +10,25 @@ PixelClockEffect::PixelClockEffect() {
|
|||||||
PixelClockEffect::~PixelClockEffect() {
|
PixelClockEffect::~PixelClockEffect() {
|
||||||
delete _color_seconds;
|
delete _color_seconds;
|
||||||
delete _color_minutes;
|
delete _color_minutes;
|
||||||
delete window;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PixelClockEffect::loop(uint16_t ms) {
|
void PixelClockEffect::loop(uint16_t ms) {
|
||||||
uint8_t x, y; // Temporary variables for calculating positions
|
uint8_t x, y; // Temporary variables for calculating positions
|
||||||
window->clear();
|
window->clear();
|
||||||
|
time_t now;
|
||||||
|
tm timeinfo;
|
||||||
|
time(&now);
|
||||||
|
localtime_r(&now, &timeinfo);
|
||||||
|
|
||||||
// Seconds
|
// Seconds
|
||||||
uint8_t seconds = ntpClient.getSeconds();
|
uint8_t seconds = timeinfo.tm_sec;
|
||||||
for (uint8_t s=0; s<60; s++) {
|
for (uint8_t s=0; s<60; s++) {
|
||||||
x = window->width - 1 - s/10;
|
x = window->width - 1 - s/10;
|
||||||
y = window->height - 1 - (s % 10);
|
y = window->height - 1 - (s % 10);
|
||||||
if (s<seconds) window->setPixel(x, y, _color_seconds);
|
if (s<seconds) window->setPixel(x, y, _color_seconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t minutes = ntpClient.getMinutes();
|
uint8_t minutes = timeinfo.tm_min;
|
||||||
for (uint8_t m=0; m<60; m++) {
|
for (uint8_t m=0; m<60; m++) {
|
||||||
x = 6 - m/10;
|
x = 6 - m/10;
|
||||||
y = window->height - 1 - (m % 10);
|
y = window->height - 1 - (m % 10);
|
@ -1,10 +1,10 @@
|
|||||||
#include "effect_sinematrix3.h"
|
#include "effects/sinematrix3.h"
|
||||||
#include "prototypes.h"
|
#include "prototypes.h"
|
||||||
#include "functions.h"
|
#include "functions.h"
|
||||||
#include "Effect.h"
|
#include "Effect.h"
|
||||||
|
#include "my_color_palettes.h"
|
||||||
|
|
||||||
boolean Sinematrix3Effect::can_be_shown_with_clock() { return true; };
|
boolean Sinematrix3Effect::can_be_shown_with_clock() { return true; };
|
||||||
boolean Sinematrix3Effect::clock_as_mask() { return true; };
|
|
||||||
void Sinematrix3Effect::loop(uint16_t ms) {
|
void Sinematrix3Effect::loop(uint16_t ms) {
|
||||||
pangle = addmodpi( pangle, 0.0133 + (angle / 256) );
|
pangle = addmodpi( pangle, 0.0133 + (angle / 256) );
|
||||||
angle = cos(pangle) * PI;
|
angle = cos(pangle) * PI;
|
||||||
@ -44,8 +44,22 @@ void Sinematrix3Effect::loop(uint16_t ms) {
|
|||||||
for ( int y = 0; y < LED_HEIGHT; y++ ) {
|
for ( int y = 0; y < LED_HEIGHT; y++ ) {
|
||||||
Vector c = add(multiply( multiply(rotate, zoom), { .x1 = x - rcx, .x2 = y - rcy } ), translate);
|
Vector c = add(multiply( multiply(rotate, zoom), { .x1 = x - rcx, .x2 = y - rcy } ), translate);
|
||||||
int sat = (basecol + basefield(c.x1, c.x2)) * 255;
|
int sat = (basecol + basefield(c.x1, c.x2)) * 255;
|
||||||
CRGB color(CHSV(sat, 120, 255));
|
CRGB color(_get_color(sat));
|
||||||
window->setPixel(x, y, &color);
|
window->setPixel(x, y, &color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CRGB Sinematrix3Effect::_get_color(int sat) {
|
||||||
|
switch(_color_scheme) {
|
||||||
|
case SINEMATRIX_COLOR_PASTEL_RAINBOW: return CRGB(CHSV(sat, 120, 255));
|
||||||
|
case SINEMATRIX_COLOR_RAINBOW: return CRGB(CHSV(sat, 255, 255));
|
||||||
|
case SINEMATRIX_COLOR_PURPLEFLY: return ColorFromPalette((CRGBPalette16)palette_purplefly_gp, (uint8_t)sat);
|
||||||
|
}
|
||||||
|
return CRGB(0xFF00FF);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean Sinematrix3Effect::clock_as_mask() {
|
||||||
|
if (_color_scheme == SINEMATRIX_COLOR_PASTEL_RAINBOW) return true;
|
||||||
|
return false;
|
||||||
|
};
|
@ -1,4 +1,4 @@
|
|||||||
#include "effect_sines.h"
|
#include "effects/sines.h"
|
||||||
|
|
||||||
SinesEffectSinus::SinesEffectSinus(Window* w) {
|
SinesEffectSinus::SinesEffectSinus(Window* w) {
|
||||||
_window = w;
|
_window = w;
|
344
src/effects/snake.cpp
Normal file
344
src/effects/snake.cpp
Normal file
@ -0,0 +1,344 @@
|
|||||||
|
#include "effects/snake.h"
|
||||||
|
#include "functions.h"
|
||||||
|
|
||||||
|
SnakeEffect::SnakeEffect() {
|
||||||
|
window = &Window::window_with_clock;
|
||||||
|
_pixels = window->width * window->height;
|
||||||
|
_map = new uint8_t[_pixels];
|
||||||
|
_init();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SnakeEffect::_init() {
|
||||||
|
_dying = 0;
|
||||||
|
_round = 0;
|
||||||
|
_last_apple_at = millis();
|
||||||
|
_last_move_at = millis();
|
||||||
|
_dir = SNAKE_DIR_NORTH;
|
||||||
|
_length = 4;
|
||||||
|
_pos = {(uint8_t)(window->width/2), (uint8_t)(window->height/2)};
|
||||||
|
for (int i=0; i<_pixels; i++) _map[i]=0;
|
||||||
|
_map[_xy2i(_pos)]=1;
|
||||||
|
_map[_xy2i(_pos)+window->width*1]=2;
|
||||||
|
_map[_xy2i(_pos)+window->width*2]=3;
|
||||||
|
_map[_xy2i(_pos)+window->width*3]=4;
|
||||||
|
_place_apple();
|
||||||
|
}
|
||||||
|
|
||||||
|
SnakeEffect::~SnakeEffect() {
|
||||||
|
delete _map;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SnakeEffect::_place_apple() {
|
||||||
|
if (_length < _pixels) {
|
||||||
|
uint8_t start = random8(_pixels);
|
||||||
|
for (int i=0; i<_pixels; i++) {
|
||||||
|
if (_map[start + i]==0) {
|
||||||
|
_apple = _i2xy(start + i);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SnakeEffect::_decide() {
|
||||||
|
uint8_t f_l = _free_spaces(_dir - 1);
|
||||||
|
uint8_t f_s = _free_spaces(_dir);
|
||||||
|
uint8_t f_r = _free_spaces(_dir + 1);
|
||||||
|
uint8_t a_l = _to_apple(_dir - 1);
|
||||||
|
uint8_t a_s = _to_apple(_dir);
|
||||||
|
uint8_t a_r = _to_apple(_dir + 1);
|
||||||
|
|
||||||
|
float* inputs = new float[6];
|
||||||
|
inputs[0] = f_l;
|
||||||
|
inputs[1] = f_s;
|
||||||
|
inputs[2] = f_r;
|
||||||
|
inputs[3] = a_l;
|
||||||
|
inputs[4] = a_s;
|
||||||
|
inputs[5] = a_r;
|
||||||
|
if (SNAKE_DEBUG) DBG("SnakeEffect * Position: %d, %d - Inputs: %3.1f %3.1f %3.1f %3.1f %3.1f %3.1f", _pos.x, _pos.y, inputs[0], inputs[1], inputs[2], inputs[3], inputs[4], inputs[5]);
|
||||||
|
float* outputs = NULL;
|
||||||
|
uint8_t i=0;
|
||||||
|
for (uint8_t layer=1; layer<_net_layers; layer++) {
|
||||||
|
outputs = new float[_net_layout[layer]];
|
||||||
|
for (uint8_t j=0; j<_net_layout[layer]; j++) {
|
||||||
|
outputs[j] = 0.0;
|
||||||
|
}
|
||||||
|
for (uint8_t idx_out=0; idx_out<_net_layout[layer]; idx_out++) {
|
||||||
|
for (uint8_t idx_in=0; idx_in<_net_layout[layer-1]; idx_in++) {
|
||||||
|
float weight;
|
||||||
|
memcpy(&weight, &(_weights[i]), sizeof(weight));
|
||||||
|
outputs[idx_out] += weight * inputs[idx_in];
|
||||||
|
//outputs[idx_out] += (*(float*)&(_weights[i])) * inputs[idx_in];
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete inputs;
|
||||||
|
inputs = outputs;
|
||||||
|
}
|
||||||
|
|
||||||
|
int8_t decision = 0;
|
||||||
|
float last;
|
||||||
|
for (uint8_t i=0; i<_net_layout[_net_layers - 1]; i++) {
|
||||||
|
if (i==0 || outputs[i]>last) {
|
||||||
|
last = outputs[i];
|
||||||
|
decision = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
decision = decision - 1;
|
||||||
|
delete outputs;
|
||||||
|
|
||||||
|
if (SNAKE_DEBUG) DBG("SnakeEffect * Decision: %d", decision);
|
||||||
|
|
||||||
|
_dir += decision;
|
||||||
|
if (_dir < 0) _dir += 4;
|
||||||
|
if (_dir > 3) _dir -= 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is old (and hence disabled) code, showing
|
||||||
|
* a simple, hand-crafted "AI" for plaing snake.
|
||||||
|
**
|
||||||
|
int8_t SnakeEffect::_manual_decision() {
|
||||||
|
bool free_l = _is_free(_dir - 1);
|
||||||
|
bool free_s = _is_free(_dir);
|
||||||
|
bool free_r = _is_free(_dir + 1);
|
||||||
|
bool apple_l = _to_apple(_dir - 1);
|
||||||
|
bool apple_s = _to_apple(_dir);
|
||||||
|
bool apple_r = _to_apple(_dir + 1);
|
||||||
|
|
||||||
|
if (!free_s) {
|
||||||
|
if (apple_l && free_l) return -1;
|
||||||
|
if (apple_r && free_r) return 1;
|
||||||
|
if (free_l) return -1;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (apple_s) return 0;
|
||||||
|
if (apple_l && free_l) return -1;
|
||||||
|
if (apple_r && free_r) return 1;
|
||||||
|
return 0;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
/* This uses a predefined meandering path through the complete field.
|
||||||
|
The snake always tries to reach the field matching following criteria:
|
||||||
|
(1) Being free.
|
||||||
|
(2) Having a number smaller than the field the apple is on. (Watch out for "overflows".)
|
||||||
|
*/
|
||||||
|
int8_t SnakeEffect::_manual_decision() {
|
||||||
|
uint8_t head_index = _coords_to_field_id(_pos);
|
||||||
|
uint8_t apple_index = _coords_to_field_id(_apple);
|
||||||
|
uint8_t tail_index = _coords_to_field_id(_tail);
|
||||||
|
if (SNAKE_DEBUG) DBG("SnakeEffect * Decision. head: %d, apple: %d, tail: %d", head_index, apple_index, tail_index);
|
||||||
|
|
||||||
|
uint16_t best_distance = 0xFFFF;
|
||||||
|
int8_t decision = 0;
|
||||||
|
|
||||||
|
for (int i=-1; i<=1; i++) { // Test all thre possible directions (left, ahead, right)
|
||||||
|
Coords new_pos = _new_pos(_dir + i);
|
||||||
|
uint8_t new_index = _coords_to_field_id(new_pos);
|
||||||
|
|
||||||
|
int16_t distance;
|
||||||
|
if (apple_index >= new_index) {
|
||||||
|
distance = apple_index - new_index;
|
||||||
|
} else {
|
||||||
|
distance = (window->width * window->height) - apple_index + new_index;
|
||||||
|
}
|
||||||
|
if (SNAKE_DEBUG) DBG("SnakeEffect * Decision: %d would have distance %d", i, distance);
|
||||||
|
if (distance < best_distance && _is_free(_dir + i)) {
|
||||||
|
best_distance = distance;
|
||||||
|
decision = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SNAKE_DEBUG) DBG("SnakeEffect * Decision taken: %d with distance %d", decision, best_distance);
|
||||||
|
|
||||||
|
/* apple_index > new_index && head_index > apple_index
|
||||||
|
apple_index > new_index && head_index < apple_index && new_index > head_index
|
||||||
|
|
||||||
|
uint16_t head_index = (_head_rounds<<8) | _coords_to_field_id(_pos);
|
||||||
|
uint16_t apple_index = (_head_rounds<<8) | _coords_to_field_id(_apple);
|
||||||
|
uint16_t tail_index = (_tail_rounds<<8) | _coords_to_field_id(_tail);
|
||||||
|
|
||||||
|
if (apple_index < head_index) apple_index += 0x100;
|
||||||
|
|
||||||
|
uint8_t best_dist = 0xFF;
|
||||||
|
int8_t decision = 0;
|
||||||
|
for (int i=-1; i<=1; i++) {
|
||||||
|
Coords new_pos = _new_pos(_dir + i);
|
||||||
|
uint16_t theoretical_index = (_head_rounds<<8) | _coords_to_field_id(new_pos);
|
||||||
|
if (theoretical_index < head_index) theoretical_index += 0x100;
|
||||||
|
int16_t dist = apple_index - theoretical_index;
|
||||||
|
if (dist < 0) dist += window->height * window->width;
|
||||||
|
if (dist < best_dist && _is_free(_dir + i) && theoretical_index<tail_index && theoretical_index<tail_index) {
|
||||||
|
decision = i;
|
||||||
|
best_dist = dist;
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
_dir = (_dir + decision) % 4;
|
||||||
|
return decision;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SnakeEffect::_is_free(uint8_t dir) {
|
||||||
|
return _free_spaces(dir)!=0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t SnakeEffect::_free_spaces(uint8_t dir) {
|
||||||
|
int8_t x=0;
|
||||||
|
int8_t y=0;
|
||||||
|
uint8_t d = dir % 4;
|
||||||
|
switch(d) {
|
||||||
|
case SNAKE_DIR_NORTH: y=-1; break;
|
||||||
|
case SNAKE_DIR_EAST: x=1; break;
|
||||||
|
case SNAKE_DIR_SOUTH: y=1; break;
|
||||||
|
case SNAKE_DIR_WEST: x=-1; break;
|
||||||
|
}
|
||||||
|
Coords p(_pos);
|
||||||
|
uint8_t i=0;
|
||||||
|
while (true) {
|
||||||
|
p.x += x;
|
||||||
|
p.y += y;
|
||||||
|
if (p.x<0 || p.x>=window->width || p.y<0 || p.y>=window->height || _map[_xy2i(p)]!=0) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t SnakeEffect::_coords_to_field_id(Coords c) {
|
||||||
|
if (c.y==0) return window->width - c.x;
|
||||||
|
if (c.x % 2 == 0) {
|
||||||
|
// even columns
|
||||||
|
return window->width + c.x*(window->height - 1) + c.y - 1;
|
||||||
|
} else {
|
||||||
|
// odd columns
|
||||||
|
return window->width + (c.x+1)*(window->height-1) - c.y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t SnakeEffect::_to_apple(uint8_t dir) {
|
||||||
|
uint8_t d = dir % 4;
|
||||||
|
int8_t d_x = _apple.x - _pos.x;
|
||||||
|
int8_t d_y = _apple.y - _pos.y;
|
||||||
|
|
||||||
|
switch(d) {
|
||||||
|
case SNAKE_DIR_NORTH: return d_y < 0 ? -d_y : 0;
|
||||||
|
case SNAKE_DIR_EAST: return d_x > 0 ? d_x : 0;
|
||||||
|
case SNAKE_DIR_SOUTH: return d_y > 0 ? d_y : 0;
|
||||||
|
case SNAKE_DIR_WEST: return d_x < 0 ? -d_x : 0;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Coords SnakeEffect::_new_pos(uint8_t dir) {
|
||||||
|
uint8_t d = dir % 4;
|
||||||
|
Coords p(_pos);
|
||||||
|
switch(d) {
|
||||||
|
case SNAKE_DIR_NORTH: p.y--; break;
|
||||||
|
case SNAKE_DIR_EAST: p.x++; break;
|
||||||
|
case SNAKE_DIR_SOUTH: p.y++; break;
|
||||||
|
case SNAKE_DIR_WEST: p.x--; break;
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t SnakeEffect::_xy2i(Coords c) {
|
||||||
|
return _xy2i(c.x, c.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t SnakeEffect::_xy2i(uint8_t x, uint8_t y) {
|
||||||
|
return y*window->width + x;
|
||||||
|
}
|
||||||
|
|
||||||
|
Coords SnakeEffect::_i2xy(uint16_t i) {
|
||||||
|
return {(uint16_t)(i%window->width), (uint16_t)(i/window->width)};
|
||||||
|
}
|
||||||
|
|
||||||
|
void SnakeEffect::_move() {
|
||||||
|
|
||||||
|
|
||||||
|
if (_dying > 0) {
|
||||||
|
_dying--;
|
||||||
|
|
||||||
|
if (_dying==0) {
|
||||||
|
_init();
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned long now = millis();
|
||||||
|
if (_last_move_at < now && now - _last_move_at < 50) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_round++;
|
||||||
|
_last_move_at = now;
|
||||||
|
_manual_decision();
|
||||||
|
|
||||||
|
if (_dying==0 && !_is_free(_dir)) {
|
||||||
|
_dying = 150;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_pos = _new_pos(_dir);
|
||||||
|
|
||||||
|
uint8_t index_head = _coords_to_field_id(_pos);
|
||||||
|
uint8_t index_tail = _coords_to_field_id(_tail);
|
||||||
|
|
||||||
|
if (SNAKE_DEBUG) LOGln("SnakeEffect * new_pos: %d, %d", _pos.x, _pos.y);
|
||||||
|
if (SNAKE_DEBUG) LOGln("SnakeEffect * apple: %d, %d", _apple.x, _apple.y);
|
||||||
|
if (_pos.x==_apple.x && _pos.y==_apple.y) {
|
||||||
|
_last_apple_at = millis();
|
||||||
|
_length++;
|
||||||
|
}
|
||||||
|
for (int i=0; i<_pixels; i++) {
|
||||||
|
if (_map[i]>0 && _map[i]<_length-1) {
|
||||||
|
_map[i]++;
|
||||||
|
if (_map[i]==_length-1) {
|
||||||
|
_tail = _i2xy(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else _map[i]=0;
|
||||||
|
}
|
||||||
|
_map[_xy2i(_pos)] = 1;
|
||||||
|
if (_pos.x==_apple.x && _pos.y==_apple.y) {
|
||||||
|
_place_apple();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index_head > _coords_to_field_id(_pos)) _head_rounds++;
|
||||||
|
if (index_tail > _coords_to_field_id(_tail)) _tail_rounds++;
|
||||||
|
if (_head_rounds > 0 && _head_rounds > 0) {
|
||||||
|
uint8_t min = (_head_rounds < _tail_rounds) ? _tail_rounds : _head_rounds;
|
||||||
|
_head_rounds -= min;
|
||||||
|
_tail_rounds -= min;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SnakeEffect::_draw() {
|
||||||
|
if (_dying) {
|
||||||
|
window->fadeToBlackBy(4);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window->clear();
|
||||||
|
CRGB red(0xBB0000);
|
||||||
|
for (int i=0; i<_pixels; i++) {
|
||||||
|
if (_map[i]>0) window->setPixelByIndex(i, &red);
|
||||||
|
}
|
||||||
|
CRGB white(0xFFFFFF);
|
||||||
|
window->setPixel(_pos.x, _pos.y, &white);
|
||||||
|
CRGB green(0xFFFF00);
|
||||||
|
window->setPixel(_apple.x, _apple.y, &green);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SnakeEffect::loop(uint16_t ms) {
|
||||||
|
//window->fadeToBlackBy(2);
|
||||||
|
//CRGB color(CHSV(hue, 200, 255));
|
||||||
|
//window->setPixel(this->coords.x, this->coords.y, &color);
|
||||||
|
//hue++;
|
||||||
|
if (_dying==0 && (millis() < _last_apple_at || millis() - _last_apple_at > 30000)) {
|
||||||
|
_dying = 150;
|
||||||
|
}
|
||||||
|
_move();
|
||||||
|
_draw();
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean SnakeEffect::can_be_shown_with_clock() { return true; }
|
@ -1,4 +1,4 @@
|
|||||||
#include "effect_static.h"
|
#include "effects/static.h"
|
||||||
#include "functions.h"
|
#include "functions.h"
|
||||||
#include "my_fastled.h"
|
#include "my_fastled.h"
|
||||||
|
|
38
src/effects/timer.cpp
Normal file
38
src/effects/timer.cpp
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
#include "effects/timer.h"
|
||||||
|
#include <FastLED.h>
|
||||||
|
#include "functions.h"
|
||||||
|
#include "fonts.h"
|
||||||
|
#include "ntp.h"
|
||||||
|
|
||||||
|
void TimerEffect::loop(uint16_t ms) {
|
||||||
|
if (timer==0) return;
|
||||||
|
|
||||||
|
CRGB bg_color(0x000000);
|
||||||
|
CRGB fg_color(0xCCCCCC);
|
||||||
|
time_t now;
|
||||||
|
time(&now);
|
||||||
|
long diff = timer - now;
|
||||||
|
window->clear(&bg_color);
|
||||||
|
if (diff < 0 && (now & 1)==0) return;
|
||||||
|
if (diff < 0) diff = 0;
|
||||||
|
int hours = diff / 3600;
|
||||||
|
int minutes = diff / 60;
|
||||||
|
int seconds = diff % 60;
|
||||||
|
if (minutes > 99) {
|
||||||
|
seconds = minutes % 60;
|
||||||
|
minutes = hours;
|
||||||
|
}
|
||||||
|
//void drawChar(Font f, uint8_t x, uint8_t y, const char c, CRGB* color, bool mask=false);
|
||||||
|
window->drawChar(&font_numbers3x5, 0<<8, 0<<8, '0' + (minutes / 10), &fg_color);
|
||||||
|
window->drawChar(&font_numbers3x5, 4<<8, 0<<8, '0' + (minutes % 10), &fg_color);
|
||||||
|
window->drawChar(&font_numbers3x5, 9<<8, 0<<8, '0' + (seconds / 10), &fg_color);
|
||||||
|
window->drawChar(&font_numbers3x5, 13<<8, 0<<8, '0' + (seconds % 10), &fg_color);
|
||||||
|
if (now & 1) {
|
||||||
|
window->setPixel(7, 1, &fg_color);
|
||||||
|
window->setPixel(7, 3, &fg_color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TimerEffect::~TimerEffect() {
|
||||||
|
delete window;
|
||||||
|
}
|
181
src/effects/tpm2_net.cpp
Normal file
181
src/effects/tpm2_net.cpp
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
#include "effects/tpm2_net.h"
|
||||||
|
#include "my_fastled.h"
|
||||||
|
|
||||||
|
void Tpm2NetEffect::loop(uint16_t ms) {
|
||||||
|
int packetsize = _udp.parsePacket();
|
||||||
|
uint8_t data[255];
|
||||||
|
uint8_t type = 0x00;
|
||||||
|
if (packetsize > 0) {
|
||||||
|
_last_packet_at = millis();
|
||||||
|
DBG("TPM2.Net * Received %d bytes from %s", packetsize, _udp.remoteIP().toString().c_str());
|
||||||
|
if (packetsize < 7) {
|
||||||
|
LOGln("TPM2.Net * Packet is too short. Ignoring it.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_udp.read(data, 6);
|
||||||
|
if (data[0] != 0x9C) {
|
||||||
|
LOGln("TPM2.Net * Block start byte is 0x%02X, expected 0x9C", data[0]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (data[1] == 0xC0) {
|
||||||
|
DBG("TPM2.Net * Received a command packet.");
|
||||||
|
type = 0xC0;
|
||||||
|
} else if (data[1] == 0xDA) {
|
||||||
|
DBG("TPM2.Net * Received a data packet.");
|
||||||
|
type = 0xDA;
|
||||||
|
} else {
|
||||||
|
LOGln("TPM2.Net * Unexpected packet type 0x%02X, expected 0xC0 or 0xDA.", data[1]);
|
||||||
|
}
|
||||||
|
uint16_t framesize = (data[2]<<8) | data[3];
|
||||||
|
uint8_t packet_number = data[4];
|
||||||
|
//uint8_t packet_count = data[5];
|
||||||
|
|
||||||
|
if (packetsize != framesize + 7) {
|
||||||
|
LOGln("TPM2.Net * Invalid packet size. Expected %d bytes, was %d bytes.", framesize+7, packetsize);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type==0xC0) {
|
||||||
|
_parse_command(framesize, packet_number);
|
||||||
|
} else if (type==0xDA) {
|
||||||
|
_parse_data(framesize, packet_number);
|
||||||
|
}
|
||||||
|
} else if (_last_packet_at + 5000 < millis()) {
|
||||||
|
window->fill_with_checkerboard();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tpm2NetEffect::_parse_command(uint16_t size, uint8_t packet_number) {
|
||||||
|
if (packet_number!=0) {
|
||||||
|
LOGln("TPM2.Net * Command packet with packet_number > 0 (%d). Ignoring it.", packet_number);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (size < 2) {
|
||||||
|
LOGln("TPM2.Net * Command packet need at least 2 data bytes.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uint8_t ctrl = _udp.read();
|
||||||
|
bool write = ctrl & 0x80;
|
||||||
|
bool respond = ctrl & 0x40;
|
||||||
|
uint8_t cmd = _udp.read();
|
||||||
|
uint8_t data = 0;
|
||||||
|
if (write) {
|
||||||
|
if (size<3) {
|
||||||
|
LOGln("TPM2.Net * Got a write command, but no data to write.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
data = _udp.read();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmd == 0x0A) { // Master Brightness
|
||||||
|
if (write) {
|
||||||
|
FastLED.setBrightness(data);
|
||||||
|
} else {
|
||||||
|
uint8_t data[1] = {FastLED.getBrightness()};
|
||||||
|
_respond_with_data(data, 1);
|
||||||
|
}
|
||||||
|
} else if (cmd == 0x10 && !write) {
|
||||||
|
uint16_t pixels = window->width * window->height;
|
||||||
|
uint8_t data[2] = {(uint8_t)(pixels >> 8), (uint8_t)(pixels & 0xFF)};
|
||||||
|
_respond_with_data(data, 2);
|
||||||
|
} else {
|
||||||
|
LOGln("TPM2.Net * Unknown command. write:%d, command:0x%02X", write, cmd);
|
||||||
|
if (respond) {
|
||||||
|
_respond_unknown_command();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tpm2NetEffect::_parse_data(uint16_t framesize, uint8_t packet_number) {
|
||||||
|
if (packet_number==0) {
|
||||||
|
_pixel_index=0;
|
||||||
|
}
|
||||||
|
uint8_t len;
|
||||||
|
uint8_t data[3];
|
||||||
|
CRGB color;
|
||||||
|
while ((len = _udp.read(data, 3))==3) {
|
||||||
|
// We got 3 bytes
|
||||||
|
color.setRGB(data[0], data[1], data[2]);
|
||||||
|
window->setPixelByIndex(_pixel_index, &color);
|
||||||
|
_pixel_index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
bool dir_changed = false;
|
||||||
|
|
||||||
|
_x += _x_dir * settings.effects.dvd.speed;
|
||||||
|
_y += _y_dir * settings.effects.dvd.speed;
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tpm2NetEffect::_respond(uint8_t* data, uint8_t len) {
|
||||||
|
_udp.beginPacket(_udp.remoteIP(), 65442);
|
||||||
|
_udp.write(data, len);
|
||||||
|
_udp.endPacket();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tpm2NetEffect::_respond_ack() {
|
||||||
|
uint8_t data[8] = {0x9C, 0xAC, 0x00, 0x01, 0x00, 0x01, 0x00, 0x36};
|
||||||
|
_respond(data, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tpm2NetEffect::_respond_unknown_command() {
|
||||||
|
uint8_t data[8] = {0x9C, 0xAC, 0x00, 0x01, 0x00, 0x01, 0x02, 0x36};
|
||||||
|
_respond(data, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tpm2NetEffect::_respond_with_data(uint8_t* dat, uint8_t len) {
|
||||||
|
uint8_t data[8 + len];
|
||||||
|
data[0] = 0x9C;
|
||||||
|
data[1] = 0xAD;
|
||||||
|
data[2] = (len+1)>>8;
|
||||||
|
data[3] = (len+1)&0xFF;
|
||||||
|
data[4] = 0x00;
|
||||||
|
data[5] = 0x01;
|
||||||
|
data[6] = 0x00;
|
||||||
|
memcpy(&(data[7]), dat, len);
|
||||||
|
data[8 + len - 1] = 0x36;
|
||||||
|
_respond(data, 8 + len);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Tpm2NetEffect::can_be_shown_with_clock() { return false; }
|
||||||
|
|
||||||
|
Tpm2NetEffect::Tpm2NetEffect() {
|
||||||
|
_udp.begin(65506);
|
||||||
|
}
|
||||||
|
|
||||||
|
Tpm2NetEffect::~Tpm2NetEffect() {
|
||||||
|
_udp.stop();
|
||||||
|
}
|
27
src/effects/tv_static.cpp
Normal file
27
src/effects/tv_static.cpp
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
#include "effects/tv_static.h"
|
||||||
|
|
||||||
|
void TvStaticEffect::loop(uint16_t ms) {
|
||||||
|
//uint8_t dark_position = (millis() % settings.effects.tv_static.black_bar_speed) * _window->width / settings.effects.tv_static.black_bar_speed;
|
||||||
|
accum88 dark_position = (beat16(settings.effects.tv_static.black_bar_speed) * _window->width) >> 8;
|
||||||
|
for (uint8_t y=0; y<_window->height; y++) {
|
||||||
|
accum88 row_dark_position = (dark_position + (y<<8)/3) % (_window->width<<8);
|
||||||
|
for (uint8_t x=0; x<_window->width; x++) {
|
||||||
|
uint8_t brightness = random8();
|
||||||
|
uint8_t darkening = 0;
|
||||||
|
accum88 distance = (x<<8) - row_dark_position;
|
||||||
|
if (distance < 256) darkening = random8(distance, 255);
|
||||||
|
else if (distance < (4<<8)) darkening = random8(distance >> 2, 255);
|
||||||
|
|
||||||
|
if (darkening > brightness) brightness = 0;
|
||||||
|
else brightness -= darkening;
|
||||||
|
CRGB color(brightness, brightness, brightness);
|
||||||
|
_window->setPixel(x, y, &color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TvStaticEffect::can_be_shown_with_clock() { return true; }
|
||||||
|
|
||||||
|
TvStaticEffect::~TvStaticEffect() {
|
||||||
|
delete _window;
|
||||||
|
}
|
@ -1,8 +1,8 @@
|
|||||||
#include "effect_twirl.h"
|
#include "effects/twirl.h"
|
||||||
#include "functions.h"
|
#include "functions.h"
|
||||||
|
|
||||||
boolean TwirlEffect::can_be_shown_with_clock() { return true; };
|
boolean TwirlEffect::can_be_shown_with_clock() { return true; };
|
||||||
boolean TwirlEffect::clock_as_mask() { return true; };
|
boolean TwirlEffect::clock_as_mask() { return false; };
|
||||||
|
|
||||||
void TwirlEffect::loop(uint16_t ms) {
|
void TwirlEffect::loop(uint16_t ms) {
|
||||||
double center_x = _real_center_x; // - (cos8(_center_offset_angle)>>6);
|
double center_x = _real_center_x; // - (cos8(_center_offset_angle)>>6);
|
@ -16,13 +16,29 @@ bool font_numbers3x5_check(const char c) { return c>='0' && c<='9'; }
|
|||||||
uint16_t font_numbers3x5_get(const char c) { return c - '0'; }
|
uint16_t font_numbers3x5_get(const char c) { return c - '0'; }
|
||||||
Font font_numbers3x5 = {3, 5, &font_numbers3x5_data[0], font_numbers3x5_check, font_numbers3x5_get};
|
Font font_numbers3x5 = {3, 5, &font_numbers3x5_data[0], font_numbers3x5_check, font_numbers3x5_get};
|
||||||
|
|
||||||
|
const uint8_t font_numbers3x3_data[] PROGMEM = {
|
||||||
|
B111, B101, B111, // 0
|
||||||
|
B101, B111, B001, // 1
|
||||||
|
B100, B111, B001, // 2
|
||||||
|
B101, B111, B111, // 3
|
||||||
|
B110, B010, B111, // 4
|
||||||
|
B001, B111, B100, // 5
|
||||||
|
B111, B011, B011, // 6
|
||||||
|
B100, B100, B111, // 7
|
||||||
|
B011, B111, B111, // 8
|
||||||
|
B110, B110, B111, // 9
|
||||||
|
};
|
||||||
|
bool font_numbers3x3_check(const char c) { return c>='0' && c<='9'; }
|
||||||
|
uint16_t font_numbers3x3_get(const char c) { return c - '0'; }
|
||||||
|
Font font_numbers3x3 = {3, 3, &font_numbers3x3_data[0], font_numbers3x3_check, font_numbers3x3_get};
|
||||||
|
|
||||||
const uint8_t font_numbers3x5_blocky_data[] PROGMEM = {
|
const uint8_t font_numbers3x5_blocky_data[] PROGMEM = {
|
||||||
B11111, B10001, B11111, // 0
|
B11111, B10001, B11111, // 0
|
||||||
B00000, B11111, B00000, // 1
|
B00000, B11111, B00000, // 1
|
||||||
B10111, B10101, B11101, // 2
|
B10111, B10101, B11101, // 2
|
||||||
B10001, B10101, B11111, // 3
|
B10001, B10101, B11111, // 3
|
||||||
B11100, B00100, B11111, // 4
|
B11100, B00100, B11111, // 4
|
||||||
B11101, B10101, B11111, // 5
|
B11101, B10101, B10111, // 5
|
||||||
B11111, B10101, B10111, // 6
|
B11111, B10101, B10111, // 6
|
||||||
B10000, B10000, B11111, // 7
|
B10000, B10000, B11111, // 7
|
||||||
B11111, B10101, B11111, // 8
|
B11111, B10101, B11111, // 8
|
||||||
@ -150,12 +166,6 @@ const uint8_t font5x7_data[] PROGMEM = {
|
|||||||
0x08, 0x08, 0x2A, 0x1C, 0x08, // ->
|
0x08, 0x08, 0x2A, 0x1C, 0x08, // ->
|
||||||
0x08, 0x1C, 0x2A, 0x08, 0x08, // <-
|
0x08, 0x1C, 0x2A, 0x08, 0x08, // <-
|
||||||
};
|
};
|
||||||
|
|
||||||
bool font5x7_check(const char c) { return c>=' ' && c<='}'; }
|
bool font5x7_check(const char c) { return c>=' ' && c<='}'; }
|
||||||
|
|
||||||
uint16_t font5x7_get(const char c) { return c - ' '; }
|
uint16_t font5x7_get(const char c) { return c - ' '; }
|
||||||
|
|
||||||
Font font5x7 = {5, 7, &font5x7_data[0], font5x7_check, font5x7_get};
|
Font font5x7 = {5, 7, &font5x7_data[0], font5x7_check, font5x7_get};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -5,156 +5,378 @@
|
|||||||
#include "http_server.h"
|
#include "http_server.h"
|
||||||
#include "effects.h"
|
#include "effects.h"
|
||||||
#include "my_wifi.h"
|
#include "my_wifi.h"
|
||||||
|
#include "functions.h"
|
||||||
#include "prototypes.h"
|
#include "prototypes.h"
|
||||||
#include <FS.h>
|
#include <FS.h>
|
||||||
|
|
||||||
#if defined( ESP8266 )
|
AsyncWebServer http_server(HTTP_SERVER_PORT);
|
||||||
ESP8266WebServer http_server(HTTP_SERVER_PORT);
|
AsyncWebSocket ws("/ws");
|
||||||
#elif defined( ESP32 )
|
uint32_t monitor_client = 0;
|
||||||
ESP32WebServer http_server(HTTP_SERVER_PORT);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
File upload_file;
|
void http_server_handle_file_upload(AsyncWebServerRequest* request, String filename, size_t index, uint8_t* data, size_t len, bool final) {
|
||||||
|
File upload_file;
|
||||||
void http_server_handle_file_upload() {
|
if (index == 0) { // Start of upload
|
||||||
if (http_server.uri() != "/upload") return;
|
LOGln("HTTP * Upload of %s starting...", filename.c_str());
|
||||||
|
|
||||||
HTTPUpload upload = http_server.upload();
|
|
||||||
|
|
||||||
if (upload.status == UPLOAD_FILE_START) {
|
|
||||||
String filename = upload.filename;
|
|
||||||
if (!filename.startsWith("/")) filename = "/" + filename;
|
|
||||||
LOGln("HTTP * Upload of %s starting...", upload.filename.c_str());
|
|
||||||
upload_file = SPIFFS.open(filename, "w");
|
upload_file = SPIFFS.open(filename, "w");
|
||||||
} else if (upload.status == UPLOAD_FILE_WRITE) {
|
} else {
|
||||||
if (upload_file) upload_file.write(upload.buf, upload.currentSize);
|
upload_file = SPIFFS.open(filename, "a");
|
||||||
} else if (upload.status == UPLOAD_FILE_END) {
|
}
|
||||||
if (upload_file) upload_file.close();
|
|
||||||
LOGln("HTTP * Upload of %s with %d bytes done.", upload.filename.c_str(), upload.totalSize);
|
upload_file.write(data, len);
|
||||||
|
|
||||||
|
if (final) {
|
||||||
|
LOGln("HTTP * Upload of %s done.", filename.c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void http_server_400() {
|
void ws_send_effects(AsyncWebSocketClient* client) {
|
||||||
http_server.send(400);
|
String msg = "{\"effects\": [";
|
||||||
|
for (int i=0; i<effects_size; i++) {
|
||||||
|
if (i>0) msg += ", ";
|
||||||
|
msg += '"';
|
||||||
|
msg += effects[i].name;
|
||||||
|
msg += '"';
|
||||||
|
}
|
||||||
|
msg += "]}";
|
||||||
|
client->text(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ws_send_settings(AsyncWebSocketClient* client) {
|
||||||
|
String msg = "{\"settings\": [";
|
||||||
|
for (int i=0; i<all_settings_size; i++) {
|
||||||
|
if (i>0) msg += ", ";
|
||||||
|
msg += "{\"name\":\"";
|
||||||
|
msg += all_settings[i].name;
|
||||||
|
msg += "\",\"value\":";
|
||||||
|
msg += *(all_settings[i].value);
|
||||||
|
msg += ",\"type\":\"";
|
||||||
|
switch (all_settings[i].type) {
|
||||||
|
case TYPE_UINT8: msg += "uint8"; break;
|
||||||
|
case TYPE_UINT16: msg += "uint16"; break;
|
||||||
|
case TYPE_BOOL: msg += "bool"; break;
|
||||||
|
default: msg += "unknown";
|
||||||
|
}
|
||||||
|
msg += "\"}";
|
||||||
|
}
|
||||||
|
msg += "]}";
|
||||||
|
client->text(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ws_set_setting(String s) {
|
||||||
|
int8_t index = s.indexOf(":");
|
||||||
|
if (index < 1) return;
|
||||||
|
String key = s.substring(0, index);
|
||||||
|
String value = s.substring(index+1);
|
||||||
|
change_setting(key.c_str(), value.toInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
void handle_ws(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len) {
|
||||||
|
if (type == WS_EVT_CONNECT) {
|
||||||
|
LOGln("Websocket * Client connected. ID: %d", client->id());
|
||||||
|
} else if (type == WS_EVT_DISCONNECT) {
|
||||||
|
if (monitor_client == client->id()) monitor_client=0;
|
||||||
|
LOGln("Websocket * Client disconnected.");
|
||||||
|
} else if (type == WS_EVT_DATA) {
|
||||||
|
AwsFrameInfo* info = (AwsFrameInfo*)arg;
|
||||||
|
if (info->opcode == WS_TEXT) {
|
||||||
|
data[len] = 0;
|
||||||
|
String msg = String((char*)data);
|
||||||
|
LOGln("Websocket * In: %s", msg.c_str());
|
||||||
|
if (msg.startsWith("effect:")) {
|
||||||
|
change_current_effect(msg.substring(7));
|
||||||
|
} else if (msg.equals("effects?")) {
|
||||||
|
ws_send_effects(client);
|
||||||
|
} else if (msg.equals("settings?")) {
|
||||||
|
ws_send_settings(client);
|
||||||
|
} else if (msg.startsWith("setting:")) {
|
||||||
|
ws_set_setting(msg.substring(8));
|
||||||
|
} else if (msg.equals("monitor:1")) {
|
||||||
|
monitor_client = client->id();
|
||||||
|
} else if (msg.equals("monitor:0")) {
|
||||||
|
if (monitor_client == client->id()) monitor_client=0;
|
||||||
|
} else {
|
||||||
|
client->text("Unknown command. Accepted commands:\neffects?\nsettings?\nsetting:<key>:<value>\neffect:<effect>\nmonitor:<0/1>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void http_server_setup() {
|
void http_server_setup() {
|
||||||
PGM_P text_plain = PSTR("text/plain");
|
static const char* PROGMEM text_plain = "text/plain";
|
||||||
http_server.on("/", HTTP_GET, [&](){
|
|
||||||
|
http_server.on("/", HTTP_GET, [&](AsyncWebServerRequest* request){
|
||||||
LOGln("HTTP * GET /");
|
LOGln("HTTP * GET /");
|
||||||
String message = "<html><head><title>Pitrix</title></head><body><h1>Pitrix</h1><a href='/settings'>Settings</a><p>Known animations:</p>";
|
String response = String(F("<html><head><title>Pitrix</title></head><body><h1>Pitrix</h1><p><a href='/settings'>Settings</a></p><p><a href='/effects'>Effect</a></p><p>Known animations:</p>"));
|
||||||
if (!SPIFFS.begin()) {
|
if (!SPIFFS.begin()) {
|
||||||
message += "<strong>No SPIFFS file system found.</strong>";
|
response += F("<strong>No SPIFFS file system found.</strong>");
|
||||||
} else {
|
} else {
|
||||||
message += "<ul>";
|
response += F("<ul>");
|
||||||
Dir dir = SPIFFS.openDir("/");
|
Dir dir = SPIFFS.openDir("/");
|
||||||
while (dir.next()) {
|
while (dir.next()) {
|
||||||
message += "<li>" + dir.fileName() + " (<a href='/delete?" + dir.fileName() + "'>delete</a>)</li>";
|
char buffer[100];
|
||||||
|
snprintf_P(buffer, 100, PSTR("<li>%s (<a href='/delete?%s'>delete</a>)</li>"), dir.fileName().c_str(), dir.fileName().c_str());
|
||||||
|
response += buffer;
|
||||||
}
|
}
|
||||||
message += "</ul>";
|
response += F("</ul>");
|
||||||
message += "<form action='/upload' method='POST'><input type='file' name='file' /><input type='submit' value='Upload file' /></form>";
|
response += F("<form action='/upload' method='POST'><input type='file' name='file' /><input type='submit' value='Upload file' /></form>");
|
||||||
}
|
}
|
||||||
message += "</body></html>";
|
response += F("</body></html>");
|
||||||
http_server.send(200, "text/html", message);
|
request->send(200, "text/html", response);
|
||||||
});
|
});
|
||||||
http_server.on("/settings", HTTP_GET, [&]() {
|
http_server.on("/settings", HTTP_GET, [&](AsyncWebServerRequest* request) {
|
||||||
String message = "<html><head><title>Pitrix settings</title></head><body><h1>Pitrix settings</h1><a href='/'>Back to main page</a><table>";
|
String message = F("<html><head><title>Pitrix settings</title></head><body><h1>Pitrix settings</h1><a href='/'>Back to main page</a><table>\n");
|
||||||
for (int i=0; i<all_settings_size; i++) {
|
for (int i=0; i<all_settings_size; i++) {
|
||||||
message += "<tr><td>";
|
Setting s = all_settings[i];
|
||||||
message += all_settings[i].name;
|
uint16_t default_value = setting_default(&s);
|
||||||
message += "</td><td>";
|
uint16_t value = *(s.value);
|
||||||
message += *all_settings[i].value;
|
|
||||||
message += "</td></tr>";
|
message += F("<tr><td>");
|
||||||
|
if (default_value != value) {
|
||||||
|
message += F("<strong>");
|
||||||
|
}
|
||||||
|
message += s.name;
|
||||||
|
if (default_value != value) {
|
||||||
|
message += F("<strong>");
|
||||||
|
}
|
||||||
|
message += F("</td><td>");
|
||||||
|
message += value;
|
||||||
|
if (default_value != value) {
|
||||||
|
message += " (";
|
||||||
|
message += default_value;
|
||||||
|
message += ")";
|
||||||
|
}
|
||||||
|
char buffer[150];
|
||||||
|
snprintf_P(buffer, 150, PSTR("</td><td><form method='POST' action='/settings?redir=1'><input type='hidden' name='key' value='%s'/>"), s.name);
|
||||||
|
message += buffer;
|
||||||
|
if (s.type==TYPE_UINT8 || s.type==TYPE_UINT16) {
|
||||||
|
snprintf_P(buffer, 150, PSTR("<input type='number' name='value' value='%d' min='0' max='%d' />"), value, s.type==TYPE_UINT8 ? 255 : 65535);
|
||||||
|
} else if (s.type==TYPE_BOOL) {
|
||||||
|
snprintf_P(buffer, 150, PSTR("<input type='radio' name='value' value='0' %s> Off / <input type='radio' name='value' value='1' %s> On"), value==0?"checked":"", value==1?"checked":"");
|
||||||
|
}
|
||||||
|
message += buffer;
|
||||||
|
message += F("<input type='submit' value='Save' /></form></td></tr>\n");
|
||||||
}
|
}
|
||||||
message += "</table></body></html>";
|
message += F("</table></body></html>");
|
||||||
http_server.send(200, "text/html", message);
|
request->send(200, "text/html", message);
|
||||||
});
|
});
|
||||||
http_server.on("/settings", HTTP_POST, [&]() {
|
http_server.on("/settings", HTTP_POST, [&](AsyncWebServerRequest* request) {
|
||||||
if (!http_server.hasArg("key") || !http_server.hasArg("value")) {
|
if (!request->hasParam("key", true) || !request->hasParam("value", true)) {
|
||||||
http_server.send(400, "text/plain", "Missing argument.");
|
request->send(400, "text/plain", "Missing argument.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String name = http_server.arg("key");
|
String name = request->getParam("key", true)->value();
|
||||||
uint16_t value = http_server.arg("value").toInt();
|
uint16_t value = request->getParam("value", true)->value().toInt();
|
||||||
|
|
||||||
if (change_setting(name.c_str(), value)) {
|
if (change_setting(name.c_str(), value)) {
|
||||||
http_server.send(200, "text/plain", "OK");
|
if (request->hasParam("redir")) {
|
||||||
|
request->redirect("/settings");
|
||||||
|
} else {
|
||||||
|
request->send(200, "text/plain", "OK");
|
||||||
|
}
|
||||||
|
save_settings();
|
||||||
} else {
|
} else {
|
||||||
http_server.send(400, "text/plain", "Could not change setting.");
|
request->send(400, "text/plain", "Could not change setting.");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
http_server.on("/delete", HTTP_GET, [&]() {
|
http_server.on("/settings/load", HTTP_POST, [&](AsyncWebServerRequest* request) {
|
||||||
LOGln("HTTP * GET /delete");
|
load_settings();
|
||||||
if (http_server.args()==0) {
|
request->send(200, "text/plain", "OK");
|
||||||
http_server.send_P(400, text_plain, PSTR("No filename given"));
|
});
|
||||||
|
http_server.on("/settings/save", HTTP_POST, [&](AsyncWebServerRequest* request) {
|
||||||
|
save_settings();
|
||||||
|
request->send(200, "text/plain", "OK");
|
||||||
|
});
|
||||||
|
http_server.on("/settings.txt", HTTP_GET, [&](AsyncWebServerRequest* request) {
|
||||||
|
File f;
|
||||||
|
if (SPIFFS.begin()) {
|
||||||
|
f=SPIFFS.open("/pitrix_settings.txt", "r");
|
||||||
|
if (f) {
|
||||||
|
String s = f.readString();
|
||||||
|
f.close();
|
||||||
|
request->send(200, "text/plain", s);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
request->send(500, "text/plain", "Could not read settings.");
|
||||||
|
});
|
||||||
|
http_server.on("/effects", HTTP_GET, [&](AsyncWebServerRequest* request) {
|
||||||
|
String message = F("<html><head><title>Pitrix effects</title></head><body><h1>Pitrix settings</h1><a href='/'>Back to main page</a><table>");
|
||||||
|
char buffer[500];
|
||||||
|
for (int i=0; i<effects_size; i++) {
|
||||||
|
snprintf_P(buffer, 500, PSTR("<tr><td>%s</td><td><form method='post' action='/effects'><input type='hidden' name='name' value='%s'><input type='hidden' name='redir' value='1'><input type='submit' value='Select'></form></td></tr>"), effects[i].name, effects[i].name);
|
||||||
|
message += buffer;
|
||||||
|
}
|
||||||
|
message += F("</table></body></html>");
|
||||||
|
request->send(200, "text/html", message);
|
||||||
|
});
|
||||||
|
http_server.on("/effects", HTTP_POST, [&](AsyncWebServerRequest* request) {
|
||||||
|
if (!request->hasParam("name", true)) {
|
||||||
|
request->send(400, "text/plain", "Missing argument 'name'.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String file = http_server.arg(0);
|
String name = request->getParam("name", true)->value();
|
||||||
|
if (change_current_effect(name)) {
|
||||||
|
if (request->hasParam("redir")) {
|
||||||
|
request->redirect("/effects");
|
||||||
|
} else {
|
||||||
|
request->send(200, "text/plain", "OK");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
request->send(404, "text/plain", "Effect not found.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
http_server.on("/delete", HTTP_GET, [&](AsyncWebServerRequest* request) {
|
||||||
|
LOGln("HTTP * GET /delete");
|
||||||
|
if (request->params()==0) {
|
||||||
|
request->send_P(400, text_plain, PSTR("No filename given"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String file = request->getParam(0)->value();
|
||||||
if (file == "/") {
|
if (file == "/") {
|
||||||
http_server.send_P(400, text_plain, PSTR("Invalid path"));
|
request->send_P(400, text_plain, PSTR("Invalid path"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!SPIFFS.exists(file)) {
|
if (!SPIFFS.exists(file)) {
|
||||||
http_server.send_P(400, text_plain, PSTR("File does not exist."));
|
request->send_P(400, text_plain, PSTR("File does not exist."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
SPIFFS.remove(file);
|
SPIFFS.remove(file);
|
||||||
http_server.send_P(200, text_plain, PSTR("OK"));
|
request->send_P(200, text_plain, PSTR("OK"));
|
||||||
});
|
});
|
||||||
http_server.on("/upload", HTTP_POST, []() {
|
http_server.on("/upload", HTTP_POST, [](AsyncWebServerRequest* request) {
|
||||||
LOGln("HTTP * POST /upload");
|
request->send(200);
|
||||||
http_server.send(200, "text/plain", "OK");
|
|
||||||
}, http_server_handle_file_upload);
|
}, http_server_handle_file_upload);
|
||||||
http_server.on("/free_heap", HTTP_GET, [&](){
|
http_server.on("/free_heap", HTTP_GET, [&](AsyncWebServerRequest* request){
|
||||||
LOGln("HTTP * GET /free_heap");
|
LOGln("HTTP * GET /free_heap");
|
||||||
http_server.send(200, "text/plain", String(ESP.getFreeHeap()));
|
request->send(200, "text/plain", String(ESP.getFreeHeap()));
|
||||||
});
|
});
|
||||||
http_server.on("/uptime", HTTP_GET, [&](){
|
http_server.on("/uptime", HTTP_GET, [&](AsyncWebServerRequest* request){
|
||||||
LOGln("HTTP * GET /uptime");
|
LOGln("HTTP * GET /uptime");
|
||||||
http_server.send(200, "text/plain", String(millis()/1000));
|
request->send(200, "text/plain", String(millis()/1000));
|
||||||
});
|
});
|
||||||
http_server.on("/fps", HTTP_GET, [](){
|
http_server.on("/fps", HTTP_GET, [](AsyncWebServerRequest* request){
|
||||||
LOGln("HTTP * GET /fps");
|
LOGln("HTTP * GET /fps");
|
||||||
http_server.send(200, "text/plain", String(FastLED.getFPS()));
|
request->send(200, "text/plain", String(FastLED.getFPS()));
|
||||||
});
|
});
|
||||||
http_server.on("/reboot", HTTP_POST, [](){
|
http_server.on("/reboot", HTTP_POST, [](AsyncWebServerRequest* request){
|
||||||
LOGln("HTTP * POST /reboot");
|
LOGln("HTTP * POST /reboot");
|
||||||
ESP.restart();
|
ESP.restart();
|
||||||
});
|
});
|
||||||
http_server.on("/brightness", HTTP_POST, [&](){
|
http_server.on("/mode", HTTP_POST, [&](AsyncWebServerRequest* request){
|
||||||
LOGln("HTTP * POST /brightness with value %s", http_server.arg("plain").c_str());
|
LOGln("HTTP * POST /mode with value %s", request->getParam("plain", true)->value().c_str());
|
||||||
if (!http_server.hasArg("plain")) {
|
if (!request->hasParam("plain", true)) {
|
||||||
http_server.send_P(400, text_plain, PSTR("No brightness given"));
|
request->send_P(400, text_plain, PSTR("No effect given."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
long val = http_server.arg("plain").toInt();
|
String val = request->getParam("plain", true)->value();
|
||||||
if (val==0 || val>255) {
|
|
||||||
http_server.send_P(400, text_plain, PSTR("New value out of bounds. (1-255)"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
FastLED.setBrightness(val);
|
|
||||||
http_server.send(200, "text/plain", "OK");
|
|
||||||
});
|
|
||||||
http_server.on("/mode", HTTP_POST, [&](){
|
|
||||||
LOGln("HTTP * POST /mode with value %s", http_server.arg("plain").c_str());
|
|
||||||
if (!http_server.hasArg("plain")) {
|
|
||||||
http_server.send_P(400, text_plain, PSTR("No effect given."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
String val = http_server.arg("plain");
|
|
||||||
if (change_current_effect(val)) {
|
if (change_current_effect(val)) {
|
||||||
http_server.send(200, "text/plain", "OK");
|
request->send(200, "text/plain", "OK");
|
||||||
} else {
|
} else {
|
||||||
http_server.send_P(400, text_plain, PSTR("Unknown effect."));
|
request->send_P(400, text_plain, PSTR("Unknown effect."));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
http_server.begin();
|
http_server.on("/monitor", HTTP_GET, [&](AsyncWebServerRequest* request) {
|
||||||
|
static const char* html PROGMEM = R"(
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Pitrix</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>
|
||||||
|
<button id='monitor_off'>Monitor OFF</button>
|
||||||
|
<button id='monitor_on'>Monitor ON</button>
|
||||||
|
</p>
|
||||||
|
<canvas id='target' width='800' height='500'></canvas>
|
||||||
|
<script type='text/javascript'>
|
||||||
|
width = height = 32;
|
||||||
|
active = false;
|
||||||
|
ctx = document.getElementById('target').getContext('2d');
|
||||||
|
socket = new WebSocket((document.location.protocol=='https:' ? 'wss' : 'ws') + '://' + document.location.host + '/ws');
|
||||||
|
socket.onopen = function() {
|
||||||
|
socket.send('effects?');
|
||||||
|
socket.send('settings?');
|
||||||
|
};
|
||||||
|
socket.binaryType = 'arraybuffer';
|
||||||
|
socket.onmessage = function(message) {
|
||||||
|
if ((typeof message.data) == 'string') {
|
||||||
|
var j = JSON.parse(message.data);
|
||||||
|
if (j.effects) {
|
||||||
|
console.log('Got effects.');
|
||||||
|
console.log(j.effects);
|
||||||
|
}
|
||||||
|
if (j.settings) {
|
||||||
|
console.log('Got settings.');
|
||||||
|
console.log(j.settings);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!active) return;
|
||||||
|
var buffer = new Uint8Array(message.data);
|
||||||
|
width = buffer[0];
|
||||||
|
height = buffer[1];
|
||||||
|
if (buffer[2] != 255) return;
|
||||||
|
var offset = 3;
|
||||||
|
ctx.fillStyle = '#000000';
|
||||||
|
ctx.fillRect(0, 0, 20*width, 20*height);
|
||||||
|
for (var y=0; y<height; y++) for (var x=0; x<width; x++) {
|
||||||
|
var r = buffer[offset + 0];
|
||||||
|
var g = buffer[offset + 1];
|
||||||
|
var b = buffer[offset + 2];
|
||||||
|
offset = offset + 3;
|
||||||
|
ctx.fillStyle = 'rgb('+r+','+g+','+b+')';
|
||||||
|
ctx.fillRect(x*20+2, y*20+2, 16, 16);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
document.getElementById('monitor_on').onclick = function() {
|
||||||
|
socket.send('monitor:1');
|
||||||
|
active = true;
|
||||||
|
};
|
||||||
|
document.getElementById('monitor_off').onclick = function() {
|
||||||
|
socket.send('monitor:0');
|
||||||
|
active = false;
|
||||||
|
ctx.fillStyle = '0x80808080';
|
||||||
|
ctx.fillRect(0, 0, width*20, height*20);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
)";
|
||||||
|
|
||||||
|
request->send_P(200, PSTR("text/html"), html);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
ws.onEvent(handle_ws);
|
||||||
|
http_server.addHandler(&ws);
|
||||||
|
|
||||||
|
|
||||||
|
http_server.begin();
|
||||||
|
|
||||||
MDNS.addService("_http", "_tcp", 80);
|
MDNS.addService("_http", "_tcp", 80);
|
||||||
}
|
}
|
||||||
|
|
||||||
void http_server_loop() {
|
void http_server_send_framedata() {
|
||||||
http_server.handleClient();
|
if (ws.count()>0 && monitor_client>0) {
|
||||||
|
if (ws.hasClient(monitor_client)) {
|
||||||
|
uint16_t _size = LED_WIDTH * LED_HEIGHT * 3 + 4;
|
||||||
|
uint8_t* _buffer = new uint8_t[_size];
|
||||||
|
_buffer[0] = LED_WIDTH;
|
||||||
|
_buffer[1] = LED_HEIGHT;
|
||||||
|
_buffer[2] = 255;
|
||||||
|
for (uint8_t y=0; y<LED_HEIGHT; y++) for(uint8_t x=0; x<LED_WIDTH; x++) {
|
||||||
|
uint16_t index = XYsafe(x, y);
|
||||||
|
CRGB pixel = leds[index];
|
||||||
|
_buffer[3 + (y*LED_WIDTH + x)*3 + 0] = (pixel.r==255 ? 254 : pixel.r);
|
||||||
|
_buffer[3 + (y*LED_WIDTH + x)*3 + 1] = (pixel.g==255 ? 254 : pixel.g);
|
||||||
|
_buffer[3 + (y*LED_WIDTH + x)*3 + 2] = (pixel.b==255 ? 254 : pixel.b);
|
||||||
|
}
|
||||||
|
_buffer[_size - 1] = 255;
|
||||||
|
ws.binary(monitor_client, _buffer, _size);
|
||||||
|
delete _buffer;
|
||||||
|
} else {
|
||||||
|
monitor_client = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
42
src/mqtt.cpp
42
src/mqtt.cpp
@ -23,7 +23,7 @@ void mqtt_callback(char* original_topic, byte* pl, unsigned int length) {
|
|||||||
pl[length] = '\0';
|
pl[length] = '\0';
|
||||||
String payload((char*)pl);
|
String payload((char*)pl);
|
||||||
String topic (original_topic);
|
String topic (original_topic);
|
||||||
if (topic.equals(MQTT_TOPIC "log") || topic.equals(MQTT_TOPIC "status") || topic.equals(MQTT_TOPIC "metrics")) {
|
if (topic.equals(MQTT_TOPIC "log") || topic.equals(MQTT_TOPIC "status") || topic.equals(MQTT_TOPIC "status_details") || topic.startsWith(MQTT_TOPIC "metrics")) {
|
||||||
// Return our own messages
|
// Return our own messages
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -31,7 +31,7 @@ void mqtt_callback(char* original_topic, byte* pl, unsigned int length) {
|
|||||||
if (topic.startsWith(MQTT_TOPIC_WEATHER)) {
|
if (topic.startsWith(MQTT_TOPIC_WEATHER)) {
|
||||||
// Weather stuff
|
// Weather stuff
|
||||||
topic.remove(0, strlen(MQTT_TOPIC_WEATHER));
|
topic.remove(0, strlen(MQTT_TOPIC_WEATHER));
|
||||||
LOGln("MQTT * Weather stuff.");
|
DBG("MQTT * Weather stuff.");
|
||||||
if (topic.startsWith("icons/")) {
|
if (topic.startsWith("icons/")) {
|
||||||
topic.remove(0, 6);
|
topic.remove(0, 6);
|
||||||
uint8_t id = topic.toInt();
|
uint8_t id = topic.toInt();
|
||||||
@ -39,7 +39,7 @@ void mqtt_callback(char* original_topic, byte* pl, unsigned int length) {
|
|||||||
uint8_t val = payload.toInt();
|
uint8_t val = payload.toInt();
|
||||||
if (val==0) return;
|
if (val==0) return;
|
||||||
weather_icon_ids[id] = val;
|
weather_icon_ids[id] = val;
|
||||||
LOGln("Set weather_icon_ids[%d] to value %d", id, val);
|
DBG("Set weather_icon_ids[%d] to value %d", id, val);
|
||||||
} else if (topic.startsWith("temperatures/")) {
|
} else if (topic.startsWith("temperatures/")) {
|
||||||
topic.remove(0, 13);
|
topic.remove(0, 13);
|
||||||
uint8_t id = topic.toInt();
|
uint8_t id = topic.toInt();
|
||||||
@ -47,9 +47,13 @@ void mqtt_callback(char* original_topic, byte* pl, unsigned int length) {
|
|||||||
uint8_t val = payload.toInt();
|
uint8_t val = payload.toInt();
|
||||||
if (val==0) return;
|
if (val==0) return;
|
||||||
weather_temperatures[id] = val;
|
weather_temperatures[id] = val;
|
||||||
LOGln("Set weather_temperatures[%d] to value %d", id, val);
|
DBG("Set weather_temperatures[%d] to value %d", id, val);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
} else if (topic.equals(MQTT_TOPIC_TIMER)) {
|
||||||
|
timer = payload.toInt();
|
||||||
|
LOGln("Set timer to %lu.", timer);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
topic.remove(0, strlen(MQTT_TOPIC)); // Strip MQTT_TOPIC from the beginning
|
topic.remove(0, strlen(MQTT_TOPIC)); // Strip MQTT_TOPIC from the beginning
|
||||||
@ -94,13 +98,33 @@ void mqtt_callback(char* original_topic, byte* pl, unsigned int length) {
|
|||||||
|
|
||||||
boolean mqtt_connect() {
|
boolean mqtt_connect() {
|
||||||
LOGln("MQTT * Connecting to MQTT server with client id %s", hostname);
|
LOGln("MQTT * Connecting to MQTT server with client id %s", hostname);
|
||||||
|
mqtt_client.setBufferSize(350);
|
||||||
if (mqtt_client.connect(hostname, MQTT_USER, MQTT_PASS, MQTT_TOPIC "status", 0, true, "OFFLINE", true)) {
|
if (mqtt_client.connect(hostname, MQTT_USER, MQTT_PASS, MQTT_TOPIC "status", 0, true, "OFFLINE", true)) {
|
||||||
LOGln("MQTT * Connected.");
|
LOGln("MQTT * Connected.");
|
||||||
|
LOGln("Core * Flash chip id: 0x%X. Size: %d bytes. 'Real' size: %d bytes.", ESP.getFlashChipId(), ESP.getFlashChipSize(), ESP.getFlashChipRealSize());
|
||||||
char buffer[40];
|
char buffer[40];
|
||||||
snprintf(buffer, 40, "ONLINE %s %s", hostname, WiFi.localIP().toString().c_str());
|
snprintf(buffer, 40, "ONLINE %s %s", hostname, WiFi.localIP().toString().c_str());
|
||||||
mqtt_client.publish(MQTT_TOPIC "status", buffer, true);
|
mqtt_client.publish(MQTT_TOPIC "status", "ONLINE", true);
|
||||||
|
mqtt_client.publish(MQTT_TOPIC "status_details", buffer, true);
|
||||||
mqtt_client.subscribe(MQTT_TOPIC "+");
|
mqtt_client.subscribe(MQTT_TOPIC "+");
|
||||||
mqtt_client.subscribe(MQTT_TOPIC_WEATHER "#");
|
mqtt_client.subscribe(MQTT_TOPIC_WEATHER "#");
|
||||||
|
mqtt_client.subscribe(MQTT_TOPIC_TIMER);
|
||||||
|
|
||||||
|
#ifdef MQTT_TOPIC_HOMEASSISTANT
|
||||||
|
// Set MQTT values for homeassistant auto device discovery
|
||||||
|
String topic = MQTT_TOPIC_HOMEASSISTANT "/light/";
|
||||||
|
topic += hostname;
|
||||||
|
topic += "/config";
|
||||||
|
String message = "{\"~\":\"" MQTT_TOPIC "\",\"opt\":1,\"avty_t\":\"~status\",\"pl_avail\":\"ONLINE\",\"pl_not_avail\":\"OFFLINE\",";
|
||||||
|
message += "\"bri_cmd_t\": \"~brightness\",\"bri_scl\":255,\"fx_cmd_t\":\"~modus\",\"name\":\"Pitrix\",\"uniq_id\":\"";
|
||||||
|
message += hostname;
|
||||||
|
message += "\",";
|
||||||
|
message += "\"stat_t\":\"~modus\",\"cmd_t\":\"~modus\",\"pl_on\":\"cycle\",\"pl_off\":\"night_clock\"}";
|
||||||
|
mqtt_client.publish(topic.c_str(), message.c_str(), true);
|
||||||
|
DBG("MQTT * Homeassistant data:");
|
||||||
|
DBG("MQTT * Topic: %s", topic.c_str());
|
||||||
|
DBG("MQTT * Data: %s", message.c_str());
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
return mqtt_client.connected();
|
return mqtt_client.connected();
|
||||||
}
|
}
|
||||||
@ -127,16 +151,16 @@ void mqtt_loop() {
|
|||||||
|
|
||||||
String mqtt_log_str = String();
|
String mqtt_log_str = String();
|
||||||
|
|
||||||
void mqtt_publish(const char* topic, int number) {
|
void mqtt_publish(const char* topic, int number, bool retain) {
|
||||||
char b[32];
|
char b[32];
|
||||||
sprintf(b, "%d", number);
|
sprintf(b, "%d", number);
|
||||||
mqtt_publish(topic, b);
|
mqtt_publish(topic, b, retain);
|
||||||
}
|
}
|
||||||
|
|
||||||
void mqtt_publish(const char* topic, const char* message) {
|
void mqtt_publish(const char* topic, const char* message, bool retain) {
|
||||||
char t[127];
|
char t[127];
|
||||||
sprintf(t, MQTT_TOPIC "%s", topic);
|
sprintf(t, MQTT_TOPIC "%s", topic);
|
||||||
mqtt_client.publish(t, message);
|
mqtt_client.publish(t, message, retain);
|
||||||
}
|
}
|
||||||
|
|
||||||
void mqtt_log(const char* message) {
|
void mqtt_log(const char* message) {
|
||||||
|
25
src/ntp.cpp
25
src/ntp.cpp
@ -1,13 +1,26 @@
|
|||||||
#include <ntp.h>
|
#include <sntp.h>
|
||||||
|
#include <coredecls.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <TZ.h>
|
||||||
#include "my_fastled.h"
|
#include "my_fastled.h"
|
||||||
|
|
||||||
void updateCallback(NTPClient* c) {
|
/*void updateCallback(NTPClient* c) {
|
||||||
random16_add_entropy(c->getEpochMillis() & 0xFFFF);
|
random16_add_entropy(c->getEpochMillis() & 0xFFFF);
|
||||||
}
|
LOGln("Received current time. Epoch is %lu.", ntpClient.getEpochTime());
|
||||||
|
}*/
|
||||||
|
|
||||||
WiFiUDP ntpUDP;
|
void time_changed() {
|
||||||
NTPClient ntpClient(ntpUDP, NTP_SERVER, NTP_OFFSET, NTP_INTERVAL);
|
random16_add_entropy(millis() & 0xFFFF);
|
||||||
|
time_t now;
|
||||||
|
tm timeinfo;
|
||||||
|
time(&now);
|
||||||
|
localtime_r(&now, &timeinfo);
|
||||||
|
char time_s[30];
|
||||||
|
strftime(time_s, 30, "%a %d.%m.%Y %T", &timeinfo);
|
||||||
|
LOGln("NTP * Received time: %s", time_s);
|
||||||
|
}
|
||||||
|
|
||||||
void ntp_setup() {
|
void ntp_setup() {
|
||||||
ntpClient.setUpdateCallback(updateCallback);
|
settimeofday_cb(time_changed);
|
||||||
|
configTime(TZ_Europe_Berlin, "de.pool.ntp.org");
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
#include <FS.h>
|
||||||
|
|
||||||
#include "ntp.h"
|
#include "ntp.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
@ -9,14 +10,16 @@
|
|||||||
#include "functions.h"
|
#include "functions.h"
|
||||||
#include "effects.h"
|
#include "effects.h"
|
||||||
#include "http_server.h"
|
#include "http_server.h"
|
||||||
#include "recorder.h"
|
#include "settings.h"
|
||||||
|
|
||||||
uint8_t starting_up = OTA_STARTUP_DELAY;
|
uint8_t starting_up = OTA_STARTUP_DELAY;
|
||||||
int loop_timeouts = 0;
|
int loop_timeouts = 0;
|
||||||
long loop_started_at = 0;
|
long loop_started_at = 0;
|
||||||
uint8_t baseHue = 0; // defined as extern in prototypes.h
|
uint8_t baseHue = 0; // defined as extern in prototypes.h
|
||||||
char hostname[30]; // defined as extern in prototypes.h
|
char hostname[30]; // defined as extern in prototypes.h
|
||||||
|
uint16_t frame = 0; // defined as extern in prototypes.h
|
||||||
unsigned long _last_effect_loop_finished_at = 0;
|
unsigned long _last_effect_loop_finished_at = 0;
|
||||||
|
unsigned long timer = 0;
|
||||||
#ifdef RECORDER_ENABLE
|
#ifdef RECORDER_ENABLE
|
||||||
Recorder* recorder;
|
Recorder* recorder;
|
||||||
#endif
|
#endif
|
||||||
@ -42,17 +45,23 @@ void setup() {
|
|||||||
ntp_setup();
|
ntp_setup();
|
||||||
ota_setup();
|
ota_setup();
|
||||||
fastled_setup();
|
fastled_setup();
|
||||||
ntpClient.begin();
|
|
||||||
#ifdef HTTP_SERVER_ENABLE
|
#ifdef HTTP_SERVER_ENABLE
|
||||||
http_server_setup();
|
http_server_setup();
|
||||||
#endif
|
#endif
|
||||||
#ifdef MQTT_ENABLE
|
#ifdef MQTT_ENABLE
|
||||||
mqtt_setup();
|
mqtt_setup();
|
||||||
#endif
|
#endif
|
||||||
#ifdef RECORDER_ENABLE
|
if (!SPIFFS.begin()) {
|
||||||
recorder = new Recorder();
|
LOGln("Core * Could not open SPIFFS filesystem");
|
||||||
#endif
|
} else {
|
||||||
SPIFFS.begin();
|
LOGln("Core * Files in SPIFFS filesystem:");
|
||||||
|
Dir d = SPIFFS.openDir("/");
|
||||||
|
while(d.next()) {
|
||||||
|
LOGln("Core * %s", d.fileName().c_str());
|
||||||
|
}
|
||||||
|
LOGln("Core * End of SPIFFS file listing.");
|
||||||
|
}
|
||||||
|
load_settings();
|
||||||
LOGln("Core * Setup complete");
|
LOGln("Core * Setup complete");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,7 +73,7 @@ void loop() {
|
|||||||
EVERY_N_SECONDS(1) {
|
EVERY_N_SECONDS(1) {
|
||||||
Serial.print("Core * Waiting for OTA... "); Serial.println(starting_up);
|
Serial.print("Core * Waiting for OTA... "); Serial.println(starting_up);
|
||||||
starting_up--;
|
starting_up--;
|
||||||
Window* w = Window::getFullWindow();
|
Window* w = &Window::window_full;
|
||||||
CRGB color(0xFF0000);
|
CRGB color(0xFF0000);
|
||||||
w->clear();
|
w->clear();
|
||||||
for (int i=0; i<starting_up; i++) {
|
for (int i=0; i<starting_up; i++) {
|
||||||
@ -75,19 +84,16 @@ void loop() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ntpClient.update();
|
|
||||||
#ifdef MQTT_ENABLE
|
#ifdef MQTT_ENABLE
|
||||||
mqtt_loop();
|
mqtt_loop();
|
||||||
#endif
|
#endif
|
||||||
#ifdef HTTP_SERVER_ENABLE
|
|
||||||
http_server_loop();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
EVERY_N_MILLISECONDS(100) {
|
EVERY_N_MILLISECONDS(100) {
|
||||||
baseHue++;
|
baseHue++;
|
||||||
}
|
}
|
||||||
|
|
||||||
EVERY_N_MILLISECONDS(1000 / FPS) {
|
EVERY_N_MILLISECONDS(1000 / FPS) {
|
||||||
|
frame++;
|
||||||
// Calculate the delay since the last time loop() was called.
|
// Calculate the delay since the last time loop() was called.
|
||||||
// This way, the effect can handle varying frame rates.
|
// This way, the effect can handle varying frame rates.
|
||||||
uint16_t last_loop_ago;
|
uint16_t last_loop_ago;
|
||||||
@ -98,23 +104,29 @@ void loop() {
|
|||||||
last_loop_ago = 0;
|
last_loop_ago = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef MQTT_REPORT_METRICS
|
||||||
|
unsigned long effect_loop_started = millis();
|
||||||
|
#endif
|
||||||
|
|
||||||
current_effect->loop(last_loop_ago);
|
current_effect->loop(last_loop_ago);
|
||||||
|
|
||||||
|
#ifdef MQTT_REPORT_METRICS
|
||||||
|
metrics_frame_count++;
|
||||||
|
metrics_frame_time += (millis() - effect_loop_started);
|
||||||
|
#endif
|
||||||
|
|
||||||
// Save the time for the next run.
|
// Save the time for the next run.
|
||||||
_last_effect_loop_finished_at = now;
|
_last_effect_loop_finished_at = now;
|
||||||
|
|
||||||
if (current_effect->can_be_shown_with_clock()) {
|
if (current_effect->can_be_shown_with_clock()) {
|
||||||
effect_clock.loop_with_invert(current_effect->clock_as_mask());
|
effect_clock.loop_with_invert(current_effect->clock_as_mask());
|
||||||
}
|
}
|
||||||
FastLED.show();
|
|
||||||
#ifdef MQTT_REPORT_METRICS
|
effect_timer.loop(last_loop_ago);
|
||||||
metrics_frame_count++;
|
|
||||||
metrics_frame_time += (millis() - loop_started_at);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef RECORDER_ENABLE
|
FastLED.show();
|
||||||
recorder->loop();
|
|
||||||
#endif
|
http_server_send_framedata();
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(MQTT_ENABLE) && defined(MQTT_REPORT_METRICS)
|
#if defined(MQTT_ENABLE) && defined(MQTT_REPORT_METRICS)
|
||||||
|
@ -1,71 +0,0 @@
|
|||||||
#include "recorder.h"
|
|
||||||
#include "my_fastled.h"
|
|
||||||
#include "functions.h"
|
|
||||||
#include "effects.h"
|
|
||||||
#include "Window.h"
|
|
||||||
#include <WiFiUdp.h>
|
|
||||||
|
|
||||||
#ifdef RECORDER_ENABLE
|
|
||||||
|
|
||||||
Recorder::Recorder() {
|
|
||||||
_buffer_len = LED_WIDTH * LED_HEIGHT * 3 + 3;
|
|
||||||
_buffer = (char*)malloc(_buffer_len);
|
|
||||||
_server = new AsyncServer(RECORDER_PORT);
|
|
||||||
_server->onClient([&](void* a, AsyncClient* c) {
|
|
||||||
LOGln("Recorder * New client: %s. Waiting for port number...", c->remoteIP().toString().c_str());
|
|
||||||
if (_client) {
|
|
||||||
LOGln("Recorder * Killing old client.");
|
|
||||||
_client->close();
|
|
||||||
_client_port = 0;
|
|
||||||
delete _client;
|
|
||||||
}
|
|
||||||
_client = c;
|
|
||||||
_msgid = 0;
|
|
||||||
char dim[3] = {LED_WIDTH, LED_HEIGHT, 255};
|
|
||||||
_client->write(dim, 3);
|
|
||||||
_client->onDisconnect([&](void* a, AsyncClient* client) {
|
|
||||||
LOGln("Recorder * Client disconnected");
|
|
||||||
_client_port = 0;
|
|
||||||
}, NULL);
|
|
||||||
_client->onData([&](void* a, AsyncClient* client, void* data, size_t len) {
|
|
||||||
if (*(char*)data == 'P') {
|
|
||||||
LOGln("Found.");
|
|
||||||
if (len >= 3) {
|
|
||||||
uint8_t* d = (uint8_t*)data;
|
|
||||||
_client_port = d[1]<<8 | d[2];
|
|
||||||
LOGln("Recorder * Sending data to port %d", _client_port);
|
|
||||||
}
|
|
||||||
} else if (*(char*)data == 'E') {
|
|
||||||
String effect = String((char*)data+1);
|
|
||||||
LOGln("Recorder * Setting effect %s", effect.c_str());
|
|
||||||
Window::getFullWindow()->clear();
|
|
||||||
change_current_effect(effect);
|
|
||||||
}
|
|
||||||
}, NULL);
|
|
||||||
}, _server);
|
|
||||||
_server->begin();
|
|
||||||
LOGln("Recorder * Listening on port %d", RECORDER_PORT);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Recorder::loop() {
|
|
||||||
if (_client && _client_port) {
|
|
||||||
_skip_next_frame = !_skip_next_frame;
|
|
||||||
if (_skip_next_frame == false) return;
|
|
||||||
_buffer[0] = _msgid >> 8;
|
|
||||||
_buffer[1] = _msgid & 0xFF;
|
|
||||||
for (uint8_t y=0; y<LED_HEIGHT; y++) for(uint8_t x=0; x<LED_WIDTH; x++) {
|
|
||||||
uint16_t index = XYsafe(x, y);
|
|
||||||
CRGB pixel = leds[index];
|
|
||||||
_buffer[2 + (y*LED_WIDTH + x)*3 + 0] = (pixel.r==255 ? 254 : pixel.r);
|
|
||||||
_buffer[2 + (y*LED_WIDTH + x)*3 + 1] = (pixel.g==255 ? 254 : pixel.g);
|
|
||||||
_buffer[2 + (y*LED_WIDTH + x)*3 + 2] = (pixel.b==255 ? 254 : pixel.b);
|
|
||||||
}
|
|
||||||
_buffer[_buffer_len - 1] = 255;
|
|
||||||
_udp.beginPacket("10.10.2.1", 13330);
|
|
||||||
_udp.write(_buffer, _buffer_len);
|
|
||||||
_udp.endPacket();
|
|
||||||
_msgid++;
|
|
||||||
//_client->write(_buffer, _buffer_len);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
111
src/settings.cpp
111
src/settings.cpp
@ -1,55 +1,67 @@
|
|||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
#include <FS.h>
|
||||||
|
|
||||||
Settings settings;
|
Settings settings;
|
||||||
|
|
||||||
Setting all_settings[] = {
|
Setting all_settings[] = {
|
||||||
{"fps", &settings.fps, TYPE_UINT8},
|
{"fps", &settings.fps, TYPE_UINT8},
|
||||||
|
|
||||||
|
{"effects.big_clock.spacing", &settings.effects.big_clock.spacing, TYPE_UINT8},
|
||||||
|
|
||||||
{"effects.confetti.pixels_per_loop", &settings.effects.confetti.pixels_per_loop, TYPE_UINT8},
|
{"effects.confetti.pixels_per_loop", &settings.effects.confetti.pixels_per_loop, TYPE_UINT8},
|
||||||
|
|
||||||
|
{"effects.blur2d.count", &settings.effects.blur2d.count, TYPE_UINT8},
|
||||||
|
|
||||||
{"effects.cycle.random", &settings.effects.cycle.random, TYPE_BOOL},
|
{"effects.cycle.random", &settings.effects.cycle.random, TYPE_BOOL},
|
||||||
{"effects.cycle.time", &settings.effects.cycle.time, TYPE_UINT16},
|
{"effects.cycle.time", &settings.effects.cycle.time, TYPE_UINT16},
|
||||||
|
|
||||||
{"effects.dvd.width", &settings.effects.dvd.width, TYPE_UINT8},
|
{"effects.dvd.width", &settings.effects.dvd.width, TYPE_UINT8},
|
||||||
{"effects.dvd.height", &settings.effects.dvd.height, TYPE_UINT8},
|
{"effects.dvd.height", &settings.effects.dvd.height, TYPE_UINT8},
|
||||||
{"effects.dvd.speed", &settings.effects.dvd.speed, 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.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.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_loop_time", &settings.effects.dynamic.big_loop_time, TYPE_UINT16},
|
||||||
{"effects.dynamic.big_size", &settings.effects.dynamic.big_size, TYPE_UINT8},
|
{"effects.dynamic.big_size", &settings.effects.dynamic.big_size, TYPE_UINT8},
|
||||||
|
|
||||||
{"effects.fire.cooldown", &settings.effects.fire.cooldown, TYPE_UINT8},
|
{"effects.fire.cooldown", &settings.effects.fire.cooldown, TYPE_UINT8},
|
||||||
{"effects.fire.spark_chance", &settings.effects.fire.spark_chance, TYPE_UINT8},
|
{"effects.fire.spark_chance", &settings.effects.fire.spark_chance, TYPE_UINT8},
|
||||||
|
|
||||||
{"effects.firework.drag", &settings.effects.firework.drag, TYPE_UINT8},
|
{"effects.firework.drag", &settings.effects.firework.drag, TYPE_UINT8},
|
||||||
{"effects.firework.bounce", &settings.effects.firework.bounce, TYPE_UINT8},
|
{"effects.firework.bounce", &settings.effects.firework.bounce, TYPE_UINT8},
|
||||||
{"effects.firework.gravity", &settings.effects.firework.gravity, TYPE_UINT8},
|
{"effects.firework.gravity", &settings.effects.firework.gravity, TYPE_UINT8},
|
||||||
{"effects.firework.sparks", &settings.effects.firework.sparks, TYPE_UINT8},
|
{"effects.firework.sparks", &settings.effects.firework.sparks, TYPE_UINT8},
|
||||||
|
|
||||||
{"effects.gol.start_percentage", &settings.effects.gol.start_percentage, 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.blend_speed", &settings.effects.gol.blend_speed, TYPE_UINT8},
|
||||||
{"effects.gol.restart_after_steps", &settings.effects.gol.restart_after_steps, TYPE_UINT8},
|
{"effects.gol.restart_after_steps", &settings.effects.gol.restart_after_steps, TYPE_UINT8},
|
||||||
|
|
||||||
|
{"effects.lightspeed.count", &settings.effects.lightspeed.count, TYPE_UINT8},
|
||||||
|
|
||||||
{"effects.matrix.length_min", &settings.effects.matrix.length_min, 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.length_max", &settings.effects.matrix.length_max, TYPE_UINT8},
|
||||||
{"effects.matrix.speed_min", &settings.effects.matrix.speed_min, 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.matrix.speed_max", &settings.effects.matrix.speed_max, TYPE_UINT8},
|
||||||
|
{"effects.matrix.count", &settings.effects.matrix.count, TYPE_UINT8},
|
||||||
|
{"effects.matrix.random_count", &settings.effects.matrix.random_count, TYPE_UINT8},
|
||||||
|
|
||||||
{"effects.sines.count", &settings.effects.sines.count, TYPE_UINT8},
|
{"effects.sines.count", &settings.effects.sines.count, TYPE_UINT8},
|
||||||
|
|
||||||
{"effects.snake.direction_change", &settings.effects.snake.direction_change, TYPE_UINT8}
|
{"effects.snake.direction_change", &settings.effects.snake.direction_change, TYPE_UINT8},
|
||||||
|
|
||||||
|
{"effects.tv_static.black_bar_speed", &settings.effects.tv_static.black_bar_speed, TYPE_UINT16},
|
||||||
};
|
};
|
||||||
|
|
||||||
const uint8_t all_settings_size = 25;
|
const uint8_t all_settings_size = 32;
|
||||||
|
|
||||||
bool change_setting(const char* key, uint16_t new_value) {
|
bool change_setting(const char* key, uint16_t new_value) {
|
||||||
LOGln("Settings * Trying to set setting %s to new value %d...", key, new_value);
|
LOGln("Settings * Setting %s to new value %d.", key, new_value);
|
||||||
Setting* s = NULL;
|
Setting* s = NULL;
|
||||||
for (uint8_t i=0; i<all_settings_size; i++) {
|
for (uint8_t i=0; i<all_settings_size; i++) {
|
||||||
if (strcmp(key, all_settings[i].name)==0) {
|
s = &(all_settings[i]);
|
||||||
s = &all_settings[i];
|
|
||||||
|
if (strcmp(key, s->name)==0) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -57,7 +69,7 @@ bool change_setting(const char* key, uint16_t new_value) {
|
|||||||
LOGln("Settings * No setting matching the name %s found.", key);
|
LOGln("Settings * No setting matching the name %s found.", key);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check data size
|
// Check data size
|
||||||
if (s->type == TYPE_BOOL && new_value > 1) {
|
if (s->type == TYPE_BOOL && new_value > 1) {
|
||||||
LOGln("Settings * Data type of %s is boolean, but new value is > 1.", key);
|
LOGln("Settings * Data type of %s is boolean, but new value is > 1.", key);
|
||||||
@ -67,8 +79,75 @@ bool change_setting(const char* key, uint16_t new_value) {
|
|||||||
LOGln("Settings * Data type of %s is uint8_t, but new value is > 0xFF.", key);
|
LOGln("Settings * Data type of %s is uint8_t, but new value is > 0xFF.", key);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
*(s->value) = new_value;
|
*(s->value) = new_value;
|
||||||
LOGln("Settings * Success. New value for %s is %d.", key, new_value);
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t setting_default(Setting* s) {
|
||||||
|
Settings defaults;
|
||||||
|
auto p_settings = std::addressof(settings);
|
||||||
|
auto p_defaults = std::addressof(defaults);
|
||||||
|
uint16_t* p_this = s->value;
|
||||||
|
uint16_t diff = p_this - (uint16_t*)p_settings;
|
||||||
|
uint16_t default_value = *((uint16_t*)p_defaults + diff);
|
||||||
|
return default_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool save_settings() {
|
||||||
|
if (!SPIFFS.begin()) {
|
||||||
|
LOGln("Settings * Could not open SPIFFS for saving.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
File f = SPIFFS.open("/pitrix_settings.txt", "w");
|
||||||
|
if (!f) {
|
||||||
|
LOGln("Settings * Could not open /pitrix_settings.txt for writing.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Setting* s;
|
||||||
|
for (uint8_t i=0; i<all_settings_size; i++) {
|
||||||
|
s = &(all_settings[i]);
|
||||||
|
uint16_t value = *(s->value);
|
||||||
|
uint16_t default_value = setting_default(s);
|
||||||
|
if (default_value != value) {
|
||||||
|
char buf[50];
|
||||||
|
snprintf(buf, 50, "%s=%d", s->name, value);
|
||||||
|
LOGln("Saving: %s", buf);
|
||||||
|
f.println(buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
f.close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool load_settings() {
|
||||||
|
if (!SPIFFS.begin()) {
|
||||||
|
LOGln("Settings * Could not open SPIFFS for loading.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
File f = SPIFFS.open("/pitrix_settings.txt", "r");
|
||||||
|
if (!f) {
|
||||||
|
LOGln("Settings * Could not open /pitrix_settings.txt for reading.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String s = f.readString();
|
||||||
|
f.close();
|
||||||
|
|
||||||
|
while (s.length() > 0) {
|
||||||
|
int pos = s.lastIndexOf('\n');
|
||||||
|
String part = s.substring(pos + 1);
|
||||||
|
if (pos==-1) pos=0;
|
||||||
|
s.remove(pos);
|
||||||
|
pos = part.indexOf('=');
|
||||||
|
if (pos < 1) continue;
|
||||||
|
String key = part.substring(0, pos);
|
||||||
|
uint16_t value = part.substring(pos + 1).toInt();
|
||||||
|
change_setting(key.c_str(), value);
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -16,9 +16,13 @@ namespace tests {
|
|||||||
for (int j=0; j<3; j++) {
|
for (int j=0; j<3; j++) {
|
||||||
int free_at_start = ESP.getFreeHeap();
|
int free_at_start = ESP.getFreeHeap();
|
||||||
effect = select_effect(i);
|
effect = select_effect(i);
|
||||||
effect->loop(1);
|
|
||||||
if (effect == NULL) return;
|
if (effect == NULL) return;
|
||||||
effect_name = effect->get_name();
|
effect_name = effect->get_name();
|
||||||
|
if (effect_name && !effect_name.equals("cycle")) {
|
||||||
|
LOGln("Testing effect %s...", effect_name.c_str());
|
||||||
|
delay(1);
|
||||||
|
effect->loop(1);
|
||||||
|
}
|
||||||
delete effect;
|
delete effect;
|
||||||
diffs[i] = ESP.getFreeHeap() - free_at_start;
|
diffs[i] = ESP.getFreeHeap() - free_at_start;
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
IP="$1"
|
IP="$1"
|
||||||
EFFECTS=`egrep "case" ../effects.cpp | tr -s "\t" " " | cut -d" " -f 7 | sort`
|
EFFECTS=`./list_effects.rb $IP | sort`
|
||||||
|
|
||||||
mkdir effects
|
mkdir effects
|
||||||
|
|
||||||
|
26
src/tools/list_effects.rb
Executable file
26
src/tools/list_effects.rb
Executable file
@ -0,0 +1,26 @@
|
|||||||
|
#!/usr/bin/env ruby
|
||||||
|
require 'websocket-eventmachine-client'
|
||||||
|
require 'json'
|
||||||
|
|
||||||
|
IP = ARGV[0] or raise "No IP given"
|
||||||
|
uri = "ws://#{IP}:80/ws"
|
||||||
|
|
||||||
|
EM.run do
|
||||||
|
ws = WebSocket::EventMachine::Client.connect(uri: uri)
|
||||||
|
|
||||||
|
ws.onopen do
|
||||||
|
ws.send "effects?"
|
||||||
|
end
|
||||||
|
|
||||||
|
ws.onmessage do |msg, type|
|
||||||
|
if type==:text
|
||||||
|
j = JSON.parse(msg)
|
||||||
|
if j["effects"]
|
||||||
|
j["effects"].each do |effect|
|
||||||
|
puts effect
|
||||||
|
end
|
||||||
|
exit
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
92
src/tools/monitor.rb
Normal file → Executable file
92
src/tools/monitor.rb
Normal file → Executable file
@ -1,59 +1,49 @@
|
|||||||
#!/usr/bin/env ruby
|
#!/usr/bin/env ruby
|
||||||
require 'socket'
|
require 'websocket-eventmachine-client'
|
||||||
require 'pp'
|
require 'pp'
|
||||||
|
|
||||||
def rgb2ansi(r, g, b)
|
IP = ARGV[0]
|
||||||
if r==g && g==b
|
EFFECT = ARGV[1]
|
||||||
return 16 if r<8
|
uri = "ws://#{IP}:80/ws"
|
||||||
return 231 if r>248
|
puts "Connecting to #{uri}..."
|
||||||
return (((r - 8) / 247.0) * 24).round + 232
|
|
||||||
|
EM.run do
|
||||||
|
ws = WebSocket::EventMachine::Client.connect(uri: uri)
|
||||||
|
|
||||||
|
ws.onopen do
|
||||||
|
puts "\033[2J\033[H\n Connected."
|
||||||
|
ws.send "effect:#{EFFECT}" if EFFECT
|
||||||
|
ws.send "monitor:1"
|
||||||
end
|
end
|
||||||
|
|
||||||
return 16 + 36*(r/51.0).round + 6*(g/51.0).round + (b/51.0).round
|
ws.onmessage do |msg, type|
|
||||||
end
|
if type==:binary
|
||||||
|
data = msg.unpack("C*")
|
||||||
IP = ARGV[0]
|
width = data.shift
|
||||||
PORT = 2122
|
height = data.shift
|
||||||
EFFECT = ARGV[1]
|
splitter = data.shift
|
||||||
|
raise "Unexpected value #{splitter} at index 2" unless splitter==0xFF
|
||||||
puts "Connecting to #{IP}:#{PORT}..."
|
expected_length = width * height * 3 + 4
|
||||||
|
raise "Unexpected message length. Expected: #{expected_length}, was: #{data.count + 3}" unless data.count + 3==expected_length
|
||||||
s = TCPSocket.new(IP, PORT)
|
|
||||||
|
str = "\033[H+#{"-"*width}+\n"
|
||||||
puts "Connected."
|
(0...height).each do |y|
|
||||||
init = s.recv(3).unpack("C*")
|
str += "|"
|
||||||
|
(0...width).each do |x|
|
||||||
raise "Initial data packet wasn't usable!" if init[2] != 0xFF
|
r, g, b = *data.shift(3)
|
||||||
puts "Got initial data packet."
|
str += "\033[38;2;#{r};#{g};#{b}m●"
|
||||||
|
end
|
||||||
dim_x, dim_y = *init
|
str += "\033[0m|\n"
|
||||||
len = dim_x * dim_y * 3 + 3
|
end
|
||||||
|
str += "+#{"-"*width}+\n"
|
||||||
puts "Size: #{dim_x}x#{dim_y}. Expecting packet length #{len}."
|
puts str
|
||||||
|
else
|
||||||
puts "Opening local UDP socket..."
|
puts msg
|
||||||
udp = UDPSocket.new
|
exit
|
||||||
udp.bind("10.10.2.1", 13330)
|
|
||||||
puts "Waiting for UDP packets on port 13330..."
|
|
||||||
s.sendmsg("P\x12\x34\x00")
|
|
||||||
s.sendmsg("E#{EFFECT}\x00") if EFFECT
|
|
||||||
|
|
||||||
|
|
||||||
while 1
|
|
||||||
data = udp.recvfrom(1024)[0].unpack("C*")
|
|
||||||
#puts "Packet. ID: #{data[0]}, length: #{data.length}"
|
|
||||||
raise "Unexpected packet length" unless data.count == len
|
|
||||||
raise "Invalid data packet" unless data[len - 1]==0xFF
|
|
||||||
id = data.shift << 8 | data.shift
|
|
||||||
#next
|
|
||||||
#print "."
|
|
||||||
puts "\033[2J"
|
|
||||||
(0...dim_y).each do |y|
|
|
||||||
(0...dim_x).each do |x|
|
|
||||||
r, g, b = *data.shift(3)
|
|
||||||
color_code = rgb2ansi(r, g, b)
|
|
||||||
print "\033[48;5;#{color_code}m "
|
|
||||||
end
|
end
|
||||||
puts "\033[0m"
|
end
|
||||||
|
|
||||||
|
ws.onclose do |msg, reason|
|
||||||
|
puts "Disconnected. Message: #{msg}. Reason: #{reason}."
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,39 +1,19 @@
|
|||||||
#!/usr/bin/env ruby
|
#!/usr/bin/env ruby
|
||||||
require 'socket'
|
require 'websocket-eventmachine-client'
|
||||||
require 'pp'
|
require 'pp'
|
||||||
require 'rmagick'
|
require 'rmagick'
|
||||||
|
|
||||||
include Magick
|
include Magick
|
||||||
|
|
||||||
IP = ARGV[0]
|
IP = ARGV[0]
|
||||||
PORT = 2122
|
|
||||||
FILE = ARGV[1] or raise "No filename given"
|
FILE = ARGV[1] or raise "No filename given"
|
||||||
EFFECT = ARGV[2]
|
EFFECT = ARGV[2]
|
||||||
FRAMES = 125
|
FRAMES = 125
|
||||||
FACTOR = 2
|
FACTOR = 2
|
||||||
delay = 50
|
delay = 50
|
||||||
|
|
||||||
puts "Connecting to #{IP}:#{PORT}..."
|
uri = "ws://#{IP}:80/ws"
|
||||||
|
puts "Connecting to #{uri}..."
|
||||||
s = TCPSocket.new(IP, PORT)
|
|
||||||
|
|
||||||
puts "Connected."
|
|
||||||
init = s.recv(3).unpack("C*")
|
|
||||||
|
|
||||||
raise "Initial data packet wasn't usable!" if init[2] != 0xFF
|
|
||||||
puts "Got initial data packet."
|
|
||||||
|
|
||||||
dim_x, dim_y = *init
|
|
||||||
len = dim_x * dim_y * 3 + 3
|
|
||||||
|
|
||||||
puts "Size: #{dim_x}x#{dim_y}. Expecting packet length #{len}."
|
|
||||||
|
|
||||||
puts "Opening local UDP socket..."
|
|
||||||
udp = UDPSocket.new
|
|
||||||
udp.bind("10.10.2.1", 13330)
|
|
||||||
puts "Waiting for UDP packets on port 13330..."
|
|
||||||
s.sendmsg("P\x12\x34\x00")
|
|
||||||
s.sendmsg("E#{EFFECT}\x00") if EFFECT
|
|
||||||
|
|
||||||
gif = ImageList.new
|
gif = ImageList.new
|
||||||
last_id = 255
|
last_id = 255
|
||||||
@ -41,42 +21,66 @@ last_frame_time = nil
|
|||||||
img = nil
|
img = nil
|
||||||
last_diff = nil
|
last_diff = nil
|
||||||
|
|
||||||
while gif.length < FRAMES do
|
EM.run do
|
||||||
data = udp.recvfrom(1024)[0].unpack("C*")
|
ws = WebSocket::EventMachine::Client.connect(uri: uri)
|
||||||
if delay > 0
|
|
||||||
delay -= 1
|
ws.onopen do
|
||||||
next
|
puts "Connected."
|
||||||
|
puts "Waiting for delay..." if delay>0
|
||||||
|
ws.send "effect:#{EFFECT}" if EFFECT
|
||||||
|
ws.send "monitor:1"
|
||||||
end
|
end
|
||||||
#puts "Packet. ID: #{data[0]}, length: #{data.length}"
|
|
||||||
raise "Unexpected packet length" unless data.count == len
|
|
||||||
raise "Invalid data packet" unless data[len - 1]==0xFF
|
|
||||||
|
|
||||||
id = data.shift << 8 | data.shift
|
ws.onmessage do |msg, type|
|
||||||
if last_id != id-1 && last_id != id-2
|
if type==:binary
|
||||||
puts "Skipped from #{last_id} to #{id}."
|
if delay > 0
|
||||||
gif = ImageList.new
|
delay -= 1
|
||||||
end
|
next
|
||||||
last_id = id
|
end
|
||||||
|
data = msg.unpack("C*")
|
||||||
img = Image.new(dim_x, dim_y)
|
width = data.shift
|
||||||
gc = Draw.new
|
height = data.shift
|
||||||
|
splitter = data.shift
|
||||||
#next
|
raise "Unexpected value #{splitter} at index 2" unless splitter==0xFF
|
||||||
print "."
|
expected_length = width * height * 3 + 4
|
||||||
print "#{gif.length}" if gif.length%50==0
|
raise "Unexpected message length. Expected: #{expected_length}, was: #{data.count + 3}" unless data.count + 3==expected_length
|
||||||
(0...dim_y).each do |y|
|
|
||||||
(0...dim_x).each do |x|
|
img = Image.new(width, height)
|
||||||
r, g, b = *data.shift(3)
|
gc = Draw.new
|
||||||
gc.fill("rgb(#{r}, #{g}, #{b})")
|
|
||||||
gc.point(x, y)
|
print "."
|
||||||
#img.pixel_color(x, y, Pixel.new(r, g, b, 255))
|
print "#{gif.length}" if gif.length%50==0
|
||||||
|
(0...height).each do |y|
|
||||||
|
(0...width).each do |x|
|
||||||
|
r, g, b = *data.shift(3)
|
||||||
|
gc.fill("rgb(#{r}, #{g}, #{b})")
|
||||||
|
gc.point(x, y)
|
||||||
|
#img.pixel_color(x, y, Pixel.new(r, g, b, 255))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
gc.draw(img)
|
||||||
|
img.sample!(FACTOR)
|
||||||
|
gif << img
|
||||||
|
|
||||||
|
if gif.length >= FRAMES
|
||||||
|
ws.close
|
||||||
|
end
|
||||||
|
else
|
||||||
|
puts "-->#{msg}"
|
||||||
|
exit
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
gc.draw(img)
|
|
||||||
img.sample!(FACTOR)
|
ws.onerror do |error|
|
||||||
gif << img
|
puts "Error: #{error}"
|
||||||
|
end
|
||||||
|
|
||||||
|
ws.onclose do |msg, reason|
|
||||||
|
puts "Disconnected."
|
||||||
|
EventMachine.stop_event_loop
|
||||||
|
end
|
||||||
end
|
end
|
||||||
s.close
|
|
||||||
puts
|
puts
|
||||||
puts "Generating gif..."
|
puts "Generating gif..."
|
||||||
gif.ticks_per_second = 100
|
gif.ticks_per_second = 100
|
||||||
|
6
src/tools/snakenet/plot
Executable file
6
src/tools/snakenet/plot
Executable file
@ -0,0 +1,6 @@
|
|||||||
|
#!/usr/bin/gnuplot -c
|
||||||
|
set term dumb 79 49
|
||||||
|
plot 'data_set.dat' using 1:2 title 'Points', \
|
||||||
|
# 'data_set.dat' using 1:3 title 'Length' axes x1y2, \
|
||||||
|
# 'data_set.dat' using 1:4 title 'Stopped' axes x1y2, \
|
||||||
|
# 'data_set.dat' using 1:5 title 'Dead' axes x1y2
|
440
src/tools/snakenet/snakenet.rb
Normal file
440
src/tools/snakenet/snakenet.rb
Normal file
@ -0,0 +1,440 @@
|
|||||||
|
#!/usr/bin/env ruby
|
||||||
|
require 'rubygems'
|
||||||
|
require 'pp'
|
||||||
|
require 'thread/pool'
|
||||||
|
|
||||||
|
GAMES_PER_ROUND = 50
|
||||||
|
FLOAT = true
|
||||||
|
|
||||||
|
class Game
|
||||||
|
WIDTH = 16
|
||||||
|
HEIGHT = 10
|
||||||
|
|
||||||
|
POINTS_APPLE = 10
|
||||||
|
POINTS_MOVING_CLOSER = 1
|
||||||
|
POINTS_MOVING_FAR = -1.5
|
||||||
|
|
||||||
|
attr_reader :points, :dead, :ai, :length
|
||||||
|
attr_accessor :apple
|
||||||
|
|
||||||
|
def initialize(a, debug=false)
|
||||||
|
@debug = debug
|
||||||
|
@ai = a
|
||||||
|
@data = [0]*(WIDTH*HEIGHT)
|
||||||
|
@dir = 0
|
||||||
|
@pos = [WIDTH/2, HEIGHT/2]
|
||||||
|
@data[(@pos[1] )*WIDTH + @pos[0]]=1
|
||||||
|
@data[(@pos[1]+1)*WIDTH + @pos[0]]=2
|
||||||
|
@data[(@pos[1]+2)*WIDTH + @pos[0]]=3
|
||||||
|
@data[(@pos[1]+3)*WIDTH + @pos[0]]=4
|
||||||
|
@length = 4
|
||||||
|
@points = 0.0
|
||||||
|
@dead = false
|
||||||
|
@round = 0
|
||||||
|
@last_apple_at = 0
|
||||||
|
@count_left = @count_right = 0
|
||||||
|
place_apple()
|
||||||
|
end
|
||||||
|
|
||||||
|
def place_apple
|
||||||
|
x=-1
|
||||||
|
while @data[x]!=0 || x==-1
|
||||||
|
x = rand(WIDTH*HEIGHT)
|
||||||
|
end
|
||||||
|
@apple = [x%WIDTH, x/WIDTH]
|
||||||
|
@old_distance = apple_distance()
|
||||||
|
end
|
||||||
|
|
||||||
|
def apple_distance
|
||||||
|
return (@pos[0] - @apple[0]).abs + (@pos[1] - @apple[1]).abs
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s
|
||||||
|
str = @data.join("").gsub("0", " ")
|
||||||
|
str[@apple[1]*WIDTH+@apple[0]] = "*"
|
||||||
|
s = "+" + "-"*(WIDTH-@points.to_s.length-1)+" "+@points.to_s+"+\n"
|
||||||
|
(0...HEIGHT).each do |y|
|
||||||
|
s += "|" + str[y*WIDTH, WIDTH] + "|\n"
|
||||||
|
end
|
||||||
|
s += "+" + "-"*WIDTH+"+\n"
|
||||||
|
return s
|
||||||
|
end
|
||||||
|
|
||||||
|
def draw; puts to_s; puts; end
|
||||||
|
|
||||||
|
def loop
|
||||||
|
#puts "Loop. Position: #{@pos}"
|
||||||
|
return if @dead
|
||||||
|
decision = @ai.decide(free?(@dir-1), free?(@dir), free?(@dir+1), apple?(@dir-1), apple?(@dir), apple?(@dir+1))
|
||||||
|
#puts "Decision: #{decision}"
|
||||||
|
@count_left += 1 if decision==-1
|
||||||
|
@count_right += 1 if decision==1
|
||||||
|
@dir = (@dir + decision) % 4
|
||||||
|
if (free?(@dir)==0)
|
||||||
|
#puts "Dead."
|
||||||
|
die()
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
move
|
||||||
|
end
|
||||||
|
|
||||||
|
def ranking; @length*10 - (@dead ? 200 : 0) - (stopped? ? 100 : 0) - (@count_right - @count_left).abs * 0.05; end
|
||||||
|
|
||||||
|
def move
|
||||||
|
newpos = calc_new_pos(@pos, @dir)
|
||||||
|
puts "Newpos: #{newpos}" if @debug
|
||||||
|
if newpos==@apple
|
||||||
|
@length+=1
|
||||||
|
@points += POINTS_APPLE
|
||||||
|
@last_apple_at = @round
|
||||||
|
place_apple
|
||||||
|
end
|
||||||
|
@data.each_with_index do |value, key|
|
||||||
|
@data[key]=value+1 if value>0
|
||||||
|
@data[key]=0 if value>=@length
|
||||||
|
end
|
||||||
|
@data[newpos[1]*WIDTH + newpos[0]] = 1
|
||||||
|
@pos = newpos
|
||||||
|
ad_d = apple_distance - @old_distance
|
||||||
|
@old_distance = apple_distance()
|
||||||
|
if (ad_d < 0)
|
||||||
|
@points += POINTS_MOVING_CLOSER
|
||||||
|
elsif (ad_d > 0)
|
||||||
|
@points += POINTS_MOVING_FAR
|
||||||
|
end
|
||||||
|
@round+=1
|
||||||
|
end
|
||||||
|
|
||||||
|
def since_last_apple; @round - @last_apple_at; end
|
||||||
|
|
||||||
|
def calc_new_pos(p, d)
|
||||||
|
d = d%4
|
||||||
|
np = p.dup
|
||||||
|
case d
|
||||||
|
when 0 then np[1]-=1
|
||||||
|
when 1 then np[0]+=1
|
||||||
|
when 2 then np[1]+=1
|
||||||
|
when 3 then np[0]-=1
|
||||||
|
end
|
||||||
|
return np
|
||||||
|
end
|
||||||
|
|
||||||
|
def free?(dir)
|
||||||
|
# count the free fields from @pos in dir until a wall or something
|
||||||
|
dir = dir % 4
|
||||||
|
x=y=0
|
||||||
|
case dir
|
||||||
|
when 0 then y=-1
|
||||||
|
when 1 then x=+1
|
||||||
|
when 2 then y=+1
|
||||||
|
when 3 then x=-1
|
||||||
|
end
|
||||||
|
i = 0
|
||||||
|
pos = @pos.dup
|
||||||
|
|
||||||
|
[WIDTH, HEIGHT].max.times do
|
||||||
|
pos[0]+=x
|
||||||
|
pos[1]+=y
|
||||||
|
break if pos[0]<0 || pos[0]>=WIDTH || pos[1]<0 || pos[1]>=HEIGHT || @data[pos[1]*WIDTH + pos[0]]!=0
|
||||||
|
i+=1
|
||||||
|
end
|
||||||
|
return i
|
||||||
|
end
|
||||||
|
|
||||||
|
def apple?(dir)
|
||||||
|
dir = dir%4
|
||||||
|
d_x = @apple[0] - @pos[0]
|
||||||
|
d_y = @apple[1] - @pos[1]
|
||||||
|
case dir
|
||||||
|
when 0 then return @apple[1]<@pos[1] ? -d_y : 0
|
||||||
|
when 1 then return @apple[0]>@pos[0] ? d_x : 0
|
||||||
|
when 2 then return @apple[1]>@pos[1] ? d_y : 0
|
||||||
|
when 3 then return @apple[0]<@pos[0] ? -d_x : 0
|
||||||
|
#when 0 then return d_y<0 && d_x.abs<d_y.abs
|
||||||
|
#when 1 then return d_x>0 && d_x.abs>d_y.abs
|
||||||
|
#when 2 then return d_y>0 && d_x.abs<d_y.abs
|
||||||
|
#when 3 then return d_x<0 && d_x.abs>d_y.abs
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def die
|
||||||
|
@dead = true
|
||||||
|
end
|
||||||
|
|
||||||
|
def stopped?; since_last_apple >= WIDTH*HEIGHT*2; end
|
||||||
|
|
||||||
|
def ai_ranking; ai.ranking; end
|
||||||
|
end
|
||||||
|
|
||||||
|
class AI
|
||||||
|
NETWORK_LAYOUT = [6, 4, 3]
|
||||||
|
attr_reader :weights, :id
|
||||||
|
attr_accessor :ranking, :rounds, :count_dead, :count_stopped, :sum_length
|
||||||
|
|
||||||
|
def initialize(w=nil, debug=false)
|
||||||
|
@debug = debug
|
||||||
|
reset()
|
||||||
|
@rounds = 1
|
||||||
|
@id = rand(0xFFFFFF)
|
||||||
|
if w==nil
|
||||||
|
@weights = Array.new(network_size()) { FLOAT ? rand() * 2.0 - 1.0 : rand(256) - 128 }
|
||||||
|
puts "Initialized with random values: #{@weights}" if @debug
|
||||||
|
else
|
||||||
|
if w[0].is_a?(Integer) && FLOAT
|
||||||
|
@weights = w.map{|s| s.to_s(16).rjust(8, "0").split("").each_slice(2).to_a.map(&:join).map{|s| s.to_i(16).chr}.join.unpack("g")}.flatten
|
||||||
|
else
|
||||||
|
@weights = w
|
||||||
|
end
|
||||||
|
puts "Initialized with given values: #{@weights}" if @debug
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
def network_size
|
||||||
|
s = 0
|
||||||
|
(0...(NETWORK_LAYOUT.count-1)).each do |i|
|
||||||
|
s += NETWORK_LAYOUT[i] * NETWORK_LAYOUT[i+1]
|
||||||
|
end
|
||||||
|
return s
|
||||||
|
end
|
||||||
|
|
||||||
|
def reset
|
||||||
|
@ranking = 0.0
|
||||||
|
@count_dead = 0
|
||||||
|
@count_stopped = 0
|
||||||
|
@sum_length = 0
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_ranking(g)
|
||||||
|
@ranking += g.ranking
|
||||||
|
@count_dead += 1 if g.dead
|
||||||
|
@count_stopped += 1 if g.stopped?
|
||||||
|
@sum_length += g.length
|
||||||
|
end
|
||||||
|
|
||||||
|
def decide(left_free, straight_free, right_free, apple_left, apple_straight, apple_right)
|
||||||
|
inputs = [left_free, straight_free, right_free, apple_left, apple_straight, apple_right]
|
||||||
|
puts "Inputs: #{inputs}" if @debug
|
||||||
|
outputs = nil
|
||||||
|
x = 0
|
||||||
|
(1...(NETWORK_LAYOUT.count)).each do |i|
|
||||||
|
c_in = NETWORK_LAYOUT[i-1]
|
||||||
|
c_out = NETWORK_LAYOUT[i]
|
||||||
|
outputs = Array.new(c_out){FLOAT ? 0.0 : 0}
|
||||||
|
(0...c_out).each do |o|
|
||||||
|
(0...c_in).each do |i|
|
||||||
|
outputs[o] += inputs[i] * @weights[x]
|
||||||
|
x+=1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
inputs = outputs
|
||||||
|
end
|
||||||
|
|
||||||
|
max = 0
|
||||||
|
take = 0
|
||||||
|
(0...(NETWORK_LAYOUT.last)).each do |x|
|
||||||
|
if outputs[x]>max
|
||||||
|
max = outputs[x]
|
||||||
|
take = x
|
||||||
|
end
|
||||||
|
end
|
||||||
|
puts "Decision: #{take-1}" if @debug
|
||||||
|
return take-1
|
||||||
|
end
|
||||||
|
|
||||||
|
def evolve
|
||||||
|
w = @weights.dup
|
||||||
|
action = rand(4)
|
||||||
|
#if action==0 #swap
|
||||||
|
# i1 = rand(network_size())
|
||||||
|
# i2 = rand(network_size())
|
||||||
|
# temp = w[i1]
|
||||||
|
# w[i1] = w[i2]
|
||||||
|
# w[i2] = temp
|
||||||
|
if action==0 #change single value
|
||||||
|
i = rand(network_size())
|
||||||
|
diff = FLOAT ? rand() * 0.2 - 0.1 : rand(256) - 128
|
||||||
|
w2 = w.dup
|
||||||
|
w[i] += diff
|
||||||
|
if FLOAT
|
||||||
|
w[i] = 1.0 if w[i]>1.0
|
||||||
|
w[i] = -1.0 if w[i]<-1.0
|
||||||
|
else
|
||||||
|
w[i] = 127 if w[i]>127
|
||||||
|
w[i] = -128 if w[i]<-128
|
||||||
|
end
|
||||||
|
w2[i] -= diff
|
||||||
|
if FLOAT
|
||||||
|
w2[i] = 1.0 if w2[i]>1.0
|
||||||
|
w2[i] = -1.0 if w2[i]<-1.0
|
||||||
|
else
|
||||||
|
w2[i] = 127 if w2[i]>127
|
||||||
|
w2[i] = -128 if w2[i]<-128
|
||||||
|
end
|
||||||
|
return [AI.new(w), AI.new(w2)]
|
||||||
|
elsif action==1 #invert single value
|
||||||
|
i = rand(network_size())
|
||||||
|
w[i] *= FLOAT ? -1.0 : -1
|
||||||
|
elsif action==2
|
||||||
|
(0...network_size()).each do |i|
|
||||||
|
w[i] = (FLOAT ? rand() * 2 - 1.0 : rand(256) - 128) if rand(5)==0
|
||||||
|
end
|
||||||
|
else #change multiple values
|
||||||
|
w2 = w.dup
|
||||||
|
(0...network_size()).each do |i|
|
||||||
|
if (rand(5)==0)
|
||||||
|
diff = FLOAT ? rand() * 0.2 - 0.1 : rand(256) - 128
|
||||||
|
w[i] += diff
|
||||||
|
if FLOAT
|
||||||
|
w[i] = 1.0 if w[i]>1.0
|
||||||
|
w[i] = -1.0 if w[i]<-1.0
|
||||||
|
else
|
||||||
|
w[i] = 127 if w[i]>127
|
||||||
|
w[i] = -128 if w[i]<-128
|
||||||
|
end
|
||||||
|
w2[i] -= diff
|
||||||
|
if FLOAT
|
||||||
|
w2[i] = 1.0 if w2[i]>1.0
|
||||||
|
w2[i] = -1.0 if w2[i]<-1.0
|
||||||
|
else
|
||||||
|
w2[i] = 127 if w2[i]>127
|
||||||
|
w2[i] = -128 if w2[i]<-128
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return [AI.new(w), AI.new(w2)]
|
||||||
|
end
|
||||||
|
|
||||||
|
return AI.new(w)
|
||||||
|
end
|
||||||
|
|
||||||
|
def merge(ai)
|
||||||
|
w = @weights.dup
|
||||||
|
w2 = ai.weights.dup
|
||||||
|
(0...network_size()).each do |i|
|
||||||
|
if rand(2)==0
|
||||||
|
w[i] = w2[i]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return AI.new(w)
|
||||||
|
end
|
||||||
|
|
||||||
|
def average(ai)
|
||||||
|
w = @weights.dup
|
||||||
|
w2 = ai.weights
|
||||||
|
(0...network_size()).each do |i|
|
||||||
|
w[i] = (w[i] + w2[i]) / (FLOAT ? 2.0 : 2)
|
||||||
|
end
|
||||||
|
return AI.new(w)
|
||||||
|
end
|
||||||
|
|
||||||
|
def dump
|
||||||
|
if FLOAT
|
||||||
|
puts "const uint32_t _weights[#{network_size()}] = {#{@weights.map{|x| "0x" + [x].pack('g').split("").map(&:ord).map{|i| i.to_s(16).rjust(2, '0')}.join}.join(", ")}};"
|
||||||
|
else
|
||||||
|
puts "const int8_t _weights[#{network_size()}] = {#{@weights.join(", ")}};"
|
||||||
|
end
|
||||||
|
#puts "Simplified: #{simplified}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
## Simulate
|
||||||
|
=begin
|
||||||
|
ai = AI.new([0xbd547c6d, 0xbedc84a5, 0x3e750239, 0x3ec5ae8a, 0xbcc9a683, 0x3f18715a, 0x3e947ed4, 0xbe4b8bf2, 0xbf2ee4ec, 0xbf3f0a75,
|
||||||
|
0x3f5392dc, 0xbf06687b, 0xbedca2f2, 0xbcde3698, 0x3edd6a8a, 0xbd7284ca, 0x3ea7bac9, 0xbe5323c1, 0x3eccf87d,
|
||||||
|
0xbf2d4796, 0xbf62b6e8, 0xbf71daf6, 0xbeff40aa, 0xbf207014, 0x3e26c03c, 0xbf497837, 0xbee4d175, 0x3ec601de, 0x3e4e0695, 0x3eef2619,
|
||||||
|
0xbe849370, 0xbf18fb2b, 0x3f128e17, 0xbf3dcd78, 0x3f517299, 0x3eef3270], true)
|
||||||
|
g = Game.new(ai, true)
|
||||||
|
g.apple = [3, 3]
|
||||||
|
10.times do
|
||||||
|
g.loop
|
||||||
|
end
|
||||||
|
exit
|
||||||
|
=end
|
||||||
|
|
||||||
|
|
||||||
|
graph = File.open(File.dirname(__FILE__) + "/data_set.dat", "w")
|
||||||
|
graph.puts("# Round - Points - Length - Stopped - Dead")
|
||||||
|
|
||||||
|
ais = []
|
||||||
|
round = 1
|
||||||
|
games = []
|
||||||
|
(0...50).each do |x|
|
||||||
|
ais[x] = AI.new#(SEEDS.sample)
|
||||||
|
end
|
||||||
|
|
||||||
|
best_old_game = nil
|
||||||
|
best_old_ai = nil
|
||||||
|
begin
|
||||||
|
loop do
|
||||||
|
GAMES_PER_ROUND.times do
|
||||||
|
(0...50).each do |x|
|
||||||
|
games[x] = Game.new(ais[x])
|
||||||
|
end
|
||||||
|
|
||||||
|
pool = Thread.pool(16)
|
||||||
|
games.each do |g|
|
||||||
|
|
||||||
|
pool.process do
|
||||||
|
15_000.times do
|
||||||
|
g.loop
|
||||||
|
break if g.dead || g.stopped?
|
||||||
|
end
|
||||||
|
g.ai.add_ranking(g)
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
pool.shutdown
|
||||||
|
end
|
||||||
|
|
||||||
|
games_sorted = games.sort_by(&:ai_ranking).reverse.take(5)
|
||||||
|
g = games_sorted[0]
|
||||||
|
|
||||||
|
if (round-1)%50==0
|
||||||
|
puts "----------------------------------------------------"
|
||||||
|
puts "Round | Points | Length | Stopped | Dead | ID "
|
||||||
|
puts "----------------------------------------------------"
|
||||||
|
end
|
||||||
|
|
||||||
|
puts "%5d | %7.1f | %6.0f | %6.0f%% | %3.0f%% | 0x%06x" % [round, g.ai_ranking / GAMES_PER_ROUND, g.ai.sum_length.to_f / GAMES_PER_ROUND, g.ai.count_stopped.to_f / GAMES_PER_ROUND * 100, g.ai.count_dead.to_f / GAMES_PER_ROUND * 100, g.ai.id]
|
||||||
|
graph.puts("%d %f %f %f %f" % [round, g.ai_ranking / GAMES_PER_ROUND, g.ai.sum_length.to_f / GAMES_PER_ROUND, g.ai.count_stopped.to_f / GAMES_PER_ROUND * 100, g.ai.count_dead.to_f / GAMES_PER_ROUND * 100])
|
||||||
|
graph.flush
|
||||||
|
|
||||||
|
if round%10==0
|
||||||
|
g.ai.dump
|
||||||
|
end
|
||||||
|
|
||||||
|
best_old_game = g
|
||||||
|
best_old_ai = g.ai.dup
|
||||||
|
|
||||||
|
ais = []
|
||||||
|
games_sorted.each do |g|
|
||||||
|
g.ai.reset
|
||||||
|
g.ai.rounds += 1
|
||||||
|
ais << g.ai
|
||||||
|
9.times do
|
||||||
|
ais << g.ai.evolve
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
5.times do
|
||||||
|
ais << games_sorted[0].ai.merge(games_sorted[1].ai)
|
||||||
|
end
|
||||||
|
5.times do
|
||||||
|
ais << games_sorted[1].ai.merge(games_sorted[0].ai)
|
||||||
|
end
|
||||||
|
ais << games_sorted[0].ai.average(games_sorted[1].ai)
|
||||||
|
10.times do
|
||||||
|
ais << AI.new
|
||||||
|
end
|
||||||
|
ais = ais.flatten
|
||||||
|
round+=1
|
||||||
|
end
|
||||||
|
rescue SystemExit, Interrupt
|
||||||
|
puts
|
||||||
|
puts
|
||||||
|
puts "// Round %d, %5.1f points, length %3d, %2.0f%% stopped, %2.0f%% died" % [round-1, best_old_game.ai_ranking / GAMES_PER_ROUND, best_old_ai.sum_length.to_f / GAMES_PER_ROUND, best_old_ai.count_stopped.to_f / GAMES_PER_ROUND * 100, best_old_ai.count_dead.to_f / GAMES_PER_ROUND * 100]
|
||||||
|
best_old_ai.dump
|
||||||
|
graph.close
|
||||||
|
end
|
18
src/wifi.cpp
18
src/wifi.cpp
@ -6,10 +6,20 @@
|
|||||||
void wifi_setup() {
|
void wifi_setup() {
|
||||||
WiFi.mode(WIFI_STA);
|
WiFi.mode(WIFI_STA);
|
||||||
WiFi.begin(WIFI_SSID, WIFI_PASS);
|
WiFi.begin(WIFI_SSID, WIFI_PASS);
|
||||||
while (WiFi.waitForConnectResult() != WL_CONNECTED) {
|
uint8_t result;
|
||||||
Serial.println("WiFi * Connection Failed! Rebooting...");
|
uint8_t counter = 0;
|
||||||
delay(5000);
|
while ((result = WiFi.waitForConnectResult()) != WL_CONNECTED) {
|
||||||
ESP.restart();
|
counter++;
|
||||||
|
if (counter > 100) {
|
||||||
|
Serial.print("WiFi * Connection Failed! Last result was: ");
|
||||||
|
Serial.print(result);
|
||||||
|
Serial.println(" Rebooting...");
|
||||||
|
delay(100);
|
||||||
|
ESP.restart();
|
||||||
|
}
|
||||||
|
Serial.print("WiFi * Still waiting for WiFi to connect. This is loop number ");
|
||||||
|
Serial.println(counter);
|
||||||
|
delay(100);
|
||||||
}
|
}
|
||||||
Serial.println("WiFi * Ready");
|
Serial.println("WiFi * Ready");
|
||||||
Serial.print("WiFi * IP address: ");
|
Serial.print("WiFi * IP address: ");
|
||||||
|
Reference in New Issue
Block a user