#pragma once /** * @file DisplayManager.h * @brief E-Ink display driver abstraction using GxEPD2_BW. * * Responsibilities: * - Manage the 400×300 Waveshare 4.2" GxEPD2 display via SPI. * - Render a 2×2 tile grid; each tile shows one data field. * - Perform partial refresh for numeric value updates (fast, no flicker). * - Perform full refresh on page change or periodically to prevent ghosting. * - Show a splash/boot screen, BLE status overlay and AP-mode notice. * * Partial refresh strategy: * Each tile's "value area" (excluding the fixed label and border) is * re-drawn with partial update. The borders and labels are only redrawn * during a full refresh, saving ~250 ms per cycle. */ #include "Config.h" #include #include #include #include // ── Display model — match the validated reference sketch for the 4.2" panel. // The generic 420 driver is used here to avoid a controller mismatch that can // leave the panel completely blank during init. using DisplayType = GxEPD2_BW; class DisplayManager { public: /** * @brief Initialise the SPI bus and display. Shows the splash screen. * Must be called from setup() before any render methods. */ void begin(); /** * @brief Main render call — invoke from loop(). * Decides between partial and full refresh based on: * - Whether data has changed since the last render. * - Whether FULL_REFRESH_INTERVAL_MS has elapsed. * - An explicit forceFullRefresh() request. * @param state Current boat data snapshot. * @param cfg Current page configuration. */ void render(const BoatState& state, const PageConfig& cfg); /** @brief Request a full (slow) refresh on the next render() call. */ void forceFullRefresh() { _needFullRefresh = true; } /** @brief Show a BLE scanning / connecting notice. */ void showBleStatus(AppState::BleStatus status); /** @brief Show the AP mode access screen with SSID and IP. */ void showApScreen(const char* ssid, const char* ip); /** @brief Show the OTA progress screen. */ void showOtaScreen(uint8_t percent); /** @brief Show the passkey entry prompt. */ void showPasskeyPrompt(uint32_t pin); /** @brief Hibernate the display to save power (full blank). */ void hibernate(); // Internal access for GxEPD2 DisplayType& epd() { return _display; } private: DisplayType _display { GxEPD2_420(Pins::EPD_CS, Pins::EPD_DC, Pins::EPD_RST, Pins::EPD_BUSY) }; bool _initialised = false; bool _needFullRefresh = true; uint32_t _lastFullRefresh = 0; uint32_t _lastRender = 0; // Cached value strings per tile to detect changes char _prevValue[Display::TILE_COUNT][16]; // ── Drawing helpers ─────────────────────────────────────────────────────── void _drawSplash(); void _drawGrid(); void _drawTile(uint8_t idx, const TileConfig& tile, const BoatState& state, bool partial); void _drawTileLabel(uint8_t idx, const TileConfig& tile); void _drawTileValue(uint8_t idx, const char* value, bool partial); // Compute the pixel rect for a tile struct Rect { uint16_t x, y, w, h; }; Rect _tileRect(uint8_t idx) const; Rect _valuRect(uint8_t idx) const; ///< Sub-rect for the value only // Extract a display string from the boat state for a given field void _fieldToString(DataField field, const BoatState& state, char* out, size_t outLen) const; // Centre text in a rect void _drawCentred(const char* text, uint16_t x, uint16_t y, uint16_t w, uint16_t h); }; // Singleton accessor extern DisplayManager displayManager;