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>
</head>
<body>
<svg width="800" height="325" id="display">
<svg width="800" height="325" id="display" style="background-color: black">
</svg>
<table>
<tr>
@ -121,7 +121,8 @@
segment.setAttribute("y", y + "vw");
segment.setAttribute("width", 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);
display.appendChild(segment);
segments.push(segment);
@ -152,7 +153,18 @@
socket.timeout = 1;
socket.addEventListener('open', _ => console.log('websocket connected'));
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', _ => {
console.log('websocket disconnected');

View File

@ -9,6 +9,8 @@ class App {
const char *name;
const String path;
JsonDocument configJson;
JsonObject config = configJson.to<JsonObject>();
@ -17,8 +19,9 @@ class App {
public:
explicit App(const char *name)
: name(name) {
explicit App(const char *name) :
name(name),
path(String("/") + name + ".json") {
//
}
@ -74,16 +77,25 @@ public:
//
}
virtual bool setConfig(const String& key, const String& valueStr) {
return false;
}
protected:
template<typename T>
T configRead(const char *key, T fallback) {
T configGet(const char *key, T fallback) {
if (config[key].is<T>()) {
return config[key].as<T>();
}
return fallback;
}
template<typename T>
void configSet(const char *key, T value) {
config[key] = value;
}
virtual void _start() {
//
}
@ -104,14 +116,51 @@ protected:
dirty = true;
}
private:
void configLoad() {
const auto path = String("/apps/") + name + ".json";
configJson.clear();
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>();
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:
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;
setState(PAUSE);
}
@ -83,7 +97,7 @@ protected:
if (blinkIntervalMillis > 0) {
const auto now = millis();
if (now - blinkMillis > 500) {
if (now - blinkMillis > blinkIntervalMillis) {
blinkMillis = now;
blinkState = !blinkState;
markDirty();
@ -104,24 +118,34 @@ protected:
void draw() override {
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 (totalMinutes > 0) {
display.setColor(totalMillis < configMillis / 2 ? YELLOW : GREEN);
display.printf("%2d:%02d", totalMinutes, partSeconds);
info("%2d:%02d", totalMinutes, partSeconds);
} else if (totalMillis > 0) {
display.setColor(RED);
display.printf("%2d.%02d", partSeconds, partCentis);
} else {
display.printf("00:00");
}
} else if (state == PAUSE) {
info("PAUS", totalMinutes, partSeconds);
display.printf("PAUS");
}
display.flush();
}
void confirm() override {
if (state == END) {
return;
}
if (state == PAUSE) {
setState(MINUTES);
} else {
@ -157,7 +181,7 @@ private:
blinkEnable(0);
break;
case END:
blinkEnable(100);
blinkEnable(500);
break;
}
info("state changed to %s", getStateName());
@ -166,11 +190,16 @@ private:
const char *getStateName() const {
switch (state) {
case PAUSE: return "PAUSE";
case MINUTES: return "MINUTES";
case SECONDS: return "SECONDS";
case END: return "END";
default: return "[???]";
case PAUSE:
return "PAUSE";
case MINUTES:
return "MINUTES";
case SECONDS:
return "SECONDS";
case END:
return "END";
default:
return "[???]";
}
}

View File

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

View File

@ -16,45 +16,65 @@ void httpIndex(AsyncWebServerRequest *request) {
}
void httpActionLeft(AsyncWebServerRequest *request) {
request->send(200);
if (app != nullptr) {
app->left();
}
request->send(200);
}
void httpActionUp(AsyncWebServerRequest *request) {
request->send(200);
if (app != nullptr) {
app->up();
}
request->send(200);
}
void httpActionDown(AsyncWebServerRequest *request) {
request->send(200);
if (app != nullptr) {
app->down();
}
request->send(200);
}
void httpActionRight(AsyncWebServerRequest *request) {
request->send(200);
if (app != nullptr) {
app->right();
}
request->send(200);
}
void httpActionCancel(AsyncWebServerRequest *request) {
request->send(200);
if (app != nullptr) {
app->cancel();
}
request->send(200);
}
void httpActionConfirm(AsyncWebServerRequest *request) {
request->send(200);
if (app != nullptr) {
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() {
@ -91,6 +111,7 @@ void httpSetup() {
server.on("/action/right", HTTP_GET, httpActionRight);
server.on("/action/cancel", HTTP_GET, httpActionCancel);
server.on("/action/confirm", HTTP_GET, httpActionConfirm);
server.on("/app/config", HTTP_GET, httpAppConfig);
server.begin();
websocketStarted = true;
}

View File

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

View File

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