Display digits/dots fix + config setting via http [PERSISTENCE NOT WORKING]

This commit is contained in:
Patrick Haßel 2025-01-09 16:39:07 +01:00
parent 12c089f7b9
commit 542c90dc92
7 changed files with 204 additions and 86 deletions

View File

@ -37,7 +37,7 @@
</style> </style>
</head> </head>
<body> <body>
<svg width="800" height="325" id="display"> <svg width="800" height="325" id="display" style="background-color: black">
</svg> </svg>
<table> <table>
<tr> <tr>
@ -121,7 +121,8 @@
segment.setAttribute("y", y + "vw"); segment.setAttribute("y", y + "vw");
segment.setAttribute("width", S + "vw"); segment.setAttribute("width", S + "vw");
segment.setAttribute("height", S + "vw"); segment.setAttribute("height", S + "vw");
segment.setAttribute("fill", "#555"); segment.setAttribute("stroke", "magenta");
segment.setAttribute("fill", "none");
segment.setAttribute("id", "segment" + segments.length); segment.setAttribute("id", "segment" + segments.length);
display.appendChild(segment); display.appendChild(segment);
segments.push(segment); segments.push(segment);
@ -152,7 +153,18 @@
socket.timeout = 1; socket.timeout = 1;
socket.addEventListener('open', _ => console.log('websocket connected')); socket.addEventListener('open', _ => console.log('websocket connected'));
socket.addEventListener('message', event => { socket.addEventListener('message', event => {
event.data.split(',').filter((color, index) => color !== '').forEach((color, index) => segments[index].setAttribute("fill", "#" + color)); const len = event.data.length;
let index = 0;
let start = 0;
while (start < len) {
const end = start + 3;
const color = event.data.substring(start, end);
const segment = segments[index];
segment.setAttribute("fill", "#" + color)
segment.setAttribute("stroke", "none");
index++;
start = end;
}
}); });
socket.addEventListener('close', _ => { socket.addEventListener('close', _ => {
console.log('websocket disconnected'); console.log('websocket disconnected');

View File

@ -9,6 +9,8 @@ class App {
const char *name; const char *name;
const String path;
JsonDocument configJson; JsonDocument configJson;
JsonObject config = configJson.to<JsonObject>(); JsonObject config = configJson.to<JsonObject>();
@ -17,8 +19,9 @@ class App {
public: public:
explicit App(const char *name) explicit App(const char *name) :
: name(name) { name(name),
path(String("/") + name + ".json") {
// //
} }
@ -74,16 +77,25 @@ public:
// //
} }
virtual bool setConfig(const String& key, const String& valueStr) {
return false;
}
protected: protected:
template<typename T> template<typename T>
T configRead(const char *key, T fallback) { T configGet(const char *key, T fallback) {
if (config[key].is<T>()) { if (config[key].is<T>()) {
return config[key].as<T>(); return config[key].as<T>();
} }
return fallback; return fallback;
} }
template<typename T>
void configSet(const char *key, T value) {
config[key] = value;
}
virtual void _start() { virtual void _start() {
// //
} }
@ -104,14 +116,51 @@ protected:
dirty = true; dirty = true;
} }
private:
void configLoad() { void configLoad() {
const auto path = String("/apps/") + name + ".json";
configJson.clear(); configJson.clear();
auto file = LittleFS.open(path, "r"); auto file = LittleFS.open(path, "r");
deserializeJson(configJson, file); if (!file) {
error("failed to open file for config read: %s", path.c_str());
return;
}
if (deserializeJson(configJson, file)) {
info("config loaded: %s", path.c_str());
} else {
error("failed to load config: %s", path.c_str());
}
file.close();
config = configJson.to<JsonObject>(); config = configJson.to<JsonObject>();
char buffer[256];
serializeJsonPretty(configJson, buffer, sizeof buffer);
Serial.println(buffer);
}
void configSave() {
auto file = LittleFS.open(path, "w");
if (!file) {
error("failed to open file for config write: %s", path.c_str());
return;
}
if (serializeJson(configJson, file)) {
info("config written: %s", path.c_str());
} else {
error("failed to write config: %s", path.c_str());
}
file.close();
auto file2 = LittleFS.open(path, "r");
if (file2) {
while (file2.available()) {
Serial.write(file2.read());
}
file2.close();
} else {
Serial.println("Failed to open file2 for reading");
}
} }
}; };

