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); } };