diff --git a/data/http/index.html b/data/http/index.html
index 11504ad..6698a3a 100644
--- a/data/http/index.html
+++ b/data/http/index.html
@@ -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);
diff --git a/src/app/App.h b/src/app/App.h
index 2c7dd3b..1483833 100644
--- a/src/app/App.h
+++ b/src/app/App.h
@@ -6,6 +6,8 @@
#include
#include
+#define CONFIG_WRITE_DELAY_MILLIS (30 * 1000)
+
class App {
const char *name;
@@ -16,6 +18,8 @@ class App {
JsonObject config = configJsonDoc.to();
+ 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
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;
}
diff --git a/src/app/AppMatch.h b/src/app/AppMatch.h
index bfa9d56..f02978f 100644
--- a/src/app/AppMatch.h
+++ b/src/app/AppMatch.h
@@ -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(CONFIG_SECONDS_KEY, CONFIG_SECONDS_DEFAULT) * 1000;
+ configMillis = configGet(CONFIG_SECONDS_KEY, CONFIG_SECONDS_DEFAULT) * MS_PER_SEC;
configCentis = configGet(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 (updateSeconds != totalSeconds) {
- updateSeconds = totalSeconds;
- markDirty();
- } else if (state == SECONDS && configCentis) {
- markDirty();
+ if (state == RUNNING) {
+ if (totalMinutes > 0 || !configCentis) {
+ if (updateSeconds != totalSeconds) {
+ updateSeconds = totalSeconds;
+ 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:
- blinkEnable(500);
- break;
+ updateSeconds = totalSeconds;
+ blinkEnable(0);
+ if (state == END) {
+ blinkEnable(500);
}
- 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(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
diff --git a/src/core/http.cpp b/src/core/http.cpp
index 1ea1ffd..b5ea73b 100644
--- a/src/core/http.cpp
+++ b/src/core/http.cpp
@@ -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", "*");
diff --git a/src/display/Display.h b/src/display/Display.h
index b5a26aa..afca211 100644
--- a/src/display/Display.h
+++ b/src/display/Display.h
@@ -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);
}
};