#include "Dashboard.h" #define COL_BG 0x1082 #define COL_BAR 0x2104 #define COL_RED 0xF800 #define COL_ORANGE 0xFBE0 #define COL_GREEN 0x07E0 #define COL_CYAN 0x07FF #define COL_PURPLE 0x780F #define COL_WHITE 0xFFFF #define COL_GRAY 0x8410 #define COL_DARK_TILE 0x18E3 static const uint16_t tileBG[] = { COL_RED, COL_ORANGE, COL_CYAN, COL_PURPLE, COL_DARK_TILE, COL_DARK_TILE }; static const uint16_t tileFG[] = { COL_WHITE, COL_WHITE, 0x0000, COL_WHITE, COL_WHITE, COL_WHITE }; Dashboard::Dashboard(Gfx& tft) : _tft(tft), _sprite(&tft) { _tiles[TILE_LAST_ALERT] = { "!", "LAST ALERT", "none", "", 0, 0, true }; _tiles[TILE_STATS] = { "#", "TODAY", "0 alerts", "", 0, 0, true }; _tiles[TILE_NETWORK] = { "~", "NETWORK", "---", "", 0, 0, true }; _tiles[TILE_MUTE] = { "M", "MUTE", "OFF", "", 0, 0, true }; _tiles[TILE_HISTORY] = { ">", "HISTORY", "tap to view", "", 0, 0, true }; _tiles[TILE_SYSTEM] = { "*", "SYSTEM", "---", "", 0, 0, true }; for (int i = 0; i < TILE_COUNT; i++) { _tiles[i].bgColor = tileBG[i]; _tiles[i].fgColor = tileFG[i]; } } void Dashboard::begin() { _sprite.createSprite(TILE_W, TILE_H); _sprite.setTextDatum(MC_DATUM); } void Dashboard::drawAll() { _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) { int col = id % DASH_COLS; int row = id / DASH_COLS; x = DASH_MARGIN + col * (TILE_W + DASH_MARGIN); y = DASH_TOP_BAR + DASH_MARGIN + row * (TILE_H + DASH_MARGIN); } void Dashboard::drawTile(TileID id) { TileData& t = _tiles[id]; int tx, ty; tilePosition(id, tx, ty); _sprite.fillSprite(COL_BG); _sprite.fillRoundRect(0, 0, TILE_W, TILE_H, 8, t.bgColor); _sprite.drawRoundRect(0, 0, TILE_W, TILE_H, 8, COL_GRAY); _sprite.setTextColor(t.fgColor, t.bgColor); _sprite.setTextFont(4); _sprite.setTextSize(2); _sprite.setTextDatum(TC_DATUM); _sprite.drawString(t.icon, TILE_W / 2, 8); _sprite.setTextSize(1); _sprite.setTextFont(2); _sprite.setTextDatum(MC_DATUM); _sprite.drawString(t.label, TILE_W / 2, TILE_H / 2 + 5); _sprite.setTextFont(2); _sprite.setTextDatum(BC_DATUM); _sprite.drawString(t.value, TILE_W / 2, TILE_H - 25); if (strlen(t.sub) > 0) { _sprite.setTextFont(1); _sprite.setTextDatum(BC_DATUM); _sprite.drawString(t.sub, TILE_W / 2, TILE_H - 8); } _sprite.pushSprite(tx, ty); t.dirty = false; } void Dashboard::drawTopBar(const char* time, int rssi, bool wifiOk) { _tft.fillRect(0, 0, SCREEN_WIDTH, DASH_TOP_BAR, COL_BAR); _tft.setTextColor(COL_WHITE, COL_BAR); _tft.setTextFont(4); _tft.setTextSize(1); _tft.setTextDatum(ML_DATUM); _tft.drawString("KLUBHAUS ALERT", DASH_MARGIN, DASH_TOP_BAR / 2); _tft.setTextDatum(MR_DATUM); _tft.drawString(time, SCREEN_WIDTH - 10, DASH_TOP_BAR / 2); int bars = 0; if (wifiOk) { if (rssi > -50) bars = 4; else if (rssi > -60) bars = 3; else if (rssi > -70) bars = 2; else bars = 1; } int barX = SCREEN_WIDTH - 110, barW = 6, barGap = 3; for (int i = 0; i < 4; i++) { int barH = 6 + i * 5; int barY = DASH_TOP_BAR - 8 - barH; uint16_t col = (i < bars) ? COL_GREEN : COL_GRAY; _tft.fillRect(barX + i * (barW + barGap), barY, barW, barH, col); } strncpy(_barTime, time, sizeof(_barTime) - 1); _barRSSI = rssi; _barWifiOk = wifiOk; } void Dashboard::updateTile(TileID id, const char* value, const char* sub) { TileData& t = _tiles[id]; 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); t.value[31] = '\0'; if (sub) { strncpy(t.sub, sub, 31); t.sub[31] = '\0'; } t.dirty = true; drawTile(id); } int Dashboard::handleTouch(int x, int y) { for (int i = 0; i < TILE_COUNT; i++) { int tx, ty; tilePosition((TileID)i, tx, ty); if (x >= tx && x < tx + TILE_W && y >= ty && y < ty + TILE_H) return i; } return -1; } void Dashboard::refreshFromState(const ScreenState& state) { bool barChanged = (strcmp(_barTime, state.timeString) != 0) || (_barRSSI != state.wifiRSSI) || (_barWifiOk != state.wifiConnected); if (barChanged) { drawTopBar(state.timeString, state.wifiRSSI, state.wifiConnected); } if (state.alertHistoryCount > 0) { updateTile(TILE_LAST_ALERT, state.alertHistory[0].message, state.alertHistory[0].timestamp); } else { updateTile(TILE_LAST_ALERT, "none", ""); } char statsBuf[32]; snprintf(statsBuf, sizeof(statsBuf), "%d alert%s", state.alertHistoryCount, state.alertHistoryCount == 1 ? "" : "s"); updateTile(TILE_STATS, statsBuf, "this session"); 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..."); } updateTile(TILE_MUTE, "OFF", "tap to mute"); 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", ""); } 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); }