using Patrix library now

This commit is contained in:
Patrick Haßel 2025-01-23 14:24:29 +01:00
parent bee63d137f
commit 80b83705ac
24 changed files with 54 additions and 1619 deletions

View File

@ -1,20 +1,10 @@
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[basic] [basic]
platform = espressif32 platform = espressif32
board = esp32dev board = esp32dev
framework = arduino framework = arduino
lib_deps = https://github.com/adafruit/Adafruit_NeoPixel board_build.filesystem = littlefs
https://github.com/knolleary/pubsubclient lib_deps = ../Patrix
build_flags = build_flags = -DWIFI_SSID=\"HappyNet\" -DWIFI_PKEY=\"1Grausame!Sackratte7\" -DWIFI_HOST=\"RGBMatrixDisplay\"
monitor_port = /dev/ttyUSB0 monitor_port = /dev/ttyUSB0
monitor_speed = 115200 monitor_speed = 115200
monitor_filters = esp32_exception_decoder monitor_filters = esp32_exception_decoder
@ -23,6 +13,7 @@ monitor_filters = esp32_exception_decoder
platform = ${basic.platform} platform = ${basic.platform}
board = ${basic.board} board = ${basic.board}
framework = ${basic.framework} framework = ${basic.framework}
board_build.filesystem = ${basic.board_build.filesystem}
lib_deps = ${basic.lib_deps} lib_deps = ${basic.lib_deps}
build_flags = ${basic.build_flags} build_flags = ${basic.build_flags}
monitor_port = ${basic.monitor_port} monitor_port = ${basic.monitor_port}
@ -35,6 +26,7 @@ upload_speed = 921600
platform = ${basic.platform} platform = ${basic.platform}
board = ${basic.board} board = ${basic.board}
framework = ${basic.framework} framework = ${basic.framework}
board_build.filesystem = ${basic.board_build.filesystem}
lib_deps = ${basic.lib_deps} lib_deps = ${basic.lib_deps}
build_flags = ${basic.build_flags} build_flags = ${basic.build_flags}
monitor_port = ${basic.monitor_port} monitor_port = ${basic.monitor_port}

View File

@ -1,13 +0,0 @@
#include "BASICS.h"
double doStep(double valueCurrent, double valueMin, double valueMax, long long millisecondsTotal, microseconds_t microsecondsDelta) {
double valueRange = valueMax - valueMin;
double timeRatio = (double) microsecondsDelta / ((double) millisecondsTotal * 1000.0);
double valueStep = valueRange * timeRatio;
double valueNew = max(valueMin, min(valueMax, valueCurrent + valueStep));
return valueNew;
}
bool randomBool(int uncertainty) {
return random(uncertainty) == 0;
}

View File

@ -1,24 +0,0 @@
#ifndef BASICS_H
#define BASICS_H
#include <Arduino.h>
#define X true
#define _ false
#define ____ 0
#define QUAR 64
#define HALF 128
#define FULL 255
#define countof(x) (sizeof(x) / sizeof(x[0]))
typedef int64_t microseconds_t;
typedef unsigned long milliseconds_t;
double doStep(double valueCurrent, double valueMin, double valueMax, microseconds_t millisecondsTotal, microseconds_t microsecondsDelta);
bool randomBool(int uncertainty);
#endif

36
src/Node.h Normal file
View File

@ -0,0 +1,36 @@
#ifndef NODE_H
#define NODE_H
#include <patrix/core/Config.h>
#include <patrix/display/DisplayMatrix.h>
#include <patrix/node/PatrixNode.h>
Config config("/test.json");
DisplayMatrix<32, 8> display(27);
class Node final : public PatrixNode {
public:
explicit Node() : PatrixNode(true, true, true) {
//
}
void setup() override {
config.read();
display.setup();
display.setBrightness(6);
display.clear();
display.foreground = Blue;
display.printf("Test");
}
void loop() override {
config.loop();
display.loop();
}
};
#endif

View File

