Compare commits

..

2 Commits

Author SHA1 Message Date
71b99cca5e forcing save from ui 2024-12-31 12:16:59 +01:00
399723d77a modifiable date for countdown + COUNT_DOWN_SLEEP 2024-12-31 12:01:32 +01:00
8 changed files with 191 additions and 81 deletions

View File

@ -58,6 +58,12 @@ void config_loop() {
Serial.printf("Config saved to EEPROM.\n"); Serial.printf("Config saved to EEPROM.\n");
} }
void configSaveNowIfDirty() {
if (dirty) {
lastDirtyMillis = millis() - 30000;
}
}
bool config_load() { bool config_load() {
Config tmp{}; Config tmp{};
@ -83,9 +89,10 @@ bool validateChecksum(uint32_t checksumEEPROM, Config &tmp) {
} }
void config_defaults() { void config_defaults() {
config.mode = GAME_OF_LIFE_GRAYSCALE; config.mode = COUNT_DOWN_SLEEP;
config.speed = 1.0; config.speed = 1.0;
config.brightness = 16; config.brightness = 16;
config.date = {0};
Serial.printf("Config DEFAULTS loaded.\n"); Serial.printf("Config DEFAULTS loaded.\n");
} }

View File

@ -7,6 +7,7 @@ struct Config {
ModeId mode; ModeId mode;
double speed; double speed;
uint8_t brightness; uint8_t brightness;
tm date;
}; };
extern Config config; extern Config config;
@ -15,6 +16,8 @@ void config_setup();
void config_loop(); void config_loop();
void configSaveNowIfDirty();
bool config_load(); bool config_load();
void config_set_dirty(); void config_set_dirty();

View File

