snapshot
This commit is contained in:
@@ -1,29 +1,19 @@
|
|||||||
#include "Dashboard.h"
|
#include "Dashboard.h"
|
||||||
|
|
||||||
// 16-bit color helpers
|
#define COL_BG 0x1082
|
||||||
#define COL_BG 0x1082 // dark charcoal
|
#define COL_BAR 0x2104
|
||||||
#define COL_BAR 0x2104 // slightly lighter
|
|
||||||
#define COL_RED 0xF800
|
#define COL_RED 0xF800
|
||||||
#define COL_ORANGE 0xFBE0
|
#define COL_ORANGE 0xFBE0
|
||||||
#define COL_GREEN 0x07E0
|
#define COL_GREEN 0x07E0
|
||||||
#define COL_CYAN 0x07FF
|
#define COL_CYAN 0x07FF
|
||||||
#define COL_BLUE 0x001F
|
|
||||||
#define COL_PURPLE 0x780F
|
#define COL_PURPLE 0x780F
|
||||||
#define COL_YELLOW 0xFFE0
|
|
||||||
#define COL_WHITE 0xFFFF
|
#define COL_WHITE 0xFFFF
|
||||||
#define COL_GRAY 0x8410
|
#define COL_GRAY 0x8410
|
||||||
#define COL_DARK_TILE 0x18E3
|
#define COL_DARK_TILE 0x18E3
|
||||||
|
|
||||||
// Tile color themes
|
|
||||||
static const uint16_t tileBG[] = {
|
static const uint16_t tileBG[] = {
|
||||||
COL_RED, // LAST_ALERT
|
COL_RED, COL_ORANGE, COL_CYAN, COL_PURPLE, COL_DARK_TILE, COL_DARK_TILE
|
||||||
COL_ORANGE, // STATS
|
|
||||||
COL_CYAN, // NETWORK
|
|
||||||
COL_PURPLE, // MUTE
|
|
||||||
COL_DARK_TILE, // HISTORY
|
|
||||||
COL_DARK_TILE // SYSTEM
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static const uint16_t tileFG[] = {
|
static const uint16_t tileFG[] = {
|
||||||
COL_WHITE, COL_WHITE, 0x0000, COL_WHITE, COL_WHITE, COL_WHITE
|
COL_WHITE, COL_WHITE, 0x0000, COL_WHITE, COL_WHITE, COL_WHITE
|
||||||
};
|
};
|
||||||
@@ -31,12 +21,12 @@ static const uint16_t tileFG[] = {
|
|||||||
Dashboard::Dashboard(TFT_eSPI& tft)
|
Dashboard::Dashboard(TFT_eSPI& tft)
|
||||||
: _tft(tft), _sprite(&tft)
|
: _tft(tft), _sprite(&tft)
|
||||||
{
|
{
|
||||||
_tiles[TILE_LAST_ALERT] = { "!", "LAST ALERT", "none", "", 0, 0, true };
|
_tiles[TILE_LAST_ALERT] = { "!", "LAST ALERT", "none", "", 0, 0, true };
|
||||||
_tiles[TILE_STATS] = { "#", "TODAY", "0 alerts", "", 0, 0, true };
|
_tiles[TILE_STATS] = { "#", "TODAY", "0 alerts", "", 0, 0, true };
|
||||||
_tiles[TILE_NETWORK] = { "~", "NETWORK", "---", "", 0, 0, true };
|
_tiles[TILE_NETWORK] = { "~", "NETWORK", "---", "", 0, 0, true };
|
||||||
_tiles[TILE_MUTE] = { "M", "MUTE", "OFF", "", 0, 0, true };
|
_tiles[TILE_MUTE] = { "M", "MUTE", "OFF", "", 0, 0, true };
|
||||||
_tiles[TILE_HISTORY] = { ">", "HISTORY", "tap to view","", 0, 0, true };
|
_tiles[TILE_HISTORY] = { ">", "HISTORY", "tap to view", "", 0, 0, true };
|
||||||
_tiles[TILE_SYSTEM] = { "*", "SYSTEM", "---", "", 0, 0, true };
|
_tiles[TILE_SYSTEM] = { "*", "SYSTEM", "---", "", 0, 0, true };
|
||||||
|
|
||||||
for (int i = 0; i < TILE_COUNT; i++) {
|
for (int i = 0; i < TILE_COUNT; i++) {
|
||||||
_tiles[i].bgColor = tileBG[i];
|
_tiles[i].bgColor = tileBG[i];
|
||||||
@@ -72,25 +62,21 @@ void Dashboard::drawTile(TileID id) {
|
|||||||
_sprite.fillSprite(t.bgColor);
|
_sprite.fillSprite(t.bgColor);
|
||||||
_sprite.drawRoundRect(0, 0, TILE_W, TILE_H, 8, COL_GRAY);
|
_sprite.drawRoundRect(0, 0, TILE_W, TILE_H, 8, COL_GRAY);
|
||||||
|
|
||||||
// Icon — large character at top
|
|
||||||
_sprite.setTextColor(t.fgColor, t.bgColor);
|
_sprite.setTextColor(t.fgColor, t.bgColor);
|
||||||
_sprite.setTextFont(4);
|
_sprite.setTextFont(4);
|
||||||
_sprite.setTextSize(2);
|
_sprite.setTextSize(2);
|
||||||
_sprite.setTextDatum(TC_DATUM);
|
_sprite.setTextDatum(TC_DATUM);
|
||||||
_sprite.drawString(t.icon, TILE_W / 2, 8);
|
_sprite.drawString(t.icon, TILE_W / 2, 8);
|
||||||
|
|
||||||
// Label
|
|
||||||
_sprite.setTextSize(1);
|
_sprite.setTextSize(1);
|
||||||
_sprite.setTextFont(2);
|
_sprite.setTextFont(2);
|
||||||
_sprite.setTextDatum(MC_DATUM);
|
_sprite.setTextDatum(MC_DATUM);
|
||||||
_sprite.drawString(t.label, TILE_W / 2, TILE_H / 2 + 5);
|
_sprite.drawString(t.label, TILE_W / 2, TILE_H / 2 + 5);
|
||||||
|
|
||||||
// Value
|
|
||||||
_sprite.setTextFont(2);
|
_sprite.setTextFont(2);
|
||||||
_sprite.setTextDatum(BC_DATUM);
|
_sprite.setTextDatum(BC_DATUM);
|
||||||
_sprite.drawString(t.value, TILE_W / 2, TILE_H - 25);
|
_sprite.drawString(t.value, TILE_W / 2, TILE_H - 25);
|
||||||
|
|
||||||
// Sub text
|
|
||||||
if (strlen(t.sub) > 0) {
|
if (strlen(t.sub) > 0) {
|
||||||
_sprite.setTextFont(1);
|
_sprite.setTextFont(1);
|
||||||
_sprite.setTextDatum(BC_DATUM);
|
_sprite.setTextDatum(BC_DATUM);
|
||||||
@@ -103,7 +89,6 @@ void Dashboard::drawTile(TileID id) {
|
|||||||
|
|
||||||
void Dashboard::drawTopBar(const char* time, int rssi, bool wifiOk) {
|
void Dashboard::drawTopBar(const char* time, int rssi, bool wifiOk) {
|
||||||
_tft.fillRect(0, 0, 480, DASH_TOP_BAR, COL_BAR);
|
_tft.fillRect(0, 0, 480, DASH_TOP_BAR, COL_BAR);
|
||||||
|
|
||||||
_tft.setTextColor(COL_WHITE, COL_BAR);
|
_tft.setTextColor(COL_WHITE, COL_BAR);
|
||||||
_tft.setTextFont(4);
|
_tft.setTextFont(4);
|
||||||
_tft.setTextSize(1);
|
_tft.setTextSize(1);
|
||||||
@@ -114,7 +99,6 @@ void Dashboard::drawTopBar(const char* time, int rssi, bool wifiOk) {
|
|||||||
_tft.setTextDatum(MR_DATUM);
|
_tft.setTextDatum(MR_DATUM);
|
||||||
_tft.drawString(time, 470, DASH_TOP_BAR / 2);
|
_tft.drawString(time, 470, DASH_TOP_BAR / 2);
|
||||||
|
|
||||||
// WiFi signal bars
|
|
||||||
int bars = 0;
|
int bars = 0;
|
||||||
if (wifiOk) {
|
if (wifiOk) {
|
||||||
if (rssi > -50) bars = 4;
|
if (rssi > -50) bars = 4;
|
||||||
@@ -122,7 +106,6 @@ void Dashboard::drawTopBar(const char* time, int rssi, bool wifiOk) {
|
|||||||
else if (rssi > -70) bars = 2;
|
else if (rssi > -70) bars = 2;
|
||||||
else bars = 1;
|
else bars = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int barX = 370, barW = 6, barGap = 3;
|
int barX = 370, barW = 6, barGap = 3;
|
||||||
for (int i = 0; i < 4; i++) {
|
for (int i = 0; i < 4; i++) {
|
||||||
int barH = 6 + i * 5;
|
int barH = 6 + i * 5;
|
||||||
@@ -131,26 +114,19 @@ void Dashboard::drawTopBar(const char* time, int rssi, bool wifiOk) {
|
|||||||
_tft.fillRect(barX + i * (barW + barGap), barY, barW, barH, col);
|
_tft.fillRect(barX + i * (barW + barGap), barY, barW, barH, col);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache values
|
|
||||||
strncpy(_barTime, time, sizeof(_barTime) - 1);
|
strncpy(_barTime, time, sizeof(_barTime) - 1);
|
||||||
_barRSSI = rssi;
|
_barRSSI = rssi;
|
||||||
_barWifiOk = wifiOk;
|
_barWifiOk = wifiOk;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Dashboard::updateTile(TileID id, const char* value, const char* sub) {
|
void Dashboard::updateTile(TileID id, const char* value, const char* sub) {
|
||||||
TileData& t = _tiles[id];
|
TileData& t = _tiles[id];
|
||||||
|
|
||||||
// Dirty check — skip redraw if nothing changed
|
|
||||||
bool changed = (strcmp(t.value, value) != 0);
|
bool changed = (strcmp(t.value, value) != 0);
|
||||||
if (sub && strcmp(t.sub, sub) != 0) changed = true;
|
if (sub && strcmp(t.sub, sub) != 0) changed = true;
|
||||||
if (!changed && !t.dirty) return;
|
if (!changed && !t.dirty) return;
|
||||||
|
|
||||||
strncpy(t.value, value, 31);
|
strncpy(t.value, value, 31); t.value[31] = '\0';
|
||||||
t.value[31] = '\0';
|
if (sub) { strncpy(t.sub, sub, 31); t.sub[31] = '\0'; }
|
||||||
if (sub) {
|
|
||||||
strncpy(t.sub, sub, 31);
|
|
||||||
t.sub[31] = '\0';
|
|
||||||
}
|
|
||||||
t.dirty = true;
|
t.dirty = true;
|
||||||
drawTile(id);
|
drawTile(id);
|
||||||
}
|
}
|
||||||
@@ -159,19 +135,13 @@ int Dashboard::handleTouch(int x, int y) {
|
|||||||
for (int i = 0; i < TILE_COUNT; i++) {
|
for (int i = 0; i < TILE_COUNT; i++) {
|
||||||
int tx, ty;
|
int tx, ty;
|
||||||
tilePosition((TileID)i, tx, ty);
|
tilePosition((TileID)i, tx, ty);
|
||||||
if (x >= tx && x < tx + TILE_W &&
|
if (x >= tx && x < tx + TILE_W && y >= ty && y < ty + TILE_H)
|
||||||
y >= ty && y < ty + TILE_H) {
|
|
||||||
return i;
|
return i;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// =====================================================================
|
|
||||||
// Refresh all tiles from ScreenState — only redraws changed tiles
|
|
||||||
// =====================================================================
|
|
||||||
void Dashboard::refreshFromState(const ScreenState& state) {
|
void Dashboard::refreshFromState(const ScreenState& state) {
|
||||||
// Top bar — only redraw if changed
|
|
||||||
bool barChanged = (strcmp(_barTime, state.timeString) != 0) ||
|
bool barChanged = (strcmp(_barTime, state.timeString) != 0) ||
|
||||||
(_barRSSI != state.wifiRSSI) ||
|
(_barRSSI != state.wifiRSSI) ||
|
||||||
(_barWifiOk != state.wifiConnected);
|
(_barWifiOk != state.wifiConnected);
|
||||||
@@ -179,7 +149,6 @@ void Dashboard::refreshFromState(const ScreenState& state) {
|
|||||||
drawTopBar(state.timeString, state.wifiRSSI, state.wifiConnected);
|
drawTopBar(state.timeString, state.wifiRSSI, state.wifiConnected);
|
||||||
}
|
}
|
||||||
|
|
||||||
// LAST ALERT tile
|
|
||||||
if (state.alertHistoryCount > 0) {
|
if (state.alertHistoryCount > 0) {
|
||||||
updateTile(TILE_LAST_ALERT,
|
updateTile(TILE_LAST_ALERT,
|
||||||
state.alertHistory[0].message,
|
state.alertHistory[0].message,
|
||||||
@@ -188,14 +157,12 @@ void Dashboard::refreshFromState(const ScreenState& state) {
|
|||||||
updateTile(TILE_LAST_ALERT, "none", "");
|
updateTile(TILE_LAST_ALERT, "none", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
// STATS tile
|
|
||||||
char statsBuf[32];
|
char statsBuf[32];
|
||||||
snprintf(statsBuf, sizeof(statsBuf), "%d alert%s",
|
snprintf(statsBuf, sizeof(statsBuf), "%d alert%s",
|
||||||
state.alertHistoryCount,
|
state.alertHistoryCount,
|
||||||
state.alertHistoryCount == 1 ? "" : "s");
|
state.alertHistoryCount == 1 ? "" : "s");
|
||||||
updateTile(TILE_STATS, statsBuf, "this session");
|
updateTile(TILE_STATS, statsBuf, "this session");
|
||||||
|
|
||||||
// NETWORK tile
|
|
||||||
if (state.wifiConnected) {
|
if (state.wifiConnected) {
|
||||||
char rssiBuf[16];
|
char rssiBuf[16];
|
||||||
snprintf(rssiBuf, sizeof(rssiBuf), "%d dBm", state.wifiRSSI);
|
snprintf(rssiBuf, sizeof(rssiBuf), "%d dBm", state.wifiRSSI);
|
||||||
@@ -204,10 +171,8 @@ void Dashboard::refreshFromState(const ScreenState& state) {
|
|||||||
updateTile(TILE_NETWORK, "DOWN", "reconnecting...");
|
updateTile(TILE_NETWORK, "DOWN", "reconnecting...");
|
||||||
}
|
}
|
||||||
|
|
||||||
// MUTE tile (placeholder)
|
|
||||||
updateTile(TILE_MUTE, "OFF", "tap to mute");
|
updateTile(TILE_MUTE, "OFF", "tap to mute");
|
||||||
|
|
||||||
// HISTORY tile — show 2nd and 3rd alerts
|
|
||||||
if (state.alertHistoryCount > 1) {
|
if (state.alertHistoryCount > 1) {
|
||||||
char histBuf[48];
|
char histBuf[48];
|
||||||
snprintf(histBuf, sizeof(histBuf), "%s %.20s",
|
snprintf(histBuf, sizeof(histBuf), "%s %.20s",
|
||||||
@@ -220,7 +185,6 @@ void Dashboard::refreshFromState(const ScreenState& state) {
|
|||||||
updateTile(TILE_HISTORY, "no history", "");
|
updateTile(TILE_HISTORY, "no history", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
// SYSTEM tile
|
|
||||||
char heapBuf[16];
|
char heapBuf[16];
|
||||||
snprintf(heapBuf, sizeof(heapBuf), "%lu KB", state.freeHeapKB);
|
snprintf(heapBuf, sizeof(heapBuf), "%lu KB", state.freeHeapKB);
|
||||||
char uptimeBuf[20];
|
char uptimeBuf[20];
|
||||||
|
|||||||
@@ -2,17 +2,13 @@
|
|||||||
#include <TFT_eSPI.h>
|
#include <TFT_eSPI.h>
|
||||||
#include "ScreenData.h"
|
#include "ScreenData.h"
|
||||||
|
|
||||||
// Grid layout constants
|
|
||||||
#define DASH_COLS 3
|
#define DASH_COLS 3
|
||||||
#define DASH_ROWS 2
|
#define DASH_ROWS 2
|
||||||
#define DASH_MARGIN 8
|
#define DASH_MARGIN 8
|
||||||
#define DASH_TOP_BAR 40
|
#define DASH_TOP_BAR 40
|
||||||
|
#define TILE_W ((480 - (DASH_COLS + 1) * DASH_MARGIN) / DASH_COLS)
|
||||||
|
#define TILE_H ((320 - DASH_TOP_BAR - (DASH_ROWS + 1) * DASH_MARGIN) / DASH_ROWS)
|
||||||
|
|
||||||
// Tile dimensions (calculated for 480x320)
|
|
||||||
#define TILE_W ((480 - (DASH_COLS + 1) * DASH_MARGIN) / DASH_COLS) // ~148
|
|
||||||
#define TILE_H ((320 - DASH_TOP_BAR - (DASH_ROWS + 1) * DASH_MARGIN) / DASH_ROWS) // ~128
|
|
||||||
|
|
||||||
// Tile IDs
|
|
||||||
enum TileID : uint8_t {
|
enum TileID : uint8_t {
|
||||||
TILE_LAST_ALERT = 0,
|
TILE_LAST_ALERT = 0,
|
||||||
TILE_STATS,
|
TILE_STATS,
|
||||||
@@ -37,13 +33,11 @@ class Dashboard {
|
|||||||
public:
|
public:
|
||||||
Dashboard(TFT_eSPI& tft);
|
Dashboard(TFT_eSPI& tft);
|
||||||
|
|
||||||
void begin(); // create sprite (call once)
|
void begin();
|
||||||
void drawAll(); // fill screen + draw all tiles
|
void drawAll();
|
||||||
void drawTopBar(const char* time, int rssi, bool wifiOk);
|
void drawTopBar(const char* time, int rssi, bool wifiOk);
|
||||||
void updateTile(TileID id, const char* value, const char* sub = nullptr);
|
void updateTile(TileID id, const char* value, const char* sub = nullptr);
|
||||||
int handleTouch(int x, int y); // returns TileID or -1
|
int handleTouch(int x, int y);
|
||||||
|
|
||||||
// Populate tiles from ScreenState — only redraws changed tiles
|
|
||||||
void refreshFromState(const ScreenState& state);
|
void refreshFromState(const ScreenState& state);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -51,7 +45,6 @@ private:
|
|||||||
TFT_eSprite _sprite;
|
TFT_eSprite _sprite;
|
||||||
TileData _tiles[TILE_COUNT];
|
TileData _tiles[TILE_COUNT];
|
||||||
|
|
||||||
// Cached top bar values for dirty check
|
|
||||||
char _barTime[12] = "";
|
char _barTime[12] = "";
|
||||||
int _barRSSI = 0;
|
int _barRSSI = 0;
|
||||||
bool _barWifiOk = false;
|
bool _barWifiOk = false;
|
||||||
|
|||||||
@@ -9,10 +9,12 @@ DisplayManager::DisplayManager()
|
|||||||
void DisplayManager::begin() {
|
void DisplayManager::begin() {
|
||||||
pinMode(PIN_LCD_BL, OUTPUT);
|
pinMode(PIN_LCD_BL, OUTPUT);
|
||||||
setBacklight(true);
|
setBacklight(true);
|
||||||
|
|
||||||
_tft.init();
|
_tft.init();
|
||||||
_tft.setRotation(1); // landscape: 480x320
|
_tft.setRotation(1);
|
||||||
_tft.fillScreen(COL_BLACK);
|
_tft.fillScreen(COL_BLACK);
|
||||||
|
|
||||||
|
uint16_t calData[5] = { 300, 3600, 300, 3600, 1 };
|
||||||
|
_tft.setTouch(calData);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DisplayManager::setBacklight(bool on) {
|
void DisplayManager::setBacklight(bool on) {
|
||||||
@@ -35,10 +37,53 @@ int DisplayManager::dashboardTouch(uint16_t x, uint16_t y) {
|
|||||||
return _dash.handleTouch(x, y);
|
return _dash.handleTouch(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// =====================================================================
|
||||||
|
// Hold detection
|
||||||
|
// =====================================================================
|
||||||
|
HoldState DisplayManager::updateHold(unsigned long requiredMs) {
|
||||||
|
HoldState h;
|
||||||
|
h.targetMs = requiredMs;
|
||||||
|
|
||||||
|
uint16_t tx, ty;
|
||||||
|
bool touching = _tft.getTouch(&tx, &ty);
|
||||||
|
|
||||||
|
if (touching) {
|
||||||
|
if (!_holdActive) {
|
||||||
|
_holdActive = true;
|
||||||
|
_holdStartMs = millis();
|
||||||
|
_holdX = tx;
|
||||||
|
_holdY = ty;
|
||||||
|
}
|
||||||
|
h.active = true;
|
||||||
|
h.x = _holdX;
|
||||||
|
h.y = _holdY;
|
||||||
|
h.holdMs = millis() - _holdStartMs;
|
||||||
|
_holdProgress = min((float)h.holdMs / (float)requiredMs, 1.0f);
|
||||||
|
|
||||||
|
if (h.holdMs >= requiredMs) {
|
||||||
|
h.completed = true;
|
||||||
|
_holdActive = false;
|
||||||
|
_holdProgress = 0.0f;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_holdActive = false;
|
||||||
|
_holdProgress = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
// =====================================================================
|
||||||
|
// Render
|
||||||
|
// =====================================================================
|
||||||
void DisplayManager::render(const ScreenState& state) {
|
void DisplayManager::render(const ScreenState& state) {
|
||||||
// Detect screen change → force full redraw
|
|
||||||
if (state.screen != _lastScreen) {
|
if (state.screen != _lastScreen) {
|
||||||
_needsFullRedraw = true;
|
_needsFullRedraw = true;
|
||||||
|
if (state.screen == ScreenID::OFF) {
|
||||||
|
setBacklight(false);
|
||||||
|
} else if (_lastScreen == ScreenID::OFF) {
|
||||||
|
setBacklight(true);
|
||||||
|
}
|
||||||
_lastScreen = state.screen;
|
_lastScreen = state.screen;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,6 +105,9 @@ void DisplayManager::render(const ScreenState& state) {
|
|||||||
drawAlertScreen(state);
|
drawAlertScreen(state);
|
||||||
_lastBlink = state.blinkPhase;
|
_lastBlink = state.blinkPhase;
|
||||||
}
|
}
|
||||||
|
if (_holdProgress > 0.0f) {
|
||||||
|
drawSilenceProgress(_holdProgress);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case ScreenID::STATUS:
|
case ScreenID::STATUS:
|
||||||
if (_needsFullRedraw) drawStatusScreen(state);
|
if (_needsFullRedraw) drawStatusScreen(state);
|
||||||
@@ -68,42 +116,76 @@ void DisplayManager::render(const ScreenState& state) {
|
|||||||
drawDashboard(state);
|
drawDashboard(state);
|
||||||
break;
|
break;
|
||||||
case ScreenID::OFF:
|
case ScreenID::OFF:
|
||||||
|
if (_needsFullRedraw) {
|
||||||
|
_tft.fillScreen(COL_BLACK);
|
||||||
|
_dashSpriteReady = false;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
_needsFullRedraw = false;
|
_needsFullRedraw = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----- Dashboard -----
|
// =====================================================================
|
||||||
|
// Dashboard
|
||||||
|
// =====================================================================
|
||||||
void DisplayManager::drawDashboard(const ScreenState& s) {
|
void DisplayManager::drawDashboard(const ScreenState& s) {
|
||||||
if (_needsFullRedraw) {
|
if (_needsFullRedraw) {
|
||||||
if (!_dashSpriteReady) {
|
if (!_dashSpriteReady) {
|
||||||
_dash.begin(); // create sprite (once)
|
_dash.begin();
|
||||||
_dashSpriteReady = true;
|
_dashSpriteReady = true;
|
||||||
}
|
}
|
||||||
_dash.drawAll(); // fill screen + draw all tiles
|
_dash.drawAll();
|
||||||
_dash.refreshFromState(s); // update with real data
|
_dash.refreshFromState(s);
|
||||||
_lastDashRefresh = millis();
|
_lastDashRefresh = millis();
|
||||||
} else if (millis() - _lastDashRefresh > 2000) {
|
} else if (millis() - _lastDashRefresh > 2000) {
|
||||||
_lastDashRefresh = millis();
|
_lastDashRefresh = millis();
|
||||||
_dash.refreshFromState(s); // only redraws changed tiles
|
_dash.refreshFromState(s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----- Helpers -----
|
// =====================================================================
|
||||||
|
// Silence progress bar
|
||||||
|
// =====================================================================
|
||||||
|
void DisplayManager::drawSilenceProgress(float progress) {
|
||||||
|
int barX = 10;
|
||||||
|
int barY = SCREEN_HEIGHT - 35;
|
||||||
|
int barW = SCREEN_WIDTH - 20;
|
||||||
|
int barH = 25;
|
||||||
|
int fillW = (int)(barW * progress);
|
||||||
|
|
||||||
|
_tft.fillRect(barX, barY, barW, barH, COL_BLACK);
|
||||||
|
if (fillW > 0) {
|
||||||
|
_tft.fillRect(barX, barY, fillW, barH, COL_GREEN);
|
||||||
|
}
|
||||||
|
_tft.drawRect(barX, barY, barW, barH, COL_WHITE);
|
||||||
|
|
||||||
|
_tft.setTextFont(1);
|
||||||
|
_tft.setTextSize(2);
|
||||||
|
_tft.setTextDatum(MC_DATUM);
|
||||||
|
if (progress >= 1.0f) {
|
||||||
|
_tft.setTextColor(COL_BLACK);
|
||||||
|
_tft.drawString("SILENCED", SCREEN_WIDTH / 2, barY + barH / 2);
|
||||||
|
} else {
|
||||||
|
_tft.setTextColor(COL_WHITE);
|
||||||
|
_tft.drawString("HOLD TO SILENCE", SCREEN_WIDTH / 2, barY + barH / 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =====================================================================
|
||||||
|
// Helpers
|
||||||
|
// =====================================================================
|
||||||
void DisplayManager::drawCentered(const char* txt, int y, int sz, uint16_t col) {
|
void DisplayManager::drawCentered(const char* txt, int y, int sz, uint16_t col) {
|
||||||
_tft.setTextFont(1); // ← ADDED: force GLCD font
|
_tft.setTextFont(1);
|
||||||
_tft.setTextSize(sz);
|
_tft.setTextSize(sz);
|
||||||
_tft.setTextColor(col);
|
_tft.setTextColor(col);
|
||||||
int w = _tft.textWidth(txt); // ← use TFT_eSPI's own width calc
|
int w = _tft.textWidth(txt);
|
||||||
_tft.setCursor(max(0, (SCREEN_WIDTH - w) / 2), y);
|
_tft.setCursor(max(0, (SCREEN_WIDTH - w) / 2), y);
|
||||||
_tft.print(txt);
|
_tft.print(txt);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DisplayManager::drawInfoLine(int x, int y, uint16_t col, const char* text) {
|
void DisplayManager::drawInfoLine(int x, int y, uint16_t col, const char* text) {
|
||||||
_tft.setTextFont(1); // ← ADDED
|
_tft.setTextFont(1);
|
||||||
_tft.setTextSize(1);
|
_tft.setTextSize(1);
|
||||||
_tft.setTextColor(col);
|
_tft.setTextColor(col);
|
||||||
_tft.setCursor(x, y);
|
_tft.setCursor(x, y);
|
||||||
@@ -111,23 +193,24 @@ void DisplayManager::drawInfoLine(int x, int y, uint16_t col, const char* text)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void DisplayManager::drawHeaderBar(uint16_t col, const char* label, const char* timeStr) {
|
void DisplayManager::drawHeaderBar(uint16_t col, const char* label, const char* timeStr) {
|
||||||
_tft.setTextFont(1); // ← ADDED
|
_tft.setTextFont(1);
|
||||||
_tft.setTextSize(2);
|
_tft.setTextSize(2);
|
||||||
_tft.setTextColor(col);
|
_tft.setTextColor(col);
|
||||||
_tft.setCursor(8, 8);
|
_tft.setCursor(8, 8);
|
||||||
_tft.print(label);
|
_tft.print(label);
|
||||||
|
int tw = _tft.textWidth(timeStr);
|
||||||
int tw = _tft.textWidth(timeStr); // ← proper width calc
|
|
||||||
_tft.setCursor(SCREEN_WIDTH - tw - 8, 8);
|
_tft.setCursor(SCREEN_WIDTH - tw - 8, 8);
|
||||||
_tft.print(timeStr);
|
_tft.print(timeStr);
|
||||||
}
|
}
|
||||||
// ----- Screens -----
|
|
||||||
|
|
||||||
|
// =====================================================================
|
||||||
|
// Screens
|
||||||
|
// =====================================================================
|
||||||
void DisplayManager::drawBootSplash(const ScreenState& s) {
|
void DisplayManager::drawBootSplash(const ScreenState& s) {
|
||||||
_tft.fillScreen(COL_BLACK);
|
_tft.fillScreen(COL_BLACK);
|
||||||
drawCentered("KLUBHAUS", 60, 4, COL_NEON_TEAL);
|
drawCentered("KLUBHAUS", 60, 4, COL_NEON_TEAL);
|
||||||
drawCentered("ALERT", 110, 4, COL_HOT_FUCHSIA);
|
drawCentered("ALERT", 110, 4, COL_HOT_FUCHSIA);
|
||||||
drawCentered("v5.0 E32R35T", 180, 2, COL_DARK_GRAY);
|
drawCentered("v5.1 E32R35T", 180, 2, COL_DARK_GRAY);
|
||||||
if (s.debugMode) {
|
if (s.debugMode) {
|
||||||
drawCentered("DEBUG MODE", 210, 2, COL_YELLOW);
|
drawCentered("DEBUG MODE", 210, 2, COL_YELLOW);
|
||||||
}
|
}
|
||||||
@@ -179,26 +262,25 @@ void DisplayManager::drawAlertScreen(const ScreenState& s) {
|
|||||||
drawCentered(s.alertMessage, (SCREEN_HEIGHT - 8 * sz) / 2, sz, fg);
|
drawCentered(s.alertMessage, (SCREEN_HEIGHT - 8 * sz) / 2, sz, fg);
|
||||||
}
|
}
|
||||||
|
|
||||||
drawCentered("TAP TO SILENCE", SCREEN_HEIGHT - 25, 2, fg);
|
// Only show hint text if not currently holding
|
||||||
|
if (_holdProgress == 0.0f) {
|
||||||
|
drawCentered("HOLD TO SILENCE", SCREEN_HEIGHT - 25, 2, fg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DisplayManager::drawStatusScreen(const ScreenState& s) {
|
void DisplayManager::drawStatusScreen(const ScreenState& s) {
|
||||||
_tft.fillScreen(COL_BLACK);
|
_tft.fillScreen(COL_BLACK);
|
||||||
drawHeaderBar(COL_MINT, "KLUBHAUS", s.timeString);
|
drawHeaderBar(COL_MINT, "KLUBHAUS", s.timeString);
|
||||||
|
|
||||||
drawCentered("MONITORING", 60, 3, COL_WHITE);
|
drawCentered("MONITORING", 60, 3, COL_WHITE);
|
||||||
|
|
||||||
char buf[80];
|
char buf[80];
|
||||||
int y = 110;
|
int y = 110, sp = 22, x = 20;
|
||||||
int sp = 22;
|
|
||||||
int x = 20;
|
|
||||||
|
|
||||||
snprintf(buf, sizeof(buf), "WiFi: %s (%ddBm)",
|
snprintf(buf, sizeof(buf), "WiFi: %s (%ddBm)",
|
||||||
s.wifiConnected ? s.wifiSSID : "DOWN", s.wifiRSSI);
|
s.wifiConnected ? s.wifiSSID : "DOWN", s.wifiRSSI);
|
||||||
drawInfoLine(x, y, COL_WHITE, buf); y += sp;
|
drawInfoLine(x, y, COL_WHITE, buf); y += sp;
|
||||||
|
|
||||||
snprintf(buf, sizeof(buf), "IP: %s",
|
snprintf(buf, sizeof(buf), "IP: %s", s.wifiConnected ? s.wifiIP : "---");
|
||||||
s.wifiConnected ? s.wifiIP : "---");
|
|
||||||
drawInfoLine(x, y, COL_WHITE, buf); y += sp;
|
drawInfoLine(x, y, COL_WHITE, buf); y += sp;
|
||||||
|
|
||||||
snprintf(buf, sizeof(buf), "Up: %lu min Heap: %lu KB",
|
snprintf(buf, sizeof(buf), "Up: %lu min Heap: %lu KB",
|
||||||
@@ -222,66 +304,13 @@ void DisplayManager::drawStatusScreen(const ScreenState& s) {
|
|||||||
for (int i = 0; i < s.alertHistoryCount; i++) {
|
for (int i = 0; i < s.alertHistoryCount; i++) {
|
||||||
uint16_t col = (i == 0) ? COL_YELLOW : COL_DARK_GRAY;
|
uint16_t col = (i == 0) ? COL_YELLOW : COL_DARK_GRAY;
|
||||||
snprintf(buf, sizeof(buf), "%s %.35s",
|
snprintf(buf, sizeof(buf), "%s %.35s",
|
||||||
s.alertHistory[i].timestamp,
|
s.alertHistory[i].timestamp, s.alertHistory[i].message);
|
||||||
s.alertHistory[i].message);
|
|
||||||
drawInfoLine(x, y, col, buf); y += sp;
|
drawInfoLine(x, y, col, buf); y += sp;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
drawInfoLine(x, y, COL_DARK_GRAY, "No alerts yet"); y += sp;
|
drawInfoLine(x, y, COL_DARK_GRAY, "No alerts yet"); y += sp;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (s.debugMode) {
|
|
||||||
drawInfoLine(x, y, COL_YELLOW, "DEBUG MODE - _test topics");
|
|
||||||
}
|
|
||||||
|
|
||||||
drawCentered("tap to dismiss", SCREEN_HEIGHT - 18, 1, COL_DARK_GRAY);
|
drawCentered("tap to dismiss", SCREEN_HEIGHT - 18, 1, COL_DARK_GRAY);
|
||||||
}
|
}
|
||||||
|
|
||||||
HoldState DisplayManager::updateHold(unsigned long requiredMs) {
|
|
||||||
HoldState h;
|
|
||||||
h.targetMs = requiredMs;
|
|
||||||
|
|
||||||
uint16_t tx, ty;
|
|
||||||
bool touching = _tft.getTouch(&tx, &ty);
|
|
||||||
|
|
||||||
if (touching) {
|
|
||||||
if (!_holdActive) {
|
|
||||||
_holdActive = true;
|
|
||||||
_holdStartMs = millis();
|
|
||||||
_holdX = tx;
|
|
||||||
_holdY = ty;
|
|
||||||
}
|
|
||||||
h.active = true;
|
|
||||||
h.x = _holdX;
|
|
||||||
h.y = _holdY;
|
|
||||||
h.holdMs = millis() - _holdStartMs;
|
|
||||||
|
|
||||||
if (h.holdMs >= requiredMs) {
|
|
||||||
h.completed = true;
|
|
||||||
_holdActive = false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_holdActive = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return h;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DisplayManager::drawSilenceProgress(float progress) {
|
|
||||||
int barY = SCREEN_HEIGHT - 30;
|
|
||||||
int barH = 20;
|
|
||||||
int barW = (int)(SCREEN_WIDTH * progress);
|
|
||||||
|
|
||||||
_tft.fillRect(0, barY, SCREEN_WIDTH, barH, COL_BLACK);
|
|
||||||
_tft.fillRect(0, barY, barW, barH, COL_GREEN);
|
|
||||||
_tft.drawRect(0, barY, SCREEN_WIDTH, barH, COL_WHITE);
|
|
||||||
|
|
||||||
_tft.setTextFont(1);
|
|
||||||
_tft.setTextSize(2);
|
|
||||||
_tft.setTextColor(progress < 1.0f ? COL_WHITE : COL_BLACK);
|
|
||||||
_tft.setTextDatum(MC_DATUM);
|
|
||||||
_tft.drawString(
|
|
||||||
progress < 1.0f ? "HOLD TO SILENCE" : "SILENCED",
|
|
||||||
SCREEN_WIDTH / 2, barY + barH / 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,14 +3,13 @@
|
|||||||
#include "ScreenData.h"
|
#include "ScreenData.h"
|
||||||
#include "Dashboard.h"
|
#include "Dashboard.h"
|
||||||
|
|
||||||
// Hold gesture result
|
|
||||||
struct HoldState {
|
struct HoldState {
|
||||||
bool active = false; // finger currently down
|
bool active = false;
|
||||||
bool completed = false; // hold duration met
|
bool completed = false;
|
||||||
uint16_t x = 0;
|
uint16_t x = 0;
|
||||||
uint16_t y = 0;
|
uint16_t y = 0;
|
||||||
unsigned long holdMs = 0; // how long held so far
|
unsigned long holdMs = 0;
|
||||||
unsigned long targetMs = 0; // required hold duration
|
unsigned long targetMs = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class DisplayManager {
|
class DisplayManager {
|
||||||
@@ -21,29 +20,25 @@ public:
|
|||||||
void setBacklight(bool on);
|
void setBacklight(bool on);
|
||||||
TouchEvent readTouch();
|
TouchEvent readTouch();
|
||||||
|
|
||||||
// Dashboard tile touch — returns TileID or -1
|
int dashboardTouch(uint16_t x, uint16_t y);
|
||||||
int dashboardTouch(uint16_t x, uint16_t y);
|
|
||||||
|
|
||||||
// Hold detection — call each loop iteration
|
|
||||||
HoldState updateHold(unsigned long requiredMs);
|
HoldState updateHold(unsigned long requiredMs);
|
||||||
|
|
||||||
// Draw hold-to-silence progress bar on alert screen
|
|
||||||
void drawSilenceProgress(float progress);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
TFT_eSPI _tft;
|
TFT_eSPI _tft;
|
||||||
Dashboard _dash;
|
Dashboard _dash;
|
||||||
ScreenID _lastScreen = ScreenID::BOOT_SPLASH;
|
|
||||||
bool _needsFullRedraw = true;
|
ScreenID _lastScreen = ScreenID::BOOT_SPLASH;
|
||||||
bool _lastBlink = false;
|
bool _needsFullRedraw = true;
|
||||||
bool _dashSpriteReady = false;
|
bool _lastBlink = false;
|
||||||
|
bool _dashSpriteReady = false;
|
||||||
unsigned long _lastDashRefresh = 0;
|
unsigned long _lastDashRefresh = 0;
|
||||||
|
|
||||||
// Hold tracking state
|
// Hold tracking
|
||||||
bool _holdActive = false;
|
bool _holdActive = false;
|
||||||
unsigned long _holdStartMs = 0;
|
unsigned long _holdStartMs = 0;
|
||||||
uint16_t _holdX = 0;
|
uint16_t _holdX = 0;
|
||||||
uint16_t _holdY = 0;
|
uint16_t _holdY = 0;
|
||||||
|
float _holdProgress = 0.0f;
|
||||||
|
|
||||||
// Colors
|
// Colors
|
||||||
static constexpr uint16_t COL_NEON_TEAL = 0x07D7;
|
static constexpr uint16_t COL_NEON_TEAL = 0x07D7;
|
||||||
@@ -64,6 +59,7 @@ private:
|
|||||||
void drawAlertScreen(const ScreenState& s);
|
void drawAlertScreen(const ScreenState& s);
|
||||||
void drawStatusScreen(const ScreenState& s);
|
void drawStatusScreen(const ScreenState& s);
|
||||||
void drawDashboard(const ScreenState& s);
|
void drawDashboard(const ScreenState& s);
|
||||||
|
void drawSilenceProgress(float progress);
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
void drawCentered(const char* txt, int y, int sz, uint16_t col);
|
void drawCentered(const char* txt, int y, int sz, uint16_t col);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#include "DoorbellLogic.h"
|
#include "DoorbellLogic.h"
|
||||||
|
|
||||||
// =====================================================================
|
// =====================================================================
|
||||||
// Lifecycle
|
// Lifecycle
|
||||||
// =====================================================================
|
// =====================================================================
|
||||||
void DoorbellLogic::begin() {
|
void DoorbellLogic::begin() {
|
||||||
_bootTime = millis();
|
_bootTime = millis();
|
||||||
@@ -13,12 +13,12 @@ void DoorbellLogic::begin() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void DoorbellLogic::beginWiFi() {
|
void DoorbellLogic::beginWiFi() {
|
||||||
_instance = this; // ← MISSING
|
_instance = this;
|
||||||
|
|
||||||
WiFi.mode(WIFI_STA);
|
WiFi.mode(WIFI_STA);
|
||||||
WiFi.setSleep(false);
|
WiFi.setSleep(false);
|
||||||
WiFi.setAutoReconnect(true); // ← MISSING
|
WiFi.setAutoReconnect(true);
|
||||||
WiFi.onEvent(onWiFiEvent); // ← MISSING
|
WiFi.onEvent(onWiFiEvent);
|
||||||
|
|
||||||
for (int i = 0; i < NUM_WIFI; i++) {
|
for (int i = 0; i < NUM_WIFI; i++) {
|
||||||
_wifiMulti.addAP(wifiNetworks[i].ssid, wifiNetworks[i].pass);
|
_wifiMulti.addAP(wifiNetworks[i].ssid, wifiNetworks[i].pass);
|
||||||
@@ -78,16 +78,16 @@ void DoorbellLogic::finishBoot() {
|
|||||||
flushStatus();
|
flushStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
Serial.printf("[CONFIG] ALERT_URL: %s\n", ALERT_URL);
|
Serial.printf("[CONFIG] ALERT_URL: %s\n", ALERT_URL);
|
||||||
Serial.printf("[CONFIG] SILENCE_URL: %s\n", SILENCE_URL);
|
Serial.printf("[CONFIG] SILENCE_URL: %s\n", SILENCE_URL);
|
||||||
Serial.printf("[CONFIG] ADMIN_URL: %s\n", ADMIN_URL);
|
Serial.printf("[CONFIG] ADMIN_URL: %s\n", ADMIN_URL);
|
||||||
|
|
||||||
transitionTo(DeviceState::SILENT);
|
transitionTo(DeviceState::SILENT);
|
||||||
Serial.printf("[BOOT] Grace period: %d ms\n", BOOT_GRACE_MS);
|
Serial.printf("[BOOT] Grace period: %d ms\n", BOOT_GRACE_MS);
|
||||||
}
|
}
|
||||||
|
|
||||||
// =====================================================================
|
// =====================================================================
|
||||||
// Main Update Loop
|
// Main Update Loop
|
||||||
// =====================================================================
|
// =====================================================================
|
||||||
void DoorbellLogic::update() {
|
void DoorbellLogic::update() {
|
||||||
unsigned long now = millis();
|
unsigned long now = millis();
|
||||||
@@ -117,11 +117,6 @@ void DoorbellLogic::update() {
|
|||||||
|
|
||||||
switch (_state) {
|
switch (_state) {
|
||||||
case DeviceState::ALERTING:
|
case DeviceState::ALERTING:
|
||||||
// if (now - _alertStart > ALERT_TIMEOUT_MS) {
|
|
||||||
// Serial.println("[ALERT] Timeout — auto-silencing");
|
|
||||||
// handleSilence("timeout");
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
if (now - _lastBlink >= BLINK_INTERVAL_MS) {
|
if (now - _lastBlink >= BLINK_INTERVAL_MS) {
|
||||||
_lastBlink = now;
|
_lastBlink = now;
|
||||||
_blinkState = !_blinkState;
|
_blinkState = !_blinkState;
|
||||||
@@ -138,32 +133,32 @@ void DoorbellLogic::update() {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (now - _lastHeartbeat >= HEARTBEAT_INTERVAL_MS) {
|
if (now - _lastHeartbeat >= HEARTBEAT_INTERVAL_MS) {
|
||||||
_lastHeartbeat = now;
|
_lastHeartbeat = now;
|
||||||
uint32_t heap = ESP.getFreeHeap();
|
uint32_t heap = ESP.getFreeHeap();
|
||||||
Serial.printf("[%lus] %s | WiFi:%s RSSI:%d | heap:%dKB | minHeap:%dKB\n",
|
Serial.printf("[%lus] %s | WiFi:%s RSSI:%d | heap:%dKB | minHeap:%dKB\n",
|
||||||
now / 1000,
|
now / 1000,
|
||||||
_state == DeviceState::SILENT ? "SILENT" :
|
_state == DeviceState::SILENT ? "SILENT" :
|
||||||
_state == DeviceState::ALERTING ? "ALERT" : "WAKE",
|
_state == DeviceState::ALERTING ? "ALERT" : "WAKE",
|
||||||
WiFi.isConnected() ? "OK" : "DOWN",
|
WiFi.isConnected() ? "OK" : "DOWN",
|
||||||
WiFi.RSSI(),
|
WiFi.RSSI(),
|
||||||
heap / 1024,
|
heap / 1024,
|
||||||
ESP.getMinFreeHeap() / 1024);
|
ESP.getMinFreeHeap() / 1024);
|
||||||
|
|
||||||
if (heap < 20000) {
|
if (heap < 20000) {
|
||||||
Serial.println("[HEAP] CRITICAL — rebooting!");
|
Serial.println("[HEAP] CRITICAL — rebooting!");
|
||||||
queueStatus("REBOOTING", "low heap");
|
queueStatus("REBOOTING", "low heap");
|
||||||
flushStatus();
|
flushStatus();
|
||||||
delay(200);
|
delay(200);
|
||||||
ESP.restart();
|
ESP.restart();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
updateScreenState();
|
updateScreenState();
|
||||||
}
|
}
|
||||||
|
|
||||||
// =====================================================================
|
// =====================================================================
|
||||||
// Input Events
|
// Input Events
|
||||||
// =====================================================================
|
// =====================================================================
|
||||||
void DoorbellLogic::onTouch(const TouchEvent& evt) {
|
void DoorbellLogic::onTouch(const TouchEvent& evt) {
|
||||||
if (!evt.pressed) return;
|
if (!evt.pressed) return;
|
||||||
@@ -196,7 +191,7 @@ void DoorbellLogic::onSerialCommand(const String& cmd) {
|
|||||||
checkNetwork();
|
checkNetwork();
|
||||||
} else if (cmd == "STATUS") {
|
} else if (cmd == "STATUS") {
|
||||||
Serial.printf("[CMD] State:%s WiFi:%s RSSI:%d Heap:%dKB NTP:%s\n",
|
Serial.printf("[CMD] State:%s WiFi:%s RSSI:%d Heap:%dKB NTP:%s\n",
|
||||||
_state == DeviceState::SILENT ? "SILENT" :
|
_state == DeviceState::SILENT ? "SILENT" :
|
||||||
_state == DeviceState::ALERTING ? "ALERT" : "WAKE",
|
_state == DeviceState::ALERTING ? "ALERT" : "WAKE",
|
||||||
WiFi.isConnected() ? WiFi.SSID().c_str() : "DOWN",
|
WiFi.isConnected() ? WiFi.SSID().c_str() : "DOWN",
|
||||||
WiFi.RSSI(),
|
WiFi.RSSI(),
|
||||||
@@ -218,7 +213,7 @@ void DoorbellLogic::onSerialCommand(const String& cmd) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// =====================================================================
|
// =====================================================================
|
||||||
// State Transitions
|
// State Transitions
|
||||||
// =====================================================================
|
// =====================================================================
|
||||||
void DoorbellLogic::transitionTo(DeviceState newState) {
|
void DoorbellLogic::transitionTo(DeviceState newState) {
|
||||||
_state = newState;
|
_state = newState;
|
||||||
@@ -232,28 +227,27 @@ void DoorbellLogic::transitionTo(DeviceState newState) {
|
|||||||
break;
|
break;
|
||||||
case DeviceState::ALERTING:
|
case DeviceState::ALERTING:
|
||||||
_alertStart = now;
|
_alertStart = now;
|
||||||
_lastBlink = now;
|
_lastBlink = now;
|
||||||
_blinkState = false;
|
_blinkState = false;
|
||||||
_screen.screen = ScreenID::ALERT;
|
_screen.screen = ScreenID::ALERT;
|
||||||
Serial.printf("-> ALERTING: %s\n", _currentMessage.c_str());
|
Serial.printf("-> ALERTING: %s\n", _currentMessage.c_str());
|
||||||
break;
|
break;
|
||||||
case DeviceState::WAKE:
|
case DeviceState::WAKE:
|
||||||
_wakeStart = now;
|
_wakeStart = now;
|
||||||
_screen.screen = ScreenID::DASHBOARD;
|
_screen.screen = ScreenID::DASHBOARD; // ← CHANGED from STATUS
|
||||||
Serial.println("-> WAKE (dashboard)");
|
Serial.println("-> WAKE (dashboard)"); // ← CHANGED
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// =====================================================================
|
// =====================================================================
|
||||||
// Message Handlers
|
// Message Handlers
|
||||||
// =====================================================================
|
// =====================================================================
|
||||||
void DoorbellLogic::handleAlert(const String& msg) {
|
void DoorbellLogic::handleAlert(const String& msg) {
|
||||||
if (_state == DeviceState::ALERTING && _currentMessage == msg) return;
|
if (_state == DeviceState::ALERTING && _currentMessage == msg) return;
|
||||||
_currentMessage = msg;
|
_currentMessage = msg;
|
||||||
_alertMsgEpoch = _lastParsedMsgEpoch;
|
_alertMsgEpoch = _lastParsedMsgEpoch;
|
||||||
|
|
||||||
// Push into history (shift older entries down)
|
|
||||||
for (int i = ALERT_HISTORY_SIZE - 1; i > 0; i--) {
|
for (int i = ALERT_HISTORY_SIZE - 1; i > 0; i--) {
|
||||||
_screen.alertHistory[i] = _screen.alertHistory[i - 1];
|
_screen.alertHistory[i] = _screen.alertHistory[i - 1];
|
||||||
}
|
}
|
||||||
@@ -278,8 +272,6 @@ void DoorbellLogic::handleSilence(const String& msg) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If this came from ntfy poll (_lastParsedMsgEpoch > 0), reject if it
|
|
||||||
// predates or equals the alert. Both timestamps are from ntfy's server clock.
|
|
||||||
if (_lastParsedMsgEpoch > 0 && _alertMsgEpoch > 0 &&
|
if (_lastParsedMsgEpoch > 0 && _alertMsgEpoch > 0 &&
|
||||||
_lastParsedMsgEpoch <= _alertMsgEpoch) {
|
_lastParsedMsgEpoch <= _alertMsgEpoch) {
|
||||||
Serial.printf("[SILENCE] Ignored — predates alert (silence:%ld <= alert:%ld)\n",
|
Serial.printf("[SILENCE] Ignored — predates alert (silence:%ld <= alert:%ld)\n",
|
||||||
@@ -298,13 +290,13 @@ void DoorbellLogic::handleSilence(const String& msg) {
|
|||||||
void DoorbellLogic::handleAdmin(const String& msg) {
|
void DoorbellLogic::handleAdmin(const String& msg) {
|
||||||
Serial.printf("[ADMIN] %s\n", msg.c_str());
|
Serial.printf("[ADMIN] %s\n", msg.c_str());
|
||||||
|
|
||||||
if (msg == "SILENCE") handleSilence("admin");
|
if (msg == "SILENCE") handleSilence("admin");
|
||||||
else if (msg == "PING") queueStatus("PONG", "ping");
|
else if (msg == "PING") queueStatus("PONG", "ping");
|
||||||
else if (msg == "test") handleAlert("TEST ALERT");
|
else if (msg == "test") handleAlert("TEST ALERT");
|
||||||
else if (msg == "status") {
|
else if (msg == "status") {
|
||||||
char buf[128];
|
char buf[128];
|
||||||
snprintf(buf, sizeof(buf), "State:%s WiFi:%s RSSI:%d Heap:%dKB",
|
snprintf(buf, sizeof(buf), "State:%s WiFi:%s RSSI:%d Heap:%dKB",
|
||||||
_state == DeviceState::SILENT ? "SILENT" :
|
_state == DeviceState::SILENT ? "SILENT" :
|
||||||
_state == DeviceState::ALERTING ? "ALERT" : "WAKE",
|
_state == DeviceState::ALERTING ? "ALERT" : "WAKE",
|
||||||
WiFi.SSID().c_str(), WiFi.RSSI(), ESP.getFreeHeap() / 1024);
|
WiFi.SSID().c_str(), WiFi.RSSI(), ESP.getFreeHeap() / 1024);
|
||||||
queueStatus("STATUS", buf);
|
queueStatus("STATUS", buf);
|
||||||
@@ -321,15 +313,15 @@ void DoorbellLogic::handleAdmin(const String& msg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// =====================================================================
|
// =====================================================================
|
||||||
// Screen State Sync
|
// Screen State Sync
|
||||||
// =====================================================================
|
// =====================================================================
|
||||||
void DoorbellLogic::updateScreenState() {
|
void DoorbellLogic::updateScreenState() {
|
||||||
_screen.deviceState = _state;
|
_screen.deviceState = _state;
|
||||||
_screen.blinkPhase = _blinkState;
|
_screen.blinkPhase = _blinkState;
|
||||||
strncpy(_screen.alertMessage, _currentMessage.c_str(), sizeof(_screen.alertMessage) - 1);
|
strncpy(_screen.alertMessage, _currentMessage.c_str(), sizeof(_screen.alertMessage) - 1);
|
||||||
_screen.alertMessage[sizeof(_screen.alertMessage) - 1] = '\0';
|
_screen.alertMessage[sizeof(_screen.alertMessage) - 1] = '\0';
|
||||||
|
|
||||||
_screen.wifiConnected = WiFi.isConnected();
|
_screen.wifiConnected = WiFi.isConnected();
|
||||||
if (_screen.wifiConnected) {
|
if (_screen.wifiConnected) {
|
||||||
strncpy(_screen.wifiSSID, WiFi.SSID().c_str(), sizeof(_screen.wifiSSID) - 1);
|
strncpy(_screen.wifiSSID, WiFi.SSID().c_str(), sizeof(_screen.wifiSSID) - 1);
|
||||||
_screen.wifiSSID[sizeof(_screen.wifiSSID) - 1] = '\0';
|
_screen.wifiSSID[sizeof(_screen.wifiSSID) - 1] = '\0';
|
||||||
@@ -338,20 +330,20 @@ void DoorbellLogic::updateScreenState() {
|
|||||||
_screen.wifiRSSI = WiFi.RSSI();
|
_screen.wifiRSSI = WiFi.RSSI();
|
||||||
}
|
}
|
||||||
|
|
||||||
_screen.ntpSynced = _ntpSynced;
|
_screen.ntpSynced = _ntpSynced;
|
||||||
if (_ntpSynced) {
|
if (_ntpSynced) {
|
||||||
strncpy(_screen.timeString, _timeClient->getFormattedTime().c_str(),
|
strncpy(_screen.timeString, _timeClient->getFormattedTime().c_str(),
|
||||||
sizeof(_screen.timeString) - 1);
|
sizeof(_screen.timeString) - 1);
|
||||||
_screen.timeString[sizeof(_screen.timeString) - 1] = '\0';
|
_screen.timeString[sizeof(_screen.timeString) - 1] = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
_screen.uptimeMinutes = (millis() - _bootTime) / 60000;
|
_screen.uptimeMinutes = (millis() - _bootTime) / 60000;
|
||||||
_screen.freeHeapKB = ESP.getFreeHeap() / 1024;
|
_screen.freeHeapKB = ESP.getFreeHeap() / 1024;
|
||||||
_screen.networkOK = _networkOK;
|
_screen.networkOK = _networkOK;
|
||||||
}
|
}
|
||||||
|
|
||||||
// =====================================================================
|
// =====================================================================
|
||||||
// WiFi & Network
|
// WiFi & Network
|
||||||
// =====================================================================
|
// =====================================================================
|
||||||
void DoorbellLogic::syncNTP() {
|
void DoorbellLogic::syncNTP() {
|
||||||
if (_timeClient->update()) {
|
if (_timeClient->update()) {
|
||||||
@@ -385,7 +377,7 @@ void DoorbellLogic::checkNetwork() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// =====================================================================
|
// =====================================================================
|
||||||
// ntfy Polling
|
// ntfy Polling
|
||||||
// =====================================================================
|
// =====================================================================
|
||||||
void DoorbellLogic::pollTopics() {
|
void DoorbellLogic::pollTopics() {
|
||||||
Serial.printf("[POLL] Starting poll cycle... WiFi:%s NTP:%d Grace:%d\n",
|
Serial.printf("[POLL] Starting poll cycle... WiFi:%s NTP:%d Grace:%d\n",
|
||||||
@@ -393,16 +385,16 @@ void DoorbellLogic::pollTopics() {
|
|||||||
|
|
||||||
pollTopic(ALERT_URL, &DoorbellLogic::handleAlert, "ALERT", _lastAlertId);
|
pollTopic(ALERT_URL, &DoorbellLogic::handleAlert, "ALERT", _lastAlertId);
|
||||||
yield();
|
yield();
|
||||||
pollTopic(SILENCE_URL, &DoorbellLogic::handleSilence, "SILENCE", _lastSilenceId);
|
pollTopic(SILENCE_URL, &DoorbellLogic::handleSilence, "SILENCE", _lastSilenceId);
|
||||||
yield();
|
yield();
|
||||||
pollTopic(ADMIN_URL, &DoorbellLogic::handleAdmin, "ADMIN", _lastAdminId);
|
pollTopic(ADMIN_URL, &DoorbellLogic::handleAdmin, "ADMIN", _lastAdminId);
|
||||||
|
|
||||||
Serial.printf("[POLL] Done. Heap: %dKB\n", ESP.getFreeHeap() / 1024);
|
Serial.printf("[POLL] Done. Heap: %dKB\n", ESP.getFreeHeap() / 1024);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DoorbellLogic::pollTopic(const char* url,
|
void DoorbellLogic::pollTopic(const char* url,
|
||||||
void (DoorbellLogic::*handler)(const String&),
|
void (DoorbellLogic::*handler)(const String&),
|
||||||
const char* name, String& lastId) {
|
const char* name, String& lastId) {
|
||||||
Serial.printf("[%s] Polling: %s\n", name, url);
|
Serial.printf("[%s] Polling: %s\n", name, url);
|
||||||
|
|
||||||
if (!WiFi.isConnected()) {
|
if (!WiFi.isConnected()) {
|
||||||
@@ -444,13 +436,12 @@ void DoorbellLogic::pollTopic(const char* url,
|
|||||||
|
|
||||||
http.end();
|
http.end();
|
||||||
client.stop();
|
client.stop();
|
||||||
|
|
||||||
yield();
|
yield();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DoorbellLogic::parseMessages(String& response, const char* name,
|
void DoorbellLogic::parseMessages(String& response, const char* name,
|
||||||
void (DoorbellLogic::*handler)(const String&),
|
void (DoorbellLogic::*handler)(const String&),
|
||||||
String& lastId) {
|
String& lastId) {
|
||||||
Serial.printf("[%s] parseMessages: grace=%d ntp=%d epoch=%ld\n",
|
Serial.printf("[%s] parseMessages: grace=%d ntp=%d epoch=%ld\n",
|
||||||
name, _inBootGrace, _ntpSynced, (long)_lastEpoch);
|
name, _inBootGrace, _ntpSynced, (long)_lastEpoch);
|
||||||
|
|
||||||
@@ -480,12 +471,12 @@ void DoorbellLogic::parseMessages(String& response, const char* name,
|
|||||||
const char* event = doc["event"];
|
const char* event = doc["event"];
|
||||||
const char* msgId = doc["id"];
|
const char* msgId = doc["id"];
|
||||||
const char* message = doc["message"];
|
const char* message = doc["message"];
|
||||||
time_t msgTime = doc["time"] | 0;
|
time_t msgTime = doc["time"] | 0;
|
||||||
|
|
||||||
Serial.printf("[%s] msg#%d: event=%s id=%s time=%ld msg=%.30s\n",
|
Serial.printf("[%s] msg#%d: event=%s id=%s time=%ld msg=%.30s\n",
|
||||||
name, msgCount,
|
name, msgCount,
|
||||||
event ? event : "null",
|
event ? event : "null",
|
||||||
msgId ? msgId : "null",
|
msgId ? msgId : "null",
|
||||||
(long)msgTime,
|
(long)msgTime,
|
||||||
message ? message : "null");
|
message ? message : "null");
|
||||||
|
|
||||||
@@ -529,7 +520,7 @@ void DoorbellLogic::parseMessages(String& response, const char* name,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// =====================================================================
|
// =====================================================================
|
||||||
// Status Publishing (deferred)
|
// Status Publishing
|
||||||
// =====================================================================
|
// =====================================================================
|
||||||
void DoorbellLogic::queueStatus(const char* st, const String& msg) {
|
void DoorbellLogic::queueStatus(const char* st, const String& msg) {
|
||||||
_pendingStatus = true;
|
_pendingStatus = true;
|
||||||
@@ -567,7 +558,6 @@ DoorbellLogic* DoorbellLogic::_instance = nullptr;
|
|||||||
|
|
||||||
void DoorbellLogic::onWiFiEvent(WiFiEvent_t event) {
|
void DoorbellLogic::onWiFiEvent(WiFiEvent_t event) {
|
||||||
if (!_instance) return;
|
if (!_instance) return;
|
||||||
|
|
||||||
switch (event) {
|
switch (event) {
|
||||||
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
|
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
|
||||||
Serial.println("[WIFI] Disconnected — will reconnect");
|
Serial.println("[WIFI] Disconnected — will reconnect");
|
||||||
@@ -584,4 +574,3 @@ void DoorbellLogic::onWiFiEvent(WiFiEvent_t event) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
|
||||||
// =====================================================================
|
|
||||||
// Shared enums and structs — NO library dependencies
|
|
||||||
// =====================================================================
|
|
||||||
|
|
||||||
enum class DeviceState : uint8_t {
|
enum class DeviceState : uint8_t {
|
||||||
SILENT,
|
SILENT,
|
||||||
ALERTING,
|
ALERTING,
|
||||||
@@ -18,50 +14,42 @@ enum class ScreenID : uint8_t {
|
|||||||
WIFI_FAILED,
|
WIFI_FAILED,
|
||||||
ALERT,
|
ALERT,
|
||||||
STATUS,
|
STATUS,
|
||||||
DASHBOARD, // <-- NEW
|
DASHBOARD,
|
||||||
OFF // backlight off, nothing to draw
|
OFF
|
||||||
};
|
};
|
||||||
|
|
||||||
#define ALERT_HISTORY_SIZE 3
|
#define ALERT_HISTORY_SIZE 3
|
||||||
|
|
||||||
struct AlertRecord {
|
struct AlertRecord {
|
||||||
char message[64] = "";
|
char message[64] = "";
|
||||||
char timestamp[12] = ""; // "HH:MM:SS"
|
char timestamp[12] = "";
|
||||||
};
|
};
|
||||||
|
|
||||||
// Everything the display needs to render any screen
|
|
||||||
struct ScreenState {
|
struct ScreenState {
|
||||||
ScreenID screen = ScreenID::BOOT_SPLASH;
|
ScreenID screen = ScreenID::BOOT_SPLASH;
|
||||||
DeviceState deviceState = DeviceState::SILENT;
|
DeviceState deviceState = DeviceState::SILENT;
|
||||||
bool blinkPhase = false;
|
bool blinkPhase = false;
|
||||||
|
|
||||||
// Alert
|
|
||||||
char alertMessage[64] = "";
|
char alertMessage[64] = "";
|
||||||
|
|
||||||
// WiFi
|
|
||||||
bool wifiConnected = false;
|
bool wifiConnected = false;
|
||||||
char wifiSSID[33] = "";
|
char wifiSSID[33] = "";
|
||||||
int wifiRSSI = 0;
|
int wifiRSSI = 0;
|
||||||
char wifiIP[16] = "";
|
char wifiIP[16] = "";
|
||||||
|
|
||||||
// NTP
|
|
||||||
bool ntpSynced = false;
|
bool ntpSynced = false;
|
||||||
char timeString[12] = "";
|
char timeString[12] = "";
|
||||||
|
|
||||||
// System
|
|
||||||
uint32_t uptimeMinutes = 0;
|
uint32_t uptimeMinutes = 0;
|
||||||
uint32_t freeHeapKB = 0;
|
uint32_t freeHeapKB = 0;
|
||||||
bool networkOK = false;
|
bool networkOK = false;
|
||||||
|
|
||||||
// Debug
|
|
||||||
bool debugMode = false;
|
bool debugMode = false;
|
||||||
|
|
||||||
// Alert history (newest first)
|
|
||||||
AlertRecord alertHistory[ALERT_HISTORY_SIZE] = {};
|
AlertRecord alertHistory[ALERT_HISTORY_SIZE] = {};
|
||||||
int alertHistoryCount = 0;
|
int alertHistoryCount = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Touch event passed from display to logic
|
|
||||||
struct TouchEvent {
|
struct TouchEvent {
|
||||||
bool pressed = false;
|
bool pressed = false;
|
||||||
uint16_t x = 0;
|
uint16_t x = 0;
|
||||||
|
|||||||
@@ -1,88 +1,80 @@
|
|||||||
/*
|
#include <SPI.h>
|
||||||
* KLUBHAUS ALERT v5.0 — E32R35T Edition
|
|
||||||
*
|
|
||||||
* Target: LCDWiki E32R35T (ESP32-WROOM-32E + 3.5" ST7796S + XPT2046)
|
|
||||||
*
|
|
||||||
* Refactored: business logic separated from display code.
|
|
||||||
* Business logic knows nothing about TFT_eSPI.
|
|
||||||
* Display knows nothing about ntfy/WiFi/state machine.
|
|
||||||
* They communicate through ScreenState (plain struct).
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
#include "DisplayManager.h"
|
|
||||||
#include "DoorbellLogic.h"
|
#include "DoorbellLogic.h"
|
||||||
|
#include "DisplayManager.h"
|
||||||
|
|
||||||
|
#define HOLD_TO_SILENCE_MS 1000
|
||||||
|
|
||||||
DisplayManager display;
|
|
||||||
DoorbellLogic logic;
|
DoorbellLogic logic;
|
||||||
|
DisplayManager display;
|
||||||
#include <TFT_eSPI.h>
|
|
||||||
|
|
||||||
#ifndef LOAD_GLCD
|
|
||||||
#error "LOAD_GLCD is NOT defined — fonts missing!"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef ST7796_DRIVER
|
|
||||||
#error "ST7796_DRIVER is NOT defined — wrong setup!"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void setup() {
|
void setup() {
|
||||||
Serial.begin(115200);
|
Serial.begin(115200);
|
||||||
unsigned long t = millis();
|
delay(1000);
|
||||||
while (!Serial && millis() - t < 3000) delay(10);
|
Serial.println("\n=== KLUBHAUS ALERT v5.1 ===");
|
||||||
delay(500);
|
|
||||||
|
|
||||||
Serial.println("\n========================================");
|
|
||||||
Serial.println(" KLUBHAUS ALERT v5.0 — E32R35T");
|
|
||||||
#if DEBUG_MODE
|
|
||||||
Serial.println(" *** DEBUG MODE — _test topics ***");
|
|
||||||
#endif
|
|
||||||
Serial.println("========================================");
|
|
||||||
|
|
||||||
// 1. Init display hardware
|
|
||||||
display.begin();
|
display.begin();
|
||||||
|
|
||||||
// 2. Init logic (sets boot splash screen state)
|
|
||||||
logic.begin();
|
logic.begin();
|
||||||
display.render(logic.getScreenState());
|
display.render(logic.getScreenState());
|
||||||
delay(1500);
|
delay(2000);
|
||||||
|
|
||||||
// 3. WiFi (logic updates screen state, we render each phase)
|
logic.beginWiFi();
|
||||||
// We need a small coupling here for the blocking WiFi connect
|
|
||||||
// This could be made async later
|
|
||||||
logic.beginWiFi(); // sets screen to WIFI_CONNECTING
|
|
||||||
display.render(logic.getScreenState());
|
display.render(logic.getScreenState());
|
||||||
|
|
||||||
logic.connectWiFiBlocking(); // blocks, sets CONNECTED or FAILED
|
logic.connectWiFiBlocking();
|
||||||
display.render(logic.getScreenState());
|
display.render(logic.getScreenState());
|
||||||
delay(1500);
|
delay(1500);
|
||||||
|
|
||||||
// 4. Finish boot
|
|
||||||
logic.finishBoot();
|
logic.finishBoot();
|
||||||
display.setBacklight(false);
|
display.render(logic.getScreenState());
|
||||||
|
|
||||||
Serial.println("[BOOT] Ready — monitoring ntfy.sh\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void loop() {
|
void loop() {
|
||||||
// 1. Read touch
|
logic.update();
|
||||||
TouchEvent touch = display.readTouch();
|
|
||||||
logic.onTouch(touch);
|
|
||||||
|
|
||||||
// 2. Read serial commands
|
const ScreenState& state = logic.getScreenState();
|
||||||
|
|
||||||
|
// ---- Touch handling (varies by screen) ----
|
||||||
|
|
||||||
|
if (state.screen == ScreenID::ALERT) {
|
||||||
|
// Hold-to-silence: progress bar drawn by render()
|
||||||
|
HoldState hold = display.updateHold(HOLD_TO_SILENCE_MS);
|
||||||
|
if (hold.completed) {
|
||||||
|
Serial.println("[HOLD] Silence hold completed");
|
||||||
|
logic.onTouch(TouchEvent{true, hold.x, hold.y});
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (state.screen == ScreenID::DASHBOARD) {
|
||||||
|
// Dashboard: tile taps don't dismiss, outside taps dismiss
|
||||||
|
TouchEvent evt = display.readTouch();
|
||||||
|
if (evt.pressed) {
|
||||||
|
int tile = display.dashboardTouch(evt.x, evt.y);
|
||||||
|
if (tile >= 0) {
|
||||||
|
Serial.printf("[DASH] Tile %d tapped\n", tile);
|
||||||
|
// Tile-specific actions go here later
|
||||||
|
} else {
|
||||||
|
// Tap outside tiles — dismiss dashboard
|
||||||
|
logic.onTouch(evt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// All other screens (OFF, boot, etc): simple touch
|
||||||
|
TouchEvent evt = display.readTouch();
|
||||||
|
if (evt.pressed) {
|
||||||
|
logic.onTouch(evt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Render ----
|
||||||
|
display.render(logic.getScreenState());
|
||||||
|
|
||||||
|
// ---- Serial commands ----
|
||||||
if (Serial.available()) {
|
if (Serial.available()) {
|
||||||
String cmd = Serial.readStringUntil('\n');
|
String cmd = Serial.readStringUntil('\n');
|
||||||
cmd.trim();
|
cmd.trim();
|
||||||
logic.onSerialCommand(cmd);
|
if (cmd.length() > 0) logic.onSerialCommand(cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Update business logic
|
|
||||||
logic.update();
|
|
||||||
|
|
||||||
// 4. Render
|
|
||||||
const ScreenState& state = logic.getScreenState();
|
|
||||||
display.setBacklight(state.screen != ScreenID::OFF);
|
|
||||||
display.render(state);
|
|
||||||
|
|
||||||
delay(20);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user