View File

@ -47,10 +47,24 @@ 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;
if (seconds > 0 && configMillis != newMillis) {
configMillis = newMillis;
configSet(CONFIG_SECONDS_KEY, seconds);
configSave();
return true;
}
}
return false;
}
protected: protected:
void _start() override { void _start() override {
configMillis = configRead<unsigned long>(CONFIG_SECONDS_KEY,CONFIG_SECONDS_DEFAULT) * 1000; configMillis = configGet<unsigned long>(CONFIG_SECONDS_KEY, CONFIG_SECONDS_DEFAULT) * 1000;
totalMillis = configMillis; totalMillis = configMillis;
setState(PAUSE); setState(PAUSE);
} }
@ -83,7 +97,7 @@ protected:
if (blinkIntervalMillis > 0) { if (blinkIntervalMillis > 0) {
const auto now = millis(); const auto now = millis();
if (now - blinkMillis > 500) { if (now - blinkMillis > blinkIntervalMillis) {
blinkMillis = now; blinkMillis = now;
blinkState = !blinkState; blinkState = !blinkState;
markDirty(); markDirty();
@ -104,24 +118,34 @@ protected:
void draw() override { void draw() override {
display.clear(); display.clear();
if (state == PAUSE) {
display.setColor(BLUE);
} else if (totalMillis * 2 >= configMillis) {
display.setColor(GREEN);
} else if (totalMillis >= 10 * 1000) {
display.setColor(YELLOW);
} else {
display.setColor(RED);
}
if (blinkIntervalMillis == 0 || blinkState) { if (blinkIntervalMillis == 0 || blinkState) {
if (totalMinutes > 0) { if (totalMinutes > 0) {
display.setColor(totalMillis < configMillis / 2 ? YELLOW : GREEN);
display.printf("%2d:%02d", totalMinutes, partSeconds); display.printf("%2d:%02d", totalMinutes, partSeconds);
info("%2d:%02d", totalMinutes, partSeconds); info("%2d:%02d", totalMinutes, partSeconds);
} else if (totalMillis > 0) { } else if (totalMillis > 0) {
display.setColor(RED);
display.printf("%2d.%02d", partSeconds, partCentis); display.printf("%2d.%02d", partSeconds, partCentis);
} else { } else {
display.printf("00:00"); display.printf("00:00");
} }
} else if (state == PAUSE) { } else if (state == PAUSE) {
info("PAUS", totalMinutes, partSeconds); display.printf("PAUS");
} }
display.flush(); display.flush();
} }
void confirm() override { void confirm() override {
if (state == END) {
return;
}
if (state == PAUSE) { if (state == PAUSE) {
setState(MINUTES); setState(MINUTES);
} else { } else {
@ -157,7 +181,7 @@ private:
blinkEnable(0); blinkEnable(0);
break; break;
case END: case END:
blinkEnable(100); blinkEnable(500);
break; break;
} }
info("state changed to %s", getStateName()); info("state changed to %s", getStateName());
@ -166,11 +190,16 @@ private:
const char *getStateName() const { const char *getStateName() const {
switch (state) { switch (state) {
case PAUSE: return "PAUSE"; case PAUSE:
case MINUTES: return "MINUTES"; return "PAUSE";
case SECONDS: return "SECONDS"; case MINUTES:
case END: return "END"; return "MINUTES";
default: return "[???]"; case SECONDS:
return "SECONDS";
case END:
return "END";
default:
return "[???]";
} }
} }

View File

@ -1,11 +1,12 @@
#include "filesystem.h" #include "filesystem.h"
#include <FS.h>
#include <LittleFS.h> #include <LittleFS.h>
#include "log.h" #include "log.h"
void filesystemMount() { void filesystemMount() {
if (LittleFS.begin(true)) { if (LittleFS.begin()) {
info("filesystem mounted"); info("filesystem mounted: %3d%% used", (int) round(100.0 * (LittleFS.usedBytes()) / LittleFS.totalBytes()));
} else { } else {
error("failed to mount filesystem"); error("failed to mount filesystem");
} }

View File

@ -16,45 +16,65 @@ void httpIndex(AsyncWebServerRequest *request) {
} }
void httpActionLeft(AsyncWebServerRequest *request) { void httpActionLeft(AsyncWebServerRequest *request) {
request->send(200);
if (app != nullptr) { if (app != nullptr) {
app->left(); app->left();
} }
request->send(200);
} }
void httpActionUp(AsyncWebServerRequest *request) { void httpActionUp(AsyncWebServerRequest *request) {
request->send(200);
if (app != nullptr) { if (app != nullptr) {
app->up(); app->up();
} }
request->send(200);
} }
void httpActionDown(AsyncWebServerRequest *request) { void httpActionDown(AsyncWebServerRequest *request) {
request->send(200);
if (app != nullptr) { if (app != nullptr) {
app->down(); app->down();
} }
request->send(200);
} }
void httpActionRight(AsyncWebServerRequest *request) { void httpActionRight(AsyncWebServerRequest *request) {
request->send(200);
if (app != nullptr) { if (app != nullptr) {
app->right(); app->right();
} }
request->send(200);
} }
void httpActionCancel(AsyncWebServerRequest *request) { void httpActionCancel(AsyncWebServerRequest *request) {
request->send(200);
if (app != nullptr) { if (app != nullptr) {
app->cancel(); app->cancel();
} }
request->send(200);
} }
void httpActionConfirm(AsyncWebServerRequest *request) { void httpActionConfirm(AsyncWebServerRequest *request) {
request->send(200);
if (app != nullptr) { if (app != nullptr) {
app->confirm(); app->confirm();
} }
request->send(200);
}
void httpAppConfig(AsyncWebServerRequest *request) {
if (!request->hasArg("key")) {
error("missing parameter 'key'");
request->send(400);
return;
}
if (!request->hasArg("value")) {
error("missing parameter 'value'");
request->send(400);
return;
}
if (app != nullptr) {
if (app->setConfig(request->arg("key"), request->arg("value"))) {
request->send(200);
return;
}
}
request->send(400);
} }
void httpSetup() { void httpSetup() {
@ -91,6 +111,7 @@ void httpSetup() {
server.on("/action/right", HTTP_GET, httpActionRight); server.on("/action/right", HTTP_GET, httpActionRight);
server.on("/action/cancel", HTTP_GET, httpActionCancel); server.on("/action/cancel", HTTP_GET, httpActionCancel);
server.on("/action/confirm", HTTP_GET, httpActionConfirm); server.on("/action/confirm", HTTP_GET, httpActionConfirm);
server.on("/app/config", HTTP_GET, httpAppConfig);
server.begin(); server.begin();
websocketStarted = true; websocketStarted = true;
} }

View File

@ -3,6 +3,7 @@
#include <cstdarg> #include <cstdarg>
#include <cstdio> #include <cstdio>
#include <WiFi.h> #include <WiFi.h>
#include <LittleFS.h>
#include "clock.h" #include "clock.h"
@ -23,6 +24,7 @@ void execute(const char *cmd) {
info(" reboot"); info(" reboot");
info(" reconnect"); info(" reconnect");
info(" time"); info(" time");
info(" ls");
} else if (strcmp(cmd, "network") == 0) { } else if (strcmp(cmd, "network") == 0) {
info("NETWORK"); info("NETWORK");
info(" %-10s %s", "mac:", WiFi.macAddress().c_str()); info(" %-10s %s", "mac:", WiFi.macAddress().c_str());
@ -47,27 +49,30 @@ void execute(const char *cmd) {
info(" %-10s %s", "real:", getClockStr()); info(" %-10s %s", "real:", getClockStr());
info(" %-10s %s", "startup:", getStartupStr()); info(" %-10s %s", "startup:", getStartupStr());
info(" %-10s %s", "uptime:", getUptimeStr()); info(" %-10s %s", "uptime:", getUptimeStr());
} else if (strcmp(cmd, "ls") == 0) {
info("TODO");
} else { } else {
info("UNKNOWN COMMAND: %s", cmd); info("UNKNOWN COMMAND: %s", cmd);
} }
} }
void logLoop() { void logLoop() {
static char cmd[64]; static char buffer[64];
static auto write = cmd; static auto write = buffer;
if (Serial.available() > 0) { if (Serial.available() > 0) {
const auto c = static_cast<char>(Serial.read()); const auto c = static_cast<char>(Serial.read());
switch (c) { switch (c) {
case 10:
break;
case 13: case 13:
Serial.print(c); Serial.print(c);
execute(cmd); execute(buffer);
write = cmd; write = buffer;
*write = 0;
break; break;
case 8: case 8:
if (write > cmd) { if (write > buffer) {
Serial.print(c); Serial.print(c);
*write-- = 0; write--;
} }
break; break;
default: default:
@ -76,6 +81,7 @@ void logLoop() {
write++; write++;
break; break;
} }
*write = 0;
} }
} }

View File

@ -12,20 +12,19 @@
#define PIXELS_PER_SEGMENT 3 #define PIXELS_PER_SEGMENT 3
#define SEGMENTS_PER_DIGIT 7 #define SEGMENTS_PER_DIGIT 7
#define PIXELS_PER_DOT 1 #define PIXELS_PER_DOT 1
#define DOTS_PER_DIGIT 4 #define DOT_COUNT 4
#define DIGITS 4 #define DIGITS 4
#define MAX_STR_LEN (2 * DIGITS - 1)
#define PIXELS_PER_DIGIT_SEGMENTS (PIXELS_PER_SEGMENT * SEGMENTS_PER_DIGIT) #define PIXELS_PER_DIGIT (PIXELS_PER_SEGMENT * SEGMENTS_PER_DIGIT)
#define PIXELS_PER_DIGIT_DOTS (PIXELS_PER_DOT * DOTS_PER_DIGIT) #define TOTAL_DOT_PIXEL_COUNT (PIXELS_PER_DOT * DOT_COUNT)
#define PIXELS_PER_DIGIT_AND_DOTS (PIXELS_PER_DIGIT_SEGMENTS + PIXELS_PER_DIGIT_DOTS) #define TOTAL_PIXEL_COUNT (DIGITS * PIXELS_PER_DIGIT + TOTAL_DOT_PIXEL_COUNT)
#define PIXEL_COUNT (DIGITS * PIXELS_PER_DIGIT_AND_DOTS - PIXELS_PER_DIGIT_DOTS) #define TOTAL_PIXEL_BYTE_COUNT (TOTAL_PIXEL_COUNT * sizeof(Color))
#define PIXEL_BYTE_COUNT (PIXEL_COUNT * sizeof(Color))
class Display { class Display {
// Adafruit_NeoPixel leds; // Adafruit_NeoPixel leds;
Color pixels[PIXEL_COUNT] = {}; Color pixels[TOTAL_PIXEL_COUNT] = {};
Color color = WHITE; Color color = WHITE;
@ -38,8 +37,8 @@ public:
flush(); flush();
} }
void setColor(const Color color) { void setColor(const Color newColor) {
this->color = color; this->color = newColor;
} }
void setBrightness(const int brightness) { void setBrightness(const int brightness) {
@ -47,7 +46,7 @@ public:
} }
void clear() { void clear() {
memset(pixels, 0, PIXEL_BYTE_COUNT); memset(pixels, 0, TOTAL_PIXEL_BYTE_COUNT);
} }
void flush() { void flush() {
@ -66,55 +65,58 @@ public:
char buffer[512]; char buffer[512];
char *b = buffer; char *b = buffer;
for (const auto& pixel: pixels) { for (const auto& pixel: pixels) {
b += snprintf(b, sizeof buffer - (b - buffer), "%02X%02X%02X,", pixel.r, pixel.g, pixel.b); b += snprintf(b, sizeof buffer - (b - buffer), "%X%X%X", pixel.r / 16, pixel.g / 16, pixel.b / 16);
} }
websocketSend(buffer); websocketSend(buffer);
} }
int printf(const char *format, ...) { void printf(const char *format, ...) {
char buffer[MAX_STR_LEN + 1]; char buffer[64];
va_list args; va_list args;
va_start(args, format); va_start(args, format);
vsnprintf(buffer, sizeof buffer, format, args); vsnprintf(buffer, sizeof buffer, format, args);
va_end(args); va_end(args);
return print(buffer); print(buffer);
} }
int print(const char *str) { void print(const char *str) {
auto pixel = 0; auto digit = 0;
for (auto character = str; *character != 0 && character - str < MAX_STR_LEN; character++) { for (auto c = str; *c != 0 && digit < 4; c++) {
pixel = print(pixel, *character); switch (*c) {
}
return pixel;
}
int print(const int pixel, const char character) {
switch (character) {
case '\'': case '\'':
case '"': case '"':
case '`': case '`':
case '.': case '.':
return printDots(pixel, false, false, false, true); printDots(false, false, false, true);
digit = 2;
break;
case ',': case ',':
return printDots(pixel, false, false, true, true); printDots(false, false, true, true);
digit = 2;
break;
case ':': case ':':
return printDots(pixel, false, true, true, false); printDots(false, true, true, false);
digit = 2;
break;
case ';': case ';':
return printDots(pixel, false, true, true, true); printDots(false, true, true, true);
digit = 2;
break;
case '|': case '|':
return printDots(pixel, true, true, true, true); printDots(true, true, true, true);
digit = 2;
break;
default: default:
return printCharacter(pixel, character); printCharacter(digit++, *c);
break;
}
} }
} }
int printDots(int pixel, const bool dot0, const bool dot1, const bool dot2, const bool dot3) { void printDots(const bool dot0, const bool dot1, const bool dot2, const bool dot3) {
pixel = pixel / PIXELS_PER_DIGIT_AND_DOTS - PIXELS_PER_DIGIT_DOTS; auto pixel = PIXELS_PER_DIGIT * 2;
if (pixel < 0) {
return 0;
}
if (dot0) { if (dot0) {
pixels[pixel] = color; pixels[pixel] = color;
} }
@ -130,11 +132,10 @@ public:
if (dot3) { if (dot3) {
pixels[pixel] = color; pixels[pixel] = color;
} }
return pixel;
} }
int printCharacter(int pixel, const char character) { void printCharacter(int digit, const char character) {
pixel = pixel / PIXELS_PER_DIGIT_AND_DOTS; auto pixel = digit * PIXELS_PER_DIGIT + (digit >= 2 ? TOTAL_DOT_PIXEL_COUNT : 0);
const auto symbol = getSymbol(character); const auto symbol = getSymbol(character);
for (auto s = *symbol; s < *symbol + SYMBOL_SIZE; s++) { for (auto s = *symbol; s < *symbol + SYMBOL_SIZE; s++) {
if (*s) { if (*s) {
@ -145,7 +146,6 @@ public:
pixel += 3; pixel += 3;
} }
} }
return pixel;
} }
}; };