@ -106,6 +106,34 @@ bool SYMBOLS[SYMBOL_COUNT][DISPLAY_CHAR_WIDTH * DISPLAY_CHAR_HEIGHT] = {
X, _, _, X, _, _,
_, _, X, _, _, X,
}, },
{
X, X, X,
_, X, _,
_, X, _,
_, X, _,
_, X, _,
},
{
_, X, _,
X, _, X,
X, X, X,
X, _, X,
X, _, X,
},
{
_, X, X,
X, _, _,
X, X, X,
X, _, X,
_, X, _,
},
{
X, X, X,
X, _, _,
X, X, X,
X, _, _,
X, X, X,
},
// this must always be the last symbol (fallback) // this must always be the last symbol (fallback)
{ {
X, X, X, X, X, X,

View File

@ -5,9 +5,16 @@
#include "Adafruit_NeoPixel.h" #include "Adafruit_NeoPixel.h"
#include "Vector.h" #include "Vector.h"
#define SYMBOL_COUNT 16 #define SYMBOL_COUNT 20
#define SYMBOL_DASH 13 #define SYMBOL_DASH 13
#define SYMBOL_PERCENT 14 #define SYMBOL_PERCENT 14
#define SYMBOL_T 15
#define SYMBOL_A 16
#define SYMBOL_G 17
#define SYMBOL_E 18
#define DISPLAY_CHAR_WIDTH 3 #define DISPLAY_CHAR_WIDTH 3
#define DISPLAY_CHAR_HEIGHT 5 #define DISPLAY_CHAR_HEIGHT 5

View File

@ -99,10 +99,13 @@ void loadNewMode() {
mode = new SpaceInvaders(display); mode = new SpaceInvaders(display);
break; break;
case COUNT_DOWN: case COUNT_DOWN:
mode = new CountDown(display, false); mode = new CountDown(display, false, false);
break; break;
case COUNT_DOWN_BARS: case COUNT_DOWN_BARS:
mode = new CountDown(display, true); mode = new CountDown(display, true, false);
break;
case COUNT_DOWN_SLEEP:
mode = new CountDown(display, false, true);
break; break;
case STARFIELD: case STARFIELD:
mode = new Starfield(display); mode = new Starfield(display);

View File

@ -14,14 +14,22 @@ private:
uint16_t days = 0; uint16_t days = 0;
uint16_t hours = 0;
uint16_t minutes = 0;
uint16_t seconds = 0;
uint8_t level = 0; uint8_t level = 0;
bool bars; bool bars;
bool plus1DayForSleepingCount;
public: public:
CountDown(Display &display, bool bars) : CountDown(Display &display, bool bars, bool plus1DayForSleepingCount) :
Mode(display), bars(bars) { Mode(display), bars(bars), plus1DayForSleepingCount(plus1DayForSleepingCount) {
for (auto &firework: fireworks) { for (auto &firework: fireworks) {
firework.init(display); firework.init(display);
} }
@ -40,25 +48,45 @@ protected:
void step(microseconds_t microseconds) override { void step(microseconds_t microseconds) override {
if (!realtimeOK) { if (!realtimeOK) {
setMode(NO_TIME); setMode(NO_TIME);
} else if (now.tm_mon != 1 || now.tm_mday != 1 || now.tm_hour != 0) { return;
days = getDayCountForYear(now.tm_year) - now.tm_yday - 1; }
if (dateReached()) {
setMode(FIREWORK);
for (auto &firework: fireworks) {
firework.step(microseconds);
}
markDirty();
return;
}
// GRRRRRRR...
config.date.tm_year -= 1900;
config.date.tm_mon -= 1;
const time_t dateEpochSeconds = mktime(&config.date);
config.date.tm_year += 1900;
config.date.tm_mon += 1;
// ---
const double diffSeconds = difftime(dateEpochSeconds, nowEpochSeconds);
days = (int) floor(diffSeconds / (24 * 60 * 60));
hours = (int) floor(diffSeconds / (60 * 60)) % 24;
minutes = (int) floor(diffSeconds / 60) % 60;
seconds = (int) diffSeconds % 60;
// Serial.printf("now=%4d.%02d.%02d (%ld), conf=%4d.%02d.%02d (%ld), diff=%f, %dd %2d:%02d:%02d\n",
// now.tm_year, now.tm_mon, now.tm_mday, nowEpochSeconds,
// config.date.tm_year, config.date.tm_mon, config.date.tm_mday, dateEpochSeconds,
// diffSeconds,
// days, hours, minutes, seconds);
setMode(COUNTDOWN); setMode(COUNTDOWN);
if (days == 0) { if (days == 0) {
loopLastDay(); loopLastDay();
} else { } else {
loopMultipleDays(); loopMultipleDays();
} }
} else {
setMode(FIREWORK);
for (auto &firework: fireworks) {
firework.step(microseconds);
}
markDirty();
}
} }
void loopLastDay() { void loopLastDay() {
int levelTmp = (int) round(32 * realtimeMilliseconds / 1000.0);; int levelTmp = (int) round(32 * realtimeMilliseconds / 1000.0);
if (level != levelTmp) { if (level != levelTmp) {
level = levelTmp; level = levelTmp;
markDirty(); markDirty();
@ -71,17 +99,21 @@ protected:
} }
} }
bool dateReached() {
return now.tm_year == config.date.tm_year && now.tm_mon == config.date.tm_mon && now.tm_mday == config.date.tm_mday;
}
void draw(Display &display) override { void draw(Display &display) override {
display.clear(); display.clear();
if (!realtimeOK) { if (!realtimeOK) {
drawNoTime(display); drawNoTime(display);
} else if (now.tm_mon == 1 && now.tm_mday == 1 && now.tm_hour == 0) { } else if (dateReached()) {
for (auto &firework: fireworks) { for (auto &firework: fireworks) {
firework.draw(display); firework.draw(display);
} }
drawYear(display, now.tm_year); drawYear(display, now.tm_year);
} else { } else {
drawCountdown(display, now); drawCountdown(display);
} }
} }
@ -102,25 +134,37 @@ private:
} }
} }
void drawCountdown(Display &display, const tm &now) { void drawCountdown(Display &display) {
uint8_t hours = (24 - now.tm_hour - (now.tm_min > 0 || now.tm_sec > 0 ? 1 : 0)); if (plus1DayForSleepingCount) {
uint8_t minutes = (60 - now.tm_min - (now.tm_sec > 0 ? 1 : 0)) % 60; drawSleepingCount(display);
uint8_t seconds = (60 - now.tm_sec) % 60; } else if (days <= 0 && bars) {
if (days <= 0 && bars) { drawCountdownBars(display);
drawCountdownBars(display, hours, minutes, seconds);
} else { } else {
drawCountdownNumbers(display, hours, minutes, seconds); drawCountdownNumbers(display);
} }
} }
void drawCountdownBars(Display &display, uint8_t hours, uint8_t minutes, uint8_t seconds) { void drawSleepingCount(Display &display) const {
drawBar(display, 0, 0, 24, 1, 0, 24, hours, RED, MAGENTA, 0); int sleepCount = days + 1;
drawBar(display, 0, 2, 30, 2, 0, 60, minutes, BLUE, VIOLET, 5); int y = 1;
drawBar(display, 0, 5, 30, 2, 0, 60, seconds, GREEN, YELLOW, 5); uint8_t x = display.print2(3 * (DISPLAY_CHAR_WIDTH + 1), y, sleepCount, WHITE);
x += 2;
x += display.print(x, y, SYMBOL_T, WHITE, true) + 1;
x += display.print(x, y, SYMBOL_A, WHITE, true) + 1;
x += display.print(x, y, SYMBOL_G, WHITE, true) + 1;
if (sleepCount != 1) {
display.print(x, y, SYMBOL_E, WHITE, true);
}
} }
void drawBar(Display &display, uint8_t _x, uint8_t _y, uint8_t _w, uint8_t _h, uint8_t min, uint8_t max, uint8_t value, const Color &color, const Color &tickColor, uint8_t ticks) { void drawCountdownBars(Display &display) const {
auto totalOnCount = (uint8_t) round(((double) value - min) / (max - min) * _w * _h); drawBar(display, 0, 24, 1, 24, hours, RED, MAGENTA, 0);
drawBar(display, 2, 30, 2, 60, minutes, BLUE, VIOLET, 5);
drawBar(display, 5, 30, 2, 60, seconds, GREEN, YELLOW, 5);
}
static void drawBar(Display &display, uint8_t _y, uint8_t _w, uint8_t _h, uint8_t max, uint8_t value, const Color &color, const Color &tickColor, uint8_t ticks) {
auto totalOnCount = (uint8_t) round((double) value / max * _w * _h);
uint8_t doneOnCount = 0; uint8_t doneOnCount = 0;
for (uint8_t y = 0; y < _h; y++) { for (uint8_t y = 0; y < _h; y++) {
for (uint8_t x = 0; x < _w; x++) { for (uint8_t x = 0; x < _w; x++) {
@ -134,20 +178,12 @@ private:
c = tickColor; c = tickColor;
} }
} }
display.set(_x + x, _y + y, c); display.set(x, _y + y, c);
} }
} }
} }
static Color factor(Color color, double factor) { void drawCountdownNumbers(Display &display) const {
return {
(uint8_t) round(color.r * factor),
(uint8_t) round(color.g * factor),
(uint8_t) round(color.b * factor),
};
}
void drawCountdownNumbers(Display &display, uint8_t hours, uint8_t minutes, uint8_t seconds) const {
uint8_t x = 0; uint8_t x = 0;
if (days > 0) { if (days > 0) {
drawDay(display, days, &x); drawDay(display, days, &x);
@ -179,40 +215,36 @@ private:
x += display.print(x, 1, 10, WHITE, true); x += display.print(x, 1, 10, WHITE, true);
x += display.print(x, 1, SYMBOL_DASH, WHITE, true); x += display.print(x, 1, SYMBOL_DASH, WHITE, true);
x++; x++;
x += display.print(x, 1, SYMBOL_DASH, WHITE, true); display.print(x, 1, SYMBOL_DASH, WHITE, true);
} }
static void drawDay(Display &display, int days, uint8_t *x) { static void drawDay(Display &display, int days, uint8_t *x) {
if (days >= 100) { if (days >= 100) {
*x += display.print(*x, 1, days / 100, WHITE, true); *x += display.print(*x, 1, days / 100, WHITE, true) + 1;
} else { } else {
*x += 3; *x += DISPLAY_CHAR_WIDTH + 1;
} }
(*x)++;
if (days >= 10) { if (days >= 10) {
*x += display.print(*x, 1, days / 10 % 10, WHITE, true); *x += display.print(*x, 1, days / 10 % 10, WHITE, true) + 1;
} else { } else {
*x += 3; *x += DISPLAY_CHAR_WIDTH + 1;
} }
(*x)++;
*x += display.print(*x, 1, days % 10, WHITE, true); *x += display.print(*x, 1, days % 10, WHITE, true);
} }
static void drawHour(Display &display, int days, int hours, uint8_t *x) { static void drawHour(Display &display, int days, int hours, uint8_t *x) {
if (days > 0 || hours >= 10) { if (days > 0 || hours >= 10) {
*x += display.print(*x, 1, hours / 10, WHITE, true); *x += display.print(*x, 1, hours / 10, WHITE, true) + 1;
} else { } else {
*x += 3; *x += DISPLAY_CHAR_WIDTH + 1;
} }
(*x)++;
*x += display.print(*x, 1, hours % 10, WHITE, true); *x += display.print(*x, 1, hours % 10, WHITE, true);
} }
static void draw2Digit(Display &display, int value, uint8_t *x) { static void draw2Digit(Display &display, int value, uint8_t *x) {
*x += display.print(*x, 1, value / 10, WHITE, true); *x += display.print(*x, 1, value / 10, WHITE, true) + 1;
(*x)++;
*x += display.print(*x, 1, value % 10, WHITE, true); *x += display.print(*x, 1, value % 10, WHITE, true);
} }
@ -234,23 +266,12 @@ private:
} }
} }
static int getDayCountForYear(int year) {
bool leapYear = year % 4 == 0 && (year % 400 == 0 || year % 100 != 0);
if (leapYear) {
return 366;
}
return 365;
}
static void drawYear(Display &display, int year) { static void drawYear(Display &display, int year) {
uint8_t x = 8; uint8_t x = 8;
x += display.print(x, 1, year / 1000 % 10, WHITE, true); x += display.print(x, 1, year / 1000 % 10, WHITE, true) + 1;
x++; x += display.print(x, 1, year / 100 % 10, WHITE, true) + 1;
x += display.print(x, 1, year / 100 % 10, WHITE, true); x += display.print(x, 1, year / 10 % 10, WHITE, true) + 1;
x++; display.print(x, 1, year / 1 % 10, WHITE, true);
x += display.print(x, 1, year / 10 % 10, WHITE, true);
x++;
x += display.print(x, 1, year / 1 % 10, WHITE, true);
} }
}; };