@ -1,7 +1,7 @@
#ifndef POSITION_H #ifndef POSITION_H
#define POSITION_H #define POSITION_H
#include "BASICS.h" #include <Arduino.h>
class Vector { class Vector {

View File

@ -1,105 +0,0 @@
#include <EEPROM.h>
#include "config.h"
#include "display.h"
#define WRITE_DELAY_MS (30 * 1000)
uint32_t calculateChecksum(Config *ptr);
void config_defaults();
bool validateChecksum(uint32_t checksumEEPROM, Config &tmp);
Config config;
bool dirty = false;
bool notify = false;
milliseconds_t lastDirtyMillis = 0;
void config_setup() {
if (!config_load()) {
config_defaults();
}
}
void config_set_dirty() {
dirty = true;
lastDirtyMillis = millis();
}
void config_loop() {
if (notify && millis() - lastDirtyMillis > WRITE_DELAY_MS + 2000) {
notify = false;
}
if (notify) {
bool blink = ((millis() - lastDirtyMillis) / 100) % 2 == 0;
display.set(0, 0, blink ? MAGENTA : BLACK);
}
if (!dirty) {
return;
}
if (millis() - lastDirtyMillis <= 30000) {
return;
}
dirty = false;
notify = true;
uint32_t checksum = calculateChecksum(&config);
EEPROM.begin(512);
EEPROM.writeBytes(0, &config, sizeof(Config));
EEPROM.writeUInt(sizeof(Config), checksum);
EEPROM.end();
Serial.printf("Config saved to EEPROM.\n");
}
void configSaveNowIfDirty() {
if (dirty) {
lastDirtyMillis = millis() - 30000;
}
}
bool config_load() {
Config tmp{};
EEPROM.begin(512);
EEPROM.readBytes(0, &tmp, sizeof(Config));
uint32_t checksum = EEPROM.readUInt(sizeof(Config));
EEPROM.end();
bool success = validateChecksum(checksum, tmp);
if (success) {
memcpy(&config, &tmp, sizeof(Config));
Serial.printf("Config loaded from EEPROM.\n");
} else {
Serial.printf("Failed to load config from EEPROM.\n");
}
return success;
}
bool validateChecksum(uint32_t checksumEEPROM, Config &tmp) {
uint32_t calculated = calculateChecksum(&tmp);
return checksumEEPROM == calculated;
}
void config_defaults() {
config.mode = COUNT_DOWN_SLEEP;
config.speed = 1.0;
config.brightness = 16;
config.date = {0};
Serial.printf("Config DEFAULTS loaded.\n");
}
uint32_t calculateChecksum(Config *ptr) {
uint32_t checksum = 0;
for (auto *b = (uint8_t *) ptr; b < (uint8_t *) ptr + sizeof(Config); b++) {
checksum += *b;
}
return checksum;
}

View File

@ -1,25 +0,0 @@
#ifndef RGBMATRIXDISPLAY_CONFIG_H
#define RGBMATRIXDISPLAY_CONFIG_H
#include "mode/Mode.h"
struct Config {
ModeId mode;
double speed;
uint8_t brightness;
tm date;
};
extern Config config;
void config_setup();
void config_loop();
void configSaveNowIfDirty();
bool config_load();
void config_set_dirty();
#endif

View File

@ -1,17 +0,0 @@
#include "display.h"
#include "config.h"
Display display(32, 8);
void setBrightness(int brightness) {
brightness = max(1, min(brightness, 255));
if (display.getBrightness() == brightness) {
return;
}
config.brightness = brightness;
display.setBrightness(brightness);
config_set_dirty();
Serial.printf("Setting brightness to %5.1f%%\n", brightness / 2.55);
}

View File

@ -1,10 +0,0 @@
#ifndef RGBMATRIXDISPLAY_DISPLAY_H
#define RGBMATRIXDISPLAY_DISPLAY_H
#include "display/Display.h"
extern Display display;
void setBrightness(int brightness);
#endif

View File

@ -1,29 +0,0 @@
#include "Color.h"
const Color BLACK = {____, ____, ____};
const Color WHITE = {FULL, FULL, FULL};
const Color RED = {FULL, ____, ____};
const Color GREEN = {____, FULL, ____};
const Color ORANGE = {FULL, QUAR, ____};
const Color BLUE = {____, ____, FULL};
const Color YELLOW = {FULL, FULL, ____};
const Color MAGENTA = {FULL, ____, FULL};
const Color VIOLET = {HALF, ____, FULL};
const Color TURQUOISE = {____, FULL, FULL};
Color gray(uint8_t brightness) {
return {brightness, brightness, brightness};
}
Color randomColor() {
return {(uint8_t) random(255), (uint8_t) random(255), (uint8_t) random(255)};
}

View File

@ -1,36 +0,0 @@
#ifndef PIXEL_H
#define PIXEL_H
#include "BASICS.h"
struct Color {
uint8_t r;
uint8_t g;
uint8_t b;
};
Color gray(uint8_t brightness);
Color randomColor();
extern const Color BLACK;
extern const Color WHITE;
extern const Color RED;
extern const Color GREEN;
extern const Color ORANGE;
extern const Color BLUE;
extern const Color YELLOW;
extern const Color MAGENTA;
extern const Color VIOLET;
extern const Color TURQUOISE;
#endif

View File

@ -1,180 +0,0 @@
#include "Display.h"
bool SYMBOLS[SYMBOL_COUNT][DISPLAY_CHAR_WIDTH * DISPLAY_CHAR_HEIGHT] = {
{
X, X, X,
X, _, X,
X, _, X,
X, _, X,
X, X, X,
},
{
_, _, X,
_, X, X,
X, _, X,
_, _, X,
_, _, X,
},
{
X, X, X,
_, _, X,
X, X, X,
X, _, _,
X, X, X,
},
{
X, X, X,
_, _, X,
_, X, X,
_, _, X,
X, X, X,
},
{
X, _, X,
X, _, X,
X, X, X,
_, _, X,
_, _, X,
},
{
X, X, X,
X, _, _,
X, X, X,
_, _, X,
X, X, X,
},
{
X, X, X,
X, _, _,
X, X, X,
X, _, X,
X, X, X,
},
{
X, X, X,
_, _, X,
_, X, _,
X, _, _,
X, _, _,
},
{
X, X, X,
X, _, X,
X, X, X,
X, _, X,
X, X, X,
},
{
X, X, X,
X, _, X,
X, X, X,
_, _, X,
X, X, X,
},
{
_, _, _,
_, X, _,
_, _, _,
_, X, _,
_, _, _,
},
{
_, _, X,
_, _, X,
_, _, X,
X, _, X,
_, X, _,
},
{
X, _, X,
X, X, X,
_, X, _,
X, X, X,
X, _, X,
},
{
_, _, _,
_, _, _,
X, X, X,
_, _, _,
_, _, _,
},
{
X, _, _,
_, _, X,
_, X, _,
X, _, _,
_, _, X,
},
{
X, X, X,
_, X, _,
_, X, _,
_, X, _,
_, X, _,
},
{
_, X, _,
X, _, X,
X, X, X,
X, _, X,
X, _, X,
},
{
_, X, X,
X, _, _,
X, X, X,
X, _, X,
_, X, _,
},
{
X, X, X,
X, _, _,
X, X, X,
X, _, _,
X, X, X,
},
{
X, _, X,
X, _, X,
X, X, X,
X, _, X,
X, _, X,
},
{
X, X, _,
X, _, X,
X, X, _,
X, X, _,
X, _, X,
},
{
X, _, _,
X, _, _,
X, _, _,
X, _, _,
X, _, _,
},
{
X, _, _,
X, _, _,
X, _, _,
X, _, _,
X, X, X,
},
{
_, _, _,
_, _, _,
_, _, _,
_, _, _,
_, _, _,
},
// this must always be the last symbol (fallback)
{
X, X, X,
X, X, X,
X, X, X,
X, X, X,
X, X, X,
},
};

View File

@ -1,329 +0,0 @@
#ifndef DISPLAY_H
#define DISPLAY_H
#include "Color.h"
#include "Adafruit_NeoPixel.h"
#include "Vector.h"
#define SYMBOL_COUNT 26
#define SYMBOL_J 11
#define SYMBOL_X 12
#define SYMBOL_DASH 13
#define SYMBOL_PERCENT 14
#define SYMBOL_T 15
#define SYMBOL_A 16
#define SYMBOL_G 17
#define SYMBOL_E 18
#define SYMBOL_H 19
#define SYMBOL_R 20
#define SYMBOL_I 21
#define SYMBOL_L 22
#define SYMBOL_SPACE 23
#define DISPLAY_CHAR_WIDTH 3
#define DISPLAY_CHAR_HEIGHT 5
extern bool SYMBOLS[SYMBOL_COUNT][DISPLAY_CHAR_WIDTH * DISPLAY_CHAR_HEIGHT];
class Display {
public:
const uint8_t width;
const uint8_t height;
const size_t pixelCount;
const size_t pixelByteCount;
bool fpsShow = false;
private:
Adafruit_NeoPixel leds;
milliseconds_t fpsLastMillis = 0;
int fps = 0;
Color *buffer = nullptr;
uint8_t brightness = 10;
public:
Display(uint8_t width, uint8_t height) : width(width), height(height),
pixelCount(width * height),
pixelByteCount(pixelCount * sizeof(Color)),
leds(pixelCount, GPIO_NUM_13) {
buffer = (Color *) malloc(pixelByteCount);
if (buffer == nullptr) {
Serial.print("+-----------------------------------------------+\n");
Serial.print("| OUT OF MEMORY: Cannot allocate double-buffer! |\n");
Serial.print("+-----------------------------------------------+\n");
}
}
~Display() {
if (buffer == nullptr) {
return;
}
free(buffer);
buffer = nullptr;
}
void setup() {
leds.begin();
clear();
flush();
}
void loop() {
calculateFPS();
drawFpsBorder();
if (isDirty()) {
flush();
}
}
void setBrightness(uint8_t value) {
brightness = value;
}
uint8_t getBrightness() const {
return brightness;
}
void clear() {
if (buffer == nullptr) {
return;
}
memset(buffer, 0, pixelByteCount);
}
enum ALIGN {
LEFT, RIGHT
};
uint8_t print2(int x, int y, double valueDbl, Color colorPositive, Color colorZero, Color colorNegative, ALIGN align = RIGHT) {
const Color color = valueDbl == 0 ? colorZero : (valueDbl < 0 ? colorNegative : colorPositive);
return print2(x, y, valueDbl, color, align);
}
uint8_t print2(int x, int y, double valueDbl, Color color, ALIGN align = RIGHT) {
if (isnan(valueDbl)) {
x -= 3 * (DISPLAY_CHAR_WIDTH + 1) - 1;
x += print(x, y, SYMBOL_DASH, color, true) + 1;
x += print(x, y, SYMBOL_DASH, color, true) + 1;
x += print(x, y, SYMBOL_DASH, color, true) + 1;
return x;
}
const int value = (int) round(abs(valueDbl));
const bool negative = valueDbl < 0;
const int digitCount = (int) max(1.0, floor(log10(value)) + 1); // log10 is -inf for value==0, hence the max(1.0, ...)
if (align == RIGHT) {
x -= ((negative ? 1 : 0) + digitCount) * (DISPLAY_CHAR_WIDTH + 1) - 1;
}
int divider = (int) pow(10, digitCount - 1);
if (negative) {
x += print(x, y, SYMBOL_DASH, color, true) + 1;
}
bool showIfZero = false;
// Serial.printf("x=%d, y=%d, value=%d, align=%s, digitCount=%d, divider=%d\n", x, y, value, align == LEFT ? "LEFT" : "RIGHT", digitCount, divider);
for (int digitPos = 0; digitPos < digitCount; ++digitPos) {
const int digitVal = value / divider % 10;
showIfZero |= digitVal != 0 || (digitPos == digitCount - 1);
x += print(x, y, digitVal, color, showIfZero) + 1;
// Serial.printf(" digitPos=%d, x=%d, y=%d, digitVal=%d, showIfZero=%s, divider=%d\n", digitPos, x, y, digitVal, showIfZero ? "true" : "false", divider);
divider /= 10;
}
// Serial.println();
return x;
}
uint8_t print(uint8_t xPos, uint8_t yPos, uint8_t index, Color color, bool showIfZero) {
if (index == 0 && !showIfZero) {
return DISPLAY_CHAR_WIDTH;
}
if (index >= SYMBOL_COUNT) {
Serial.printf("Cannot print2 symbol #%u.\n", index);
index = SYMBOL_COUNT - 1;
}
bool *symbolBit = SYMBOLS[index];
for (uint8_t y = 0; y < DISPLAY_CHAR_HEIGHT; ++y) {
for (uint8_t x = 0; x < DISPLAY_CHAR_WIDTH; ++x) {
if (*(symbolBit++)) {
set(xPos + x, yPos + y, color);
} else {
set(xPos + x, yPos + y, BLACK);
}
}
}
return DISPLAY_CHAR_WIDTH;
}
// TODO REMOVE QUICK & DIRTY
uint8_t printM(uint8_t xPos, uint8_t yPos, Color color) {
bool sym[5][5] = {
{X,_,_,_,X},
{X,X,_,X,X},
{X,_,X,_,X},
{X,_,_,_,X},
{X,_,_,_,X},
};
for (int y = 0; y < countof(sym); ++y) {
for (int x = 0; x < countof(sym[0]); ++x) {
if (sym[y][x]) {
set(xPos + x, yPos + y, color);
} else {
set(xPos + x, yPos + y, BLACK);
}
}
}
return countof(sym[0]);
}
static bool doCreeperBlink() {
const auto now = millis();
static auto blink = false;
static auto last = now;
if (!blink) {
if (now - last >= 3000) {
last = now;
if (random(3) == 0) {
blink = true;
}
}
} else {
if (now - last >= 500) {
last = now;
blink = false;
}
}
return blink;
}
// TODO REMOVE QUICK & DIRTY
uint8_t printCreeper(uint8_t xPos, uint8_t yPos) {
const auto creeperBlink = doCreeperBlink();
bool sym[8][8] = {
{X, X, X, X, X, X, X, X},
{X, X, X, X, X, X, X, X},
{X, _, _, X, X, _, _, X},
{X, _, _, X, X, _, _, X},
{X, X, X, _, _, X, X, X},
{X, X, _, _, _, _, X, X},
{X, X, _, _, _, _, X, X},
{X, X, _, X, X, _, X, X},
};
for (int y = 0; y < countof(sym); ++y) {
for (int x = 0; x < countof(sym[0]); ++x) {
if (creeperBlink && ((x == 1 || x == 2) && y == 2)) {
set(xPos + x, yPos + y, GREEN);
} else {
set(xPos + x, yPos + y, sym[y][x] ? GREEN : BLACK);
}
}
}
return countof(sym[0]);
}
// TODO REMOVE QUICK & DIRTY
uint8_t printI(uint8_t xPos, uint8_t yPos, Color color) {
for (int y = 0; y < 5; ++y) {
set(xPos, yPos + y, color);
}
return 1;
}
void set(Vector pos, Color color) {
set((uint8_t) round(pos.x), (uint8_t) round(pos.y), color);
}
void set(uint8_t x, uint8_t y, Color color) {
if (x >= width || y >= height) {
return;
}
if ((y % 2) != 0) {
x = width - x - 1;
}
set(y * width + x, color);
}
void set(uint16_t index, Color color) {
if (buffer == nullptr) {
return;
}
buffer[index] = {
// yes, correct order is GRB !!!
(uint8_t) (color.g * brightness >> 8),
(uint8_t) (color.r * brightness >> 8),
(uint8_t) (color.b * brightness >> 8)
};
}
private:
void flush() {
if (buffer == nullptr) {
return;
}
memcpy(leds.getPixels(), buffer, pixelByteCount);
leds.show();
}
bool isDirty() const {
if (buffer == nullptr) {
return false;
}
return memcmp(leds.getPixels(), buffer, pixelByteCount) != 0;
}
void calculateFPS() {
fps = (int) round(1000.0 / (millis() - fpsLastMillis));
fpsLastMillis = millis();
}
void drawFpsBorder() {
if (!fpsShow) {
return;
}
int frames = fps;
Color color = RED;
if (frames > 3 * 76) {
frames -= 3 * 76;
color = WHITE;
} else if (frames > 2 * 76) {
frames -= 2 * 76;
color = BLUE;
} else if (frames > 76) {
frames -= 76;
color = GREEN;
}
for (int x = 0; x <= width - 1 && frames-- > 0; x++) {
set(x, 0, color);
}
for (int y = 0; y <= height - 1 && frames-- > 0; y++) {
set(width - 1, y, color);
}
for (int x = width - 1; x >= 0 && frames-- > 0; x--) {
set(x, height - 1, color);
}
for (int y = height - 1; y >= 0 && frames-- > 0; y--) {
set(0, y, color);
}
}
};
#endif

View File

@ -1,32 +1,19 @@
#include "serial.h" #include "Node.h"
#include "config.h"
#include "wifi.h"
#include "server.h" // ReSharper disable once CppUnusedIncludeDirective
#include <ArduinoJson.h>
#include "mode.h" // ReSharper disable once CppUnusedIncludeDirective
#include "display.h" #include <ArduinoOTA.h>
#include "mqtt.h"
void setup() { // ReSharper disable once CppUnusedIncludeDirective
delay(500); #include <PubSubClient.h>
serial_setup(); // ReSharper disable once CppUnusedIncludeDirective
config_setup(); #include <Adafruit_NeoPixel.h>
wifi_setup();
server_setup(); auto node = Node();
display.setup(); PatrixNode &patrixGetNode() {
display.setBrightness(config.brightness); return node;
}
void loop() {
serial_loop();
wifi_loop();
mqtt_loop();
server_loop();
mode_loop();
config_loop();
display.loop();
} }

View File

@ -1,142 +0,0 @@
#include "mode.h"
#include "config.h"
#include "mode/Border/Border.h"
#include "mode/Clock/Clock.h"
#include "mode/GameOfLife/GameOfLife.h"
#include "mode/Pong/Pong.h"
#include "mode/SpaceInvaders/SpaceInvaders.h"
#include "mode/CountDown/CountDown.h"
#include "mode/Starfield/Starfield.h"
#include "mode/Matrix/Matrix.h"
#include "display.h"
#include "mode/Power/Power.h"
#include "mode/Energy/Energy.h"
#include "mode/Timer/Timer.h"
ModeId currentModeId = NONE;
microseconds_t lastMicros = 0;
Mode *mode = nullptr;
void unloadOldMode();
void loadNewMode();
void mode_step();
void mode_loop() {
if (currentModeId != config.mode) {
unloadOldMode();
loadNewMode();
}
mode_step();
}
void setMode(ModeId value) {
if (config.mode == value) {
return;
}
config.mode = value;
config_set_dirty();
}
void modeMove(int index, int x, int y) {
if (mode != nullptr) {
mode->move(index, x, y);
}
}
void modeFire(int index) {
if (mode != nullptr) {
mode->fire(index);
}
}
void setSpeed(double speed) {
speed = min(max(0.01, speed), 10000.0);
if (config.speed == speed) {
return;
}
config.speed = speed;
config_set_dirty();
Serial.printf("Setting speed to %6.2fx\n", config.speed);
}
void unloadOldMode() {
if (mode != nullptr) {
delete mode;
mode = nullptr;
}
display.clear();
}
void loadNewMode() {
currentModeId = config.mode;
lastMicros = 0;
switch (currentModeId) {
case BORDER:
mode = new Border(display);
break;
case CLOCK:
mode = new Clock(display);
break;
case GAME_OF_LIFE_BLACK_WHITE:
mode = new GameOfLife(display, BLACK_WHITE);
break;
case GAME_OF_LIFE_GRAYSCALE:
mode = new GameOfLife(display, GRAYSCALE);
break;
case GAME_OF_LIFE_COLOR_FADE:
mode = new GameOfLife(display, COLOR_FADE);
break;
case GAME_OF_LIFE_RANDOM_COLOR:
mode = new GameOfLife(display, RANDOM_COLOR);
break;
case PONG:
mode = new Pong(display);
break;
case SPACE_INVADERS:
mode = new SpaceInvaders(display);
break;
case COUNT_DOWN:
mode = new CountDown(display, false, false);
break;
case COUNT_DOWN_BARS:
mode = new CountDown(display, true, false);
break;
case COUNT_DOWN_SLEEP:
mode = new CountDown(display, false, true);
break;
case STARFIELD:
mode = new Starfield(display);
break;
case MATRIX:
mode = new Matrix(display);
break;
case POWER:
mode = new Power(display);
break;
case ENERGY:
mode = new Energy(display);
break;
case TIMER:
mode = new Timer2(display);
break;
default:
Serial.print("No mode loaded.\n");
display.clear();
break;
}
Serial.printf("Mode: %s\n", mode == nullptr ? "None" : mode->getName());
}
void mode_step() {
if (mode == nullptr) {
return;
}
auto currentMicros = (int64_t) micros();
microseconds_t microseconds = (microseconds_t) min(1000000.0, max(1.0, (double) (currentMicros - lastMicros) * config.speed));
lastMicros = currentMicros;
mode->loop(microseconds);
}

View File

@ -1,16 +0,0 @@
#ifndef RGBMATRIXDISPLAY_MODE_H
#define RGBMATRIXDISPLAY_MODE_H
#include "mode/Mode.h"
void mode_loop();
void setMode(ModeId value);
void setSpeed(double speed);
void modeMove(int index, int x, int y);
void modeFire(int index);
#endif

View File

@ -1,141 +0,0 @@
#include <WiFiClient.h>
#include <WiFi.h>
#include "mqtt.h"
#include "PubSubClient.h"
#include "wifi.h"
#define MQTT_CONNECT_TIMEOUT_MILLISECONDS 5000
#define MQTT_MAX_MESSAGE_AGE_MILLIS 11000
#define PHOTOVOLTAIC_POWER_W "openDTU/pv/ac/power"
#define PHOTOVOLTAIC_ENERGY_KWH "openDTU/pv/ac/yieldtotal"
#define GRID_POWER_W "electricity/grid/power/signed/w"
#define GRID_IMPORT_WH "electricity/grid/energy/import/wh"
#define GRID_EXPORT_WH "electricity/grid/energy/export/wh"
WiFiClient espClient;
PubSubClient mqtt(espClient);
bool mqttConnected = false;
unsigned long mqttLastConnectTry = 0;
double photovoltaicPowerW = NAN;
unsigned long photovoltaicPowerWLast = 0;
double photovoltaicEnergyKWh = NAN;
unsigned long photovoltaicEnergyKWhLast = 0;
double gridPowerW = NAN;
unsigned long gridPowerWLast = 0;
double gridImportKWh = NAN;
unsigned long gridImportKWhLast = 0;
double gridExportKWh = NAN;
unsigned long gridExportKWhLast = 0;
void mqttDisconnect();
void mqttCallback(char *topic, uint8_t *payload, unsigned int length) {
char message[128];
if (length > sizeof message - 1) {
Serial.printf("MQTT: received too long message: topic=%s, length=%d", topic, length);
return;
}
memcpy(message, payload, length);
message[length] = 0;
if (strcmp(PHOTOVOLTAIC_POWER_W, topic) == 0) {
photovoltaicPowerW = strtod(message, nullptr);
photovoltaicPowerWLast = millis();
} else if (strcmp(PHOTOVOLTAIC_ENERGY_KWH, topic) == 0) {
photovoltaicEnergyKWh = strtod(message, nullptr);
photovoltaicEnergyKWhLast = millis();
} else if (strcmp(GRID_POWER_W, topic) == 0) {
gridPowerW = strtod(message, nullptr);
gridPowerWLast = millis();
} else if (strcmp(GRID_IMPORT_WH, topic) == 0) {
gridImportKWh = strtod(message, nullptr) / 1000;
gridImportKWhLast = millis();
} else if (strcmp(GRID_EXPORT_WH, topic) == 0) {
gridExportKWh = strtod(message, nullptr) / 1000;
gridExportKWhLast = millis();
}
}
void mqtt_loop() {
if (!wifiIsConnected()) {
mqttDisconnect();
return;
}
if (!mqtt.loop() && (mqttLastConnectTry == 0 || millis() - mqttLastConnectTry > MQTT_CONNECT_TIMEOUT_MILLISECONDS)) {
mqttLastConnectTry = millis();
mqtt.setServer("10.0.0.50", 1883);
mqttConnected = mqtt.connect(WiFiClass::getHostname());
if (mqttConnected) {
Serial.printf("Successfully connected mqtt broker at %s:%d\n", "10.0.0.50", 1883);
mqtt.setCallback(mqttCallback);
mqtt.subscribe(PHOTOVOLTAIC_POWER_W);
mqtt.subscribe(PHOTOVOLTAIC_ENERGY_KWH);
mqtt.subscribe(GRID_POWER_W);
mqtt.subscribe(GRID_IMPORT_WH);
mqtt.subscribe(GRID_EXPORT_WH);
} else {
Serial.printf("ERROR: Failed to connect MQTT broker at %s:%d\n", "10.0.0.50", 1883);
mqttDisconnect();
}
}
}
void mqttDisconnect() {
if (mqttConnected) {
mqtt.disconnect();
mqttConnected = false;
photovoltaicPowerW = NAN;
photovoltaicPowerWLast = 0;
gridPowerW = NAN;
gridPowerWLast = 0;
}
}
double getPhotovoltaicPowerW() {
if (millis() - photovoltaicPowerWLast > MQTT_MAX_MESSAGE_AGE_MILLIS) {
return NAN;
}
return photovoltaicPowerW;
}
double getPhotovoltaicEnergyKWh() {
if (millis() - photovoltaicEnergyKWhLast > MQTT_MAX_MESSAGE_AGE_MILLIS) {
return NAN;
}
return photovoltaicEnergyKWh;
}
double getGridPowerW() {
if (millis() - gridPowerWLast > MQTT_MAX_MESSAGE_AGE_MILLIS) {
return NAN;
}
return gridPowerW;
}
double getGridImportKWh() {
if (millis() - gridImportKWhLast > MQTT_MAX_MESSAGE_AGE_MILLIS) {
return NAN;
}
return gridImportKWh;
}
double getGridExportKWh() {
if (millis() - gridExportKWhLast > MQTT_MAX_MESSAGE_AGE_MILLIS) {
return NAN;
}
return gridExportKWh;
}

View File

@ -1,16 +0,0 @@
#ifndef SENSOR2_MQTT_H
#define SENSOR2_MQTT_H
void mqtt_loop();
double getPhotovoltaicPowerW();
double getPhotovoltaicEnergyKWh();
double getGridPowerW();
double getGridImportKWh();
double getGridExportKWh();
#endif

View File

@ -1,52 +0,0 @@
#include <Arduino.h>
#include "serial.h"
#include "display.h"
#include "config.h"
#include "mode.h"
void serial_setup() {
Serial.begin(115200);
Serial.println("\n\n\nStartup!");
}
void serial_loop() {
if (Serial.available()) {
int input = Serial.read();
switch (input) {
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
setMode((ModeId) (input - '0'));
break;
case 'a':
case 'b':
setMode((ModeId) (input - 'a' + 10));
break;
case 'r':
setSpeed(1.0);
break;
case '+':
setBrightness(display.getBrightness() + 10);
break;
case '-':
setBrightness(display.getBrightness() - 10);
break;
case ',':
setSpeed(config.speed / 1.1);
break;
case '.':
setSpeed(config.speed * 1.1);
break;
default:
Serial.printf("Unknown command: %c\n", input);
break;
}
}
}

View File

@ -1,8 +0,0 @@
#ifndef RGBMATRIXDISPLAY_SERIAL_H
#define RGBMATRIXDISPLAY_SERIAL_H
void serial_loop();
void serial_setup();
#endif

View File

@ -1,315 +0,0 @@
#include <WebServer.h>
#include <cmath>
#include "server.h"
#include "mode/Mode.h"
#include "mode.h"
#include "display.h"
#include "config.h"
static const char *const style = R"(
<style>
body {
font-family: sans-serif;
font-size: 8vw;
margin: 0;
}
button.player{
width: 33vmin;
height: 33vmin;
font-size: 9vw;
}
table{
border-collapse: collapse;
}
td{
text-align: center;
}
</style>
)";
static const char *const script = R"(
<script>
function get(path){
var r = new XMLHttpRequest();
r.open("GET", path, true);
r.send();
}
</script>
)";
WebServer server(80);
void web_index();
void web_player();
void web_player_move();
void web_player_fire();
void web_setMode();
void web_brighter();
void web_darker();
void web_faster();
void web_slower();
void web_fps_on();
void web_fps_off();
void web_config_date();
void web_config_save();
void server_setup() {
server.on("/", web_index);
server.on("/player", web_player);
server.on("/player/move", web_player_move);
server.on("/player/fire", web_player_fire);
server.on("/mode", web_setMode);
server.on("/brighter", web_brighter);
server.on("/darker", web_darker);
server.on("/faster", web_faster);
server.on("/slower", web_slower);
server.on("/fps/on", web_fps_on);
server.on("/fps/off", web_fps_off);
server.on("/config/date", web_config_date);
server.on("/config/save", web_config_save);
server.begin();
}
void server_loop() {
server.handleClient();
}
void web_index() {
server.setContentLength(CONTENT_LENGTH_UNKNOWN);
server.send(200, "text/html", "");
server.sendContent(style);
server.sendContent(script);
server.sendContent(R"(<p>)");
server.sendContent(R"(<a href="/player?index=0">Player 0</a><br>)");
server.sendContent(R"(<a href="/player?index=1">Player 1</a><br>)");
server.sendContent(R"(</p>)");
server.sendContent(R"(<p>)");
server.sendContent(R"(<a onclick="get('/mode?mode=0');">NONE</a><br>)");
server.sendContent(R"(<a onclick="get('/mode?mode=1');">BORDER</a><br>)");
server.sendContent(R"(<a onclick="get('/mode?mode=2');">CLOCK</a><br>)");
server.sendContent(R"(<a onclick="get('/mode?mode=3');">GAME_OF_LIFE_BLACK_WHITE</a><br>)");
server.sendContent(R"(<a onclick="get('/mode?mode=4');">GAME_OF_LIFE_GRAYSCALE</a><br>)");
server.sendContent(R"(<a onclick="get('/mode?mode=5');">GAME_OF_LIFE_COLOR_FADE</a><br>)");
server.sendContent(R"(<a onclick="get('/mode?mode=6');">GAME_OF_LIFE_RANDOM_COLOR</a><br>)");
server.sendContent(R"(<a onclick="get('/mode?mode=7');">PONG</a><br>)");
server.sendContent(R"(<a onclick="get('/mode?mode=8');">SPACE_INVADERS</a><br>)");
server.sendContent(R"(<a onclick="get('/mode?mode=9');">COUNT_DOWN</a><br>)");
server.sendContent(R"(<a onclick="get('/mode?mode=10');">COUNT_DOWN_BARS</a><br>)");
server.sendContent(R"(<a onclick="get('/mode?mode=11');">COUNT_DOWN_SLEEP</a><br>)");
server.sendContent(R"(<a onclick="get('/mode?mode=12');">STARFIELD</a><br>)");
server.sendContent(R"(<a onclick="get('/mode?mode=13');">MATRIX</a><br>)");
server.sendContent(R"(<a onclick="get('/mode?mode=14');">POWER</a><br>)");
server.sendContent(R"(<a onclick="get('/mode?mode=15');">ENERGY</a><br>)");
server.sendContent(R"(<a onclick="get('/mode?mode=16');">TIMER</a><br>)");
server.sendContent(R"(</p>)");
server.sendContent(R"(<p>)");
server.sendContent(R"(Helligkeit: <a onclick="get('/brighter');">+</a> / <a onclick="get('/darker');">-</a><br>)");
server.sendContent(R"(Geschwindigkeit: <a onclick="get('/faster');">+</a> / <a onclick="get('/slower');">-</a><br>)");
server.sendContent(R"(FPS: <a onclick="get('/fps/on');">EIN</a> / <a onclick="get('/fps/off');">AUS</a><br>)");
server.sendContent(R"(</p>)");
server.sendContent(R"(<p>)");
server.sendContent(R"(<input type="number" min="1900" max="3000" step="1" name="year" id="year">)");
server.sendContent(R"(<input type="number" min="1" max="12" step="1" name="month" id="month">)");
server.sendContent(R"(<input type="number" min="1" max="31" step="1" name="day" id="day">)");
server.sendContent(R"(<button onclick="get('/config/date?year=' + document.getElementById('year').value + '&month=' + document.getElementById('month').value + '&day=' + document.getElementById('day').value);">Datum setzen</button>)");
server.sendContent(R"(</p>)");
server.sendContent(R"(<p>)");
server.sendContent(R"(<button onclick="get('/config/save');">Speichern erzwingen</button>)");
server.sendContent(R"(</p>)");
server.client().flush();
}
void web_player() {
char buffer[128];
if (!server.hasArg("index")) {
server.send(400, "text/plain", "Missing 'index'");
return;
}
double value = strtod(server.arg("index").c_str(), nullptr);
int index = (int) value;
server.setContentLength(CONTENT_LENGTH_UNKNOWN);
server.send(200, "text/html", "");
server.sendContent(style);
server.sendContent(script);
server.sendContent(R"(<meta name="viewport" content= "width=device-width, user-scalable=no">)");
server.sendContent(R"(<table>)");
server.sendContent(R"(<tr>)");
server.sendContent(R"(<td><a href='/'>&larr;</td>)");
server.sendContent(R"(<td>)");
snprintf(buffer, sizeof buffer, R"(<button class="player" onclick="get('/player/move?index=%d&x=0&y=-1');">&uarr;</button><br>)", index);
server.sendContent(buffer);
server.sendContent(R"(</td>)");
server.sendContent(R"(<td>&nbsp;</td>)");
server.sendContent(R"(</tr>)");
server.sendContent(R"(<tr>)");
server.sendContent(R"(<td>)");
snprintf(buffer, sizeof buffer, R"(<button class="player" onclick="get('/player/move?index=%d&x=-1&y=0');">&larr;</button><br>)", index);
server.sendContent(buffer);
server.sendContent(R"(</td>)");
server.sendContent(R"(<td>)");
snprintf(buffer, sizeof buffer, R"(<button class="player" onclick="get('/player/fire?index=%d');">X</button><br>)", index);
server.sendContent(buffer);
server.sendContent(R"(</td>)");
server.sendContent(R"(<td>)");
snprintf(buffer, sizeof buffer, R"(<button class="player" onclick="get('/player/move?index=%d&x=+1&y=0');">&rarr;</button><br>)", index);
server.sendContent(buffer);
server.sendContent(R"(</td>)");
server.sendContent(R"(</tr>)");
server.sendContent(R"(<tr>)");
server.sendContent(R"(<td>&nbsp;</td>)");
server.sendContent(R"(<td>)");
snprintf(buffer, sizeof buffer, R"(<button class="player" onclick="get('/player/move?index=%d&x=0&y=+1');">&darr;</button><br>)", index);
server.sendContent(buffer);
server.sendContent(R"(</td>)");
server.sendContent(R"(<td>&nbsp;</td>)");
server.sendContent(R"(</tr>)");
server.sendContent(R"(</table>)");
server.client().flush();
}
void web_player_move() {
double value;
if (!server.hasArg("index")) {
server.send(400, "text/plain", "Missing 'index'");
return;
}
value = strtod(server.arg("index").c_str(), nullptr);
int index = (int) value;
if (!server.hasArg("x")) {
server.send(400, "text/plain", "Missing 'x'");
return;
}
value = strtod(server.arg("x").c_str(), nullptr);
int x = (int) value;
if (!server.hasArg("y")) {
server.send(400, "text/plain", "Missing 'y'");
return;
}
value = strtod(server.arg("y").c_str(), nullptr);
int y = (int) value;
modeMove(index, x, y);
server.send(200, "application/json", "true");
}
void web_player_fire() {
double value;
if (!server.hasArg("index")) {
server.send(400, "text/plain", "Missing 'index'");
return;
}
value = strtod(server.arg("index").c_str(), nullptr);
int index = (int) value;
modeFire(index);
server.send(200, "application/json", "true");
}
void web_setMode() {
if (!server.hasArg("mode")) {
server.send(400, "text/plain", "Missing 'mode'");
return;
}
double value = strtod(server.arg("mode").c_str(), nullptr);
if (isnan(value)) {
server.send(400, "text/plain", "'mode' not a number");
return;
}
setMode((ModeId) value);
server.send(200);
}
void web_brighter() {
setBrightness(display.getBrightness() + 10);
server.send(200);
}
void web_darker() {
setBrightness(display.getBrightness() - 10);
server.send(200);
}
void web_faster() {
setSpeed(config.speed * 1.1);
server.send(200);
}
void web_slower() {
setSpeed(config.speed / 1.1);
server.send(200);
}
void web_fps_on() {
display.fpsShow = true;
server.send(200);
}
void web_fps_off() {
display.fpsShow = false;
server.send(200);
}
void web_config_save() {
configSaveNowIfDirty();
server.send(200);
}
void web_config_date() {
double year = strtod(server.arg("year").c_str(), nullptr);
double month = strtod(server.arg("month").c_str(), nullptr);
double day = strtod(server.arg("day").c_str(), nullptr);
if (!isnan(year)) {
config.date.tm_year = (int) year;
config.date.tm_mon = (int) month;
config.date.tm_mday = (int) day;
config.date.tm_hour = 0;
config.date.tm_min = 0;
config.date.tm_sec = 0;
server.send(200);
}
server.send(400);
}

