merged AppMatch.State.MINUTES/SECONDS to RUNNING + web-control for configMillis + re-implemented websocketEvent + configWrite delay

This commit is contained in:
Patrick Haßel 2025-01-10 14:51:33 +01:00
parent 66a8474fca
commit b7c2899f3f
5 changed files with 153 additions and 110 deletions

View File

@ -131,7 +131,7 @@
segment.setAttribute("y", y + "vw");
segment.setAttribute("width", S + "vw");
segment.setAttribute("height", S + "vw");
segment.setAttribute("stroke", "magenta");
segment.setAttribute("stroke", "white");
segment.setAttribute("fill", "none");
segment.setAttribute("id", "segment" + segments.length);
display.appendChild(segment);
@ -155,9 +155,7 @@
function connect() {
console.log("connecting websocket...");
const url2 = url("ws", "/ws");
console.log(url2);
const socket = new WebSocket(url2);
const socket = new WebSocket(url("ws", "/ws"));
socket.timeout = 1;
socket.addEventListener('open', _ => {
console.log('websocket connected');
@ -183,14 +181,14 @@
console.log('websocket disconnected');
segments.forEach(segment => {
segment.setAttribute("fill", "none");
segment.setAttribute("stroke", "magenta");
segment.setAttribute("stroke", "white");
});
connect();
setTimeout(connect, 1000);
});
}
drawDisplay(1, 1);
connect();
setTimeout(connect, 1000);
</script>

View File

