Display digits/dots fix + config setting via http [PERSISTENCE NOT WORKING]
This commit is contained in:
parent
12c089f7b9
commit
542c90dc92
@ -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');
|
||||
|
||||
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@ -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 "[???]";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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");
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
Loading…
Reference in New Issue
Block a user