implement dashboard on wake
This commit is contained in:
@@ -16,12 +16,12 @@
|
|||||||
|
|
||||||
// Tile color themes
|
// Tile color themes
|
||||||
static const uint16_t tileBG[] = {
|
static const uint16_t tileBG[] = {
|
||||||
COL_RED, // LAST_ALERT — red
|
COL_RED, // LAST_ALERT
|
||||||
COL_ORANGE, // STATS — orange
|
COL_ORANGE, // STATS
|
||||||
COL_CYAN, // NETWORK — cyan
|
COL_CYAN, // NETWORK
|
||||||
COL_PURPLE, // MUTE — purple
|
COL_PURPLE, // MUTE
|
||||||
COL_DARK_TILE, // HISTORY — dark
|
COL_DARK_TILE, // HISTORY
|
||||||
COL_DARK_TILE // SYSTEM — dark
|
COL_DARK_TILE // SYSTEM
|
||||||
};
|
};
|
||||||
|
|
||||||
static const uint16_t tileFG[] = {
|
static const uint16_t tileFG[] = {
|
||||||
@@ -31,13 +31,12 @@ static const uint16_t tileFG[] = {
|
|||||||
Dashboard::Dashboard(TFT_eSPI& tft)
|
Dashboard::Dashboard(TFT_eSPI& tft)
|
||||||
: _tft(tft), _sprite(&tft)
|
: _tft(tft), _sprite(&tft)
|
||||||
{
|
{
|
||||||
// Initialize tile metadata
|
_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];
|
||||||
@@ -46,10 +45,16 @@ Dashboard::Dashboard(TFT_eSPI& tft)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Dashboard::begin() {
|
void Dashboard::begin() {
|
||||||
// Create sprite sized to one tile (reused for each)
|
|
||||||
_sprite.createSprite(TILE_W, TILE_H);
|
_sprite.createSprite(TILE_W, TILE_H);
|
||||||
_sprite.setTextDatum(MC_DATUM);
|
_sprite.setTextDatum(MC_DATUM);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Dashboard::drawAll() {
|
||||||
_tft.fillScreen(COL_BG);
|
_tft.fillScreen(COL_BG);
|
||||||
|
drawTopBar("--:--", 0, false);
|
||||||
|
for (int i = 0; i < TILE_COUNT; i++) {
|
||||||
|
drawTile((TileID)i);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Dashboard::tilePosition(TileID id, int& x, int& y) {
|
void Dashboard::tilePosition(TileID id, int& x, int& y) {
|
||||||
@@ -64,10 +69,7 @@ void Dashboard::drawTile(TileID id) {
|
|||||||
int tx, ty;
|
int tx, ty;
|
||||||
tilePosition(id, tx, ty);
|
tilePosition(id, tx, ty);
|
||||||
|
|
||||||
// Draw into sprite (off-screen)
|
|
||||||
_sprite.fillSprite(t.bgColor);
|
_sprite.fillSprite(t.bgColor);
|
||||||
|
|
||||||
// Rounded corner effect — draw border pixels
|
|
||||||
_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
|
// Icon — large character at top
|
||||||
@@ -77,27 +79,25 @@ void Dashboard::drawTile(TileID id) {
|
|||||||
_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 — below icon
|
// 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 — bottom area
|
// 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 — very bottom
|
// 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);
|
||||||
_sprite.drawString(t.sub, TILE_W / 2, TILE_H - 8);
|
_sprite.drawString(t.sub, TILE_W / 2, TILE_H - 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Push sprite to screen in one operation — no flicker
|
|
||||||
_sprite.pushSprite(tx, ty);
|
_sprite.pushSprite(tx, ty);
|
||||||
|
|
||||||
t.dirty = false;
|
t.dirty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,15 +108,13 @@ void Dashboard::drawTopBar(const char* time, int rssi, bool wifiOk) {
|
|||||||
_tft.setTextFont(4);
|
_tft.setTextFont(4);
|
||||||
_tft.setTextSize(1);
|
_tft.setTextSize(1);
|
||||||
|
|
||||||
// Title — left
|
|
||||||
_tft.setTextDatum(ML_DATUM);
|
_tft.setTextDatum(ML_DATUM);
|
||||||
_tft.drawString("KLUBHAUS ALERT", DASH_MARGIN, DASH_TOP_BAR / 2);
|
_tft.drawString("KLUBHAUS ALERT", DASH_MARGIN, DASH_TOP_BAR / 2);
|
||||||
|
|
||||||
// Time — right
|
|
||||||
_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 indicator — signal bars
|
// WiFi signal bars
|
||||||
int bars = 0;
|
int bars = 0;
|
||||||
if (wifiOk) {
|
if (wifiOk) {
|
||||||
if (rssi > -50) bars = 4;
|
if (rssi > -50) bars = 4;
|
||||||
@@ -125,26 +123,28 @@ void Dashboard::drawTopBar(const char* time, int rssi, bool wifiOk) {
|
|||||||
else bars = 1;
|
else bars = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int barX = 370;
|
int barX = 370, barW = 6, barGap = 3;
|
||||||
int barW = 6;
|
|
||||||
int 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;
|
||||||
int barY = DASH_TOP_BAR - 8 - barH;
|
int barY = DASH_TOP_BAR - 8 - barH;
|
||||||
uint16_t col = (i < bars) ? COL_GREEN : COL_GRAY;
|
uint16_t col = (i < bars) ? COL_GREEN : COL_GRAY;
|
||||||
_tft.fillRect(barX + i * (barW + barGap), barY, barW, barH, col);
|
_tft.fillRect(barX + i * (barW + barGap), barY, barW, barH, col);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void Dashboard::drawAll() {
|
// Cache values
|
||||||
drawTopBar("--:--", 0, false);
|
strncpy(_barTime, time, sizeof(_barTime) - 1);
|
||||||
for (int i = 0; i < TILE_COUNT; i++) {
|
_barRSSI = rssi;
|
||||||
drawTile((TileID)i);
|
_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);
|
||||||
|
if (sub && strcmp(t.sub, sub) != 0) changed = true;
|
||||||
|
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) {
|
if (sub) {
|
||||||
@@ -152,7 +152,7 @@ void Dashboard::updateTile(TileID id, const char* value, const char* sub) {
|
|||||||
t.sub[31] = '\0';
|
t.sub[31] = '\0';
|
||||||
}
|
}
|
||||||
t.dirty = true;
|
t.dirty = true;
|
||||||
drawTile(id); // immediate redraw of just this tile
|
drawTile(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
int Dashboard::handleTouch(int x, int y) {
|
int Dashboard::handleTouch(int x, int y) {
|
||||||
@@ -167,3 +167,64 @@ int Dashboard::handleTouch(int x, int y) {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// =====================================================================
|
||||||
|
// Refresh all tiles from ScreenState — only redraws changed tiles
|
||||||
|
// =====================================================================
|
||||||
|
void Dashboard::refreshFromState(const ScreenState& state) {
|
||||||
|
// Top bar — only redraw if changed
|
||||||
|
bool barChanged = (strcmp(_barTime, state.timeString) != 0) ||
|
||||||
|
(_barRSSI != state.wifiRSSI) ||
|
||||||
|
(_barWifiOk != state.wifiConnected);
|
||||||
|
if (barChanged) {
|
||||||
|
drawTopBar(state.timeString, state.wifiRSSI, state.wifiConnected);
|
||||||
|
}
|
||||||
|
|
||||||
|
// LAST ALERT tile
|
||||||
|
if (state.alertHistoryCount > 0) {
|
||||||
|
updateTile(TILE_LAST_ALERT,
|
||||||
|
state.alertHistory[0].message,
|
||||||
|
state.alertHistory[0].timestamp);
|
||||||
|
} else {
|
||||||
|
updateTile(TILE_LAST_ALERT, "none", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
// STATS tile
|
||||||
|
char statsBuf[32];
|
||||||
|
snprintf(statsBuf, sizeof(statsBuf), "%d alert%s",
|
||||||
|
state.alertHistoryCount,
|
||||||
|
state.alertHistoryCount == 1 ? "" : "s");
|
||||||
|
updateTile(TILE_STATS, statsBuf, "this session");
|
||||||
|
|
||||||
|
// NETWORK tile
|
||||||
|
if (state.wifiConnected) {
|
||||||
|
char rssiBuf[16];
|
||||||
|
snprintf(rssiBuf, sizeof(rssiBuf), "%d dBm", state.wifiRSSI);
|
||||||
|
updateTile(TILE_NETWORK, rssiBuf, state.wifiSSID);
|
||||||
|
} else {
|
||||||
|
updateTile(TILE_NETWORK, "DOWN", "reconnecting...");
|
||||||
|
}
|
||||||
|
|
||||||
|
// MUTE tile (placeholder)
|
||||||
|
updateTile(TILE_MUTE, "OFF", "tap to mute");
|
||||||
|
|
||||||
|
// HISTORY tile — show 2nd and 3rd alerts
|
||||||
|
if (state.alertHistoryCount > 1) {
|
||||||
|
char histBuf[48];
|
||||||
|
snprintf(histBuf, sizeof(histBuf), "%s %.20s",
|
||||||
|
state.alertHistory[1].timestamp,
|
||||||
|
state.alertHistory[1].message);
|
||||||
|
const char* sub = (state.alertHistoryCount > 2)
|
||||||
|
? state.alertHistory[2].message : "";
|
||||||
|
updateTile(TILE_HISTORY, histBuf, sub);
|
||||||
|
} else {
|
||||||
|
updateTile(TILE_HISTORY, "no history", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
// SYSTEM tile
|
||||||
|
char heapBuf[16];
|
||||||
|
snprintf(heapBuf, sizeof(heapBuf), "%lu KB", state.freeHeapKB);
|
||||||
|
char uptimeBuf[20];
|
||||||
|
snprintf(uptimeBuf, sizeof(uptimeBuf), "up %lum", state.uptimeMinutes);
|
||||||
|
updateTile(TILE_SYSTEM, heapBuf, uptimeBuf);
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <TFT_eSPI.h>
|
#include <TFT_eSPI.h>
|
||||||
|
#include "ScreenData.h"
|
||||||
|
|
||||||
// Grid layout constants
|
// Grid layout constants
|
||||||
#define DASH_COLS 3
|
#define DASH_COLS 3
|
||||||
@@ -8,8 +9,8 @@
|
|||||||
#define DASH_TOP_BAR 40
|
#define DASH_TOP_BAR 40
|
||||||
|
|
||||||
// Tile dimensions (calculated for 480x320)
|
// Tile dimensions (calculated for 480x320)
|
||||||
#define TILE_W ((480 - (DASH_COLS + 1) * DASH_MARGIN) / DASH_COLS) // ~148
|
#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
|
#define TILE_H ((320 - DASH_TOP_BAR - (DASH_ROWS + 1) * DASH_MARGIN) / DASH_ROWS) // ~128
|
||||||
|
|
||||||
// Tile IDs
|
// Tile IDs
|
||||||
enum TileID : uint8_t {
|
enum TileID : uint8_t {
|
||||||
@@ -23,30 +24,38 @@ enum TileID : uint8_t {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct TileData {
|
struct TileData {
|
||||||
const char* icon; // emoji/symbol character
|
const char* icon;
|
||||||
const char* label; // tile name
|
const char* label;
|
||||||
char value[32]; // dynamic value text
|
char value[32];
|
||||||
char sub[32]; // secondary line
|
char sub[32];
|
||||||
uint16_t bgColor;
|
uint16_t bgColor;
|
||||||
uint16_t fgColor;
|
uint16_t fgColor;
|
||||||
bool dirty; // needs redraw
|
bool dirty;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Dashboard {
|
class Dashboard {
|
||||||
public:
|
public:
|
||||||
Dashboard(TFT_eSPI& tft);
|
Dashboard(TFT_eSPI& tft);
|
||||||
|
|
||||||
void begin();
|
void begin(); // create sprite (call once)
|
||||||
void drawAll();
|
void drawAll(); // fill screen + draw all tiles
|
||||||
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); // returns TileID or -1
|
||||||
|
|
||||||
|
// Populate tiles from ScreenState — only redraws changed tiles
|
||||||
|
void refreshFromState(const ScreenState& state);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
TFT_eSPI& _tft;
|
TFT_eSPI& _tft;
|
||||||
TFT_eSprite _sprite;
|
TFT_eSprite _sprite;
|
||||||
TileData _tiles[TILE_COUNT];
|
TileData _tiles[TILE_COUNT];
|
||||||
|
|
||||||
|
// Cached top bar values for dirty check
|
||||||
|
char _barTime[12] = "";
|
||||||
|
int _barRSSI = 0;
|
||||||
|
bool _barWifiOk = false;
|
||||||
|
|
||||||
void drawTile(TileID id);
|
void drawTile(TileID id);
|
||||||
void tilePosition(TileID id, int& x, int& y);
|
void tilePosition(TileID id, int& x, int& y);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
#include "DisplayManager.h"
|
#include "DisplayManager.h"
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
|
|
||||||
|
DisplayManager::DisplayManager()
|
||||||
|
: _dash(_tft)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
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);
|
_tft.setRotation(1); // landscape: 480x320
|
||||||
_tft.fillScreen(COL_BLACK);
|
_tft.fillScreen(COL_BLACK);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,6 +30,11 @@ TouchEvent DisplayManager::readTouch() {
|
|||||||
return evt;
|
return evt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int DisplayManager::dashboardTouch(uint16_t x, uint16_t y) {
|
||||||
|
if (_lastScreen != ScreenID::DASHBOARD) return -1;
|
||||||
|
return _dash.handleTouch(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
void DisplayManager::render(const ScreenState& state) {
|
void DisplayManager::render(const ScreenState& state) {
|
||||||
// Detect screen change → force full redraw
|
// Detect screen change → force full redraw
|
||||||
if (state.screen != _lastScreen) {
|
if (state.screen != _lastScreen) {
|
||||||
@@ -46,7 +56,6 @@ void DisplayManager::render(const ScreenState& state) {
|
|||||||
if (_needsFullRedraw) drawWifiFailed();
|
if (_needsFullRedraw) drawWifiFailed();
|
||||||
break;
|
break;
|
||||||
case ScreenID::ALERT:
|
case ScreenID::ALERT:
|
||||||
// Alert redraws every blink cycle
|
|
||||||
if (_needsFullRedraw || state.blinkPhase != _lastBlink) {
|
if (_needsFullRedraw || state.blinkPhase != _lastBlink) {
|
||||||
drawAlertScreen(state);
|
drawAlertScreen(state);
|
||||||
_lastBlink = state.blinkPhase;
|
_lastBlink = state.blinkPhase;
|
||||||
@@ -55,6 +64,9 @@ void DisplayManager::render(const ScreenState& state) {
|
|||||||
case ScreenID::STATUS:
|
case ScreenID::STATUS:
|
||||||
if (_needsFullRedraw) drawStatusScreen(state);
|
if (_needsFullRedraw) drawStatusScreen(state);
|
||||||
break;
|
break;
|
||||||
|
case ScreenID::DASHBOARD:
|
||||||
|
drawDashboard(state);
|
||||||
|
break;
|
||||||
case ScreenID::OFF:
|
case ScreenID::OFF:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -62,6 +74,23 @@ void DisplayManager::render(const ScreenState& state) {
|
|||||||
_needsFullRedraw = false;
|
_needsFullRedraw = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----- Dashboard -----
|
||||||
|
|
||||||
|
void DisplayManager::drawDashboard(const ScreenState& s) {
|
||||||
|
if (_needsFullRedraw) {
|
||||||
|
if (!_dashSpriteReady) {
|
||||||
|
_dash.begin(); // create sprite (once)
|
||||||
|
_dashSpriteReady = true;
|
||||||
|
}
|
||||||
|
_dash.drawAll(); // fill screen + draw all tiles
|
||||||
|
_dash.refreshFromState(s); // update with real data
|
||||||
|
_lastDashRefresh = millis();
|
||||||
|
} else if (millis() - _lastDashRefresh > 2000) {
|
||||||
|
_lastDashRefresh = millis();
|
||||||
|
_dash.refreshFromState(s); // only redraws changed tiles
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ----- Helpers -----
|
// ----- 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) {
|
||||||
@@ -122,12 +151,11 @@ void DisplayManager::drawWifiFailed() {
|
|||||||
|
|
||||||
void DisplayManager::drawAlertScreen(const ScreenState& s) {
|
void DisplayManager::drawAlertScreen(const ScreenState& s) {
|
||||||
uint16_t bg = s.blinkPhase ? COL_NEON_TEAL : COL_HOT_FUCHSIA;
|
uint16_t bg = s.blinkPhase ? COL_NEON_TEAL : COL_HOT_FUCHSIA;
|
||||||
uint16_t fg = s.blinkPhase ? COL_BLACK : COL_WHITE;
|
uint16_t fg = s.blinkPhase ? COL_BLACK : COL_WHITE;
|
||||||
|
|
||||||
_tft.fillScreen(bg);
|
_tft.fillScreen(bg);
|
||||||
drawHeaderBar(fg, "ALERT", s.timeString);
|
drawHeaderBar(fg, "ALERT", s.timeString);
|
||||||
|
|
||||||
// Scale text to fit 480px wide screen
|
|
||||||
int sz = 5;
|
int sz = 5;
|
||||||
int len = strlen(s.alertMessage);
|
int len = strlen(s.alertMessage);
|
||||||
if (len > 10) sz = 4;
|
if (len > 10) sz = 4;
|
||||||
@@ -135,7 +163,6 @@ void DisplayManager::drawAlertScreen(const ScreenState& s) {
|
|||||||
if (len > 30) sz = 2;
|
if (len > 30) sz = 2;
|
||||||
|
|
||||||
if (len > 12) {
|
if (len > 12) {
|
||||||
// Two-line split
|
|
||||||
String msg(s.alertMessage);
|
String msg(s.alertMessage);
|
||||||
int mid = len / 2;
|
int mid = len / 2;
|
||||||
int sp = msg.lastIndexOf(' ', mid);
|
int sp = msg.lastIndexOf(' ', mid);
|
||||||
@@ -165,42 +192,41 @@ void DisplayManager::drawStatusScreen(const ScreenState& s) {
|
|||||||
int x = 20;
|
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",
|
||||||
s.uptimeMinutes, s.freeHeapKB);
|
s.uptimeMinutes, s.freeHeapKB);
|
||||||
drawInfoLine(x, y, COL_WHITE, buf); y += sp;
|
drawInfoLine(x, y, COL_WHITE, buf); y += sp;
|
||||||
|
|
||||||
snprintf(buf, sizeof(buf), "NTP: %s UTC",
|
snprintf(buf, sizeof(buf), "NTP: %s UTC",
|
||||||
s.ntpSynced ? s.timeString : "not synced");
|
s.ntpSynced ? s.timeString : "not synced");
|
||||||
drawInfoLine(x, y, COL_WHITE, buf); y += sp;
|
drawInfoLine(x, y, COL_WHITE, buf); y += sp;
|
||||||
|
|
||||||
const char* stName = s.deviceState == DeviceState::SILENT ? "SILENT" :
|
const char* stName = s.deviceState == DeviceState::SILENT ? "SILENT" :
|
||||||
s.deviceState == DeviceState::ALERTING ? "ALERTING" : "WAKE";
|
s.deviceState == DeviceState::ALERTING ? "ALERTING" : "WAKE";
|
||||||
snprintf(buf, sizeof(buf), "State: %s Net: %s",
|
snprintf(buf, sizeof(buf), "State: %s Net: %s",
|
||||||
stName, s.networkOK ? "OK" : "FAIL");
|
stName, s.networkOK ? "OK" : "FAIL");
|
||||||
uint16_t stCol = s.deviceState == DeviceState::ALERTING ? COL_RED :
|
uint16_t stCol = s.deviceState == DeviceState::ALERTING ? COL_RED :
|
||||||
s.deviceState == DeviceState::SILENT ? COL_GREEN : COL_NEON_TEAL;
|
s.deviceState == DeviceState::SILENT ? COL_GREEN : COL_NEON_TEAL;
|
||||||
drawInfoLine(x, y, stCol, buf); y += sp;
|
drawInfoLine(x, y, stCol, buf); y += sp;
|
||||||
|
|
||||||
// Recent alerts
|
if (s.alertHistoryCount > 0) {
|
||||||
if (s.alertHistoryCount > 0) {
|
drawInfoLine(x, y, COL_MINT, "Recent Alerts:"); y += sp;
|
||||||
drawInfoLine(x, y, COL_MINT, "Recent Alerts:"); y += sp;
|
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 {
|
||||||
|
drawInfoLine(x, y, COL_DARK_GRAY, "No alerts yet"); y += sp;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
drawInfoLine(x, y, COL_DARK_GRAY, "No alerts yet"); y += sp;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (s.debugMode) {
|
if (s.debugMode) {
|
||||||
drawInfoLine(x, y, COL_YELLOW, "DEBUG MODE - _test topics");
|
drawInfoLine(x, y, COL_YELLOW, "DEBUG MODE - _test topics");
|
||||||
|
|||||||
@@ -1,19 +1,28 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <TFT_eSPI.h>
|
#include <TFT_eSPI.h>
|
||||||
#include "ScreenData.h"
|
#include "ScreenData.h"
|
||||||
|
#include "Dashboard.h"
|
||||||
|
|
||||||
class DisplayManager {
|
class DisplayManager {
|
||||||
public:
|
public:
|
||||||
|
DisplayManager();
|
||||||
void begin();
|
void begin();
|
||||||
void render(const ScreenState& state);
|
void render(const ScreenState& state);
|
||||||
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);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
TFT_eSPI _tft;
|
TFT_eSPI _tft;
|
||||||
ScreenID _lastScreen = ScreenID::BOOT_SPLASH;
|
Dashboard _dash;
|
||||||
bool _needsFullRedraw = true;
|
|
||||||
bool _lastBlink = false;
|
ScreenID _lastScreen = ScreenID::BOOT_SPLASH;
|
||||||
|
bool _needsFullRedraw = true;
|
||||||
|
bool _lastBlink = false;
|
||||||
|
bool _dashSpriteReady = false;
|
||||||
|
unsigned long _lastDashRefresh = 0;
|
||||||
|
|
||||||
// Colors
|
// Colors
|
||||||
static constexpr uint16_t COL_NEON_TEAL = 0x07D7;
|
static constexpr uint16_t COL_NEON_TEAL = 0x07D7;
|
||||||
@@ -33,6 +42,7 @@ private:
|
|||||||
void drawWifiFailed();
|
void drawWifiFailed();
|
||||||
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);
|
||||||
|
|
||||||
// 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);
|
||||||
|
|||||||
@@ -239,8 +239,8 @@ void DoorbellLogic::transitionTo(DeviceState newState) {
|
|||||||
break;
|
break;
|
||||||
case DeviceState::WAKE:
|
case DeviceState::WAKE:
|
||||||
_wakeStart = now;
|
_wakeStart = now;
|
||||||
_screen.screen = ScreenID::STATUS;
|
_screen.screen = ScreenID::DASHBOARD;
|
||||||
Serial.println("-> WAKE");
|
Serial.println("-> WAKE (dashboard)");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
|
||||||
// =====================================================================
|
// =====================================================================
|
||||||
// Shared enums and structs — NO library dependencies
|
// Shared enums and structs — NO library dependencies
|
||||||
// =====================================================================
|
// =====================================================================
|
||||||
|
|
||||||
enum class DeviceState : uint8_t {
|
enum class DeviceState : uint8_t {
|
||||||
@@ -18,47 +18,47 @@ enum class ScreenID : uint8_t {
|
|||||||
WIFI_FAILED,
|
WIFI_FAILED,
|
||||||
ALERT,
|
ALERT,
|
||||||
STATUS,
|
STATUS,
|
||||||
OFF // backlight off, nothing to draw
|
DASHBOARD, // <-- NEW
|
||||||
|
OFF // backlight off, nothing to draw
|
||||||
};
|
};
|
||||||
|
|
||||||
#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] = ""; // "HH:MM:SS"
|
||||||
};
|
};
|
||||||
|
|
||||||
// Everything the display needs to render any screen
|
// 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
|
// Alert
|
||||||
char alertMessage[64] = "";
|
char alertMessage[64] = "";
|
||||||
|
|
||||||
// WiFi
|
// 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
|
// NTP
|
||||||
bool ntpSynced = false;
|
bool ntpSynced = false;
|
||||||
char timeString[12] = "";
|
char timeString[12] = "";
|
||||||
|
|
||||||
// System
|
// System
|
||||||
uint32_t uptimeMinutes = 0;
|
uint32_t uptimeMinutes = 0;
|
||||||
uint32_t freeHeapKB = 0;
|
uint32_t freeHeapKB = 0;
|
||||||
bool networkOK = false;
|
bool networkOK = false;
|
||||||
|
|
||||||
// Debug
|
// Debug
|
||||||
bool debugMode = false;
|
bool debugMode = false;
|
||||||
|
|
||||||
// Alert history (newest first)
|
// 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
|
// Touch event passed from display to logic
|
||||||
|
|||||||
Reference in New Issue
Block a user