View File

@ -1,8 +0,0 @@
#ifndef RGBMATRIXDISPLAY_SERVER_H
#define RGBMATRIXDISPLAY_SERVER_H
void server_setup();
void server_loop();
#endif

View File

@ -1,104 +0,0 @@
#include "wifi.h"
#include "display.h"
#include "mqtt.h"
#include <WiFi.h>
#include <ArduinoOTA.h>
#include <esp_sntp.h>
bool wifiConnected = false;
void onConnect();
uint32_t ip2int(const IPAddress &ip);
void timeSyncCallback(struct timeval *tv);
char *calculateGateway(char *calculatedGateway, size_t size);
void wifi_setup() {
WiFiClass::setHostname("RGBMatrixDisplay");
WiFi.begin("HappyNet", "1Grausame!Sackratte7");
yield();
ArduinoOTA.onStart([]() {
Serial.print("\n\nOTA Update: ");
display.clear();
display.loop();
});
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
double ratio = (double) progress / (double) total;
Serial.printf("\rOTA Update: %3.0f%%", ratio * 100);
auto index = (uint16_t) round(ratio * (double) display.pixelCount);
auto color = (uint8_t) round(ratio * 255.0);
display.set(index, {(uint8_t) (255 - color), color, 0});
display.loop();
});
ArduinoOTA.onEnd([]() {
Serial.println("\nOTA Success!\n");
display.clear();
display.loop();
});
ArduinoOTA.onError([](int error) {
Serial.println("\nOTA Failure!\n");
display.clear();
display.loop();
});
ArduinoOTA.begin();
yield();
}
void wifi_loop() {
ArduinoOTA.handle();
bool hasIp = (uint32_t) WiFi.localIP() != 0;
if (!wifiConnected) {
if (hasIp) {
wifiConnected = true;
onConnect();
}
} else {
if (!hasIp) {
wifiConnected = false;
Serial.println("WiFi disconnected!");
}
}
}
void onConnect() {
Serial.printf("WiFi connected: %s\n", WiFi.localIP().toString().c_str());
char calculatedGateway[16] = {0};
calculateGateway(calculatedGateway, sizeof(calculatedGateway));
sntp_set_time_sync_notification_cb(timeSyncCallback);
Serial.printf("configTime(%s / %s / %s)\n", WiFi.gatewayIP().toString().c_str(), calculatedGateway, "pool.ntp.org");
configTime(3600, 3600, "pool.ntp.org", WiFi.gatewayIP().toString().c_str(), calculatedGateway);
yield();
}
bool wifiIsConnected() {
return wifiConnected;
}
char *calculateGateway(char *calculatedGateway, size_t size) {
uint32_t local = ip2int(WiFi.localIP());
uint32_t netmask = ip2int(WiFi.subnetMask());
uint32_t gateway = local & netmask + 1;
snprintf(
calculatedGateway,
size,
"%u.%u.%u.%u",
(gateway >> 24) & 0xFF,
(gateway >> 16) & 0xFF,
(gateway >> 8) & 0xFF,
(gateway >> 0) & 0xFF
);
return calculatedGateway;
}
uint32_t ip2int(const IPAddress &ip) {
return ((ip[0] * 256 + ip[1]) * 256 + ip[2]) * 256 + ip[3];
}
void timeSyncCallback(struct timeval *tv) {
Serial.printf("timeSyncCallback: %ld\n", tv->tv_sec);
}

View File

@ -1,10 +0,0 @@
#ifndef RGBMATRIXDISPLAY_WIFI_H
#define RGBMATRIXDISPLAY_WIFI_H
void wifi_setup();
void wifi_loop();
bool wifiIsConnected();
#endif