View File

@ -21,6 +21,7 @@ enum ModeId {
SPACE_INVADERS, SPACE_INVADERS,
COUNT_DOWN, COUNT_DOWN,
COUNT_DOWN_BARS, COUNT_DOWN_BARS,
COUNT_DOWN_SLEEP,
STARFIELD, STARFIELD,
MATRIX, MATRIX,
POWER, POWER,
@ -63,7 +64,7 @@ protected:
milliseconds_t realtimeMilliseconds = 0; milliseconds_t realtimeMilliseconds = 0;
time_t epoch = 0; time_t nowEpochSeconds = 0;
virtual void tick(uint8_t index, microseconds_t microseconds) {}; virtual void tick(uint8_t index, microseconds_t microseconds) {};
@ -127,9 +128,9 @@ private:
tmp += ((FAKE_DAYS * 24 + FAKE_HOURS) * 60 + FAKE_MINUTES) * 60 + FAKE_SECONDS; tmp += ((FAKE_DAYS * 24 + FAKE_HOURS) * 60 + FAKE_MINUTES) * 60 + FAKE_SECONDS;
realtimeOK = tmp > 1600000000; realtimeOK = tmp > 1600000000;
if (realtimeOK) { if (realtimeOK) {
realtimeChanged = epoch != tmp; realtimeChanged = nowEpochSeconds != tmp;
if (realtimeChanged) { if (realtimeChanged) {
epoch = tmp; nowEpochSeconds = tmp;
localtime_r(&tmp, &now); localtime_r(&tmp, &now);
now.tm_year += 1900; now.tm_year += 1900;
now.tm_mon += 1; now.tm_mon += 1;

View File

@ -13,7 +13,7 @@ body {
font-size: 8vw; font-size: 8vw;
margin: 0; margin: 0;
} }
button{ button.player{
width: 33vmin; width: 33vmin;
height: 33vmin; height: 33vmin;
font-size: 9vw; font-size: 9vw;
@ -61,6 +61,10 @@ void web_fps_on();
void web_fps_off(); void web_fps_off();
void web_config_date();
void web_config_save();
void server_setup() { void server_setup() {
server.on("/", web_index); server.on("/", web_index);
server.on("/player", web_player); server.on("/player", web_player);
@ -73,6 +77,8 @@ void server_setup() {
server.on("/slower", web_slower); server.on("/slower", web_slower);
server.on("/fps/on", web_fps_on); server.on("/fps/on", web_fps_on);
server.on("/fps/off", web_fps_off); server.on("/fps/off", web_fps_off);
server.on("/config/date", web_config_date);
server.on("/config/save", web_config_save);
server.begin(); server.begin();
} }
@ -103,10 +109,11 @@ void web_index() {
server.sendContent(R"(<a onclick="get('/mode?mode=8');">SPACE_INVADERS</a><br>)"); server.sendContent(R"(<a onclick="get('/mode?mode=8');">SPACE_INVADERS</a><br>)");
server.sendContent(R"(<a onclick="get('/mode?mode=9');">COUNT_DOWN</a><br>)"); server.sendContent(R"(<a onclick="get('/mode?mode=9');">COUNT_DOWN</a><br>)");
server.sendContent(R"(<a onclick="get('/mode?mode=10');">COUNT_DOWN_BARS</a><br>)"); server.sendContent(R"(<a onclick="get('/mode?mode=10');">COUNT_DOWN_BARS</a><br>)");
server.sendContent(R"(<a onclick="get('/mode?mode=11');">STARFIELD</a><br>)"); server.sendContent(R"(<a onclick="get('/mode?mode=11');">COUNT_DOWN_SLEEP</a><br>)");
server.sendContent(R"(<a onclick="get('/mode?mode=12');">MATRIX</a><br>)"); server.sendContent(R"(<a onclick="get('/mode?mode=12');">STARFIELD</a><br>)");
server.sendContent(R"(<a onclick="get('/mode?mode=13');">POWER</a><br>)"); server.sendContent(R"(<a onclick="get('/mode?mode=13');">MATRIX</a><br>)");
server.sendContent(R"(<a onclick="get('/mode?mode=14');">ENERGY</a><br>)"); server.sendContent(R"(<a onclick="get('/mode?mode=14');">POWER</a><br>)");
server.sendContent(R"(<a onclick="get('/mode?mode=15');">ENERGY</a><br>)");
server.sendContent(R"(</p>)"); server.sendContent(R"(</p>)");
server.sendContent(R"(<p>)"); server.sendContent(R"(<p>)");
@ -114,6 +121,18 @@ void web_index() {
server.sendContent(R"(Geschwindigkeit: <a onclick="get('/faster');">+</a> / <a onclick="get('/slower');">-</a><br>)"); server.sendContent(R"(Geschwindigkeit: <a onclick="get('/faster');">+</a> / <a onclick="get('/slower');">-</a><br>)");
server.sendContent(R"(FPS: <a onclick="get('/fps/on');">EIN</a> / <a onclick="get('/fps/off');">AUS</a><br>)"); server.sendContent(R"(FPS: <a onclick="get('/fps/on');">EIN</a> / <a onclick="get('/fps/off');">AUS</a><br>)");
server.sendContent(R"(</p>)"); server.sendContent(R"(</p>)");
server.sendContent(R"(<p>)");
server.sendContent(R"(<input type="number" min="1900" max="3000" step="1" name="year" id="year">)");
server.sendContent(R"(<input type="number" min="1" max="12" step="1" name="month" id="month">)");
server.sendContent(R"(<input type="number" min="1" max="31" step="1" name="day" id="day">)");
server.sendContent(R"(<button onclick="get('/config/date?year=' + document.getElementById('year').value + '&month=' + document.getElementById('month').value + '&day=' + document.getElementById('day').value);">Datum setzen</button>)");
server.sendContent(R"(</p>)");
server.sendContent(R"(<p>)");
server.sendContent(R"(<button onclick="get('/config/save');">Speichern erzwingen</button>)");
server.sendContent(R"(</p>)");
server.client().flush(); server.client().flush();
} }
@ -141,7 +160,7 @@ void web_player() {
server.sendContent(R"(<td><a href='/'>&larr;</td>)"); server.sendContent(R"(<td><a href='/'>&larr;</td>)");
server.sendContent(R"(<td>)"); server.sendContent(R"(<td>)");
snprintf(buffer, sizeof buffer, R"(<button onclick="get('/player/move?index=%d&x=0&y=-1');">&uarr;</button><br>)", index); snprintf(buffer, sizeof buffer, R"(<button class="player" onclick="get('/player/move?index=%d&x=0&y=-1');">&uarr;</button><br>)", index);
server.sendContent(buffer); server.sendContent(buffer);
server.sendContent(R"(</td>)"); server.sendContent(R"(</td>)");
@ -152,17 +171,17 @@ void web_player() {
server.sendContent(R"(<tr>)"); server.sendContent(R"(<tr>)");
server.sendContent(R"(<td>)"); server.sendContent(R"(<td>)");
snprintf(buffer, sizeof buffer, R"(<button onclick="get('/player/move?index=%d&x=-1&y=0');">&larr;</button><br>)", index); snprintf(buffer, sizeof buffer, R"(<button class="player" onclick="get('/player/move?index=%d&x=-1&y=0');">&larr;</button><br>)", index);
server.sendContent(buffer); server.sendContent(buffer);
server.sendContent(R"(</td>)"); server.sendContent(R"(</td>)");
server.sendContent(R"(<td>)"); server.sendContent(R"(<td>)");
snprintf(buffer, sizeof buffer, R"(<button onclick="get('/player/fire?index=%d');">X</button><br>)", index); snprintf(buffer, sizeof buffer, R"(<button class="player" onclick="get('/player/fire?index=%d');">X</button><br>)", index);
server.sendContent(buffer); server.sendContent(buffer);
server.sendContent(R"(</td>)"); server.sendContent(R"(</td>)");
server.sendContent(R"(<td>)"); server.sendContent(R"(<td>)");
snprintf(buffer, sizeof buffer, R"(<button onclick="get('/player/move?index=%d&x=+1&y=0');">&rarr;</button><br>)", index); snprintf(buffer, sizeof buffer, R"(<button class="player" onclick="get('/player/move?index=%d&x=+1&y=0');">&rarr;</button><br>)", index);
server.sendContent(buffer); server.sendContent(buffer);
server.sendContent(R"(</td>)"); server.sendContent(R"(</td>)");
@ -173,7 +192,7 @@ void web_player() {
server.sendContent(R"(<td>&nbsp;</td>)"); server.sendContent(R"(<td>&nbsp;</td>)");
server.sendContent(R"(<td>)"); server.sendContent(R"(<td>)");
snprintf(buffer, sizeof buffer, R"(<button onclick="get('/player/move?index=%d&x=0&y=+1');">&darr;</button><br>)", index); snprintf(buffer, sizeof buffer, R"(<button class="player" onclick="get('/player/move?index=%d&x=0&y=+1');">&darr;</button><br>)", index);
server.sendContent(buffer); server.sendContent(buffer);
server.sendContent(R"(</td>)"); server.sendContent(R"(</td>)");
@ -272,3 +291,24 @@ void web_fps_off() {
display.fpsShow = false; display.fpsShow = false;
server.send(200); server.send(200);
} }
void web_config_save() {
configSaveNowIfDirty();
server.send(200);
}
void web_config_date() {
double year = strtod(server.arg("year").c_str(), nullptr);
double month = strtod(server.arg("month").c_str(), nullptr);
double day = strtod(server.arg("day").c_str(), nullptr);
if (!isnan(year)) {
config.date.tm_year = (int) year;
config.date.tm_mon = (int) month;
config.date.tm_mday = (int) day;
config.date.tm_hour = 0;
config.date.tm_min = 0;
config.date.tm_sec = 0;
server.send(200);
}
server.send(400);
}