Compare commits
No commits in common. "994f4894dd0fa06ab49d42b800598a5963eadfda" and "38aa654c54125924388fcb5f1616afda196c49bb" have entirely different histories.
994f4894dd
...
38aa654c54
2
.gitignore
vendored
2
.gitignore
vendored
@ -10,4 +10,4 @@ include/config.h
|
||||
.pioenvs
|
||||
.DS_Store
|
||||
.vscode
|
||||
src/tools/snakenet/data_set.*
|
||||
src/tools/snakenet/data_set.dat
|
||||
|
@ -29,9 +29,9 @@ public:
|
||||
void setSubPixel(accum88 x, accum88 y, CRGB* color, SubpixelRenderingMode m = SUBPIXEL_RENDERING_ADD);
|
||||
void setPixelByIndex(uint16_t index, CRGB* color);
|
||||
void raisePixel(uint8_t x, uint8_t y, CRGB* color);
|
||||
void line(saccum78 x1, saccum78 y1, saccum78 x2, saccum78 y2, 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, uint16_t angle, uint8_t startdist, uint8_t length, CRGB* color);
|
||||
void line(accum88 x1, accum88 y1, accum88 x2, accum88 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, uint8_t angle, uint8_t startdist, uint8_t length, 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 localToGlobalIndex(uint16_t);
|
||||
|
@ -43,6 +43,9 @@
|
||||
#define FPS 50
|
||||
#define SHOW_TEXT_DELAY 100
|
||||
|
||||
#define RECORDER_ENABLE
|
||||
#define RECORDER_PORT 2122
|
||||
|
||||
#define MONITOR_LOOP_TIMES false
|
||||
#define MONITOR_LOOP_TIME_THRESHOLD 500
|
||||
#define MONITOR_LOOP_TIME_COUNT_MAX 10
|
||||
|
@ -4,26 +4,11 @@
|
||||
#include "functions.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 {
|
||||
private:
|
||||
Window* window = new Window(0, 0, LED_WIDTH, LED_HEIGHT-6);
|
||||
uint8_t _count;
|
||||
Blur2DBlob* _blobs;
|
||||
public:
|
||||
Blur2DEffect();
|
||||
~Blur2DEffect();
|
||||
void _init();
|
||||
void _delete();
|
||||
boolean supports_window = true;
|
||||
boolean can_be_shown_with_clock();
|
||||
void loop(uint16_t ms);
|
||||
|
@ -1,32 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "Effect.h"
|
||||
#include "my_fastled.h"
|
||||
|
||||
class LightspeedEffectStar {
|
||||
private:
|
||||
uint16_t _angle;
|
||||
accum88 _start;
|
||||
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"; }
|
||||
};
|
||||
|
@ -5,13 +5,15 @@
|
||||
|
||||
#include "my_wifi.h"
|
||||
#include <FS.h>
|
||||
#include <ESPAsyncWebServer.h>
|
||||
|
||||
extern AsyncWebServer http_server;
|
||||
#if defined ( ESP8266 )
|
||||
extern ESP8266WebServer http_server;
|
||||
#elif defined ( ESP32 )
|
||||
extern ESP32WebServer http_server;
|
||||
#endif
|
||||
|
||||
extern File upload_file;
|
||||
extern uint32_t monitor_client;
|
||||
|
||||
void http_server_setup();
|
||||
void http_server_send_framedata();
|
||||
void http_server_loop();
|
||||
#endif
|
||||
|
@ -5,11 +5,13 @@
|
||||
#if defined( ESP8266 )
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <ESP8266mDNS.h>
|
||||
#include <ESP8266WebServer.h>
|
||||
#elif defined( ESP32 )
|
||||
#include <WiFi.h>
|
||||
#include <ESPmDNS.h>
|
||||
#include <WiFiClient.h>
|
||||
#include <WiFiServer.h>
|
||||
#include <ESP32WebServer.h>
|
||||
#endif
|
||||
|
||||
#include <WiFiUdp.h>
|
||||
|
@ -4,7 +4,6 @@
|
||||
|
||||
extern uint8_t baseHue;
|
||||
extern char hostname[30];
|
||||
extern uint16_t frame;
|
||||
|
||||
typedef struct {
|
||||
uint8_t width;
|
||||
|
22
include/recorder.h
Normal file
22
include/recorder.h
Normal file
@ -0,0 +1,22 @@
|
||||
#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
|
@ -36,10 +36,6 @@ struct Settings {
|
||||
uint16_t spacing = 5;
|
||||
} big_clock;
|
||||
|
||||
struct /* blur2d */ {
|
||||
uint16_t count = 5;
|
||||
} blur2d;
|
||||
|
||||
struct /* confetti */ {
|
||||
uint16_t pixels_per_loop = 2;
|
||||
} confetti;
|
||||
@ -75,10 +71,6 @@ struct Settings {
|
||||
uint16_t restart_after_steps = 100;
|
||||
} gol;
|
||||
|
||||
struct /* lightspeed */ {
|
||||
uint16_t count = 25;
|
||||
} lightspeed;
|
||||
|
||||
struct /* sines */ {
|
||||
uint16_t count = 5;
|
||||
} sines;
|
||||
|
@ -17,7 +17,8 @@ lib_deps =
|
||||
PubSubClient
|
||||
https://github.com/fabianonline/FastLED.git
|
||||
https://github.com/fabianonline/NTPClient.git
|
||||
https://github.com/me-no-dev/ESPAsyncWebServer.git
|
||||
ESP8266WebServer
|
||||
ESPAsyncTCP
|
||||
|
||||
[env:ota]
|
||||
upload_port = 10.10.2.80
|
||||
@ -26,7 +27,7 @@ platform = espressif8266
|
||||
board = esp07
|
||||
framework = arduino
|
||||
lib_deps = ${extra.lib_deps}
|
||||
lib_ldf_mode = deep
|
||||
lib_ldf_mode = chain+
|
||||
build_flags = -Wl,-Teagle.flash.2m512.ld
|
||||
|
||||
[env:local]
|
||||
@ -35,5 +36,5 @@ platform = espressif8266
|
||||
board = esp07
|
||||
framework = arduino
|
||||
lib_deps = ${extra.lib_deps}
|
||||
lib_ldf_mode = deep
|
||||
lib_ldf_mode = chain+
|
||||
build_flags = -Wl,-Teagle.flash.2m512.ld
|
||||
|
@ -150,29 +150,27 @@ void Window::fadeToBlackBy(fract8 speed) {
|
||||
}
|
||||
}
|
||||
|
||||
void Window::line(saccum78 x1, saccum78 y1, saccum78 x2, saccum78 y2, CRGB* color) {
|
||||
void Window::line(accum88 x1, accum88 y1, accum88 x2, accum88 y2, CRGB* color) {
|
||||
// Bresenham algorithm
|
||||
const uint8_t stepsize = 64;
|
||||
saccum78 dx = abs(x2 - x1);
|
||||
saccum78 dy = -abs(y2 - y1);
|
||||
int8_t sx = x1<x2 ? 1 : -1;
|
||||
int8_t sy = y1<y2 ? 1 : -1;
|
||||
saccum78 err = dx + dy;
|
||||
saccum78 e2;
|
||||
uint8_t step = 0;
|
||||
while (1) {
|
||||
if (step == 0) setSubPixel(x1, y1, color, SUBPIXEL_RENDERING_RAISE);
|
||||
if (++step >= stepsize) step=0;
|
||||
if (x1>>8==x2>>8 && y1>>8==y2>>8) break;
|
||||
e2 = 2*err;
|
||||
if (e2 > dy) {
|
||||
err += dy;
|
||||
x1 += sx;
|
||||
}
|
||||
if (e2 < dx) {
|
||||
err += dx;
|
||||
y1 += sy;
|
||||
const uint8_t stepsize = 32;
|
||||
saccum78 dx = x2-x1;
|
||||
saccum78 dy = y2-y1;
|
||||
|
||||
accum88 x = x1;
|
||||
accum88 y = y1;
|
||||
|
||||
saccum78 p = 2*dy - dx;
|
||||
|
||||
while (x < x2) {
|
||||
if (p >= 0) {
|
||||
setSubPixel(x, y, color, SUBPIXEL_RENDERING_RAISE);
|
||||
y+=stepsize;
|
||||
p = p + 2*dy - 2*dx;
|
||||
} else {
|
||||
setSubPixel(x, y, color, SUBPIXEL_RENDERING_RAISE);
|
||||
p = p + 2*dy;
|
||||
}
|
||||
x+=stepsize;
|
||||
}
|
||||
}
|
||||
|
||||
@ -204,23 +202,22 @@ void Window::circle(uint8_t x0, uint8_t y0, uint8_t radius, CRGB* color) {
|
||||
}
|
||||
}
|
||||
|
||||
void Window::lineWithAngle(uint8_t x, uint8_t y, uint16_t angle, uint8_t length, CRGB* color) {
|
||||
void Window::lineWithAngle(uint8_t x, uint8_t y, uint8_t angle, uint8_t length, CRGB* color) {
|
||||
lineWithAngle(x, y, angle, 0, length, color);
|
||||
}
|
||||
|
||||
void Window::lineWithAngle(uint8_t x, uint8_t y, uint16_t angle, uint8_t startdist, uint8_t length, CRGB* color) {
|
||||
//LOGln("lineWithAngle called. x: %d.%03d, y: %d.%03d, angle: %d", x>>8, x&0xFF, y>>8, y&0xFF, angle);
|
||||
saccum78 x1 = x<<8;
|
||||
saccum78 y1 = y<<8;
|
||||
void Window::lineWithAngle(uint8_t x, uint8_t y, uint8_t angle, uint8_t startdist, uint8_t length, CRGB* color) {
|
||||
accum88 x1 = x<<8;
|
||||
accum88 y1 = y<<8;
|
||||
|
||||
if (startdist > 0) {
|
||||
x1 = (x<<8) + (startdist<<8) * cos16(angle) / 0x10000;
|
||||
y1 = (y<<8) + (startdist<<8) * sin16(angle) / 0x10000;
|
||||
x1 = (x<<8) + (startdist<<8) * cos8(angle) / 256;
|
||||
y1 = (y<<8) + (startdist<<8) * sin8(angle) / 256;
|
||||
}
|
||||
|
||||
saccum78 x2 = (x<<8) + ((startdist + length)<<8) * cos16(angle) / 0x10000;
|
||||
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);
|
||||
accum88 x2 = (x<<8) + (startdist + length) * cos16(angle);
|
||||
accum88 y2 = (y<<8) + (startdist + length) * sin16(angle);
|
||||
|
||||
line(x1, y1, x2, y2, color);
|
||||
}
|
||||
|
||||
|
@ -1,49 +1,31 @@
|
||||
#include "effect_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();
|
||||
}
|
||||
uint8_t x1 = beatsin8(7, 0, window->width-1);
|
||||
uint8_t y1 = beatsin8(11, 0, window->height-1);
|
||||
|
||||
void Blur2DEffect::_init() {
|
||||
_count = settings.effects.blur2d.count;
|
||||
_blobs = new Blur2DBlob[_count];
|
||||
}
|
||||
uint8_t x2 = beatsin8(13, 0, window->width-1);
|
||||
uint8_t y2 = beatsin8(8, 0, window->height-1);
|
||||
|
||||
void Blur2DEffect::_delete() {
|
||||
delete[] _blobs;
|
||||
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();
|
||||
delete window;
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
#include "effect_cycle.h"
|
||||
#include "effects.h"
|
||||
#include <ErriezCRC32.h>
|
||||
|
||||
CycleEffect::CycleEffect() {
|
||||
_effects_count = 0;
|
||||
|
@ -1,63 +0,0 @@
|
||||
#include "effect_lightspeed.h"
|
||||
#include "config.h"
|
||||
#include "functions.h"
|
||||
#include "prototypes.h"
|
||||
|
||||
LightspeedEffect::LightspeedEffect() {
|
||||
window = new Window(0, 0, LED_WIDTH, LED_HEIGHT-6);
|
||||
_init();
|
||||
}
|
||||
|
||||
LightspeedEffect::~LightspeedEffect() {
|
||||
delete window;
|
||||
_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();
|
||||
}
|
||||
|
||||
void LightspeedEffectStar::_init() {
|
||||
_angle = random16();
|
||||
_start = 0;
|
||||
_speed = random16(128, 2<<8);
|
||||
_target = random16(25<<8, 35<<8);
|
||||
_saturation = random8(20);
|
||||
}
|
||||
|
||||
void LightspeedEffectStar::loop(Window* win) {
|
||||
CRGB col = CHSV(192, _saturation, 255);
|
||||
if (_start < (8<<8)) {
|
||||
win->lineWithAngle(win->width/2, win->height/2, _angle, 0, _start>>8, &col);
|
||||
} else {
|
||||
win->lineWithAngle(win->width/2, win->height/2, _angle, (_start>>8) - 8, 8, &col);
|
||||
}
|
||||
_start+=_speed;
|
||||
//_angle+=8<<8;
|
||||
if (_start > _target) _init();
|
||||
}
|
@ -23,45 +23,43 @@
|
||||
#include "effect_marquee.h"
|
||||
#include "effect_blur2d.h"
|
||||
#include "effect_tv_static.h"
|
||||
#include "effect_lightspeed.h"
|
||||
|
||||
Effect* current_effect;
|
||||
|
||||
ClockEffect effect_clock;
|
||||
|
||||
// We're using 0 instead of false to get a better visual difference between true and false.
|
||||
const EffectEntry effects[] = {
|
||||
/* 0 */ {"sinematrix3", true, [](){ return new Sinematrix3Effect(); }},
|
||||
/* 1 */ {"big_clock", true, [](){ return new BigClockEffect(); }},
|
||||
/* 2 */ {"clock", 0, [](){ return new ClockEffect(); }},
|
||||
/* 3 */ {"bell", 0, [](){ return new BellEffect(); }},
|
||||
/* 4 */ {"off", 0, [](){ return new StaticEffect(0x000000); }},
|
||||
/* 2 */ {"clock", false, [](){ return new ClockEffect(); }},
|
||||
/* 3 */ {"bell", false, [](){ return new BellEffect(); }},
|
||||
/* 4 */ {"off", false, [](){ return new StaticEffect(0x000000); }},
|
||||
/* 5 */ {"single_dynamic", true, [](){ return new SingleDynamicEffect(); }},
|
||||
/* 6 */ {"multi_dynamic", true, [](){ return new MultiDynamicEffect(); }},
|
||||
/* 7 */ {"big_dynamic", true, [](){ return new BigDynamicEffect(); }},
|
||||
/* 8 */ {"matrix", true, [](){ return new MatrixEffect(); }},
|
||||
/* 9 */ {"random_matrix", true, [](){ return new RandomMatrixEffect(); }},
|
||||
/* 10 */ {"rainbow_matrix", true, [](){ return new RainbowMatrixEffect(); }},
|
||||
/* 11 */ {"cycle", 0, [](){ return new CycleEffect(); }},
|
||||
/* 11 */ {"cycle", false, [](){ return new CycleEffect(); }},
|
||||
/* 12 */ {"twirl", true, [](){ return new TwirlEffect(); }},
|
||||
/* 13 */ {"confetti", true, [](){ return new ConfettiEffect(); }},
|
||||
/* 14 */ {"random_confetti", true, [](){ return new RandomConfettiEffect(); }},
|
||||
/* 15 */ {"snake", true, [](){ return new SnakeEffect(); }},
|
||||
/* 16 */ {"firework", true, [](){ return new FireworkEffect(); }},
|
||||
/* 17 */ {"gol", true, [](){ return new GolEffect(); }},
|
||||
/* 18 */ {"pixel_clock", 0, [](){ return new PixelClockEffect(); }},
|
||||
/* 19 */ {"dvd", 0, [](){ return new DvdEffect(); }},
|
||||
/* 20 */ {"analog_clock", 0, [](){ return new AnalogClockEffect(); }},
|
||||
/* 18 */ {"pixel_clock", false, [](){ return new PixelClockEffect(); }},
|
||||
/* 19 */ {"dvd", false, [](){ return new DvdEffect(); }},
|
||||
/* 20 */ {"analog_clock", false, [](){ return new AnalogClockEffect(); }},
|
||||
/* 21 */ {"sines", true, [](){ return new SinesEffect(); }},
|
||||
/* 22 */ {"blur2d", true, [](){ return new Blur2DEffect(); }},
|
||||
/* 23 */ {"marquee", 0, [](){ return new MarqueeEffect(); }},
|
||||
/* 24 */ {"night_clock", 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(); }},
|
||||
/* 24 */ {"night_clock", false, [](){ return new NightClockEffect(); }},
|
||||
/* 25 */ {"tv_static", false, [](){ return new TvStaticEffect(); }},
|
||||
/* 26 */ {"sinematrix3_rainbow", true,[](){ return new Sinematrix3Effect(SINEMATRIX_COLOR_RAINBOW); }},
|
||||
/* 27 */ {"sinematrix3_purplefly", true,[](){ return new Sinematrix3Effect(SINEMATRIX_COLOR_PURPLEFLY); }},
|
||||
|
||||
};
|
||||
const uint8_t effects_size = 29;
|
||||
const uint8_t effects_size = 28;
|
||||
|
||||
|
||||
Effect* select_effect(const char* name) {
|
||||
|
@ -5,378 +5,252 @@
|
||||
#include "http_server.h"
|
||||
#include "effects.h"
|
||||
#include "my_wifi.h"
|
||||
#include "functions.h"
|
||||
#include "prototypes.h"
|
||||
#include <FS.h>
|
||||
|
||||
AsyncWebServer http_server(HTTP_SERVER_PORT);
|
||||
AsyncWebSocket ws("/ws");
|
||||
uint32_t monitor_client = 0;
|
||||
#if defined( ESP8266 )
|
||||
ESP8266WebServer http_server(HTTP_SERVER_PORT);
|
||||
#elif defined( ESP32 )
|
||||
ESP32WebServer http_server(HTTP_SERVER_PORT);
|
||||
#endif
|
||||
|
||||
void http_server_handle_file_upload(AsyncWebServerRequest* request, String filename, size_t index, uint8_t* data, size_t len, bool final) {
|
||||
File upload_file;
|
||||
if (index == 0) { // Start of upload
|
||||
LOGln("HTTP * Upload of %s starting...", filename.c_str());
|
||||
File upload_file;
|
||||
|
||||
void http_server_handle_file_upload() {
|
||||
if (http_server.uri() != "/upload") return;
|
||||
|
||||
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");
|
||||
} else {
|
||||
upload_file = SPIFFS.open(filename, "a");
|
||||
}
|
||||
|
||||
upload_file.write(data, len);
|
||||
|
||||
if (final) {
|
||||
LOGln("HTTP * Upload of %s done.", filename.c_str());
|
||||
} else if (upload.status == UPLOAD_FILE_WRITE) {
|
||||
if (upload_file) upload_file.write(upload.buf, upload.currentSize);
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
|
||||
void ws_send_effects(AsyncWebSocketClient* client) {
|
||||
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_400() {
|
||||
http_server.send(400);
|
||||
}
|
||||
|
||||
void http_server_setup() {
|
||||
static const char* PROGMEM text_plain = "text/plain";
|
||||
|
||||
http_server.on("/", HTTP_GET, [&](AsyncWebServerRequest* request){
|
||||
PGM_P text_plain = PSTR("text/plain");
|
||||
http_server.on("/", HTTP_GET, [&](){
|
||||
LOGln("HTTP * GET /");
|
||||
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>"));
|
||||
String message = "<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()) {
|
||||
response += F("<strong>No SPIFFS file system found.</strong>");
|
||||
message += "<strong>No SPIFFS file system found.</strong>";
|
||||
} else {
|
||||
response += F("<ul>");
|
||||
message += "<ul>";
|
||||
Dir dir = SPIFFS.openDir("/");
|
||||
while (dir.next()) {
|
||||
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 += "<li>" + dir.fileName() + " (<a href='/delete?" + dir.fileName() + "'>delete</a>)</li>";
|
||||
}
|
||||
response += F("</ul>");
|
||||
response += F("<form action='/upload' method='POST'><input type='file' name='file' /><input type='submit' value='Upload file' /></form>");
|
||||
message += "</ul>";
|
||||
message += "<form action='/upload' method='POST'><input type='file' name='file' /><input type='submit' value='Upload file' /></form>";
|
||||
}
|
||||
response += F("</body></html>");
|
||||
request->send(200, "text/html", response);
|
||||
message += "</body></html>";
|
||||
http_server.send(200, "text/html", message);
|
||||
});
|
||||
http_server.on("/settings", HTTP_GET, [&](AsyncWebServerRequest* request) {
|
||||
String message = F("<html><head><title>Pitrix settings</title></head><body><h1>Pitrix settings</h1><a href='/'>Back to main page</a><table>\n");
|
||||
http_server.on("/settings", HTTP_GET, [&]() {
|
||||
String message = "<html><head><title>Pitrix settings</title></head><body><h1>Pitrix settings</h1><a href='/'>Back to main page</a><table>\n";
|
||||
for (int i=0; i<all_settings_size; i++) {
|
||||
Setting s = all_settings[i];
|
||||
uint16_t default_value = setting_default(&s);
|
||||
uint16_t value = *(s.value);
|
||||
|
||||
message += F("<tr><td>");
|
||||
message += "<tr><td>";
|
||||
if (default_value != value) {
|
||||
message += F("<strong>");
|
||||
message += "<strong>";
|
||||
}
|
||||
message += s.name;
|
||||
if (default_value != value) {
|
||||
message += F("<strong>");
|
||||
message += "<strong>";
|
||||
}
|
||||
message += F("</td><td>");
|
||||
message += "</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;
|
||||
message += "</td><td><form method='POST' action='/settings?redir=1'><input type='hidden' name='key' value='";
|
||||
message += s.name;
|
||||
message += "'/>";
|
||||
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);
|
||||
message += "<input type='number' name='value' value='";
|
||||
message += value;
|
||||
message += "' min='0' max='";
|
||||
if (s.type==TYPE_UINT8) {
|
||||
message += "255";
|
||||
} else {
|
||||
message += "65535";
|
||||
}
|
||||
message += "' />";
|
||||
} 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 += "<input type='radio' name='value' value='0'";
|
||||
if (value==0) {
|
||||
message += " checked='checked'";
|
||||
}
|
||||
message += buffer;
|
||||
message += F("<input type='submit' value='Save' /></form></td></tr>\n");
|
||||
message += "> Off / <input type='radio' name='value' value='1'";
|
||||
if (value==1) {
|
||||
message += " checked='checked'";
|
||||
}
|
||||
message += F("</table></body></html>");
|
||||
request->send(200, "text/html", message);
|
||||
message += "> On";
|
||||
}
|
||||
message += "<input type='submit' value='Save' />";
|
||||
message += "</form></td></tr>\n";
|
||||
}
|
||||
message += "</table></body></html>";
|
||||
http_server.send(200, "text/html", message);
|
||||
});
|
||||
http_server.on("/settings", HTTP_POST, [&](AsyncWebServerRequest* request) {
|
||||
if (!request->hasParam("key", true) || !request->hasParam("value", true)) {
|
||||
request->send(400, "text/plain", "Missing argument.");
|
||||
http_server.on("/settings", HTTP_POST, [&]() {
|
||||
if (!http_server.hasArg("key") || !http_server.hasArg("value")) {
|
||||
http_server.send(400, "text/plain", "Missing argument.");
|
||||
return;
|
||||
}
|
||||
String name = request->getParam("key", true)->value();
|
||||
uint16_t value = request->getParam("value", true)->value().toInt();
|
||||
String name = http_server.arg("key");
|
||||
uint16_t value = http_server.arg("value").toInt();
|
||||
|
||||
if (change_setting(name.c_str(), value)) {
|
||||
if (request->hasParam("redir")) {
|
||||
request->redirect("/settings");
|
||||
if (http_server.hasArg("redir")) {
|
||||
http_server.sendHeader("Location", "/settings");
|
||||
http_server.send(301);
|
||||
} else {
|
||||
request->send(200, "text/plain", "OK");
|
||||
http_server.send(200, "text/plain", "OK");
|
||||
}
|
||||
save_settings();
|
||||
} else {
|
||||
request->send(400, "text/plain", "Could not change setting.");
|
||||
http_server.send(400, "text/plain", "Could not change setting.");
|
||||
}
|
||||
});
|
||||
http_server.on("/settings/load", HTTP_POST, [&](AsyncWebServerRequest* request) {
|
||||
http_server.on("/settings/load", HTTP_POST, [&]() {
|
||||
load_settings();
|
||||
request->send(200, "text/plain", "OK");
|
||||
http_server.send(200, "text/plain", "OK");
|
||||
});
|
||||
http_server.on("/settings/save", HTTP_POST, [&](AsyncWebServerRequest* request) {
|
||||
http_server.on("/settings/save", HTTP_POST, [&]() {
|
||||
save_settings();
|
||||
request->send(200, "text/plain", "OK");
|
||||
http_server.send(200, "text/plain", "OK");
|
||||
});
|
||||
http_server.on("/settings.txt", HTTP_GET, [&](AsyncWebServerRequest* request) {
|
||||
http_server.on("/settings.txt", HTTP_GET, [&]() {
|
||||
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);
|
||||
http_server.send(200, "text/plain", s);
|
||||
return;
|
||||
}
|
||||
}
|
||||
request->send(500, "text/plain", "Could not read settings.");
|
||||
http_server.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[150];
|
||||
http_server.on("/effects", HTTP_GET, [&]() {
|
||||
String message = "<html><head><title>Pitrix effects</title></head><body><h1>Pitrix settings</h1><a href='/'>Back to main page</a><table>";
|
||||
for (int i=0; i<effects_size; i++) {
|
||||
snprintf_P(buffer, 150, 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 += "<tr><td>";
|
||||
message += effects[i].name;
|
||||
message += "</td><td><form method='post' action='/effects'><input type='hidden' name='name' value='";
|
||||
message += effects[i].name;
|
||||
message += "'><input type='hidden' name='redir' value='1'><input type='submit' value='Select'></form></td></tr>";
|
||||
}
|
||||
message += F("</table></body></html>");
|
||||
request->send(200, "text/html", message);
|
||||
message += "</table></body></html>";
|
||||
http_server.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'.");
|
||||
http_server.on("/effects", HTTP_POST, [&]() {
|
||||
if (!http_server.hasArg("name")) {
|
||||
http_server.send(400, "text/plain", "Missing argument 'name'.");
|
||||
return;
|
||||
}
|
||||
String name = request->getParam("name", true)->value();
|
||||
String name = http_server.arg("name");
|
||||
if (change_current_effect(name)) {
|
||||
if (request->hasParam("redir")) {
|
||||
request->redirect("/effects");
|
||||
if (http_server.hasArg("redir")) {
|
||||
http_server.sendHeader("Location", "/effects");
|
||||
http_server.send(301);
|
||||
} else {
|
||||
request->send(200, "text/plain", "OK");
|
||||
http_server.send(200, "text/plain", "OK");
|
||||
}
|
||||
} else {
|
||||
request->send(404, "text/plain", "Effect not found.");
|
||||
http_server.send(404, "text/plain", "Effect not found.");
|
||||
}
|
||||
});
|
||||
http_server.on("/delete", HTTP_GET, [&](AsyncWebServerRequest* request) {
|
||||
http_server.on("/delete", HTTP_GET, [&]() {
|
||||
LOGln("HTTP * GET /delete");
|
||||
if (request->params()==0) {
|
||||
request->send_P(400, text_plain, PSTR("No filename given"));
|
||||
if (http_server.args()==0) {
|
||||
http_server.send_P(400, text_plain, PSTR("No filename given"));
|
||||
return;
|
||||
}
|
||||
String file = request->getParam(0)->value();
|
||||
String file = http_server.arg(0);
|
||||
if (file == "/") {
|
||||
request->send_P(400, text_plain, PSTR("Invalid path"));
|
||||
http_server.send_P(400, text_plain, PSTR("Invalid path"));
|
||||
return;
|
||||
}
|
||||
if (!SPIFFS.exists(file)) {
|
||||
request->send_P(400, text_plain, PSTR("File does not exist."));
|
||||
http_server.send_P(400, text_plain, PSTR("File does not exist."));
|
||||
return;
|
||||
}
|
||||
SPIFFS.remove(file);
|
||||
request->send_P(200, text_plain, PSTR("OK"));
|
||||
http_server.send_P(200, text_plain, PSTR("OK"));
|
||||
});
|
||||
http_server.on("/upload", HTTP_POST, [](AsyncWebServerRequest* request) {
|
||||
request->send(200);
|
||||
http_server.on("/upload", HTTP_POST, []() {
|
||||
LOGln("HTTP * POST /upload");
|
||||
http_server.send(200, "text/plain", "OK");
|
||||
}, http_server_handle_file_upload);
|
||||
http_server.on("/free_heap", HTTP_GET, [&](AsyncWebServerRequest* request){
|
||||
http_server.on("/free_heap", HTTP_GET, [&](){
|
||||
LOGln("HTTP * GET /free_heap");
|
||||
request->send(200, "text/plain", String(ESP.getFreeHeap()));
|
||||
http_server.send(200, "text/plain", String(ESP.getFreeHeap()));
|
||||
});
|
||||
http_server.on("/uptime", HTTP_GET, [&](AsyncWebServerRequest* request){
|
||||
http_server.on("/uptime", HTTP_GET, [&](){
|
||||
LOGln("HTTP * GET /uptime");
|
||||
request->send(200, "text/plain", String(millis()/1000));
|
||||
http_server.send(200, "text/plain", String(millis()/1000));
|
||||
});
|
||||
http_server.on("/fps", HTTP_GET, [](AsyncWebServerRequest* request){
|
||||
http_server.on("/fps", HTTP_GET, [](){
|
||||
LOGln("HTTP * GET /fps");
|
||||
request->send(200, "text/plain", String(FastLED.getFPS()));
|
||||
http_server.send(200, "text/plain", String(FastLED.getFPS()));
|
||||
});
|
||||
http_server.on("/reboot", HTTP_POST, [](AsyncWebServerRequest* request){
|
||||
http_server.on("/reboot", HTTP_POST, [](){
|
||||
LOGln("HTTP * POST /reboot");
|
||||
ESP.restart();
|
||||
});
|
||||
http_server.on("/mode", HTTP_POST, [&](AsyncWebServerRequest* request){
|
||||
LOGln("HTTP * POST /mode with value %s", request->getParam("plain", true)->value().c_str());
|
||||
if (!request->hasParam("plain", true)) {
|
||||
request->send_P(400, text_plain, PSTR("No effect given."));
|
||||
http_server.on("/brightness", HTTP_POST, [&](){
|
||||
LOGln("HTTP * POST /brightness with value %s", http_server.arg("plain").c_str());
|
||||
if (!http_server.hasArg("plain")) {
|
||||
http_server.send_P(400, text_plain, PSTR("No brightness given"));
|
||||
return;
|
||||
}
|
||||
String val = request->getParam("plain", true)->value();
|
||||
long val = http_server.arg("plain").toInt();
|
||||
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)) {
|
||||
request->send(200, "text/plain", "OK");
|
||||
http_server.send(200, "text/plain", "OK");
|
||||
} else {
|
||||
request->send_P(400, text_plain, PSTR("Unknown effect."));
|
||||
http_server.send_P(400, text_plain, PSTR("Unknown effect."));
|
||||
}
|
||||
});
|
||||
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);
|
||||
}
|
||||
|
||||
void http_server_send_framedata() {
|
||||
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;
|
||||
}
|
||||
}
|
||||
void http_server_loop() {
|
||||
http_server.handleClient();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -96,7 +96,6 @@ boolean mqtt_connect() {
|
||||
LOGln("MQTT * Connecting to MQTT server with client id %s", hostname);
|
||||
if (mqtt_client.connect(hostname, MQTT_USER, MQTT_PASS, MQTT_TOPIC "status", 0, true, "OFFLINE", true)) {
|
||||
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];
|
||||
snprintf(buffer, 40, "ONLINE %s %s", hostname, WiFi.localIP().toString().c_str());
|
||||
mqtt_client.publish(MQTT_TOPIC "status", buffer, true);
|
||||
|
@ -1,5 +1,4 @@
|
||||
#include <Arduino.h>
|
||||
#include <FS.h>
|
||||
|
||||
#include "ntp.h"
|
||||
#include "config.h"
|
||||
@ -10,6 +9,7 @@
|
||||
#include "functions.h"
|
||||
#include "effects.h"
|
||||
#include "http_server.h"
|
||||
#include "recorder.h"
|
||||
#include "settings.h"
|
||||
|
||||
uint8_t starting_up = OTA_STARTUP_DELAY;
|
||||
@ -17,7 +17,6 @@ int loop_timeouts = 0;
|
||||
long loop_started_at = 0;
|
||||
uint8_t baseHue = 0; // defined as extern in prototypes.h
|
||||
char hostname[30]; // defined as extern in prototypes.h
|
||||
uint16_t frame = 0; // defined as extern in prototypes.h
|
||||
unsigned long _last_effect_loop_finished_at = 0;
|
||||
#ifdef RECORDER_ENABLE
|
||||
Recorder* recorder;
|
||||
@ -51,6 +50,9 @@ void setup() {
|
||||
#ifdef MQTT_ENABLE
|
||||
mqtt_setup();
|
||||
#endif
|
||||
#ifdef RECORDER_ENABLE
|
||||
recorder = new Recorder();
|
||||
#endif
|
||||
SPIFFS.begin();
|
||||
load_settings();
|
||||
LOGln("Core * Setup complete");
|
||||
@ -79,13 +81,15 @@ void loop() {
|
||||
#ifdef MQTT_ENABLE
|
||||
mqtt_loop();
|
||||
#endif
|
||||
#ifdef HTTP_SERVER_ENABLE
|
||||
http_server_loop();
|
||||
#endif
|
||||
|
||||
EVERY_N_MILLISECONDS(100) {
|
||||
baseHue++;
|
||||
}
|
||||
|
||||
EVERY_N_MILLISECONDS(1000 / FPS) {
|
||||
frame++;
|
||||
// Calculate the delay since the last time loop() was called.
|
||||
// This way, the effect can handle varying frame rates.
|
||||
uint16_t last_loop_ago;
|
||||
@ -115,7 +119,10 @@ void loop() {
|
||||
}
|
||||
FastLED.show();
|
||||
|
||||
http_server_send_framedata();
|
||||
|
||||
#ifdef RECORDER_ENABLE
|
||||
recorder->loop();
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(MQTT_ENABLE) && defined(MQTT_REPORT_METRICS)
|
||||
|
71
src/recorder.cpp
Normal file
71
src/recorder.cpp
Normal file
@ -0,0 +1,71 @@
|
||||
#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
|
@ -11,8 +11,6 @@ Setting all_settings[] = {
|
||||
|
||||
{"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.time", &settings.effects.cycle.time, TYPE_UINT16},
|
||||
|
||||
@ -37,8 +35,6 @@ Setting all_settings[] = {
|
||||
{"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.lightspeed.count", &settings.effects.lightspeed.count, TYPE_UINT8},
|
||||
|
||||
{"effects.matrix.length_min", &settings.effects.matrix.length_min, TYPE_UINT8},
|
||||
{"effects.matrix.length_max", &settings.effects.matrix.length_max, TYPE_UINT8},
|
||||
{"effects.matrix.speed_min", &settings.effects.matrix.speed_min, TYPE_UINT8},
|
||||
@ -53,7 +49,7 @@ Setting all_settings[] = {
|
||||
{"effects.tv_static.black_bar_speed", &settings.effects.tv_static.black_bar_speed, TYPE_UINT16},
|
||||
};
|
||||
|
||||
const uint8_t all_settings_size = 32;
|
||||
const uint8_t all_settings_size = 30;
|
||||
|
||||
bool change_setting(const char* key, uint16_t new_value) {
|
||||
LOGln("Settings * Setting %s to new value %d.", key, new_value);
|
||||
|
@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
IP="$1"
|
||||
EFFECTS=`./list_effects.rb $IP | sort`
|
||||
EFFECTS=`egrep "case" ../effects.cpp | tr -s "\t" " " | cut -d" " -f 7 | sort`
|
||||
|
||||
mkdir effects
|
||||
|
||||
|
@ -1,26 +0,0 @@
|
||||
#!/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
|
71
src/tools/monitor.rb
Executable file → Normal file
71
src/tools/monitor.rb
Executable file → Normal file
@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env ruby
|
||||
require 'websocket-eventmachine-client'
|
||||
require 'socket'
|
||||
require 'pp'
|
||||
|
||||
def rgb2ansi(r, g, b)
|
||||
@ -13,48 +13,47 @@ def rgb2ansi(r, g, b)
|
||||
end
|
||||
|
||||
IP = ARGV[0]
|
||||
PORT = 2122
|
||||
EFFECT = ARGV[1]
|
||||
uri = "ws://#{IP}:80/ws"
|
||||
puts "Connecting to #{uri}..."
|
||||
|
||||
EM.run do
|
||||
ws = WebSocket::EventMachine::Client.connect(uri: uri)
|
||||
puts "Connecting to #{IP}:#{PORT}..."
|
||||
|
||||
ws.onopen do
|
||||
puts "\033[2J\033[H\n Connected."
|
||||
ws.send "effect:#{EFFECT}" if EFFECT
|
||||
ws.send "monitor:1"
|
||||
end
|
||||
s = TCPSocket.new(IP, PORT)
|
||||
|
||||
ws.onmessage do |msg, type|
|
||||
if type==:binary
|
||||
data = msg.unpack("C*")
|
||||
width = data.shift
|
||||
height = data.shift
|
||||
splitter = data.shift
|
||||
raise "Unexpected value #{splitter} at index 2" unless splitter==0xFF
|
||||
expected_length = width * height * 3 + 4
|
||||
raise "Unexpected message length. Expected: #{expected_length}, was: #{data.count + 3}" unless data.count + 3==expected_length
|
||||
puts "Connected."
|
||||
init = s.recv(3).unpack("C*")
|
||||
|
||||
str = "\033[H+#{"-"*width}+\n"
|
||||
(0...height).each do |y|
|
||||
str += "|"
|
||||
(0...width).each do |x|
|
||||
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
|
||||
|
||||
|
||||
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)
|
||||
str += "\033[48;5;#{color_code}m "
|
||||
print "\033[48;5;#{color_code}m "
|
||||
end
|
||||
str += "\033[0m|\n"
|
||||
end
|
||||
str += "+#{"-"*width}+\n"
|
||||
puts str
|
||||
else
|
||||
puts msg
|
||||
exit
|
||||
end
|
||||
end
|
||||
|
||||
ws.onclose do |msg, reason|
|
||||
puts "Disconnected. Message: #{msg}. Reason: #{reason}."
|
||||
puts "\033[0m"
|
||||
end
|
||||
end
|
||||
|
@ -1,19 +1,39 @@
|
||||
#!/usr/bin/env ruby
|
||||
require 'websocket-eventmachine-client'
|
||||
require 'socket'
|
||||
require 'pp'
|
||||
require 'rmagick'
|
||||
|
||||
include Magick
|
||||
|
||||
IP = ARGV[0]
|
||||
PORT = 2122
|
||||
FILE = ARGV[1] or raise "No filename given"
|
||||
EFFECT = ARGV[2]
|
||||
FRAMES = 125
|
||||
FACTOR = 2
|
||||
delay = 50
|
||||
|
||||
uri = "ws://#{IP}:80/ws"
|
||||
puts "Connecting to #{uri}..."
|
||||
puts "Connecting to #{IP}:#{PORT}..."
|
||||
|
||||
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
|
||||
last_id = 255
|
||||
@ -21,37 +41,31 @@ last_frame_time = nil
|
||||
img = nil
|
||||
last_diff = nil
|
||||
|
||||
EM.run do
|
||||
ws = WebSocket::EventMachine::Client.connect(uri: uri)
|
||||
|
||||
ws.onopen do
|
||||
puts "Connected."
|
||||
puts "Waiting for delay..." if delay>0
|
||||
ws.send "effect:#{EFFECT}" if EFFECT
|
||||
ws.send "monitor:1"
|
||||
end
|
||||
|
||||
ws.onmessage do |msg, type|
|
||||
if type==:binary
|
||||
while gif.length < FRAMES do
|
||||
data = udp.recvfrom(1024)[0].unpack("C*")
|
||||
if delay > 0
|
||||
delay -= 1
|
||||
next
|
||||
end
|
||||
data = msg.unpack("C*")
|
||||
width = data.shift
|
||||
height = data.shift
|
||||
splitter = data.shift
|
||||
raise "Unexpected value #{splitter} at index 2" unless splitter==0xFF
|
||||
expected_length = width * height * 3 + 4
|
||||
raise "Unexpected message length. Expected: #{expected_length}, was: #{data.count + 3}" unless data.count + 3==expected_length
|
||||
#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
|
||||
|
||||
img = Image.new(width, height)
|
||||
id = data.shift << 8 | data.shift
|
||||
if last_id != id-1 && last_id != id-2
|
||||
puts "Skipped from #{last_id} to #{id}."
|
||||
gif = ImageList.new
|
||||
end
|
||||
last_id = id
|
||||
|
||||
img = Image.new(dim_x, dim_y)
|
||||
gc = Draw.new
|
||||
|
||||
#next
|
||||
print "."
|
||||
print "#{gif.length}" if gif.length%50==0
|
||||
(0...height).each do |y|
|
||||
(0...width).each do |x|
|
||||
(0...dim_y).each do |y|
|
||||
(0...dim_x).each do |x|
|
||||
r, g, b = *data.shift(3)
|
||||
gc.fill("rgb(#{r}, #{g}, #{b})")
|
||||
gc.point(x, y)
|
||||
@ -61,26 +75,8 @@ EM.run do
|
||||
gc.draw(img)
|
||||
img.sample!(FACTOR)
|
||||
gif << img
|
||||
|
||||
if gif.length >= FRAMES
|
||||
ws.close
|
||||
end
|
||||
else
|
||||
puts "-->#{msg}"
|
||||
exit
|
||||
end
|
||||
end
|
||||
|
||||
ws.onerror do |error|
|
||||
puts "Error: #{error}"
|
||||
end
|
||||
|
||||
ws.onclose do |msg, reason|
|
||||
puts "Disconnected."
|
||||
EventMachine.stop_event_loop
|
||||
end
|
||||
end
|
||||
|
||||
s.close
|
||||
puts
|
||||
puts "Generating gif..."
|
||||
gif.ticks_per_second = 100
|
||||
|
@ -4,7 +4,6 @@ require 'pp'
|
||||
require 'thread/pool'
|
||||
|
||||
GAMES_PER_ROUND = 50
|
||||
FLOAT = true
|
||||
|
||||
class Game
|
||||
WIDTH = 16
|
||||
@ -178,10 +177,10 @@ class AI
|
||||
@rounds = 1
|
||||
@id = rand(0xFFFFFF)
|
||||
if w==nil
|
||||
@weights = Array.new(network_size()) { FLOAT ? rand() * 2.0 - 1.0 : rand(256) - 128 }
|
||||
@weights = Array.new(network_size()) { rand() * 2.0 - 1.0 }
|
||||
puts "Initialized with random values: #{@weights}" if @debug
|
||||
else
|
||||
if w[0].is_a?(Integer) && FLOAT
|
||||
if w[0].is_a? Integer
|
||||
@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
|
||||
@ -221,7 +220,7 @@ class AI
|
||||
(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}
|
||||
outputs = Array.new(c_out){0.0}
|
||||
(0...c_out).each do |o|
|
||||
(0...c_in).each do |i|
|
||||
outputs[o] += inputs[i] * @weights[x]
|
||||
@ -254,53 +253,33 @@ class AI
|
||||
# w[i2] = temp
|
||||
if action==0 #change single value
|
||||
i = rand(network_size())
|
||||
diff = FLOAT ? rand() * 0.2 - 0.1 : rand(256) - 128
|
||||
diff = rand() * 0.2 - 0.1
|
||||
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
|
||||
w[i] *= -1.0
|
||||
elsif action==2
|
||||
(0...network_size()).each do |i|
|
||||
w[i] = (FLOAT ? rand() * 2 - 1.0 : rand(256) - 128) if rand(5)==0
|
||||
w[i] = rand() * 2 - 1.0 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
|
||||
diff = rand() * 0.2 - 0.1
|
||||
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)]
|
||||
@ -324,17 +303,13 @@ class AI
|
||||
w = @weights.dup
|
||||
w2 = ai.weights
|
||||
(0...network_size()).each do |i|
|
||||
w[i] = (w[i] + w2[i]) / (FLOAT ? 2.0 : 2)
|
||||
w[i] = (w[i] + w2[i]) / 2.0
|
||||
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
|
||||
|
Loading…
Reference in New Issue
Block a user