Files
Flipdot-display/src/esp32/app.cpp
T

271 lines
6.6 KiB
C++
Raw Normal View History

2026-10-13 01:52:34 +02:00
#include "app.h"
#include <string.h>
#include <time.h>
2026-10-13 01:52:34 +02:00
#include "config.h"
#include "display/bitmap_store.h"
#include "display/font_store.h"
#include "display/page_resources.h"
#include "display/page_store.h"
#include "net/ha_api.h"
2026-10-13 01:52:34 +02:00
#include "system/settings_store.h"
NanoLink nano(Serial2, NANO_RX_PIN, NANO_TX_PIN, RELAY_PIN, NANO_RESET_PIN);
BROSE9323 display(DISPLAY_WIDTH, DISPLAY_HEIGHT, PANEL_WIDTH);
FlipdotFrames frames(display, pageResources);
2026-10-13 01:52:34 +02:00
StatusScreen status(display, nano);
DisplayController controller(nano, frames);
static Settings settings;
static bool restart_pending = false;
static uint32_t restart_at = 0;
// Not on the stack: about 1.7 KB
static DynamicPage loaded_page;
2026-10-13 01:52:34 +02:00
static void showDynamicPage()
{
frames.setDynamicPage(loaded_page);
haApiWatch(&loaded_page);
controller.setPage(Page::Dynamic);
}
// Load the page into the frames: a dynamic page or a bitmap; the Clock page when it is gone
2026-10-13 01:52:34 +02:00
static void applyPage()
{
if (loadPage(settings.page, loaded_page))
2026-10-13 01:52:34 +02:00
{
showDynamicPage();
return;
2026-10-13 01:52:34 +02:00
}
uint8_t bitmap[BITMAP_BYTES];
if (loadBitmap(settings.page, bitmap))
{
frames.setPageBitmap(bitmap);
haApiWatch(nullptr);
controller.setPage(Page::Bitmap);
return;
}
Serial.printf("Page \"%s\" not found, showing the clock\n", settings.page);
builtinPage(DEFAULT_PAGE, loaded_page);
showDynamicPage();
2026-10-13 01:52:34 +02:00
}
// Load the off bitmap into the frames; blank when it is gone
static void applyOffPage()
{
uint8_t bitmap[BITMAP_BYTES];
if (settings.off_page[0] && loadBitmap(settings.off_page, bitmap))
frames.setOffBitmap(bitmap);
else
{
if (settings.off_page[0])
Serial.printf("Off bitmap \"%s\" not found, the display is cleared instead\n", settings.off_page);
frames.setOffBitmap(nullptr);
}
}
2026-10-13 01:52:34 +02:00
static void apply()
{
controller.setSettings(settings.schedule, settings.clock);
controller.setEffectTime(effectTime(settings.drive));
status.setDrive(settings.flip_time, settings.drive);
haApiSetSettings(settings.home_assistant);
applyOffPage();
2026-10-13 01:52:34 +02:00
applyPage();
}
// Something the current page may use changed: load it again and redraw
static void reloadPage()
{
applyPage();
controller.pageChanged();
}
2026-10-13 01:52:34 +02:00
const Settings &appSettings()
{
return settings;
}
void appLoadSettings()
{
loadSettings(settings);
apply();
}
static bool sameMqtt(const HomeAssistantSettings &a, const HomeAssistantSettings &b)
2026-10-13 01:52:34 +02:00
{
return a.enabled == b.enabled && a.port == b.port && strcmp(a.host, b.host) == 0 &&
strcmp(a.user, b.user) == 0 && strcmp(a.password, b.password) == 0;
}
const char *appSaveSettings(const Settings &new_settings, bool &restart_needed)
{
const char *error = validateSettings(new_settings);
if (error)
return error;
if (!pageExists(new_settings.page) && !bitmapExists(new_settings.page))
return "Page not found";
if (new_settings.off_page[0] && !bitmapExists(new_settings.off_page))
return "Off bitmap not found";
2026-10-13 01:52:34 +02:00
restart_needed = !sameMqtt(settings.home_assistant, new_settings.home_assistant);
const DriveSettings &a = settings.drive, &b = new_settings.drive;
bool drive_changed = settings.flip_time != new_settings.flip_time || a.flip_gap != b.flip_gap ||
a.pulses != b.pulses || a.effect != b.effect || a.effect_time != b.effect_time ||
a.effect_small_only != b.effect_small_only;
2026-10-13 01:52:34 +02:00
settings = new_settings;
saveSettings(settings);
apply();
if (drive_changed)
status.sendDrive();
2026-10-13 01:52:34 +02:00
return nullptr;
}
static void copyName(char *out, const char *name)
{
strncpy(out, name, PAGE_NAME_SIZE - 1);
out[PAGE_NAME_SIZE - 1] = '\0';
}
2026-10-13 01:52:34 +02:00
const char *appSelectPage(const char *name)
{
if (!pageExists(name) && !bitmapExists(name))
return "Page not found";
copyName(settings.page, name);
2026-10-13 01:52:34 +02:00
saveSettings(settings);
applyPage();
return nullptr;
}
const char *appSelectOffPage(const char *name)
{
if (name[0] && !bitmapExists(name))
return "Bitmap not found";
copyName(settings.off_page, name);
saveSettings(settings);
applyOffPage();
controller.pageChanged();
return nullptr;
}
const char *appSaveBitmap(const char *name, const uint8_t *bitmap)
2026-10-13 01:52:34 +02:00
{
if (pageExists(name))
return "A page has this name";
const char *error = saveBitmap(name, bitmap);
if (error)
return error;
if (sameName(settings.off_page, name))
applyOffPage();
reloadPage(); // the page or its background
return nullptr;
}
// "Used by page X ..." (the returned text stays valid until the next call)
static const char *usedBy(const String &page)
{
static char message[80];
snprintf(message, sizeof(message), "Used by page \"%s\": change that page first", page.c_str());
return message;
}
const char *appDeleteBitmap(const char *name)
{
String user = pageUsingBitmap(name);
if (user.length())
return usedBy(user);
const char *error = deleteBitmap(name);
if (error)
return error;
if (sameName(settings.page, name))
appSelectPage(DEFAULT_PAGE);
if (sameName(settings.off_page, name))
appSelectOffPage(OFF_PAGE_BLANK);
return nullptr;
}
const char *appSavePage(const char *name, const DynamicPage &page)
{
if (bitmapExists(name))
return "A bitmap has this name";
for (int i = 0; i < page.slot_count; i++)
if (page.slots[i].bitmap[0] && !bitmapExists(page.slots[i].bitmap))
return "Background bitmap not found";
for (int i = 0; i < page.element_count; i++)
if (!fontExists(page.elements[i].font))
return "Font not found";
const char *error = savePage(name, page);
if (error)
return error;
if (sameName(settings.page, name))
reloadPage();
return nullptr;
}
const char *appDeletePage(const char *name)
{
const char *error = deletePage(name);
if (error)
return error;
if (sameName(settings.page, name))
appSelectPage(DEFAULT_PAGE);
return nullptr;
}
const char *appSaveFont(const char *name, const uint8_t *data, size_t size)
{
const char *error = saveFont(name, data, size);
if (error)
return error;
controller.pageChanged(); // the page may use it
return nullptr;
}
const char *appDeleteFont(const char *name)
{
String user = pageUsingFont(name);
if (user.length())
return usedBy(user);
return deleteFont(name);
2026-10-13 01:52:34 +02:00
}
void appShowLive(const uint8_t *bitmap)
{
frames.setLiveBitmap(bitmap);
controller.showLive(millis());
}
void appStopLive()
{
controller.stopLive();
}
bool appLocalTime(LocalTime &time)
{
struct tm now = {};
if (!getLocalTime(&now, 0))
return false;
time.year = now.tm_year + 1900;
time.month = now.tm_mon + 1;
time.day = now.tm_mday;
time.weekday = now.tm_wday;
time.hour = now.tm_hour;
time.minute = now.tm_min;
return true;
}
2026-10-13 01:52:34 +02:00
void appRestartLater(uint32_t delay_ms)
{
restart_pending = true;
restart_at = millis() + delay_ms;
}
void appLoop()
{
if (restart_pending && (int32_t)(millis() - restart_at) >= 0)
{
Serial.println("Restarting for the new settings");
ESP.restart();
}
}