@ -6,6 +6,8 @@
#include <core/log.h>
#include <display/Display.h>
#define CONFIG_WRITE_DELAY_MILLIS (30 * 1000)
class App {
const char *name;
@ -16,6 +18,8 @@ class App {
JsonObject config = configJsonDoc.to<JsonObject>();
unsigned long configDirty = 0;
bool dirty = true;
bool doForceNextHexBuffer = true;
@ -36,7 +40,6 @@ public:
void start() {
configRead();
_start();
markDirty();
}
void loop() {
@ -45,6 +48,10 @@ public:
const auto dtMillis = now - lastMillis;
lastMillis = now;
_loop(dtMillis);
if (configDirty != 0 && millis() - configDirty > CONFIG_WRITE_DELAY_MILLIS) {
configDirty = 0;
configWrite();
}
if (dirty) {
dirty = false;
draw();
@ -98,6 +105,7 @@ protected:
template<typename T>
void configSet(const char *key, T value) {
config[key] = value;
configDirty = max(1UL, millis());
}
virtual void _start() {
@ -116,7 +124,7 @@ protected:
//
}
void markDirty(const bool forceNextHexBuffer = false) {
void markDirty(const bool forceNextHexBuffer) {
dirty = true;
doForceNextHexBuffer = forceNextHexBuffer;
}

View File

@ -5,13 +5,23 @@
#include "App.h"
#define MS_PER_SEC (1000L)
#define APP_MATCH_NAME "match"
#define MILLIS_STEP_UP_DOWN (60 * MS_PER_SEC)
#define MILLIS_STEP_LEFT_RIGHT (MS_PER_SEC)
#define MILLIS_MIN (MS_PER_SEC)
#define MILLIS_MAX ((99 * 60 + 59) * MS_PER_SEC)
#define CONFIG_SECONDS_KEY "seconds"
#define CONFIG_SECONDS_DEFAULT (6 * 60)
#define CONFIG_CENTIS_KEY "centis"
#define CONFIG_CENTIS_DEFAULT (6 * 60)
#define CONFIG_CENTIS_DEFAULT false
enum State {
INITIAL, RUNNING, PAUSE, END
};
class AppMatch final : public App {
@ -39,11 +49,7 @@ class AppMatch final : public App {
unsigned long updateSeconds = totalSeconds;
enum State {
PAUSE, MINUTES, SECONDS, END
};
State state = PAUSE;
State state = INITIAL;
public:
@ -55,11 +61,10 @@ public:
bool setConfig(const String& key, const String& valueStr) override {
if (key.equals(CONFIG_SECONDS_KEY)) {
const auto seconds = valueStr.toInt();
const auto newMillis = seconds * 1000;
const auto newMillis = seconds * MS_PER_SEC;
if (seconds > 0 && configMillis != newMillis) {
configMillis = newMillis;
configSet(CONFIG_SECONDS_KEY, seconds);
configWrite();
return true;
}
} else if (key.equals(CONFIG_CENTIS_KEY)) {
@ -67,50 +72,71 @@ public:
if (configCentis != newCentis) {
configCentis = newCentis;
configSet(CONFIG_CENTIS_KEY, newCentis);
configWrite();
return true;
}
}
return false;
}
void up() override {
addConfigMillis(MILLIS_STEP_UP_DOWN);
}
void down() override {
addConfigMillis(-MILLIS_STEP_UP_DOWN);
}
void right() override {
addConfigMillis(MILLIS_STEP_LEFT_RIGHT);
}
void left() override {
addConfigMillis(-MILLIS_STEP_LEFT_RIGHT);
}
void confirm() override {
if (state == END) {
return;
}
switch (state) {
case INITIAL:
case PAUSE:
setState(RUNNING);
break;
case RUNNING:
setState(PAUSE);
break;
case END:
break;
}
}
void cancel() override {
if (state == PAUSE || state == END) {
_start();
}
}
protected:
void _start() override {
configMillis = configGet<unsigned long>(CONFIG_SECONDS_KEY, CONFIG_SECONDS_DEFAULT) * 1000;
configMillis = configGet<unsigned long>(CONFIG_SECONDS_KEY, CONFIG_SECONDS_DEFAULT) * MS_PER_SEC;
configCentis = configGet<bool>(CONFIG_CENTIS_KEY, CONFIG_CENTIS_DEFAULT);
info("config:");
info(" seconds = %ld", configMillis / 1000);
info(" seconds = %ld", configMillis / MS_PER_SEC);
info(" centis = %s", configCentis ? "true" : "false");
totalMillis = configMillis;
setState(PAUSE, true);
setTotalMillis(configMillis);
setState(INITIAL, true);
}
void _loop(const unsigned long dtMillis) override {
if (state != PAUSE) {
if (state == RUNNING) {
if (totalMillis <= dtMillis) {
totalMillis = 0;
setTotalMillis(0);
} else {
totalMillis -= dtMillis;
}
}
totalCentis = totalMillis / 10;
totalSeconds = totalCentis / 100;
totalMinutes = totalSeconds / 60;
partCentis = totalCentis % 100;
partSeconds = totalSeconds % 60;
if (state != PAUSE) {
if (totalMinutes > 0) {
setState(MINUTES);
} else if (totalMillis > 0) {
setState(SECONDS);
} else {
setState(END);
setTotalMillis(totalMillis - dtMillis);
}
}
@ -119,25 +145,31 @@ protected:
if (now - blinkMillis > blinkIntervalMillis) {
blinkMillis = now;
blinkState = !blinkState;
markDirty();
markDirty(false);
}
}
if (state == RUNNING) {
if (totalMinutes > 0 || !configCentis) {
if (updateSeconds != totalSeconds) {
updateSeconds = totalSeconds;
markDirty();
} else if (state == SECONDS && configCentis) {
markDirty();
markDirty(false);
}
} else if (configCentis) {
markDirty(false);
}
}
}
void draw() override {
display.clear();
if (state == PAUSE) {
if (state == INITIAL) {
display.setColor(WHITE);
} else if (state == PAUSE) {
display.setColor(BLUE);
} else if (totalMillis * 2 >= configMillis) {
display.setColor(GREEN);
} else if (totalMillis >= 10 * 1000) {
} else if (totalMillis > 0) {
display.setColor(YELLOW);
} else {
display.setColor(RED);
@ -145,7 +177,6 @@ protected:
if (blinkIntervalMillis == 0 || blinkState) {
if (totalMinutes > 0) {
display.printf("%2d:%02d", totalMinutes, partSeconds);
info("%2d:%02d", totalMinutes, partSeconds);
} else if (totalMillis > 0) {
if (configCentis) {
display.printf("%2d.%02d", partSeconds, partCentis);
@ -158,60 +189,32 @@ protected:
}
}
void confirm() override {
if (state == END) {
return;
}
if (state == PAUSE) {
setState(MINUTES);
} else {
setState(PAUSE);
}
}
void cancel() override {
_start();
}
private:
void blinkEnable(const unsigned long intervalMillis) {
blinkState = false;
blinkIntervalMillis = intervalMillis;
blinkMillis = millis();
}
void setState(const State newState, const bool force = false) {
if (state == newState && !force) {
return;
}
state = newState;
switch (state) {
case PAUSE:
blinkEnable(0);
break;
case MINUTES:
updateSeconds = totalSeconds;
blinkEnable(0);
case SECONDS:
blinkEnable(0);
break;
case END:
if (state == END) {
blinkEnable(500);
break;
}
info("state changed to %s", getStateName());
markDirty();
info("state = %s", getStateName());
markDirty(true);
}
const char *getStateName() const {
switch (state) {
case INITIAL:
return "INITIAL";
case PAUSE:
return "PAUSE";
case MINUTES:
return "MINUTES";
case SECONDS:
return "SECONDS";
case RUNNING:
return "RUNNING";
case END:
return "END";
default:
@ -219,6 +222,44 @@ private:
}
}
void blinkEnable(const unsigned long intervalMillis) {
blinkState = false;
blinkIntervalMillis = intervalMillis;
blinkMillis = millis();
}
void addConfigMillis(const long change) {
if (state != INITIAL) {
return;
}
const auto newConfigMillis = max(MILLIS_MIN, min(static_cast<long>(configMillis) + change, MILLIS_MAX));
if (configMillis != newConfigMillis) {
configMillis = newConfigMillis;
configSet(CONFIG_SECONDS_KEY, configMillis / 1000);
setTotalMillis(configMillis);
markDirty(true);
}
}
void setTotalMillis(const unsigned long newTotalMillis) {
if (totalMillis == newTotalMillis) {
return;
}
totalMillis = newTotalMillis;
if (totalMillis == 0) {
setState(END);
}
totalCentis = totalMillis / 10;
totalSeconds = totalCentis / 100;
totalMinutes = totalSeconds / 60;
partCentis = totalCentis % 100;
partSeconds = totalSeconds % 60;
}
};
#endif

View File

@ -81,24 +81,16 @@ void httpNotFound(AsyncWebServerRequest *request) {
}
}
const char *getWebsocketTypeName(AwsEventType type) {
switch (type) {
case WS_EVT_CONNECT:
return "CONNECT";
case WS_EVT_DISCONNECT:
return "DISCONNECT";
case WS_EVT_PONG:
return "PONG";
case WS_EVT_ERROR:
return "ERROR";
case WS_EVT_DATA:
return "DATA";
default:
return "[???]";
void websocketEvent(AsyncWebSocket *socket, AsyncWebSocketClient *client, const AwsEventType type, void *arg, unsigned char *message, const unsigned length) {
if (type == WS_EVT_CONNECT) {
display.sendHexBuffer(client);
}
}
void httpSetup() {
ws.onEvent(websocketEvent);
server.addHandler(&ws);
server.on("/action/left", HTTP_GET, httpActionLeft);
server.on("/action/up", HTTP_GET, httpActionUp);
server.on("/action/down", HTTP_GET, httpActionDown);
@ -108,7 +100,6 @@ void httpSetup() {
server.on("/app/config", HTTP_GET, httpAppConfig);
server.serveStatic("/", LittleFS, "/http/", "max-age=86400").setDefaultFile("index.html");
server.onNotFound(httpNotFound);
server.addHandler(&ws);
server.begin();
DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*");

View File

@ -32,6 +32,8 @@ class Display {
Color color = WHITE;
char hexBuffer[HEX_BUFFER_SIZE] = "";
public:
Display() /* : leds(PIXEL_COUNT, GPIO_NUM_13) */ {
@ -61,7 +63,8 @@ public:
static auto last = now;
if (now - last >= HEX_BUFFER_MIN_WAIT_MS || forceNextHexBuffer) {
last = now;
sendHexBuffer();
fillHexBuffer();
websocketSendAll(hexBuffer);
}
}
@ -143,15 +146,17 @@ public:
}
}
void sendHexBuffer(AsyncWebSocketClient *client) {
client->text(hexBuffer);
}
private:
void sendHexBuffer() {
char hexBuffer[HEX_BUFFER_SIZE] = "";
void fillHexBuffer() {
auto b = hexBuffer;
for (const auto& pixel: pixels) {
b += snprintf(b, sizeof hexBuffer - (b - hexBuffer), "%X%X%X", pixel.r / 16, pixel.g / 16, pixel.b / 16);
}
websocketSendAll(hexBuffer);
}
};