http hooks for targetEpochSeconds, mode_speed, config.write, brightness, player_move, player_fire, display segments font style

This commit is contained in:
Patrick Haßel 2025-01-25 19:26:21 +01:00
parent 0b891d9604
commit 4efc101e56
11 changed files with 257 additions and 50 deletions

View File

@ -37,6 +37,16 @@ function get(path){
r.open("GET", path, true); r.open("GET", path, true);
r.send(); r.send();
} }
function configDate(){
const year = document.getElementById('year').value;
const month = document.getElementById('month').value - 1;
const day = document.getElementById('day').value;
const hour = document.getElementById('hour').value;
const minute = document.getElementById('minute').value;
const second = document.getElementById('second').value;
const targetEpochSeconds = (new Date(year, month, day, hour, minute, second).getTime() / 1000).toFixed(0);
get('/config/date?targetEpochSeconds=' + targetEpochSeconds);
}
</script> </script>
)"; )";
@ -81,14 +91,16 @@ inline void httpIndex(AsyncWebServerRequest *request) {
response->print(R"(<p>)"); response->print(R"(<p>)");
response->print(R"(Helligkeit: <a onclick="get('/brighter');">+</a> / <a onclick="get('/darker');">-</a><br>)"); response->print(R"(Helligkeit: <a onclick="get('/brighter');">+</a> / <a onclick="get('/darker');">-</a><br>)");
response->print(R"(Geschwindigkeit: <a onclick="get('/faster');">+</a> / <a onclick="get('/slower');">-</a><br>)"); response->print(R"(Geschwindigkeit: <a onclick="get('/faster');">+</a> / <a onclick="get('/slower');">-</a><br>)");
response->print(R"(FPS: <a onclick="get('/fps/on');">EIN</a> / <a onclick="get('/fps/off');">AUS</a><br>)");
response->print(R"(</p>)"); response->print(R"(</p>)");
response->print(R"(<p>)"); response->print(R"(<p>)");
response->print(R"(<input type="number" min="1900" max="3000" step="1" name="year" id="year">)"); response->printf(R"(<input type="number" min="2025" max="3000" step="1" id="year">)");
response->print(R"(<input type="number" min="1" max="12" step="1" name="month" id="month">)"); response->printf(R"(<input type="number" min="1" max="12" step="1" id="month">)");
response->print(R"(<input type="number" min="1" max="31" step="1" name="day" id="day">)"); response->printf(R"(<input type="number" min="1" max="31" step="1" id="day">)");
response->print(R"(<button onclick="get('/config/date?year=' + document.getElementById('year').value + '&month=' + document.getElementById('month').value + '&day=' + document.getElementById('day').value);">Datum setzen</button>)"); response->printf(R"(<input type="number" min="0" max="23" step="1" id="hour">)");
response->printf(R"(<input type="number" min="0" max="59" step="1" id="minute">)");
response->printf(R"(<input type="number" min="0" max="59" step="1" id="second">)");
response->print(R"(<button onclick="configDate();">Datum setzen</button>)");
response->print(R"(</p>)"); response->print(R"(</p>)");
response->print(R"(<p>)"); response->print(R"(<p>)");
@ -98,6 +110,148 @@ inline void httpIndex(AsyncWebServerRequest *request) {
request->send(response); request->send(response);
} }
inline void web_player(AsyncWebServerRequest *request) {
char buffer[128];
if (!request->hasParam("index")) {
request->send(400, "text/plain", "Missing 'index'");
return;
}
double value = request->getParam("index")->value().toDouble();
int index = (int) value;
auto *response = request->beginResponseStream("text/html");
response->print(style);
response->print(script);
response->print(R"(<meta name="viewport" content= "width=device-width, user-scalable=no">)");
response->print(R"(<table>)");
response->print(R"(<tr>)");
response->print(R"(<td><a href='/'>&larr;</td>)");
response->print(R"(<td>)");
snprintf(buffer, sizeof buffer, R"(<button class="player" onclick="get('/player/move?index=%d&x=0&y=-1');">&uarr;</button><br>)", index);
response->print(buffer);
response->print(R"(</td>)");
response->print(R"(<td>&nbsp;</td>)");
response->print(R"(</tr>)");
response->print(R"(<tr>)");
response->print(R"(<td>)");
snprintf(buffer, sizeof buffer, R"(<button class="player" onclick="get('/player/move?index=%d&x=-1&y=0');">&larr;</button><br>)", index);
response->print(buffer);
response->print(R"(</td>)");
response->print(R"(<td>)");
snprintf(buffer, sizeof buffer, R"(<button class="player" onclick="get('/player/fire?index=%d');">X</button><br>)", index);
response->print(buffer);
response->print(R"(</td>)");
response->print(R"(<td>)");
snprintf(buffer, sizeof buffer, R"(<button class="player" onclick="get('/player/move?index=%d&x=+1&y=0');">&rarr;</button><br>)", index);
response->print(buffer);
response->print(R"(</td>)");
response->print(R"(</tr>)");
response->print(R"(<tr>)");
response->print(R"(<td>&nbsp;</td>)");
response->print(R"(<td>)");
snprintf(buffer, sizeof buffer, R"(<button class="player" onclick="get('/player/move?index=%d&x=0&y=+1');">&darr;</button><br>)", index);
response->print(buffer);
response->print(R"(</td>)");
response->print(R"(<td>&nbsp;</td>)");
response->print(R"(</tr>)");
response->print(R"(</table>)");
request->send(response);
}
inline void web_player_move(AsyncWebServerRequest *request) {
double value;
if (!request->hasParam("index")) {
request->send(400, "text/plain", "Missing 'index'");
return;
}
value = request->getParam("index")->value().toDouble();
int index = (int) value;
if (!request->hasParam("x")) {
request->send(400, "text/plain", "Missing 'x'");
return;
}
value = request->getParam("x")->value().toDouble();
int x = (int) value;
if (!request->hasParam("y")) {
request->send(400, "text/plain", "Missing 'y'");
return;
}
value = request->getParam("y")->value().toDouble();
int y = (int) value;
modeMove(index, x, y);
request->send(200, "application/json", "true");
}
inline void web_player_fire(AsyncWebServerRequest *request) {
double value;
if (!request->hasParam("index")) {
request->send(400, "text/plain", "Missing 'index'");
return;
}
value = request->getParam("index")->value().toDouble();
int index = (int) value;
modeFire(index);
request->send(200, "application/json", "true");
}
inline void web_setMode(AsyncWebServerRequest *request) {
if (!request->hasParam("mode")) {
request->send(400, "text/plain", "Missing 'mode'");
return;
}
double value = request->getParam("mode")->value().toDouble();
if (isnan(value)) {
request->send(400, "text/plain", "'mode' not a number");
return;
}
setMode((ModeId) value);
request->send(200);
}
inline void web_brighter(AsyncWebServerRequest *request) {
const auto newBrightness = display.getBrightness() + 10;
display.setBrightness(newBrightness >= 255 ? 255 : newBrightness);
request->send(200);
}
inline void web_darker(AsyncWebServerRequest *request) {
const auto newBrightness = display.getBrightness() - 10;
display.setBrightness(newBrightness <= 0 ? 0 : newBrightness);
request->send(200);
}
inline void web_faster(AsyncWebServerRequest *request) {
setSpeed(getSpeed() * 1.1);
request->send(200);
}
inline void web_slower(AsyncWebServerRequest *request) {
setSpeed(getSpeed() / 1.1);
request->send(200);
}
inline void web_config_save(AsyncWebServerRequest *request) {
config.write();
request->send(200);
}
inline void web_config_date(AsyncWebServerRequest *request) {
const auto targetEpochSeconds = std::stoul(request->getParam("targetEpochSeconds")->value().c_str());
config.set("targetEpochSeconds", targetEpochSeconds);
modeLoadConfig();
request->send(200);
}
class Node final : public PatrixNode { class Node final : public PatrixNode {
public: public:
@ -110,7 +264,16 @@ public:
modeSetup(); modeSetup();
server.on("/", httpIndex); server.on("/", httpIndex);
server.on("/player", web_player);
server.on("/player/move", web_player_move);
server.on("/player/fire", web_player_fire);
server.on("/mode", httpMode); server.on("/mode", httpMode);
server.on("/brighter", web_brighter);
server.on("/darker", web_darker);
server.on("/faster", web_faster);
server.on("/slower", web_slower);
server.on("/config/date", web_config_date);
server.on("/config/save", web_config_save);
display.setup(); display.setup();
display.setBrightness(10); display.setBrightness(10);

View File

@ -12,8 +12,6 @@
#include "mode/Energy/Energy.h" #include "mode/Energy/Energy.h"
#include "mode/Timer/Timer.h" #include "mode/Timer/Timer.h"
#include <patrix/core/Config.h>
Config config("/main.json"); Config config("/main.json");
auto current = NONE; auto current = NONE;
@ -51,11 +49,21 @@ void modeMqttMessage(const char *topic, const char *message) {
} }
} }
void modeLoadConfig() {
if (mode != nullptr) {
mode->loadConfig();
}
}
void setMode(const ModeId newMode) { void setMode(const ModeId newMode) {
config.setIfNot("mode", newMode); config.setIfNot("mode", newMode);
wanted = newMode; wanted = newMode;
} }
double getSpeed() {
return modeSpeed;
}
void setSpeed(const double newSpeed) { void setSpeed(const double newSpeed) {
modeSpeed = min(max(0.01, newSpeed), 10000.0); modeSpeed = min(max(0.01, newSpeed), 10000.0);
config.setIfNot("speed", modeSpeed); config.setIfNot("speed", modeSpeed);

View File

@ -1,14 +1,19 @@
#ifndef RGB_MATRIX_DISPLAY_MODE_H #ifndef RGB_MATRIX_DISPLAY_MODE_H
#define RGB_MATRIX_DISPLAY_MODE_H #define RGB_MATRIX_DISPLAY_MODE_H
#include <patrix/core/Config.h>
#include "mode/Mode.h" #include "mode/Mode.h"
extern Config config;
void modeSetup(); void modeSetup();
void modeLoop(Display& display); void modeLoop(Display& display);
void setMode(ModeId newMode); void setMode(ModeId newMode);
double getSpeed();
void setSpeed(double newSpeed); void setSpeed(double newSpeed);
void modeMove(int index, int x, int y); void modeMove(int index, int x, int y);
@ -17,4 +22,6 @@ void modeFire(int index);
void modeMqttMessage(const char *topic, const char *message); void modeMqttMessage(const char *topic, const char *message);
void modeLoadConfig();
#endif #endif

View File

@ -18,7 +18,7 @@ public:
protected: protected:
void draw(Display& display) override { void draw(Display& display) override {
display.foreground = White; display.clear();
display.drawLineWH(0, 0, display.width, 0, 1); display.drawLineWH(0, 0, display.width, 0, 1);
display.drawLineWH(0, 0, 0, display.height, 1); display.drawLineWH(0, 0, 0, display.height, 1);
display.drawLineWH(display.width - 1, display.height - 1, -display.width, 0, 1); display.drawLineWH(display.width - 1, display.height - 1, -display.width, 0, 1);

View File

@ -27,11 +27,9 @@ protected:
void draw(Display& display) override { void draw(Display& display) override {
display.clear(); display.clear();
display.foreground = White;
display.background = Transparent;
display.cursorX = 2; display.cursorX = 2;
display.cursorY = 1; display.cursorY = 1;
display.printf("%2d:%02d:%02d", now.tm_hour, now.tm_min, now.tm_sec); display.printf(true, "%2d:%02d:%02d", now.tm_hour, now.tm_min, now.tm_sec);
} }
}; };

View File

@ -12,6 +12,8 @@ class CountDown : public Mode {
Firework fireworks[MAX_FIREWORKS]; Firework fireworks[MAX_FIREWORKS];
time_t targetEpochSeconds = 0;
tm target{}; tm target{};
uint16_t days = 0; uint16_t days = 0;
@ -43,6 +45,10 @@ public:
return "CountDown (Numbers)"; return "CountDown (Numbers)";
} }
void start() override {
targetEpochSeconds = config.get("targetEpochSeconds", 1767222000);
}
protected: protected:
void step(const microseconds_t microseconds) override { void step(const microseconds_t microseconds) override {
@ -59,22 +65,18 @@ protected:
return; return;
} }
// GRRRRRRR... localtime_r(&targetEpochSeconds, &target);
target.tm_year -= 1900;
target.tm_mon -= 1;
const auto dateEpochSeconds = mktime(&target);
target.tm_year += 1900; target.tm_year += 1900;
target.tm_mon += 1; target.tm_mon += 1;
// ---
const auto diffSeconds = difftime(dateEpochSeconds, nowEpochSeconds); const auto diffSeconds = difftime(targetEpochSeconds, nowEpochSeconds);
days = static_cast<int>(floor(diffSeconds / (24 * 60 * 60))); days = static_cast<int>(floor(diffSeconds / (24 * 60 * 60)));
hours = static_cast<int>(floor(diffSeconds / (60 * 60))) % 24; hours = static_cast<int>(floor(diffSeconds / (60 * 60))) % 24;
minutes = static_cast<int>(floor(diffSeconds / 60)) % 60; minutes = static_cast<int>(floor(diffSeconds / 60)) % 60;
seconds = static_cast<int>(diffSeconds) % 60; seconds = static_cast<int>(diffSeconds) % 60;
// Serial.printf("now=%4d.%02d.%02d (%ld), conf=%4d.%02d.%02d (%ld), diff=%f, %dd %2d:%02d:%02d\n", // Serial.printf("now=%4d.%02d.%02d (%ld), conf=%4d.%02d.%02d (%ld), diff=%f, %dd %2d:%02d:%02d\n",
// now.tm_year, now.tm_mon, now.tm_mday, nowEpochSeconds, // now.tm_year, now.tm_mon, now.tm_mday, nowEpochSeconds,
// target.tm_year, target.tm_mon, target.tm_mday, dateEpochSeconds, // target.tm_year, target.tm_mon, target.tm_mday, targetEpochSeconds,
// diffSeconds, // diffSeconds,
// days, hours, minutes, seconds); // days, hours, minutes, seconds);
setMode(COUNTDOWN); setMode(COUNTDOWN);
@ -146,7 +148,7 @@ private:
void drawSleepingCount(Display& display) const { void drawSleepingCount(Display& display) const {
const auto sleepCount = days + 1; const auto sleepCount = days + 1;
display.printf("%02d Tag%s", sleepCount, sleepCount == 1 ? "" : "e"); display.printf(true, "%3d TAG%s", sleepCount, sleepCount == 1 ? "" : "E");
} }
void drawCountdownBars(Display& display) const { void drawCountdownBars(Display& display) const {
@ -177,10 +179,10 @@ private:
void drawCountdownNumbers(Display& display) const { void drawCountdownNumbers(Display& display) const {
if (days > 0) { if (days > 0) {
display.printf("%02d. %2d:%02d", days, hours, minutes); display.printf(true, "%02d. %2d:%02d", days, hours, minutes);
drawSecondsBar(display, seconds); drawSecondsBar(display, seconds);
} else { } else {
display.printf("%2d:%02d:%02d", hours, minutes, seconds); display.printf(true, "%2d:%02d:%02d", hours, minutes, seconds);
drawSubSecondsBar(display); drawSubSecondsBar(display);
} }
} }
@ -209,11 +211,11 @@ private:
void drawYear(Display& display, const int year) const { void drawYear(Display& display, const int year) const {
if (plus1DayForSleepingCount) { if (plus1DayForSleepingCount) {
display.printf("Emil 5"); display.printf(true, "Emil 5");
display.cursorX = 32 - 8; display.cursorX = 32 - 8;
display.cursorY = 0; display.cursorY = 0;
} else { } else {
display.printf("%5d", year); display.printf(true, "%5d", year);
} }
} }

View File

@ -76,19 +76,19 @@ protected:
display.cursorY = 1; display.cursorY = 1;
if (page == 0) { if (page == 0) {
display.foreground = Green; display.foreground = Green;
display.printf("%3.0f€", costSaved); display.printf(true, "%3.0f€", costSaved);
display.foreground = White; display.foreground = White;
display.printf(" %3.0f%%", amortisationPercent); display.printf(true, " %3.0f%%", amortisationPercent);
} else if (page == 1) { } else if (page == 1) {
display.foreground = Blue; display.foreground = Blue;
display.printf("%3.0f", photovoltaicEnergyKWh); display.printf(true, "%3.0f", photovoltaicEnergyKWh);
display.foreground = Green; display.foreground = Green;
display.printf(" %3.0f", selfConsumedKWh); display.printf(true, " %3.0f", selfConsumedKWh);
} else { } else {
display.foreground = Yellow; display.foreground = Yellow;
display.printf("%4.0f", gridImportKWh); display.printf(true, "%4.0f", gridImportKWh);
display.foreground = Magenta; display.foreground = Magenta;
display.printf(" %3.0f", gridExportKWh); display.printf(true, " %3.0f", gridExportKWh);
} }
} }

View File

@ -105,6 +105,8 @@ public:
virtual void mqttMessage(const String& topic, const String& message) {} virtual void mqttMessage(const String& topic, const String& message) {}
virtual void loadConfig() {}
void loop(const microseconds_t microseconds) { void loop(const microseconds_t microseconds) {
handleRealtime(); handleRealtime();
handleTimers(microseconds); handleTimers(microseconds);

View File

@ -95,10 +95,10 @@ protected:
switch (status) { switch (status) {
case SCORE: case SCORE:
display.foreground = Green; display.foreground = Green;
display.printf("%d", player0.score); display.printf(true, "%d", player0.score);
display.foreground = Red; display.foreground = Red;
display.printf("%5d", player1.score); display.printf(true, "%5d", player1.score);
break; break;
case PLAY: case PLAY:
for (auto i = 0; i < player0.size; ++i) { for (auto i = 0; i < player0.size; ++i) {
@ -112,16 +112,16 @@ protected:
case OVER: case OVER:
if (player0.score > player1.score) { if (player0.score > player1.score) {
display.foreground = Green; display.foreground = Green;
display.printf("W", player0.score); display.printf(true, "W", player0.score);
display.foreground = Red; display.foreground = Red;
display.printf(" L", player1.score); display.printf(true, " L", player1.score);
} else if (player0.score < player1.score) { } else if (player0.score < player1.score) {
display.foreground = Red; display.foreground = Red;
display.printf("L", player0.score); display.printf(true, "L", player0.score);
display.foreground = Green; display.foreground = Green;
display.printf(" W", player1.score); display.printf(true, " W", player1.score);
} }
break; break;
} }

View File

@ -59,10 +59,10 @@ protected:
display.clear(); display.clear();
display.foreground = photovoltaicPowerW >= 100 ? Green : photovoltaicPowerW >= 20 ? Yellow : Red; display.foreground = photovoltaicPowerW >= 100 ? Green : photovoltaicPowerW >= 20 ? Yellow : Red;
display.printf("%3.0f", photovoltaicPowerW); display.printf(true, "%3.0f", photovoltaicPowerW);
display.foreground = gridPowerW >= 20 ? Yellow : gridPowerW >= -20 ? Green : Magenta; display.foreground = gridPowerW >= 20 ? Yellow : gridPowerW >= -20 ? Green : Magenta;
display.printf(" %4.0f", gridPowerW); display.printf(true, " %4.0f", gridPowerW);
} }
}; };

View File

@ -3,9 +3,15 @@
#include "mode/Mode.h" #include "mode/Mode.h"
#define DEFAULT_DURATION_MILLIS (6 * 60 * 1000L)
class Timer2 final : public Mode { class Timer2 final : public Mode {
const unsigned long targetMillis = millis() + 6 * 60 * 1000; long durationMillis = DEFAULT_DURATION_MILLIS;
long restMillis = durationMillis;
unsigned long lastMillis = 0;
uint16_t days = 0; uint16_t days = 0;
@ -15,8 +21,6 @@ class Timer2 final : public Mode {
uint16_t seconds = 0; uint16_t seconds = 0;
unsigned long diffSeconds = 0;
public: public:
explicit Timer2(Display& display) : Mode(display) { explicit Timer2(Display& display) : Mode(display) {
@ -27,30 +31,53 @@ public:
return "Timer"; return "Timer";
} }
void loadConfig() override {
const auto newDurationMillis = config.get("durationMillis", DEFAULT_DURATION_MILLIS);
if (restMillis > 0) {
restMillis += newDurationMillis - durationMillis;
}
durationMillis = newDurationMillis;
}
void start() override {
restMillis = durationMillis;
lastMillis = millis();
}
protected: protected:
void step(microseconds_t microseconds) override { void step(microseconds_t microseconds) override {
const auto now = millis(); const auto now = millis();
diffSeconds = now >= targetMillis ? 0 : (targetMillis - now) / 1000; const auto deltaMillis = now - lastMillis;
days = diffSeconds / (24 * 60 * 60); lastMillis = now;
hours = diffSeconds / (60 * 60) % 24;
minutes = diffSeconds / 60 % 60; restMillis -= static_cast<long>(deltaMillis);
seconds = diffSeconds % 60; if (restMillis < 0) {
restMillis = 0;
}
const auto restSeconds = restMillis / 1000;
days = restSeconds / (24 * 60 * 60);
hours = restSeconds / (60 * 60) % 24;
minutes = restSeconds / 60 % 60;
seconds = restSeconds % 60;
markDirty(); markDirty();
} }
void draw(Display& display) override { void draw(Display& display) override {
display.clear(); display.clear();
display.cursorX = 1;
display.cursorY = 1;
if (days > 1) { if (days > 1) {
display.printf("%4d Tage", days); display.printf(true, "%4d TAGE", days);
} else if (days > 0) { } else if (days > 0) {
display.printf("%2d. %02d:%02d", days, hours, minutes); display.printf(true, "%2d. %02d:%02d", days, hours, minutes);
} else if (hours > 0) { } else if (hours > 0) {
display.printf("%2d:%02d:%02d", hours, minutes, seconds); display.printf(true, "%2d:%02d:%02d", hours, minutes, seconds);
} else if (minutes > 0) { } else if (minutes > 0) {
display.printf("%2d:%02d", minutes, seconds); display.printf(true, "%2d:%02d", minutes, seconds);
} else { } else {
display.printf("%2d", seconds); display.printf(true, "%2d", seconds);
} }
} }