diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
index 24e11bd4d..b8285ec8e 100644
--- a/.github/copilot-instructions.md
+++ b/.github/copilot-instructions.md
@@ -216,14 +216,14 @@ Multiple display driver families in `src/graphics/`:
- **OLED**: SSD1306, SH1106, ST7567
- **TFT**: TFTDisplay (LovyanGFX-based)
-- **E-Ink**: EInkDisplay2, EInkDynamicDisplay, EInkParallelDisplay
+- **E-Ink**: `src/graphics/BaseUIEInkDisplay.*` is the OLEDDisplay-compatible adapter (peer of `TFTDisplay`). The hardware layer it drives lives in `src/graphics/eink/` — chipset drivers in `Drivers/`, panel profiles in `Panels/`, optional `Backlight/`. Shared by both BaseUI-on-eink and InkHUD builds.
-**InkHUD** (`src/graphics/niche/InkHUD/`) is an event-driven e-ink UI framework:
+**InkHUD** (`src/graphics/niche/`) is an event-driven e-ink UI framework that sits on top of the `graphics/eink/` layer:
- Applet-based architecture — modular display tiles
- Read-only, static display optimized for minimal refreshes and low power
- Configured per-variant via `nicheGraphics.h`
-- Separate PlatformIO config: `src/graphics/niche/InkHUD/PlatformioConfig.ini`
+- Build helpers in top-level `platformio.ini` — `[niche]` pulls `graphics/eink/` only (BaseUI path), `[inkhud]` extends it with `graphics/niche/`
### Input System
diff --git a/.github/prompts/new-variant.prompt.md b/.github/prompts/new-variant.prompt.md
index 1a324cea9..0d0e0ad6b 100644
--- a/.github/prompts/new-variant.prompt.md
+++ b/.github/prompts/new-variant.prompt.md
@@ -132,7 +132,7 @@ For e-ink display variants using the InkHUD framework, add `nicheGraphics.h`:
// Configure display, applets, and refresh behavior per device
```
-InkHUD has its own PlatformIO config: `src/graphics/niche/InkHUD/PlatformioConfig.ini`
+InkHUD and the shared E-Ink layer are wired up via the top-level `platformio.ini` `[niche]` (BaseUI + driver/panel layer in `src/graphics/eink/`) and `[inkhud]` (adds the InkHUD UI in `src/graphics/niche/`). Variants opt in with `extends = ..., niche` or `extends = ..., inkhud`.
## I2C Device Detection
diff --git a/platformio.ini b/platformio.ini
index 0205d1ad8..2aafe81a4 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -8,10 +8,30 @@ extra_configs =
variants/*/*.ini
variants/*/*/platformio.ini
variants/*/diy/*/platformio.ini
- src/graphics/niche/InkHUD/PlatformioConfig.ini
description = Meshtastic
+; E-Ink / NicheGraphics build helpers.
+[niche]
+build_src_filter =
+ +
+build_flags =
+ -D MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+[inkhud]
+build_src_filter =
+ ${niche.build_src_filter}
+ +
+build_flags =
+ ${niche.build_flags}
+ -D MESHTASTIC_INCLUDE_INKHUD ; Use InkHUD as the UI
+ -D MESHTASTIC_EXCLUDE_SCREEN ; Suppress default Screen class
+ -D MESHTASTIC_EXCLUDE_INPUTBROKER ; Suppress default input handling
+ -D HAS_BUTTON=0 ; Suppress default ButtonThread
+lib_deps =
+ # renovate: datasource=github-tags depName=GFX_Root packageName=ZinggJM/GFX_Root
+ https://github.com/ZinggJM/GFX_Root/archive/3195764e352a0d2567c8d277ac408ca7293a99b0.zip ; Used by InkHUD as a "slimmer" version of AdafruitGFX
+
[env]
test_build_src = true
extra_scripts =
@@ -100,7 +120,7 @@ build_unflags =
-std=gnu++11
build_flags = ${env.build_flags} -Os
-std=gnu++17
-build_src_filter = ${env.build_src_filter} - -
+build_src_filter = ${env.build_src_filter} - - -
; Common libs for communicating over TCP/IP networks such as MQTT
[networking_base]
diff --git a/src/graphics/BaseUIEInkDisplay.cpp b/src/graphics/BaseUIEInkDisplay.cpp
new file mode 100644
index 000000000..f9ce40008
--- /dev/null
+++ b/src/graphics/BaseUIEInkDisplay.cpp
@@ -0,0 +1,211 @@
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+#include "./BaseUIEInkDisplay.h"
+
+#include "configuration.h"
+#include "main.h"
+
+using namespace NicheGraphics;
+
+BaseUIEInkDisplay::BaseUIEInkDisplay(Drivers::EInk *driver, uint8_t rotation) : driver(driver), rotation(rotation & 0x3)
+{
+ this->geometry = GEOMETRY_RAWMODE;
+
+ // BaseUI draws in UI orientation. Physical panel dimensions are swapped for 90°/270°.
+ const bool swap = (this->rotation == 1) || (this->rotation == 3);
+ this->displayWidth = swap ? driver->height : driver->width;
+ this->displayHeight = swap ? driver->width : driver->height;
+
+ uint16_t shortSide = min(displayWidth, displayHeight);
+ uint16_t longSide = max(displayWidth, displayHeight);
+ if (shortSide % 8 != 0)
+ shortSide = (shortSide | 7) + 1;
+ this->displayBufferSize = longSide * (shortSide / 8);
+
+ // Panel-native row-major buffer
+ panelRowBytes = ((driver->width - 1) / 8) + 1;
+ panelBufferSize = panelRowBytes * driver->height;
+ panelBuffer = new uint8_t[panelBufferSize];
+ memset(panelBuffer, 0xFF, panelBufferSize); // All white
+}
+
+BaseUIEInkDisplay::~BaseUIEInkDisplay()
+{
+ delete[] panelBuffer;
+}
+
+bool BaseUIEInkDisplay::connect()
+{
+ LOG_INFO("Init BaseUI E-Ink (%u x %u, rot %u)", driver->width, driver->height, rotation);
+ return true;
+}
+
+void BaseUIEInkDisplay::addFrameFlag(frameFlagTypes flag)
+{
+ frameFlags = (frameFlagTypes)(frameFlags | flag);
+}
+
+void BaseUIEInkDisplay::setDisplayResilience(uint8_t fastPerFull, float stressMultiplier)
+{
+ this->fastPerFull = (fastPerFull == 0) ? 1 : fastPerFull;
+ this->stressMultiplier = stressMultiplier;
+}
+
+void BaseUIEInkDisplay::joinAsyncRefresh()
+{
+ if (driver->busy())
+ driver->await();
+}
+
+// OLEDDisplayUi tick path. Honours rate-limit unless flags demand otherwise.
+void BaseUIEInkDisplay::display()
+{
+ const bool demandFast = frameFlags & DEMAND_FAST;
+ const bool cosmetic = frameFlags & COSMETIC;
+ const bool unlimitedFast = frameFlags & UNLIMITED_FAST;
+
+ if (!demandFast && !cosmetic && !unlimitedFast) {
+ if (!forceDisplay(lastDrawMsec == 0 ? 0 : 1000))
+ return;
+ return;
+ }
+
+ forceDisplay(0);
+}
+
+// Keyframe path. Returns true if a frame was pushed (sets lastDrawMsec).
+bool BaseUIEInkDisplay::forceDisplay(uint32_t msecLimit)
+{
+ const uint32_t now = millis();
+ if (lastDrawMsec != 0 && (now - lastDrawMsec) < msecLimit)
+ return false;
+
+ const bool blocking = frameFlags & BLOCKING;
+ Drivers::EInk::UpdateTypes type = decide();
+
+ // Don't pile frames on top of a running update - wait it out.
+ if (driver->busy())
+ driver->await();
+
+ const bool pushed = commit(type, blocking);
+ if (pushed)
+ lastDrawMsec = now;
+
+ // Reset flags for next frame
+ frameFlags = BACKGROUND;
+ return pushed;
+}
+
+bool BaseUIEInkDisplay::commit(Drivers::EInk::UpdateTypes type, bool blocking)
+{
+ uint32_t hash = repack();
+
+ // Skip if frame unchanged. Exception: caller explicitly wants a refresh (COSMETIC or FULL).
+ if (hash == lastHash && type != Drivers::EInk::UpdateTypes::FULL && lastDrawMsec != 0)
+ return false;
+ lastHash = hash;
+
+ // Fall back to FULL on panels that don't advertise FAST support.
+ if (type == Drivers::EInk::UpdateTypes::FAST && !driver->supports(Drivers::EInk::UpdateTypes::FAST))
+ type = Drivers::EInk::UpdateTypes::FULL;
+
+ driver->update(panelBuffer, type);
+
+ if (blocking)
+ driver->await();
+ return true;
+}
+
+Drivers::EInk::UpdateTypes BaseUIEInkDisplay::decide()
+{
+ typedef Drivers::EInk::UpdateTypes UT;
+
+ const bool unlimitedFast = frameFlags & UNLIMITED_FAST;
+
+ // Explicit flag wins outright
+ if (frameFlags & COSMETIC) {
+ fullRefreshDebt = max(fullRefreshDebt - 1.0f, 0.0f);
+ return UT::FULL;
+ }
+ if (frameFlags & DEMAND_FAST) {
+ if (!unlimitedFast) {
+ fullRefreshDebt += (fullRefreshDebt < 1.0f) ? (1.0f / fastPerFull) : (stressMultiplier * (1.0f / fastPerFull));
+ }
+ return UT::FAST;
+ }
+
+ const bool explicitFast = frameFlags & RESPONSIVE;
+
+ if (explicitFast || unlimitedFast) {
+ if (!unlimitedFast) {
+ fullRefreshDebt += (fullRefreshDebt < 1.0f) ? (1.0f / fastPerFull) : (stressMultiplier * (1.0f / fastPerFull));
+ }
+ return UT::FAST;
+ }
+
+ // BACKGROUND / unspecified: let debt decide
+ if (fullRefreshDebt >= 1.0f) {
+ fullRefreshDebt = max(fullRefreshDebt - 1.0f, 0.0f);
+ return UT::FULL;
+ }
+ fullRefreshDebt += 1.0f / fastPerFull;
+ return UT::FAST;
+}
+
+uint32_t BaseUIEInkDisplay::repack()
+{
+ memset(panelBuffer, 0xFF, panelBufferSize); // start all-white
+
+ const uint16_t pw = driver->width;
+ const uint16_t ph = driver->height;
+
+ // OLEDDisplay buffer: byte = buffer[x + (y/8) * displayWidth]; bit = 1 << (y & 7); 1 = black
+ // Niche buffer: byte = (y * panelRowBytes) + (x/8); bit = 1 << (7 - x%8); 1 = white
+ for (uint16_t oy = 0; oy < displayHeight; oy++) {
+ for (uint16_t ox = 0; ox < displayWidth; ox++) {
+ const uint8_t b = buffer[ox + (oy / 8) * displayWidth];
+ const bool isBlack = b & (1 << (oy & 7));
+
+ uint16_t px, py;
+ switch (rotation) {
+ case 1: // 90° CW: OLED (ox,oy) → panel (pw-1-oy, ox)
+ px = pw - 1 - oy;
+ py = ox;
+ break;
+ case 2: // 180°
+ px = pw - 1 - ox;
+ py = ph - 1 - oy;
+ break;
+ case 3: // 270° CW
+ px = oy;
+ py = ph - 1 - ox;
+ break;
+ case 0:
+ default:
+ px = ox;
+ py = oy;
+ break;
+ }
+
+ if (px >= pw || py >= ph)
+ continue;
+
+ const uint32_t byteNum = (py * panelRowBytes) + (px / 8);
+ const uint8_t bitNum = 7 - (px % 8);
+ if (isBlack)
+ panelBuffer[byteNum] &= ~(1 << bitNum);
+ else
+ panelBuffer[byteNum] |= (1 << bitNum);
+ }
+ }
+
+ // FNV-1a
+ uint32_t h = 2166136261u;
+ for (uint32_t i = 0; i < panelBufferSize; i++) {
+ h ^= panelBuffer[i];
+ h *= 16777619u;
+ }
+ return h;
+}
+
+#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
diff --git a/src/graphics/BaseUIEInkDisplay.h b/src/graphics/BaseUIEInkDisplay.h
new file mode 100644
index 000000000..5a411ffb4
--- /dev/null
+++ b/src/graphics/BaseUIEInkDisplay.h
@@ -0,0 +1,98 @@
+/*
+
+OLEDDisplay adapter that routes BaseUI pixel output to a NicheGraphics::Drivers::EInk driver.
+
+One adapter serves all E-Ink variants: the panel driver and orientation are injected at construction,
+and FULL/FAST selection is made by the shared DisplayHealth model (same as InkHUD).
+
+Replaces the per-board branching in EInkDisplay2 / EInkDynamicDisplay / EInkParallelDisplay.
+
+*/
+
+#pragma once
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+#include "configuration.h"
+
+#include "graphics/eink/Drivers/EInk.h"
+
+#include
+
+namespace NicheGraphics
+{
+
+class BaseUIEInkDisplay : public OLEDDisplay
+{
+ public:
+ // Flags Screen.cpp sets via EINK_ADD_FRAMEFLAG before triggering a draw.
+ // Bits are combined; decided at render time.
+ enum frameFlagTypes : uint8_t {
+ BACKGROUND = (1 << 0), // Regular OLEDDisplayUi tick - no urgency, UNSPECIFIED
+ RESPONSIVE = (1 << 1), // User-driven refresh - prefer FAST
+ COSMETIC = (1 << 2), // Clean splash / wake-from-sleep - force FULL
+ DEMAND_FAST = (1 << 3), // Menu interaction - force FAST
+ BLOCKING = (1 << 4), // Wait for update to finish before returning
+ UNLIMITED_FAST = (1 << 5), // Suppress health-driven FULL promotion (typing modes)
+ };
+
+ BaseUIEInkDisplay(Drivers::EInk *driver, uint8_t rotation);
+ ~BaseUIEInkDisplay() override;
+
+ // OLEDDisplay overrides
+ bool connect() override;
+ void display() override;
+ void sendCommand(uint8_t com) override { (void)com; }
+ int getBufferOffset(void) override { return 0; }
+
+ // BaseUI public API (same shape as the old EInkDynamicDisplay)
+ bool forceDisplay(uint32_t msecLimit = 1000);
+ void addFrameFlag(frameFlagTypes flag);
+ void joinAsyncRefresh();
+ void enableUnlimitedFastMode() { addFrameFlag(UNLIMITED_FAST); }
+ void disableUnlimitedFastMode() { frameFlags = (frameFlagTypes)(frameFlags & ~UNLIMITED_FAST); }
+
+ // Tuning, called once per panel profile
+ void setDisplayResilience(uint8_t fastPerFull, float stressMultiplier = 2.0f);
+
+ // Exposed so Screen.cpp / variants can read the rotation passed in at construction
+ uint8_t getRotation() const { return rotation; }
+
+ private:
+ // Perform an update now, unconditionally. Returns true if a frame was pushed to the driver.
+ bool commit(Drivers::EInk::UpdateTypes type, bool blocking);
+
+ // Convert OLEDDisplay's column-major buffer into the panel's row-major MSB-left buffer.
+ // Applies rotation. Returns the hash of the panel buffer for frame-skip comparison.
+ uint32_t repack();
+
+ // Decide FULL vs FAST based on current frame flags + accumulated debt.
+ Drivers::EInk::UpdateTypes decide();
+
+ Drivers::EInk *driver = nullptr;
+ uint8_t rotation = 0; // 0=0°, 1=90°CW, 2=180°, 3=270°CW
+ uint8_t *panelBuffer = nullptr;
+ uint32_t panelBufferSize = 0;
+ uint16_t panelRowBytes = 0;
+
+ frameFlagTypes frameFlags = BACKGROUND;
+ uint32_t lastDrawMsec = 0;
+ uint32_t lastHash = 0;
+
+ // DisplayHealth-style debt tracking
+ float fullRefreshDebt = 0.0f;
+ uint8_t fastPerFull = 7;
+ float stressMultiplier = 2.0f;
+};
+
+} // namespace NicheGraphics
+
+// Compat macros used throughout Screen.cpp - route straight to the adapter.
+#define EINK_ADD_FRAMEFLAG(display, flag) \
+ static_cast(display)->addFrameFlag(NicheGraphics::BaseUIEInkDisplay::flag)
+#define EINK_JOIN_ASYNCREFRESH(display) static_cast(display)->joinAsyncRefresh()
+
+#else // !MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+#define EINK_ADD_FRAMEFLAG(display, flag)
+#define EINK_JOIN_ASYNCREFRESH(display)
+#endif
diff --git a/src/graphics/EInkDisplay2.cpp b/src/graphics/EInkDisplay2.cpp
deleted file mode 100644
index 704487bc8..000000000
--- a/src/graphics/EInkDisplay2.cpp
+++ /dev/null
@@ -1,293 +0,0 @@
-#include "configuration.h"
-
-#if defined(USE_EINK) && !defined(USE_EINK_PARALLELDISPLAY)
-#include "EInkDisplay2.h"
-#include "SPILock.h"
-#include "main.h"
-#include
-
-#ifdef GXEPD2_DRIVER_0
-#include "einkDetect.h"
-#endif
-
-/*
- The macros EINK_DISPLAY_MODEL, EINK_WIDTH, and EINK_HEIGHT are defined as build_flags in a variant's platformio.ini
- Previously, these macros were defined at the top of this file.
-
- For archival reasons, note that the following configurations had also been tested during this period:
- * ifdef RAK4631
- - 4.2 inch
- EINK_DISPLAY_MODEL: GxEPD2_420_M01
- EINK_WIDTH: 300
- EINK_WIDTH: 400
-
- - 2.9 inch
- EINK_DISPLAY_MODEL: GxEPD2_290_T5D
- EINK_WIDTH: 296
- EINK_HEIGHT: 128
-
- - 1.54 inch
- EINK_DISPLAY_MODEL: GxEPD2_154_M09
- EINK_WIDTH: 200
- EINK_HEIGHT: 200
-*/
-
-// Constructor
-EInkDisplay::EInkDisplay(uint8_t address, int sda, int scl, OLEDDISPLAY_GEOMETRY geometry, HW_I2C i2cBus)
-{
- // Set dimensions in OLEDDisplay base class
- this->geometry = GEOMETRY_RAWMODE;
- this->displayWidth = EINK_WIDTH;
- this->displayHeight = EINK_HEIGHT;
-
- // Round shortest side up to nearest byte, to prevent truncation causing an undersized buffer
- uint16_t shortSide = min(EINK_WIDTH, EINK_HEIGHT);
- uint16_t longSide = max(EINK_WIDTH, EINK_HEIGHT);
- if (shortSide % 8 != 0)
- shortSide = (shortSide | 7) + 1;
-
- this->displayBufferSize = longSide * (shortSide / 8);
-}
-
-/**
- * Force a display update if we haven't drawn within the specified msecLimit
- */
-bool EInkDisplay::forceDisplay(uint32_t msecLimit)
-{
- // No need to grab this lock because we are on our own SPI bus
- // concurrency::LockGuard g(spiLock);
-
- uint32_t now = millis();
- uint32_t sinceLast = now - lastDrawMsec;
-
- if (adafruitDisplay && (sinceLast > msecLimit || lastDrawMsec == 0))
- lastDrawMsec = now;
- else
- return false;
-
- // FIXME - only draw bits have changed (use backbuf similar to the other displays)
- const bool flipped = config.display.flip_screen;
- // HACK for L1 EInk
-#if defined(SEEED_WIO_TRACKER_L1_EINK)
- // For SEEED_WIO_TRACKER_L1_EINK, setRotation(3) is correct but mirrored; flip both axes
- for (uint32_t y = 0; y < displayHeight; y++) {
- for (uint32_t x = 0; x < displayWidth; x++) {
- auto b = buffer[x + (y / 8) * displayWidth];
- auto isset = b & (1 << (y & 7));
- adafruitDisplay->drawPixel((displayWidth - 1) - x, (displayHeight - 1) - y, isset ? GxEPD_BLACK : GxEPD_WHITE);
- }
- }
-#else
- for (uint32_t y = 0; y < displayHeight; y++) {
- for (uint32_t x = 0; x < displayWidth; x++) {
- auto b = buffer[x + (y / 8) * displayWidth];
- auto isset = b & (1 << (y & 7));
- if (flipped)
- adafruitDisplay->drawPixel((displayWidth - 1) - x, (displayHeight - 1) - y, isset ? GxEPD_BLACK : GxEPD_WHITE);
- else
- adafruitDisplay->drawPixel(x, y, isset ? GxEPD_BLACK : GxEPD_WHITE);
- }
- }
-#endif
-
- // Trigger the refresh in GxEPD2
- LOG_DEBUG("Update E-Paper");
- adafruitDisplay->nextPage();
-
- // End the update process
- endUpdate();
-
- LOG_DEBUG("done");
- return true;
-}
-
-// End the update process - virtual method, overridden in derived class
-void EInkDisplay::endUpdate()
-{
- // Power off display hardware, then deep-sleep (Except Wireless Paper V1.1, no deep-sleep)
- adafruitDisplay->hibernate();
-}
-
-// Write the buffer to the display memory
-void EInkDisplay::display(void)
-{
- // We don't allow regular 'dumb' display() calls to draw on eink until we've shown
- // at least one forceDisplay() keyframe. This prevents flashing when we should the critical
- // bootscreen (that we want to look nice)
-
- if (lastDrawMsec) {
- forceDisplay(slowUpdateMsec); // Show the first screen a few seconds after boot, then slower
- }
-}
-
-// Send a command to the display (low level function)
-void EInkDisplay::sendCommand(uint8_t com)
-{
- (void)com;
- // Drop all commands to device (we just update the buffer)
-}
-
-void EInkDisplay::setDetected(uint8_t detected)
-{
- (void)detected;
-}
-
-// Connect to the display - variant specific
-bool EInkDisplay::connect()
-{
- LOG_INFO("Do EInk init");
-
-#ifdef PIN_EINK_EN
- // backlight power, HIGH is backlight on, LOW is off
- pinMode(PIN_EINK_EN, OUTPUT);
-#ifdef ELECROW_ThinkNode_M1
- // ThinkNode M1 has a hardware dimmable backlight. Start enabled
- digitalWrite(PIN_EINK_EN, HIGH);
-#elif defined(MINI_EPAPER_S3)
- // T-Mini Epaper S3 requires panel power rail enabled before SPI transfer.
- digitalWrite(PIN_EINK_EN, HIGH);
- delay(10);
-#else
- digitalWrite(PIN_EINK_EN, LOW);
-#endif
-#endif
-
-#if defined(TTGO_T_ECHO) || defined(ELECROW_ThinkNode_M1) || defined(T_ECHO_LITE) || defined(TTGO_T_ECHO_PLUS)
- {
- auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY, SPI1);
-
- adafruitDisplay = new GxEPD2_BW(*lowLevel);
- adafruitDisplay->init();
-#if defined(ELECROW_ThinkNode_M1) || defined(T_ECHO_LITE)
- adafruitDisplay->setRotation(4);
-#else
- adafruitDisplay->setRotation(3);
-#endif
- adafruitDisplay->setPartialWindow(0, 0, displayWidth, displayHeight);
- }
-#elif defined(ELECROW_ThinkNode_M5)
- {
- // Start HSPI
- hspi = new SPIClass(HSPI);
- hspi->begin(PIN_EINK_SCLK, -1, PIN_EINK_MOSI, PIN_EINK_CS); // SCLK, MISO, MOSI, SS
-
- auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY, *hspi);
-
- adafruitDisplay = new GxEPD2_BW(*lowLevel);
- adafruitDisplay->init();
-
- adafruitDisplay->setRotation(4);
-
- adafruitDisplay->setPartialWindow(0, 0, displayWidth, displayHeight);
- }
-#elif defined(MESHLINK)
- {
- auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY, SPI1);
-
- adafruitDisplay = new GxEPD2_BW(*lowLevel);
- adafruitDisplay->init();
- adafruitDisplay->setRotation(3);
- adafruitDisplay->setPartialWindow(0, 0, displayWidth, displayHeight);
- }
-#elif defined(RAK4630) || defined(MAKERPYTHON)
- {
- if (eink_found) {
- auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY);
- adafruitDisplay = new GxEPD2_BW(*lowLevel);
- adafruitDisplay->init(115200, true, 10, false, SPI1, SPISettings(4000000, MSBFIRST, SPI_MODE0));
- // RAK14000 2.13 inch b/w 250x122 does actually now support fast refresh
- adafruitDisplay->setRotation(3);
- // Fast refresh support for 1.54, 2.13 RAK14000 b/w , 2.9 and 4.2
- // adafruitDisplay->setRotation(1);
- adafruitDisplay->setPartialWindow(0, 0, displayWidth, displayHeight);
- } else {
- (void)adafruitDisplay;
- }
- }
-
-#elif defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_VISION_MASTER_E290) || defined(TLORA_T3S3_EPAPER) || \
- defined(CROWPANEL_ESP32S3_5_EPAPER) || defined(CROWPANEL_ESP32S3_4_EPAPER) || defined(CROWPANEL_ESP32S3_2_EPAPER) || \
- defined(MINI_EPAPER_S3)
- {
- // Start HSPI
- hspi = new SPIClass(HSPI);
- hspi->begin(PIN_EINK_SCLK, -1, PIN_EINK_MOSI, PIN_EINK_CS); // SCLK, MISO, MOSI, SS
- // VExt already enabled in setup()
- // RTC GPIO hold disabled in setup()
-
- // Create GxEPD2 objects
- auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY, *hspi);
- adafruitDisplay = new GxEPD2_BW(*lowLevel);
-
- // Init GxEPD2
- adafruitDisplay->init();
-#if defined(MINI_EPAPER_S3)
- adafruitDisplay->setRotation(3);
-#else
- adafruitDisplay->setRotation(3);
-#if defined(CROWPANEL_ESP32S3_5_EPAPER) || defined(CROWPANEL_ESP32S3_4_EPAPER)
- adafruitDisplay->setRotation(0);
-#endif
-#endif
- }
-#elif defined(PCA10059) || defined(ME25LS01)
- {
- auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY);
- adafruitDisplay = new GxEPD2_BW(*lowLevel);
- adafruitDisplay->init(115200, true, 40, false, SPI1, SPISettings(4000000, MSBFIRST, SPI_MODE0));
- adafruitDisplay->setRotation(0);
- adafruitDisplay->setPartialWindow(0, 0, EINK_WIDTH, EINK_HEIGHT);
- }
-#elif defined(M5_COREINK) || defined(T_DECK_PRO)
- auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY);
- adafruitDisplay = new GxEPD2_BW(*lowLevel);
- adafruitDisplay->init(115200, true, 40, false, SPI, SPISettings(4000000, MSBFIRST, SPI_MODE0));
- adafruitDisplay->setRotation(0);
- adafruitDisplay->setPartialWindow(0, 0, EINK_WIDTH, EINK_HEIGHT);
-#elif defined(my) || defined(ESP32_S3_PICO)
- {
- auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY);
- adafruitDisplay = new GxEPD2_BW(*lowLevel);
- adafruitDisplay->init(115200, true, 40, false, SPI, SPISettings(4000000, MSBFIRST, SPI_MODE0));
- adafruitDisplay->setRotation(1);
- adafruitDisplay->setPartialWindow(0, 0, EINK_WIDTH, EINK_HEIGHT);
- }
-#elif defined(HELTEC_MESH_POCKET) || defined(SEEED_WIO_TRACKER_L1_EINK) || defined(HELTEC_MESH_SOLAR_EINK)
- {
- spi1 = &SPI1;
- spi1->begin();
- // VExt already enabled in setup()
- // RTC GPIO hold disabled in setup()
-
- // Create GxEPD2 objects
- auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY, *spi1);
- adafruitDisplay = new GxEPD2_BW(*lowLevel);
-
- // Init GxEPD2
- adafruitDisplay->init();
- adafruitDisplay->setRotation(3);
- adafruitDisplay->setPartialWindow(0, 0, EINK_WIDTH, EINK_HEIGHT);
- }
-#elif defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_VISION_MASTER_E213)
-
- // Detect display model, before starting SPI
- EInkDetectionResult displayModel = detectEInk();
-
- // Start HSPI
- hspi = new SPIClass(HSPI);
- hspi->begin(PIN_EINK_SCLK, -1, PIN_EINK_MOSI, PIN_EINK_CS); // SCLK, MISO, MOSI, SS
-
- // Create GxEPD2 object
- adafruitDisplay = new GxEPD2_Multi((uint8_t)displayModel, PIN_EINK_CS, PIN_EINK_DC,
- PIN_EINK_RES, PIN_EINK_BUSY, *hspi);
-
- // Init GxEPD2
- adafruitDisplay->init();
- adafruitDisplay->setRotation(3);
-
-#endif
-
- return true;
-}
-
-#endif
diff --git a/src/graphics/EInkDisplay2.h b/src/graphics/EInkDisplay2.h
deleted file mode 100644
index 645a3f2d0..000000000
--- a/src/graphics/EInkDisplay2.h
+++ /dev/null
@@ -1,106 +0,0 @@
-#pragma once
-
-#if defined(USE_EINK) && !defined(USE_EINK_PARALLELDISPLAY)
-
-#include "GxEPD2_BW.h"
-#include
-
-#ifdef GXEPD2_DRIVER_0 // If variant has multiple possible display models
-#include "GxEPD2Multi.h"
-#endif
-
-// Limit how often we push a full E-Ink refresh. T-Deck Pro needs faster updates for typing.
-#ifndef EINK_FORCE_DISPLAY_THROTTLE_MS
-#if defined(T_DECK_PRO)
-#define EINK_FORCE_DISPLAY_THROTTLE_MS 200
-#else
-#define EINK_FORCE_DISPLAY_THROTTLE_MS 1000
-#endif
-#endif
-
-/**
- * An adapter class that allows using the GxEPD2 library as if it was an OLEDDisplay implementation.
- *
- * Note: EInkDynamicDisplay derives from this class.
- *
- * Remaining TODO:
- * optimize display() to only draw changed pixels (see other OLED subclasses for examples)
- * implement displayOn/displayOff to turn off the TFT device (and backlight)
- * Use the fast NRF52 SPI API rather than the slow standard arduino version
- *
- * turn radio back on - currently with both on spi bus is fucked? or are we leaving chip select asserted?
- * Suggestion: perhaps similar to HELTEC_WIRELESS_PAPER issue, which resolved with rtc_gpio_hold_dis()
- */
-class EInkDisplay : public OLEDDisplay
-{
- /// How often should we update the display
- /// thereafter we do once per 5 minutes
- uint32_t slowUpdateMsec = 5 * 60 * 1000;
-
- public:
- /* constructor
- FIXME - the parameters are not used, just a temporary hack to keep working like the old displays
- */
- EInkDisplay(uint8_t, int, int, OLEDDISPLAY_GEOMETRY, HW_I2C);
-
- // Write the buffer to the display memory (for eink we only do this occasionally)
- virtual void display(void) override;
-
- /**
- * Force a display update if we haven't drawn within the specified msecLimit
- *
- * @return true if we did draw the screen
- */
- virtual bool forceDisplay(uint32_t msecLimit = EINK_FORCE_DISPLAY_THROTTLE_MS);
-
- /**
- * Run any code needed to complete an update, after the physical refresh has completed.
- * Split from forceDisplay(), to enable async refresh in derived EInkDynamicDisplay class.
- *
- */
- virtual void endUpdate();
-
- /**
- * shim to make the abstraction happy
- *
- */
- void setDetected(uint8_t detected);
-
- protected:
- // the header size of the buffer used, e.g. for the SPI command header
- virtual int getBufferOffset(void) override { return 0; }
-
- // Send a command to the display (low level function)
- virtual void sendCommand(uint8_t com) override;
-
- // Connect to the display
- virtual bool connect() override;
-
-#ifdef GXEPD2_DRIVER_0
- // AdafruitGFX display object - wrapper for multiple drivers
- // Allows runtime detection of multiple displays
- // Avoid this situation if possible!
- GxEPD2_Multi *adafruitDisplay = NULL;
-#else
- // AdafruitGFX display object (for single display model) - instantiated in connect(), variant specific
- GxEPD2_BW *adafruitDisplay = NULL;
-#endif
-
- // If display uses HSPI
-#if defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_VISION_MASTER_E213) || \
- defined(HELTEC_VISION_MASTER_E290) || defined(TLORA_T3S3_EPAPER) || defined(CROWPANEL_ESP32S3_5_EPAPER) || \
- defined(CROWPANEL_ESP32S3_4_EPAPER) || defined(CROWPANEL_ESP32S3_2_EPAPER) || defined(ELECROW_ThinkNode_M5) || \
- defined(MINI_EPAPER_S3)
- SPIClass *hspi = NULL;
-#endif
-
-#if defined(HELTEC_MESH_POCKET) || defined(SEEED_WIO_TRACKER_L1_EINK) || defined(HELTEC_MESH_SOLAR_EINK)
- SPIClass *spi1 = NULL;
-#endif
-
- private:
- // FIXME quick hack to limit drawing to a very slow rate
- uint32_t lastDrawMsec = 0;
-};
-
-#endif
diff --git a/src/graphics/EInkDynamicDisplay.cpp b/src/graphics/EInkDynamicDisplay.cpp
deleted file mode 100644
index a48ba5c93..000000000
--- a/src/graphics/EInkDynamicDisplay.cpp
+++ /dev/null
@@ -1,564 +0,0 @@
-#include "Throttle.h"
-#include "configuration.h"
-
-#if defined(USE_EINK) && defined(USE_EINK_DYNAMICDISPLAY)
-#include "EInkDynamicDisplay.h"
-
-// Constructor
-EInkDynamicDisplay::EInkDynamicDisplay(uint8_t address, int sda, int scl, OLEDDISPLAY_GEOMETRY geometry, HW_I2C i2cBus)
- : EInkDisplay(address, sda, scl, geometry, i2cBus), NotifiedWorkerThread("EInkDynamicDisplay")
-{
- // If tracking ghost pixels, grab memory
-#ifdef EINK_LIMIT_GHOSTING_PX
- dirtyPixels = std::unique_ptr(new uint8_t[EInkDisplay::displayBufferSize]()); // Init with zeros
-#endif
-}
-
-// Destructor
-EInkDynamicDisplay::~EInkDynamicDisplay()
-{
- // If we were tracking ghost pixels, free the memory
-#ifdef EINK_LIMIT_GHOSTING_PX
- dirtyPixels = nullptr;
-#endif
-}
-
-// Screen requests a BACKGROUND frame
-void EInkDynamicDisplay::display()
-{
- addFrameFlag(BACKGROUND);
- update();
-}
-
-// Screen requests a RESPONSIVE frame
-bool EInkDynamicDisplay::forceDisplay(uint32_t msecLimit)
-{
- addFrameFlag(RESPONSIVE);
- return update(); // (Unutilized) Base class promises to return true if update ran
-}
-
-// Add flag for the next frame
-void EInkDynamicDisplay::addFrameFlag(frameFlagTypes flag)
-{
- // OR the new flag into the existing flags
- this->frameFlags = (frameFlagTypes)(this->frameFlags | flag);
-}
-
-// GxEPD2 code to set fast refresh
-void EInkDynamicDisplay::configForFastRefresh()
-{
- // Variant-specific code can go here
-#if defined(PRIVATE_HW)
-#else
- // Otherwise:
- adafruitDisplay->setPartialWindow(0, 0, adafruitDisplay->width(), adafruitDisplay->height());
-#endif
-}
-
-// GxEPD2 code to set full refresh
-void EInkDynamicDisplay::configForFullRefresh()
-{
- // Variant-specific code can go here
-#if defined(PRIVATE_HW)
-#else
- // Otherwise:
- adafruitDisplay->setFullWindow();
-#endif
-}
-
-// Run any relevant GxEPD2 code, so next update will use correct refresh type
-void EInkDynamicDisplay::applyRefreshMode()
-{
- // Change from FULL to FAST
- if (currentConfig == FULL && refresh == FAST) {
- configForFastRefresh();
- currentConfig = FAST;
- }
-
- // Change from FAST back to FULL
- else if (currentConfig == FAST && refresh == FULL) {
- configForFullRefresh();
- currentConfig = FULL;
- }
-}
-
-// Update fastRefreshCount
-void EInkDynamicDisplay::adjustRefreshCounters()
-{
- if (refresh == FAST)
- fastRefreshCount++;
-
- else if (refresh == FULL)
- fastRefreshCount = 0;
-}
-
-// Trigger the display update by calling base class
-bool EInkDynamicDisplay::update()
-{
- // Determine the refresh mode to use, and start the update
- bool refreshApproved = determineMode();
- if (refreshApproved) {
- EInkDisplay::forceDisplay(0); // Bypass base class' own rate-limiting system
- storeAndReset(); // Store the result of this loop for next time. Note: call *before* endOrDetach()
- endOrDetach(); // endUpdate() right now, or set the async refresh flag (if FULL and HAS_EINK_ASYNCFULL)
- } else
- storeAndReset(); // No update, no post-update code, just store the results
-
- return refreshApproved; // (Unutilized) Base class promises to return true if update ran
-}
-
-// Figure out who runs the post-update code
-void EInkDynamicDisplay::endOrDetach()
-{
- // If the GxEPD2 version reports that it has the async modifications
-#ifdef HAS_EINK_ASYNCFULL
- if (previousRefresh == FULL) {
- asyncRefreshRunning = true; // Set the flag - checked in determineMode(); cleared by onNotify()
-
- if (previousFrameFlags & BLOCKING)
- awaitRefresh();
- else {
- // Async begins
- LOG_DEBUG("Async full-refresh begins (drop frames)");
- notifyLater(intervalPollAsyncRefresh, DUE_POLL_ASYNCREFRESH, true); // Hand-off to NotifiedWorkerThread
- }
- }
-
- // Fast Refresh
- else if (previousRefresh == FAST)
- EInkDisplay::endUpdate(); // Still block while updating, but EInkDisplay needs us to call endUpdate() ourselves.
-
- // Fallback - If using an unmodified version of GxEPD2 for some reason
-#else
- if (previousRefresh == FULL || previousRefresh == FAST) { // If refresh wasn't skipped (on unspecified..)
- LOG_WARN(
- "GxEPD2 version has not been modified to support async refresh; using fallback behavior. Please update lib_deps in "
- "variant's platformio.ini file");
- EInkDisplay::endUpdate();
- }
-#endif
-}
-
-// Assess situation, pick a refresh type
-bool EInkDynamicDisplay::determineMode()
-{
- checkInitialized();
- checkForPromotion();
-#if defined(HAS_EINK_ASYNCFULL)
- checkBusyAsyncRefresh();
-#endif
- checkRateLimiting();
-
- // If too soon for a new frame, or display busy, abort early
- if (refresh == SKIPPED)
- return false; // No refresh
-
- // -- New frame is due --
-
- resetRateLimiting(); // Once determineMode() ends, will have to wait again
- hashImage(); // Generate here, so we can still copy it to previousImageHash, even if we skip the comparison check
- LOG_DEBUG("determineMode(): "); // Begin log entry
-
- // Once mode determined, any remaining checks will bypass
- checkCosmetic();
- checkDemandingFast();
- checkFrameMatchesPrevious();
- checkConsecutiveFastRefreshes();
-#ifdef EINK_LIMIT_GHOSTING_PX
- checkExcessiveGhosting();
-#endif
- checkFastRequested();
-
- if (refresh == UNSPECIFIED)
- LOG_WARN("There was a flaw in the determineMode() logic");
-
- // -- Decision has been reached --
- applyRefreshMode();
- adjustRefreshCounters();
-
-#ifdef EINK_LIMIT_GHOSTING_PX
- // Full refresh clears any ghosting
- if (refresh == FULL)
- resetGhostPixelTracking();
-#endif
-
- // Return - call a refresh or not?
- if (refresh == SKIPPED)
- return false; // Don't trigger a refresh
- else
- return true; // Do trigger a refresh
-}
-
-// Is this the very first frame?
-void EInkDynamicDisplay::checkInitialized()
-{
- if (!initialized) {
- // Undo GxEPD2_BW::partialWindow(), if set by developer in EInkDisplay::connect()
- configForFullRefresh();
-
- // Clear any existing image, so we can draw logo with fast-refresh, but also to set GxEPD2_EPD::_initial_write
- adafruitDisplay->clearScreen();
-
- LOG_DEBUG("initialized, ");
- initialized = true;
-
- // Use a fast-refresh for the next frame; no skipping or else blank screen when waking from deep sleep
- addFrameFlag(DEMAND_FAST);
- }
-}
-
-// Was a frame skipped (rate, display busy) that should have been a FAST refresh?
-void EInkDynamicDisplay::checkForPromotion()
-{
- // If a frame was skipped (rate, display busy), then promote a BACKGROUND frame
- // Because we DID want a RESPONSIVE/COSMETIC/DEMAND_FULL frame last time, we just didn't get it
-
- switch (previousReason) {
- case ASYNC_REFRESH_BLOCKED_DEMANDFAST:
- addFrameFlag(DEMAND_FAST);
- break;
- case ASYNC_REFRESH_BLOCKED_COSMETIC:
- addFrameFlag(COSMETIC);
- break;
- case ASYNC_REFRESH_BLOCKED_RESPONSIVE:
- case EXCEEDED_RATELIMIT_FAST:
- addFrameFlag(RESPONSIVE);
- break;
- default:
- break;
- }
-}
-
-// Is it too soon for another frame of this type?
-void EInkDynamicDisplay::checkRateLimiting()
-{
- // Sanity check: millis() overflow - just let the update run..
- if (previousRunMs > millis())
- return;
-
- // Skip update: too soon for BACKGROUND
- if (frameFlags == BACKGROUND) {
- if (Throttle::isWithinTimespanMs(previousRunMs, 30000)) {
- refresh = SKIPPED;
- reason = EXCEEDED_RATELIMIT_FULL;
- return;
- }
- }
-
- // No rate-limit for these special cases
- if (frameFlags & COSMETIC || frameFlags & DEMAND_FAST)
- return;
-
- // Skip update: too soon for RESPONSIVE
- if (frameFlags & RESPONSIVE) {
- if (Throttle::isWithinTimespanMs(previousRunMs, 1000)) {
- refresh = SKIPPED;
- reason = EXCEEDED_RATELIMIT_FAST;
- LOG_DEBUG("refresh=SKIPPED, reason=EXCEEDED_RATELIMIT_FAST, frameFlags=0x%x", frameFlags);
- return;
- }
- }
-}
-
-// Is this frame COSMETIC (splash screens?)
-void EInkDynamicDisplay::checkCosmetic()
-{
- // If a decision was already reached, don't run the check
- if (refresh != UNSPECIFIED)
- return;
-
- // A full refresh is requested for cosmetic purposes: we have a decision
- if (frameFlags & COSMETIC) {
- refresh = FULL;
- reason = FLAGGED_COSMETIC;
- LOG_DEBUG("refresh=FULL, reason=FLAGGED_COSMETIC, frameFlags=0x%x", frameFlags);
- }
-}
-
-// Is this a one-off special circumstance, where we REALLY want a fast refresh?
-void EInkDynamicDisplay::checkDemandingFast()
-{
- // If a decision was already reached, don't run the check
- if (refresh != UNSPECIFIED)
- return;
-
- // A fast refresh is demanded: we have a decision
- if (frameFlags & DEMAND_FAST) {
- refresh = FAST;
- reason = FLAGGED_DEMAND_FAST;
- LOG_DEBUG("refresh=FAST, reason=FLAGGED_DEMAND_FAST, frameFlags=0x%x", frameFlags);
- }
-}
-
-// Does the new frame match the currently displayed image?
-void EInkDynamicDisplay::checkFrameMatchesPrevious()
-{
- // If a decision was already reached, don't run the check
- if (refresh != UNSPECIFIED)
- return;
-
- // If frame is *not* a duplicate, abort the check
- if (imageHash != previousImageHash)
- return;
-
-#if !defined(EINK_BACKGROUND_USES_FAST)
- // If BACKGROUND, and last update was FAST: redraw the same image in FULL (for display health + image quality)
- if (frameFlags == BACKGROUND && fastRefreshCount > 0) {
- refresh = FULL;
- reason = REDRAW_WITH_FULL;
- LOG_DEBUG("refresh=FULL, reason=REDRAW_WITH_FULL, frameFlags=0x%x", frameFlags);
- return;
- }
-#endif
-
- // Not redrawn, not COSMETIC, not DEMAND_FAST
- refresh = SKIPPED;
- reason = FRAME_MATCHED_PREVIOUS;
- LOG_DEBUG("refresh=SKIPPED, reason=FRAME_MATCHED_PREVIOUS, frameFlags=0x%x", frameFlags);
-}
-
-// Have too many fast-refreshes occurred consecutively, since last full refresh?
-void EInkDynamicDisplay::checkConsecutiveFastRefreshes()
-{
- // If a decision was already reached, don't run the check
- if (refresh != UNSPECIFIED)
- return;
-
- // Bypass limit if UNLIMITED_FAST mode is active
- if (frameFlags & UNLIMITED_FAST) {
- refresh = FAST;
- reason = NO_OBJECTIONS;
- LOG_DEBUG("refresh=FAST, reason=UNLIMITED_FAST_MODE_ACTIVE, frameFlags=0x%x", frameFlags);
- return;
- }
-
- // If too many FAST refreshes consecutively - force a FULL refresh
- if (fastRefreshCount >= EINK_LIMIT_FASTREFRESH) {
- refresh = FULL;
- reason = EXCEEDED_LIMIT_FASTREFRESH;
- LOG_DEBUG("refresh=FULL, reason=EXCEEDED_LIMIT_FASTREFRESH, frameFlags=0x%x", frameFlags);
- }
-}
-
-// No objections, we can perform fast-refresh, if desired
-void EInkDynamicDisplay::checkFastRequested()
-{
- if (refresh != UNSPECIFIED)
- return;
-
- if (frameFlags == BACKGROUND) {
-#ifdef EINK_BACKGROUND_USES_FAST
- // If we want BACKGROUND to use fast. (FULL only when a limit is hit)
- refresh = FAST;
- reason = BACKGROUND_USES_FAST;
- LOG_DEBUG("refresh=FAST, reason=BACKGROUND_USES_FAST, fastRefreshCount=%lu, frameFlags=0x%x", fastRefreshCount,
- frameFlags);
-#else
- // If we do want to use FULL for BACKGROUND updates
- refresh = FULL;
- reason = FLAGGED_BACKGROUND;
- LOG_DEBUG("refresh=FULL, reason=FLAGGED_BACKGROUND");
-#endif
- }
-
- // Sanity: confirm that we did ask for a RESPONSIVE frame.
- if (frameFlags & RESPONSIVE) {
- refresh = FAST;
- reason = NO_OBJECTIONS;
- LOG_DEBUG("refresh=FAST, reason=NO_OBJECTIONS, fastRefreshCount=%lu, frameFlags=0x%x", fastRefreshCount, frameFlags);
- }
-}
-
-// Reset the timer used for rate-limiting
-void EInkDynamicDisplay::resetRateLimiting()
-{
- previousRunMs = millis();
-}
-
-// Generate a hash of this frame, to compare against previous update
-void EInkDynamicDisplay::hashImage()
-{
- imageHash = 0;
-
- // Sum all bytes of the image buffer together
- for (uint16_t b = 0; b < (displayWidth / 8) * displayHeight; b++) {
- imageHash ^= buffer[b] << b;
- }
-}
-
-// Store the results of determineMode() for future use, and reset for next call
-void EInkDynamicDisplay::storeAndReset()
-{
- previousFrameFlags = frameFlags;
- previousRefresh = refresh;
- previousReason = reason;
-
- // Only store image hash if the display will update
- if (refresh != SKIPPED) {
- previousImageHash = imageHash;
- }
-
- frameFlags = BACKGROUND;
- refresh = UNSPECIFIED;
-}
-
-#ifdef EINK_LIMIT_GHOSTING_PX
-// Count how many ghost pixels the new image will display
-void EInkDynamicDisplay::countGhostPixels()
-{
- // If a decision was already reached, don't run the check
- if (refresh != UNSPECIFIED)
- return;
-
- // Start a new count
- ghostPixelCount = 0;
-
- // Check new image, bit by bit, for any white pixels at locations marked "dirty"
- for (uint16_t i = 0; i < displayBufferSize; i++) {
- for (uint8_t bit = 0; bit < 7; bit++) {
-
- const bool dirty = (dirtyPixels[i] >> bit) & 1; // Has pixel location been drawn to since full-refresh?
- const bool shouldBeBlank = !((buffer[i] >> bit) & 1); // Is pixel location white in the new image?
-
- // If pixel is (or has been) black since last full-refresh, and now is white: ghosting
- if (dirty && shouldBeBlank)
- ghostPixelCount++;
-
- // Update the dirty status for this pixel - will this location become a ghost if set white in future?
- if (!dirty && !shouldBeBlank)
- dirtyPixels[i] |= (1 << bit);
- }
- }
-
- LOG_DEBUG("ghostPixels=%hu, ", ghostPixelCount);
-}
-
-// Check if ghost pixel count exceeds the defined limit
-void EInkDynamicDisplay::checkExcessiveGhosting()
-{
- // If a decision was already reached, don't run the check
- if (refresh != UNSPECIFIED)
- return;
-
- countGhostPixels();
-
- // If too many ghost pixels, select full refresh
- if (ghostPixelCount > EINK_LIMIT_GHOSTING_PX) {
- refresh = FULL;
- reason = EXCEEDED_GHOSTINGLIMIT;
- LOG_DEBUG("refresh=FULL, reason=EXCEEDED_GHOSTINGLIMIT, frameFlags=0x%x", frameFlags);
- }
-}
-
-// Clear the dirty pixels array. Call when full-refresh cleans the display.
-void EInkDynamicDisplay::resetGhostPixelTracking()
-{
- // Copy the current frame into dirtyPixels[] from the display buffer
- memcpy(dirtyPixels.get(), EInkDisplay::buffer, EInkDisplay::displayBufferSize);
-}
-#endif // EINK_LIMIT_GHOSTING_PX
-
-// Handle any asyc tasks
-void EInkDynamicDisplay::onNotify(uint32_t notification)
-{
- // Which task
- switch (notification) {
- case DUE_POLL_ASYNCREFRESH:
- pollAsyncRefresh();
- break;
- }
-}
-
-#ifdef HAS_EINK_ASYNCFULL
-// Public: wait for an refresh already in progress, then run the post-update code. See Screen::setScreensaverFrames()
-void EInkDynamicDisplay::joinAsyncRefresh()
-{
- // If no async refresh running, nothing to do
- if (!asyncRefreshRunning)
- return;
-
- LOG_DEBUG("Join an async refresh in progress");
-
- // Continually poll the BUSY pin
- while (adafruitDisplay->epd2.isBusy())
- yield();
-
- // If asyncRefreshRunning flag is still set, but display's BUSY pin reports the refresh is done
- adafruitDisplay->endAsyncFull(); // Run the end of nextPage() code
- EInkDisplay::endUpdate(); // Run base-class code to finish off update (NOT our derived class override)
- asyncRefreshRunning = false; // Unset the flag
- LOG_DEBUG("Refresh complete");
-
- // Note: this code only works because of a modification to meshtastic/GxEPD2.
- // It is only equipped to intercept calls to nextPage()
-}
-
-// Called from NotifiedWorkerThread. Run the post-update code if the hardware is ready
-void EInkDynamicDisplay::pollAsyncRefresh()
-{
- // In theory, this condition should never be met
- if (!asyncRefreshRunning)
- return;
-
- // Still running, check back later
- if (adafruitDisplay->epd2.isBusy()) {
- // Schedule next call of pollAsyncRefresh()
- NotifiedWorkerThread::notifyLater(intervalPollAsyncRefresh, DUE_POLL_ASYNCREFRESH, true);
- return;
- }
-
- // If asyncRefreshRunning flag is still set, but display's BUSY pin reports the refresh is done
- adafruitDisplay->endAsyncFull(); // Run the end of nextPage() code
- EInkDisplay::endUpdate(); // Run base-class code to finish off update (NOT our derived class override)
- asyncRefreshRunning = false; // Unset the flag
- LOG_DEBUG("Async full-refresh complete");
-
- // Note: this code only works because of a modification to meshtastic/GxEPD2.
- // It is only equipped to intercept calls to nextPage()
-}
-
-// Check the status of "async full-refresh"; skip if running
-void EInkDynamicDisplay::checkBusyAsyncRefresh()
-{
- // No refresh taking place, continue with determineMode()
- if (!asyncRefreshRunning)
- return;
-
- // Full refresh still running
- if (adafruitDisplay->epd2.isBusy()) {
- // No refresh
- refresh = SKIPPED;
-
- // Set the reason, marking what type of frame we're skipping
- if (frameFlags & DEMAND_FAST)
- reason = ASYNC_REFRESH_BLOCKED_DEMANDFAST;
- else if (frameFlags & COSMETIC)
- reason = ASYNC_REFRESH_BLOCKED_COSMETIC;
- else if (frameFlags & RESPONSIVE)
- reason = ASYNC_REFRESH_BLOCKED_RESPONSIVE;
- else
- reason = ASYNC_REFRESH_BLOCKED_BACKGROUND;
-
- return;
- }
-
- // Async refresh appears to have stopped, but wasn't caught by onNotify()
- else
- pollAsyncRefresh(); // Check (and terminate) the async refresh manually
-}
-
-// Hold control while an async refresh runs
-void EInkDynamicDisplay::awaitRefresh()
-{
- // Continually poll the BUSY pin
- while (adafruitDisplay->epd2.isBusy())
- yield();
-
- // End the full-refresh process
- adafruitDisplay->endAsyncFull(); // Run the end of nextPage() code
- EInkDisplay::endUpdate(); // Run base-class code to finish off update (NOT our derived class override)
- asyncRefreshRunning = false; // Unset the flag
-}
-#endif // HAS_EINK_ASYNCFULL
-
-#endif // USE_EINK_DYNAMICDISPLAY
diff --git a/src/graphics/EInkDynamicDisplay.h b/src/graphics/EInkDynamicDisplay.h
deleted file mode 100644
index b03061873..000000000
--- a/src/graphics/EInkDynamicDisplay.h
+++ /dev/null
@@ -1,155 +0,0 @@
-#pragma once
-
-#include "configuration.h"
-#include
-
-#if defined(USE_EINK) && defined(USE_EINK_DYNAMICDISPLAY)
-
-#include "EInkDisplay2.h"
-#include "GxEPD2_BW.h"
-#include "concurrency/NotifiedWorkerThread.h"
-
-/*
- Derives from the EInkDisplay adapter class.
- Accepts suggestions from Screen class about frame type.
- Determines which refresh type is most suitable.
- (Full, Fast, Skip)
-*/
-
-class EInkDynamicDisplay : public EInkDisplay, protected concurrency::NotifiedWorkerThread
-{
- public:
- // Constructor
- // ( Parameters unused, passed to EInkDisplay. Maintains compatibility OLEDDisplay class )
- EInkDynamicDisplay(uint8_t address, int sda, int scl, OLEDDISPLAY_GEOMETRY geometry, HW_I2C i2cBus);
- ~EInkDynamicDisplay();
-
- // Methods to enable or disable unlimited fast refresh mode
- void enableUnlimitedFastMode() { addFrameFlag(UNLIMITED_FAST); }
- void disableUnlimitedFastMode() { frameFlags = (frameFlagTypes)(frameFlags & ~UNLIMITED_FAST); }
-
- // What kind of frame is this
- enum frameFlagTypes : uint8_t {
- BACKGROUND = (1 << 0), // For frames via display()
- RESPONSIVE = (1 << 1), // For frames via forceDisplay()
- COSMETIC = (1 << 2), // For splashes
- DEMAND_FAST = (1 << 3), // Special case only
- BLOCKING = (1 << 4), // Modifier - block while refresh runs
- UNLIMITED_FAST = (1 << 5)
- };
- void addFrameFlag(frameFlagTypes flag);
-
- // Set the correct frame flag, then call universal "update()" method
- void display() override;
- bool forceDisplay(uint32_t msecLimit) override; // Shadows base class. Parameter and return val unused.
-
- protected:
- enum refreshTypes : uint8_t { // Which refresh operation will be used
- UNSPECIFIED,
- FULL,
- FAST,
- SKIPPED,
- };
- enum reasonTypes : uint8_t { // How was the decision reached
- NO_OBJECTIONS,
- ASYNC_REFRESH_BLOCKED_DEMANDFAST,
- ASYNC_REFRESH_BLOCKED_COSMETIC,
- ASYNC_REFRESH_BLOCKED_RESPONSIVE,
- ASYNC_REFRESH_BLOCKED_BACKGROUND,
- EXCEEDED_RATELIMIT_FAST,
- EXCEEDED_RATELIMIT_FULL,
- FLAGGED_COSMETIC,
- FLAGGED_DEMAND_FAST,
- EXCEEDED_LIMIT_FASTREFRESH,
- EXCEEDED_GHOSTINGLIMIT,
- FRAME_MATCHED_PREVIOUS,
- BACKGROUND_USES_FAST,
- FLAGGED_BACKGROUND,
- REDRAW_WITH_FULL,
- };
-
- enum notificationTypes : uint8_t { // What was onNotify() called for
- NONE = 0, // This behavior (NONE=0) is fixed by NotifiedWorkerThread class
- DUE_POLL_ASYNCREFRESH = 1,
- };
- const uint32_t intervalPollAsyncRefresh = 100;
-
- void onNotify(uint32_t notification) override; // Handle any async tasks - overrides NotifiedWorkerThread
- void configForFastRefresh(); // GxEPD2 code to set fast-refresh
- void configForFullRefresh(); // GxEPD2 code to set full-refresh
- bool determineMode(); // Assess situation, pick a refresh type
- void applyRefreshMode(); // Run any relevant GxEPD2 code, so next update will use correct refresh type
- void adjustRefreshCounters(); // Update fastRefreshCount
- bool update(); // Trigger the display update - determine mode, then call base class
- void endOrDetach(); // Run the post-update code, or delegate it off to checkBusyAsyncRefresh()
-
- // Checks as part of determineMode()
- void checkInitialized(); // Is this the very first frame?
- void checkForPromotion(); // Was a frame skipped (rate, display busy) that should have been a FAST refresh?
- void checkRateLimiting(); // Is this frame too soon?
- void checkCosmetic(); // Was the COSMETIC flag set?
- void checkDemandingFast(); // Was the DEMAND_FAST flag set?
- void checkFrameMatchesPrevious(); // Does the new frame match the existing display image?
- void checkConsecutiveFastRefreshes(); // Too many fast-refreshes consecutively?
- void checkFastRequested(); // Was the flag set for RESPONSIVE, or only BACKGROUND?
-
- void resetRateLimiting(); // Set previousRunMs - this now counts as an update, for rate-limiting
- void hashImage(); // Generate a hashed version of this frame, to compare against previous update
- void storeAndReset(); // Keep results of determineMode() for later, tidy-up for next call
-
- // What we are determining for this frame
- frameFlagTypes frameFlags = BACKGROUND; // Frame characteristics - determineMode() input
- refreshTypes refresh = UNSPECIFIED; // Refresh type - determineMode() output
- reasonTypes reason = NO_OBJECTIONS; // Reason - why was refresh type used
-
- // What happened last time determineMode() ran
- frameFlagTypes previousFrameFlags = BACKGROUND; // (Previous) Frame flags
- refreshTypes previousRefresh = UNSPECIFIED; // (Previous) Outcome
- reasonTypes previousReason = NO_OBJECTIONS; // (Previous) Reason
-
- bool initialized = false; // Have we drawn at least one frame yet?
- uint32_t previousRunMs = -1; // When did determineMode() last run (rather than rejecting for rate-limiting)
- uint32_t imageHash = 0; // Hash of the current frame. Don't bother updating if nothing has changed!
- uint32_t previousImageHash = 0; // Hash of the previous update's frame
- uint32_t fastRefreshCount = 0; // How many fast-refreshes consecutively since last full refresh?
- refreshTypes currentConfig = FULL; // Which refresh type is GxEPD2 currently configured for
-
- // Optional - track ghosting, pixel by pixel
- // May 2024: no longer used by any display. Kept for possible future use.
-#ifdef EINK_LIMIT_GHOSTING_PX
- void countGhostPixels(); // Count any pixels which have moved from black to white since last full-refresh
- void checkExcessiveGhosting(); // Check if ghosting exceeds defined limit
- void resetGhostPixelTracking(); // Clear the dirty pixels array. Call when full-refresh cleans the display.
- std::unique_ptr dirtyPixels; // Any pixels that have been black since last full-refresh (dynamically allocated mem)
- uint32_t ghostPixelCount = 0; // Number of pixels with problematic ghosting. Retained here for LOG_DEBUG use
-#endif
-
- // Conditional - async full refresh - only with modified meshtastic/GxEPD2
-#if defined(HAS_EINK_ASYNCFULL)
- public:
- void joinAsyncRefresh(); // Main thread joins an async refresh already in progress. Blocks, then runs post-update code
-
- protected:
- void pollAsyncRefresh(); // Run the post-update code if the hardware is ready
- void checkBusyAsyncRefresh(); // Check if display is busy running an async full-refresh (rejecting new frames)
- void awaitRefresh(); // Hold control while an async refresh runs
- void endUpdate() override {} // Disable base-class behavior of running post-update immediately after forceDisplay()
- bool asyncRefreshRunning = false; // Flag, checked by checkBusyAsyncRefresh()
-#else
- public:
- void joinAsyncRefresh() {} // Dummy method
-
- protected:
- void pollAsyncRefresh() {} // Dummy method. In theory, not reachable
-#endif
-};
-
-// Hide the ugly casts used in Screen.cpp
-#define EINK_ADD_FRAMEFLAG(display, flag) static_cast(display)->addFrameFlag(EInkDynamicDisplay::flag)
-#define EINK_JOIN_ASYNCREFRESH(display) static_cast(display)->joinAsyncRefresh()
-
-#else // !USE_EINK_DYNAMICDISPLAY
-// Dummy-macro, removes the need for include guards
-#define EINK_ADD_FRAMEFLAG(display, flag)
-#define EINK_JOIN_ASYNCREFRESH(display)
-#endif
\ No newline at end of file
diff --git a/src/graphics/EInkParallelDisplay.cpp b/src/graphics/EInkParallelDisplay.cpp
deleted file mode 100644
index 293f57e81..000000000
--- a/src/graphics/EInkParallelDisplay.cpp
+++ /dev/null
@@ -1,427 +0,0 @@
-#include "EInkParallelDisplay.h"
-
-#ifdef USE_EINK_PARALLELDISPLAY
-
-#include "Wire.h"
-#include "variant.h"
-#include
-#include
-#include
-#include
-
-#include "FastEPD.h"
-
-// Thresholds for choosing partial vs full update
-#ifndef EPD_PARTIAL_THRESHOLD_ROWS
-#define EPD_PARTIAL_THRESHOLD_ROWS 128 // if changed region <= this many rows, prefer partial
-#endif
-#ifndef EPD_FULLSLOW_PERIOD
-#define EPD_FULLSLOW_PERIOD 100 // every N full updates do a slow (CLEAR_SLOW) full refresh
-#endif
-#ifndef EPD_RESPONSIVE_MIN_MS
-#define EPD_RESPONSIVE_MIN_MS 1000 // simple rate-limit (ms) for responsive updates
-#endif
-
-EInkParallelDisplay::EInkParallelDisplay(uint16_t width, uint16_t height, EpdRotation rot) : epaper(nullptr), rotation(rot)
-{
- LOG_INFO("init EInkParallelDisplay");
- // Set dimensions in OLEDDisplay base class
- this->geometry = GEOMETRY_RAWMODE;
- this->displayWidth = width;
- this->displayHeight = height;
-
- // Round shortest side up to nearest byte, to prevent truncation causing an undersized buffer
- uint16_t shortSide = min(width, height);
- uint16_t longSide = max(width, height);
- if (shortSide % 8 != 0)
- shortSide = (shortSide | 7) + 1;
-
- this->displayBufferSize = longSide * (shortSide / 8);
-
-#ifdef EINK_LIMIT_GHOSTING_PX
- // allocate dirty pixel buffer same size as epaper buffers (rowBytes * height)
- size_t rowBytes = (this->displayWidth + 7) / 8;
- dirtyPixelsSize = rowBytes * this->displayHeight;
- dirtyPixels = (uint8_t *)calloc(dirtyPixelsSize, 1);
- ghostPixelCount = 0;
-#endif
-}
-
-EInkParallelDisplay::~EInkParallelDisplay()
-{
-#ifdef EINK_LIMIT_GHOSTING_PX
- if (dirtyPixels) {
- free(dirtyPixels);
- dirtyPixels = nullptr;
- }
-#endif
- // If an async full update is running, wait for it to finish
- if (asyncFullRunning.load()) {
- // wait a short while for task to finish
- for (int i = 0; i < 50 && asyncFullRunning.load(); ++i) {
- delay(50);
- }
- if (asyncTaskHandle) {
- // Let it finish or delete it
- vTaskDelete(asyncTaskHandle);
- asyncTaskHandle = nullptr;
- }
- }
-
- delete epaper;
-}
-
-/*
- * Called by the OLEDDisplay::init() path.
- */
-bool EInkParallelDisplay::connect()
-{
- LOG_INFO("Do EPD init");
- if (!epaper) {
- epaper = new FASTEPD;
-#if defined(T5_S3_EPAPER_PRO_V1)
- epaper->initPanel(BB_PANEL_LILYGO_T5PRO, 28000000);
-#elif defined(T5_S3_EPAPER_PRO_V2)
- epaper->initPanel(BB_PANEL_LILYGO_T5PRO_V2, 28000000);
- // initialize all port 0 pins (0-7) as outputs / HIGH
- for (int i = 0; i < 8; i++) {
- epaper->ioPinMode(i, OUTPUT);
- epaper->ioWrite(i, HIGH);
- }
-#else
-#error "unsupported EPD device!"
-#endif
- }
-
- // epaper->setRotation(rotation); // does not work, messes up width/height
- epaper->setMode(BB_MODE_1BPP);
- epaper->clearWhite();
- epaper->fullUpdate(true);
-
-#ifdef EINK_LIMIT_GHOSTING_PX
- // After a full/clear the dirty tracking should be reset
- resetGhostPixelTracking();
-#endif
-
- return true;
-}
-
-/*
- * sendCommand - simple passthrough (not required for epd_driver-based path)
- */
-void EInkParallelDisplay::sendCommand(uint8_t com)
-{
- LOG_DEBUG("EInkParallelDisplay::sendCommand %d", (int)com);
-}
-
-/*
- * Start a background task that will perform a blocking fullUpdate(). This lets
- * display() return quickly while the heavy refresh runs in the background.
- */
-void EInkParallelDisplay::startAsyncFullUpdate(int clearMode)
-{
- if (asyncFullRunning.load())
- return; // already running
-
- asyncFullRunning.store(true);
- // pass 'this' as parameter
- BaseType_t rc = xTaskCreatePinnedToCore(EInkParallelDisplay::asyncFullUpdateTask, "epd_full", 4096 / sizeof(StackType_t),
- this, 2, &asyncTaskHandle,
-#if CONFIG_FREERTOS_UNICORE
- 0
-#else
- 1
-#endif
- );
- if (rc != pdPASS) {
- LOG_WARN("Failed to create async full-update task, falling back to blocking update");
- epaper->fullUpdate(clearMode, false);
- epaper->backupPlane();
- asyncFullRunning.store(false);
- asyncTaskHandle = nullptr;
- }
-}
-
-/*
- * FreeRTOS task entry: runs the full update and then backs up plane.
- */
-void EInkParallelDisplay::asyncFullUpdateTask(void *pvParameters)
-{
- EInkParallelDisplay *self = static_cast(pvParameters);
- if (!self) {
- vTaskDelete(nullptr);
- return;
- }
-
- // choose CLEAR_SLOW occasionally
- int clearMode = CLEAR_FAST;
- if (self->fastRefreshCount >= EPD_FULLSLOW_PERIOD) {
- clearMode = CLEAR_SLOW;
- self->fastRefreshCount = 0;
- } else {
- // when running async full, treat it as a full so reset fast count
- self->fastRefreshCount = 0;
- }
-
- self->epaper->fullUpdate(clearMode, false);
- self->epaper->backupPlane();
-
-#ifdef EINK_LIMIT_GHOSTING_PX
- // A full refresh clears ghosting state
- self->resetGhostPixelTracking();
-#endif
-
- self->asyncFullRunning.store(false);
- self->asyncTaskHandle = nullptr;
-
- // delete this task
- vTaskDelete(nullptr);
-}
-
-/*
- * Convert the OLEDDisplay buffer (vertical byte layout) into the 1bpp horizontal-bytes
- * buffer used by the FASTEPD library. For performance we write directly into FASTEPD's
- * currentBuffer() while comparing against previousBuffer() to detect changed rows.
- * After conversion we call FASTEPD::partialUpdate() or FASTEPD::fullUpdate() according
- * to a heuristic so only the minimal region is refreshed.
- */
-void EInkParallelDisplay::display(void)
-{
- const uint16_t w = this->displayWidth;
- const uint16_t h = this->displayHeight;
-
- // Simple rate limiting: avoid very-frequent responsive updates
- uint32_t nowMs = millis();
- if (lastUpdateMs != 0 && (nowMs - lastUpdateMs) < EPD_RESPONSIVE_MIN_MS) {
- LOG_DEBUG("rate-limited, skipping update");
- return;
- }
-
- // bytes per row in epd format (one byte = 8 horizontal pixels)
- const uint32_t rowBytes = (w + 7) / 8;
-
- // Get pointers to internal buffers
- uint8_t *cur = epaper->currentBuffer();
- const uint8_t *prev = epaper->previousBuffer(); // may be NULL on first init
-
- // Track changed row range while converting
- int newTop = h; // min changed row (initialized to out-of-range)
- int newBottom = -1; // max changed row
-
-#ifdef FAST_EPD_PARTIAL_UPDATE_BUG
- // Track changed byte column range (for clipped fullUpdate fallback)
- int newLeftByte = (int)rowBytes;
- int newRightByte = -1;
-#endif
-
- // Compute a quick hash of the incoming OLED buffer (so we can skip identical frames)
- uint32_t imageHash = 0;
- uint32_t bufBytes = (w / 8) * h; // vertical-byte layout size
- for (uint32_t bi = 0; bi < bufBytes; ++bi) {
- imageHash ^= ((uint32_t)buffer[bi]) << (bi & 31);
- }
- if (imageHash == previousImageHash) {
- // LOG_DEBUG("image identical to previous, skipping update");
- return;
- }
-
-#ifdef EINK_LIMIT_GHOSTING_PX
- // reset ghost count for this conversion pass; we'll mark bits that change
- ghostPixelCount = 0;
-#endif
-
- // Convert: OLED buffer layout -> FASTEPD 1bpp horizontal-bytes layout into cur,
- // comparing against prev when available to detect changes.
- for (uint32_t y = 0; y < h; ++y) {
- const uint32_t base = (y >> 3) * w; // (y/8) * width
- const uint8_t bitMask = (uint8_t)(1u << (y & 7)); // mask for this row in vertical-byte layout
- const uint32_t rowBase = y * rowBytes;
-
- // process full 8-pixel bytes
- for (uint32_t xb = 0; xb < rowBytes; ++xb) {
- uint32_t x0 = xb * 8;
- // read up to 8 source bytes (vertical-byte per column)
- uint8_t b0 = (x0 + 0 < w) ? buffer[base + x0 + 0] : 0;
- uint8_t b1 = (x0 + 1 < w) ? buffer[base + x0 + 1] : 0;
- uint8_t b2 = (x0 + 2 < w) ? buffer[base + x0 + 2] : 0;
- uint8_t b3 = (x0 + 3 < w) ? buffer[base + x0 + 3] : 0;
- uint8_t b4 = (x0 + 4 < w) ? buffer[base + x0 + 4] : 0;
- uint8_t b5 = (x0 + 5 < w) ? buffer[base + x0 + 5] : 0;
- uint8_t b6 = (x0 + 6 < w) ? buffer[base + x0 + 6] : 0;
- uint8_t b7 = (x0 + 7 < w) ? buffer[base + x0 + 7] : 0;
-
- // build output byte: MSB = leftmost pixel
- uint8_t out = 0;
- out |= (uint8_t)((b0 & bitMask) ? 0x80 : 0x00);
- out |= (uint8_t)((b1 & bitMask) ? 0x40 : 0x00);
- out |= (uint8_t)((b2 & bitMask) ? 0x20 : 0x00);
- out |= (uint8_t)((b3 & bitMask) ? 0x10 : 0x00);
- out |= (uint8_t)((b4 & bitMask) ? 0x08 : 0x00);
- out |= (uint8_t)((b5 & bitMask) ? 0x04 : 0x00);
- out |= (uint8_t)((b6 & bitMask) ? 0x02 : 0x00);
- out |= (uint8_t)((b7 & bitMask) ? 0x01 : 0x00);
-
- // handle partial byte at end of row by masking off invalid bits
- uint8_t mask = 0xFF;
- uint32_t bitsRemain = (w > x0) ? (w - x0) : 0;
- if (bitsRemain > 0 && bitsRemain < 8) {
- mask = (uint8_t)(0xFF << (8 - bitsRemain));
- out &= mask;
- }
-
- // invert to FASTEPD polarity
- out = (~out) & mask;
-
- uint32_t pos = rowBase + xb;
- uint8_t prevVal = prev ? (prev[pos] & mask) : 0x00;
- // Consider this byte changed if previous buffer differs (or prev is null)
- bool changed = (prev == nullptr) || (prevVal != out);
-
-#ifdef EINK_LIMIT_GHOSTING_PX
- if (changed && prev)
- markDirtyBits(prev, pos, mask, out);
-#endif
-
- // mark row changed only if the previous buffer differs
- if (changed) {
- if (y < (uint32_t)newTop)
- newTop = y;
- if ((int)y > newBottom)
- newBottom = y;
-#ifdef FAST_EPD_PARTIAL_UPDATE_BUG
- // record changed column bytes
- if ((int)xb < newLeftByte)
- newLeftByte = (int)xb;
- if ((int)xb > newRightByte)
- newRightByte = (int)xb;
-#endif
- }
-
- // Always write the computed value into the current buffer (avoid leaving stale bytes)
- cur[pos] = (cur[pos] & ~mask) | out;
- }
- }
-
- // If nothing changed, avoid any panel update
- if (newBottom < 0) {
- LOG_DEBUG("no pixel changes detected, skipping update (conv)");
- previousImageHash = imageHash; // still remember that frame
- return;
- }
-
- // Choose partial vs full update using heuristic
- // Decide if we should force a full update after many fast updates
- bool forceFull = (fastRefreshCount >= EPD_FULLSLOW_PERIOD);
-
-#ifdef EINK_LIMIT_GHOSTING_PX
- // If ghost pixels exceed limit, force a full update to clear ghosting
- if (ghostPixelCount > ghostPixelLimit) {
- LOG_WARN("ghost pixels %u > limit %u, forcing full refresh", ghostPixelCount, ghostPixelLimit);
- forceFull = true;
- }
-#endif
-
- // Compute pixel bounds from newTop/newBottom
- int startRow = (newTop / 8) * 8;
- int endRow = (newBottom / 8) * 8 + 7;
-
- LOG_DEBUG("EPD update rows=%d..%d alignedRows=%d..%d rowBytes=%u", newTop, newBottom, startRow, endRow, rowBytes);
-
- if (epaper->getMode() == BB_MODE_1BPP && !forceFull && (newBottom - newTop) <= EPD_PARTIAL_THRESHOLD_ROWS) {
- // Prefer partial update path if driver is reliable; otherwise use clipped fullUpdate fallback.
-#ifdef FAST_EPD_PARTIAL_UPDATE_BUG
- // Workaround for FastEPD partial update bug: use clipped fullUpdate instead
- // Build a pixel rectangle for a clipped fullUpdate using the changed columns
- int startCol = (newLeftByte <= newRightByte) ? (newLeftByte * 8) : 0;
- int endCol = (newLeftByte <= newRightByte) ? ((newRightByte + 1) * 8 - 1) : (w - 1);
-
- BB_RECT rect{startCol, startRow, endCol - startCol + 1, endRow - startRow + 1};
- // LOG_DEBUG("Using clipped fullUpdate rect x=%d y=%d w=%d h=%d", rect.x, rect.y, rect.w, rect.h);
- epaper->fullUpdate(CLEAR_FAST, false, &rect);
-#else
- // Use rows for partial update
- LOG_DEBUG("calling partialUpdate startRow=%d endRow=%d", startRow, endRow);
- epaper->partialUpdate(true, startRow, endRow);
-#endif
- epaper->backupPlane();
- fastRefreshCount++;
- } else {
- // Full update: run async if possible (startAsyncFullUpdate will fall back to blocking)
- startAsyncFullUpdate(forceFull ? CLEAR_SLOW : CLEAR_FAST);
- }
-
- lastUpdateMs = millis();
- previousImageHash = imageHash;
-
- // Keep same behavior as before
- lastDrawMsec = millis();
-}
-
-#ifdef EINK_LIMIT_GHOSTING_PX
-// markDirtyBits: mark per-bit dirty flags and update ghostPixelCount
-void EInkParallelDisplay::markDirtyBits(const uint8_t *prevBuf, uint32_t pos, uint8_t mask, uint8_t out)
-{
- // defensive: need dirtyPixels allocated and prevBuf valid
- if (!dirtyPixels || !prevBuf)
- return;
-
- // 'out' is in FASTEPD polarity (1 = black, 0 = white)
- uint8_t newBlack = out & mask; // bits that will be black now
- uint8_t newWhite = (~out) & mask; // bits that will be white now
-
- // previously recorded dirty bits for this byte
- uint8_t before = dirtyPixels[pos];
-
- // Ghost bits: bits that were previously marked dirty and are now being driven white
- uint8_t ghostBits = before & newWhite;
- if (ghostBits) {
- ghostPixelCount += __builtin_popcount((unsigned)ghostBits);
- }
-
- // Only mark bits dirty when they turn black now (accumulate until a full refresh)
- uint8_t newlyDirty = newBlack & (~before);
- if (newlyDirty) {
- dirtyPixels[pos] |= newlyDirty;
- }
-}
-
-// reset ghost tracking (call after a full refresh)
-void EInkParallelDisplay::resetGhostPixelTracking()
-{
- if (!dirtyPixels)
- return;
- memset(dirtyPixels, 0, dirtyPixelsSize);
- ghostPixelCount = 0;
-}
-#endif
-
-/*
- * forceDisplay: use lastDrawMsec
- */
-bool EInkParallelDisplay::forceDisplay(uint32_t msecLimit)
-{
- uint32_t now = millis();
- if (lastDrawMsec == 0 || (now - lastDrawMsec) > msecLimit) {
- display();
- return true;
- }
- return false;
-}
-
-void EInkParallelDisplay::endUpdate()
-{
- {
- // ensure any async full update is started/completed
- if (asyncFullRunning.load()) {
- // nothing to do; background task will run and call backupPlane when done
- } else {
- epaper->fullUpdate(CLEAR_FAST, false);
- epaper->backupPlane();
-#ifdef EINK_LIMIT_GHOSTING_PX
- resetGhostPixelTracking();
-#endif
- }
- }
-}
-
-#endif
\ No newline at end of file
diff --git a/src/graphics/EInkParallelDisplay.h b/src/graphics/EInkParallelDisplay.h
deleted file mode 100644
index 81189e400..000000000
--- a/src/graphics/EInkParallelDisplay.h
+++ /dev/null
@@ -1,69 +0,0 @@
-#pragma once
-
-#include "configuration.h"
-
-#ifdef USE_EINK_PARALLELDISPLAY
-#include
-
-#include
-#include
-#include
-
-class FASTEPD;
-
-/**
- * Adapter for E-Ink 8-bit parallel displays (EPD), specifically devices supported by FastEPD library
- */
-class EInkParallelDisplay : public OLEDDisplay
-{
- public:
- enum EpdRotation {
- EPD_ROT_LANDSCAPE = 0,
- EPD_ROT_PORTRAIT = 90,
- EPD_ROT_INVERTED_LANDSCAPE = 180,
- EPD_ROT_INVERTED_PORTRAIT = 270,
- };
-
- EInkParallelDisplay(uint16_t width, uint16_t height, EpdRotation rotation);
- virtual ~EInkParallelDisplay();
-
- // OLEDDisplay virtuals
- bool connect() override;
- void sendCommand(uint8_t com) override;
- int getBufferOffset(void) override { return 0; }
-
- void display(void) override;
- bool forceDisplay(uint32_t msecLimit = 1000);
- void endUpdate();
-
- protected:
- uint32_t lastDrawMsec = 0;
- FASTEPD *epaper;
-
- private:
- // Async full-refresh support
- std::atomic asyncFullRunning{false};
- TaskHandle_t asyncTaskHandle = nullptr;
- void startAsyncFullUpdate(int clearMode);
- static void asyncFullUpdateTask(void *pvParameters);
-
-#ifdef EINK_LIMIT_GHOSTING_PX
- // helpers
- void resetGhostPixelTracking();
- void markDirtyBits(const uint8_t *prevBuf, uint32_t pos, uint8_t mask, uint8_t out);
- void countGhostPixelsAndMaybePromote(int &newTop, int &newBottom, bool &forceFull);
-
- // per-bit dirty buffer (same format as epaper buffers): one bit == one pixel
- uint8_t *dirtyPixels = nullptr;
- size_t dirtyPixelsSize = 0;
- uint32_t ghostPixelCount = 0;
- uint32_t ghostPixelLimit = EINK_LIMIT_GHOSTING_PX;
-#endif
-
- EpdRotation rotation;
- uint32_t previousImageHash = 0;
- uint32_t lastUpdateMs = 0;
- int fastRefreshCount = 0;
-};
-
-#endif
diff --git a/src/graphics/GxEPD2Multi.h b/src/graphics/GxEPD2Multi.h
deleted file mode 100644
index f3807c9de..000000000
--- a/src/graphics/GxEPD2Multi.h
+++ /dev/null
@@ -1,135 +0,0 @@
-// Wrapper class for GxEPD2_BW
-
-// Generic signature at build-time, so that we can detect display model at run-time
-// Workaround for issue of GxEPD2_BW objects not having a shared base class
-// Only exposes methods which we are actually using
-
-template class GxEPD2_Multi
-{
- public:
- void drawPixel(int16_t x, int16_t y, uint16_t color)
- {
- if (which == 0)
- driver0->drawPixel(x, y, color);
- else
- driver1->drawPixel(x, y, color);
- }
-
- bool nextPage()
- {
- if (which == 0)
- return driver0->nextPage();
- else
- return driver1->nextPage();
- }
-
- void hibernate()
- {
- if (which == 0)
- driver0->hibernate();
- else
- driver1->hibernate();
- }
-
- void init(uint32_t serial_diag_bitrate = 0)
- {
- if (which == 0)
- driver0->init(serial_diag_bitrate);
- else
- driver1->init(serial_diag_bitrate);
- }
-
- void init(uint32_t serial_diag_bitrate, bool initial, uint16_t reset_duration = 20, bool pulldown_rst_mode = false)
- {
- if (which == 0)
- driver0->init(serial_diag_bitrate, initial, reset_duration, pulldown_rst_mode);
- else
- driver1->init(serial_diag_bitrate, initial, reset_duration, pulldown_rst_mode);
- }
-
- void setRotation(uint8_t x)
- {
- if (which == 0)
- driver0->setRotation(x);
- else
- driver1->setRotation(x);
- }
-
- void setPartialWindow(uint16_t x, uint16_t y, uint16_t w, uint16_t h)
- {
- if (which == 0)
- driver0->setPartialWindow(x, y, w, h);
- else
- driver1->setPartialWindow(x, y, w, h);
- }
-
- void setFullWindow()
- {
- if (which == 0)
- driver0->setFullWindow();
- else
- driver1->setFullWindow();
- }
-
- int16_t width()
- {
- if (which == 0)
- return driver0->width();
- else
- return driver1->width();
- }
-
- int16_t height()
- {
- if (which == 0)
- return driver0->height();
- else
- return driver1->height();
- }
-
- void clearScreen(uint8_t value = 0xFF)
- {
- if (which == 0)
- driver0->clearScreen();
- else
- driver1->clearScreen();
- }
-
- void endAsyncFull()
- {
- if (which == 0)
- driver0->endAsyncFull();
- else
- driver1->endAsyncFull();
- }
-
- // Exposes methods of the GxEPD2_EPD object which is usually available as GxEPD2_BW::epd
- class Epd2Wrapper
- {
- public:
- bool isBusy() { return m_epd2->isBusy(); }
- GxEPD2_EPD *m_epd2;
- } epd2;
-
- // Constructor
- // Select driver by passing whichDriver as 0 or 1
- GxEPD2_Multi(uint8_t whichDriver, int16_t cs, int16_t dc, int16_t rst, int16_t busy, SPIClass &spi)
- {
- assert(whichDriver == 0 || whichDriver == 1);
- which = whichDriver;
- LOG_DEBUG("GxEPD2_Multi driver: %d", which);
-
- if (which == 0) {
- driver0 = new GxEPD2_BW(Driver0(cs, dc, rst, busy, spi));
- epd2.m_epd2 = &(driver0->epd2);
- } else if (which == 1) {
- driver1 = new GxEPD2_BW(Driver1(cs, dc, rst, busy, spi));
- epd2.m_epd2 = &(driver1->epd2);
- }
- }
-
- private:
- uint8_t which;
- GxEPD2_BW *driver0;
- GxEPD2_BW *driver1;
-};
\ No newline at end of file
diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp
index fa9d98a0e..e99153a91 100644
--- a/src/graphics/Screen.cpp
+++ b/src/graphics/Screen.cpp
@@ -27,9 +27,14 @@ along with this program. If not, see .
#include "configuration.h"
#include "meshUtils.h"
#if HAS_SCREEN
-#include "EInkParallelDisplay.h"
#include
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+#include "graphics/BaseUIEInkDisplay.h"
+// Provided by each niche-enabled variant's nicheGraphics.h (defined once, in main.cpp TU).
+extern NicheGraphics::BaseUIEInkDisplay *setupNicheGraphicsBaseUI();
+#endif
+
#include "DisplayFormatters.h"
#include "TimeFormatters.h"
#include "draw/ClockRenderer.h"
@@ -365,14 +370,9 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O
defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS) || defined(HACKADAY_COMMUNICATOR)
dispdev = new TFTDisplay(address.address, -1, -1, geometry,
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
-#elif defined(USE_EINK) && !defined(USE_EINK_DYNAMICDISPLAY) && !defined(USE_EINK_PARALLELDISPLAY)
- dispdev = new EInkDisplay(address.address, -1, -1, geometry,
- (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
-#elif defined(USE_EINK) && defined(USE_EINK_DYNAMICDISPLAY)
- dispdev = new EInkDynamicDisplay(address.address, -1, -1, geometry,
- (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
-#elif defined(USE_EINK_PARALLELDISPLAY)
- dispdev = new EInkParallelDisplay(EPD_WIDTH, EPD_HEIGHT, EInkParallelDisplay::EPD_ROT_PORTRAIT);
+#elif defined(USE_EINK) && defined(MESHTASTIC_INCLUDE_NICHE_GRAPHICS)
+ // NicheGraphics-backed BaseUI E-Ink path. Variant provides setupNicheGraphicsBaseUI() in its nicheGraphics.h.
+ dispdev = setupNicheGraphicsBaseUI();
#elif defined(USE_ST7567)
dispdev = new ST7567Wire(address.address, -1, -1, geometry,
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
@@ -762,10 +762,8 @@ void Screen::forceDisplay(bool forceUiUpdate)
}
// Tell EInk class to update the display
-#if defined(USE_EINK_PARALLELDISPLAY)
- static_cast(dispdev)->forceDisplay();
-#elif defined(USE_EINK)
- static_cast(dispdev)->forceDisplay();
+#if defined(MESHTASTIC_INCLUDE_NICHE_GRAPHICS)
+ static_cast(dispdev)->forceDisplay();
#endif
#else
// No delay between UI frame rendering
@@ -939,12 +937,7 @@ int32_t Screen::runOnce()
NotificationRenderer::current_notification_type != notificationTypeEnum::text_input &&
!Throttle::isWithinTimespanMs(lastScreenTransition, config.display.auto_screen_carousel_secs * 1000)) {
- // If an E-Ink display struggles with fast refresh, force carousel to use full refresh instead
- // Carousel is potentially a major source of E-Ink display wear
-#if !defined(EINK_BACKGROUND_USES_FAST)
- EINK_ADD_FRAMEFLAG(dispdev, COSMETIC);
-#endif
-
+ // Carousel rotations let BaseUIEInkDisplay's DisplayHealth debt model decide FAST vs FULL.
LOG_DEBUG("LastScreenTransition exceeded %ums transition to next frame", (millis() - lastScreenTransition));
handleOnPress();
}
@@ -978,11 +971,8 @@ void Screen::setScreensaverFrames(FrameCallback einkScreensaver)
static FrameCallback screensaverFrame;
static OverlayCallback screensaverOverlay;
-#if defined(HAS_EINK_ASYNCFULL) && defined(USE_EINK_DYNAMICDISPLAY)
- // Join (await) a currently running async refresh, then run the post-update code.
- // Avoid skipping of screensaver frame. Would otherwise be handled by NotifiedWorkerThread.
+ // Join (await) any currently running async refresh before drawing the screensaver frame.
EINK_JOIN_ASYNCREFRESH(dispdev);
-#endif
// If: one-off screensaver frame passed as argument. Handles doDeepSleep()
if (einkScreensaver != NULL) {
@@ -1005,23 +995,17 @@ void Screen::setScreensaverFrames(FrameCallback einkScreensaver)
ui->update();
} while (ui->getUiState()->lastUpdate < startUpdate);
-#if defined(USE_EINK_PARALLELDISPLAY)
- static_cast(dispdev)->forceDisplay(0);
-#elif defined(USE_EINK) && !defined(USE_EINK_DYNAMICDISPLAY)
- // Old EInkDisplay class
- static_cast(dispdev)->forceDisplay(0); // Screen::forceDisplay(), but override rate-limit
+#if defined(MESHTASTIC_INCLUDE_NICHE_GRAPHICS)
+ static_cast(dispdev)->forceDisplay(0);
#endif
// Prepare now for next frame, shown when display wakes
ui->setOverlays(NULL, 0); // Clear overlay
setFrames(FOCUS_PRESERVE); // Return to normal display updates, showing same frame as before screensaver, ideally
- // Pick a refresh method, for when display wakes
-#ifdef EINK_HASQUIRK_GHOSTING
- EINK_ADD_FRAMEFLAG(dispdev, COSMETIC); // Really ugly to see ghosting from "screen paused"
-#else
- EINK_ADD_FRAMEFLAG(dispdev, RESPONSIVE); // Really nice to wake screen with a fast-refresh
-#endif
+ // Pick a refresh method for when the display wakes. RESPONSIVE = FAST; DisplayHealth
+ // will promote to FULL on its own schedule if FAST debt has built up.
+ EINK_ADD_FRAMEFLAG(dispdev, RESPONSIVE);
}
#endif
diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h
index e259f7691..62ff529a1 100644
--- a/src/graphics/Screen.h
+++ b/src/graphics/Screen.h
@@ -87,8 +87,7 @@ class Screen
#include
#endif
-#include "EInkDisplay2.h"
-#include "EInkDynamicDisplay.h"
+#include "BaseUIEInkDisplay.h"
#include "PointStruct.h"
#include "TFTDisplay.h"
#include "TypedQueue.h"
diff --git a/src/graphics/niche/Drivers/Backlight/LatchingBacklight.cpp b/src/graphics/eink/Backlight/LatchingBacklight.cpp
similarity index 100%
rename from src/graphics/niche/Drivers/Backlight/LatchingBacklight.cpp
rename to src/graphics/eink/Backlight/LatchingBacklight.cpp
diff --git a/src/graphics/niche/Drivers/Backlight/LatchingBacklight.h b/src/graphics/eink/Backlight/LatchingBacklight.h
similarity index 100%
rename from src/graphics/niche/Drivers/Backlight/LatchingBacklight.h
rename to src/graphics/eink/Backlight/LatchingBacklight.h
diff --git a/src/graphics/niche/Drivers/EInk/DEPG0213BNS800.cpp b/src/graphics/eink/Drivers/DEPG0213BNS800.cpp
similarity index 100%
rename from src/graphics/niche/Drivers/EInk/DEPG0213BNS800.cpp
rename to src/graphics/eink/Drivers/DEPG0213BNS800.cpp
diff --git a/src/graphics/niche/Drivers/EInk/DEPG0213BNS800.h b/src/graphics/eink/Drivers/DEPG0213BNS800.h
similarity index 100%
rename from src/graphics/niche/Drivers/EInk/DEPG0213BNS800.h
rename to src/graphics/eink/Drivers/DEPG0213BNS800.h
diff --git a/src/graphics/niche/Drivers/EInk/DEPG0290BNS800.cpp b/src/graphics/eink/Drivers/DEPG0290BNS800.cpp
similarity index 100%
rename from src/graphics/niche/Drivers/EInk/DEPG0290BNS800.cpp
rename to src/graphics/eink/Drivers/DEPG0290BNS800.cpp
diff --git a/src/graphics/niche/Drivers/EInk/DEPG0290BNS800.h b/src/graphics/eink/Drivers/DEPG0290BNS800.h
similarity index 100%
rename from src/graphics/niche/Drivers/EInk/DEPG0290BNS800.h
rename to src/graphics/eink/Drivers/DEPG0290BNS800.h
diff --git a/src/graphics/niche/Drivers/EInk/E0213A367.cpp b/src/graphics/eink/Drivers/E0213A367.cpp
similarity index 100%
rename from src/graphics/niche/Drivers/EInk/E0213A367.cpp
rename to src/graphics/eink/Drivers/E0213A367.cpp
diff --git a/src/graphics/niche/Drivers/EInk/E0213A367.h b/src/graphics/eink/Drivers/E0213A367.h
similarity index 100%
rename from src/graphics/niche/Drivers/EInk/E0213A367.h
rename to src/graphics/eink/Drivers/E0213A367.h
diff --git a/src/graphics/niche/Drivers/EInk/EInk.cpp b/src/graphics/eink/Drivers/EInk.cpp
similarity index 100%
rename from src/graphics/niche/Drivers/EInk/EInk.cpp
rename to src/graphics/eink/Drivers/EInk.cpp
diff --git a/src/graphics/niche/Drivers/EInk/EInk.h b/src/graphics/eink/Drivers/EInk.h
similarity index 100%
rename from src/graphics/niche/Drivers/EInk/EInk.h
rename to src/graphics/eink/Drivers/EInk.h
diff --git a/src/graphics/eink/Drivers/EInkParallel.cpp b/src/graphics/eink/Drivers/EInkParallel.cpp
new file mode 100644
index 000000000..ed0c48a26
--- /dev/null
+++ b/src/graphics/eink/Drivers/EInkParallel.cpp
@@ -0,0 +1,132 @@
+#include "./EInkParallel.h"
+
+#if defined(MESHTASTIC_INCLUDE_NICHE_GRAPHICS) && defined(ARCH_ESP32) && defined(NICHE_HAS_FASTEPD)
+
+#include "FastEPD.h"
+
+using namespace NicheGraphics::Drivers;
+
+EInkParallel::EInkParallel(uint16_t width, uint16_t height, uint32_t panelType, uint32_t panelClock)
+ : EInk(width, height, (UpdateTypes)(FULL | FAST)), panelType(panelType), panelClock(panelClock)
+{
+}
+
+EInkParallel::~EInkParallel()
+{
+ if (asyncRunning.load()) {
+ for (int i = 0; i < 50 && asyncRunning.load(); ++i)
+ delay(50);
+ if (asyncTaskHandle) {
+ vTaskDelete(asyncTaskHandle);
+ asyncTaskHandle = nullptr;
+ }
+ }
+ delete epaper;
+}
+
+void EInkParallel::begin(SPIClass *, uint8_t, uint8_t, uint8_t, uint8_t)
+{
+ // Parallel panels don't use the SPI args; FastEPD owns the bus.
+ if (!epaper) {
+ epaper = new FASTEPD;
+ epaper->initPanel((int)panelType, panelClock);
+ postPanelInit();
+ epaper->setMode(BB_MODE_1BPP);
+ epaper->clearWhite();
+ epaper->fullUpdate(true);
+ }
+}
+
+void EInkParallel::update(uint8_t *imageData, UpdateTypes type)
+{
+ if (!epaper)
+ return;
+
+ pendingType = type;
+ copyImageInverted(imageData);
+
+ if (type == FULL) {
+ // Pick CLEAR_SLOW periodically to clear ghosting.
+ const int clearMode = (fastRefreshCount >= FULL_SLOW_PERIOD) ? CLEAR_SLOW : CLEAR_FAST;
+ fastRefreshCount = 0;
+
+ if (!asyncRunning.load()) {
+ asyncRunning.store(true);
+ BaseType_t rc =
+ xTaskCreatePinnedToCore(asyncFullTask, "epd_full", 4096 / sizeof(StackType_t), this, 2, &asyncTaskHandle,
+#if CONFIG_FREERTOS_UNICORE
+ 0
+#else
+ 1
+#endif
+ );
+ if (rc != pdPASS) {
+ LOG_WARN("Async full failed; running blocking");
+ epaper->fullUpdate(clearMode, false);
+ epaper->backupPlane();
+ asyncRunning.store(false);
+ asyncTaskHandle = nullptr;
+ return; // synchronous: nothing to poll
+ }
+ // Begin polling for completion.
+ beginPolling(100, 1500);
+ }
+ } else {
+ // FAST: synchronous partial / clipped fullUpdate. Block briefly here.
+ epaper->fullUpdate(CLEAR_FAST, false);
+ epaper->backupPlane();
+ fastRefreshCount++;
+ // No polling needed; isUpdateDone() will report done immediately.
+ beginPolling(10, 0);
+ }
+}
+
+void EInkParallel::asyncFullTask(void *param)
+{
+ auto *self = static_cast(param);
+ if (!self) {
+ vTaskDelete(nullptr);
+ return;
+ }
+ self->epaper->fullUpdate(CLEAR_FAST, false);
+ self->epaper->backupPlane();
+ self->asyncRunning.store(false);
+ self->asyncTaskHandle = nullptr;
+ vTaskDelete(nullptr);
+}
+
+bool EInkParallel::isUpdateDone()
+{
+ return !asyncRunning.load();
+}
+
+void EInkParallel::finalizeUpdate()
+{
+ pendingType = UpdateTypes::UNSPECIFIED;
+}
+
+// Convert a niche-format buffer (row-major, MSB-left, 1=WHITE) into FastEPD's currentBuffer
+// (row-major, MSB-left, 1=BLACK). Polarity inversion only.
+void EInkParallel::copyImageInverted(const uint8_t *src)
+{
+ uint8_t *dst = epaper->currentBuffer();
+ if (!dst || !src)
+ return;
+
+ const uint16_t rowBytes = ((width - 1) / 8) + 1;
+ const uint32_t total = rowBytes * height;
+
+ // Mask off bits beyond the panel width in the trailing byte of each row.
+ const uint8_t trailingMask = (uint8_t)(0xFFu << ((rowBytes * 8) - width));
+
+ for (uint16_t y = 0; y < height; y++) {
+ const uint32_t base = y * rowBytes;
+ for (uint16_t b = 0; b < rowBytes - 1; b++) {
+ dst[base + b] = ~src[base + b];
+ }
+ dst[base + rowBytes - 1] = (~src[base + rowBytes - 1]) & trailingMask;
+ }
+ (void)total;
+}
+
+#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS && ARCH_ESP32 && NICHE_HAS_FASTEPD
diff --git a/src/graphics/eink/Drivers/EInkParallel.h b/src/graphics/eink/Drivers/EInkParallel.h
new file mode 100644
index 000000000..7a0978be1
--- /dev/null
+++ b/src/graphics/eink/Drivers/EInkParallel.h
@@ -0,0 +1,69 @@
+/*
+
+Parallel-EPD niche driver, backed by FastEPD.
+
+Used for boards with an 8-bit parallel EPD interface (e.g. LILYGO T5 S3 ePaper).
+The base class signature passes SPI parameters; this driver ignores them and uses FastEPD
+to drive the parallel bus directly.
+
+Gated on NICHE_HAS_FASTEPD because FastEPD is a heavy dependency that only parallel-EPD
+variants want pulled in. Variants opt in by defining NICHE_HAS_FASTEPD in their platformio.ini
+and adding the FastEPD library to lib_deps.
+
+*/
+
+#pragma once
+
+#include "configuration.h"
+
+#if defined(MESHTASTIC_INCLUDE_NICHE_GRAPHICS) && defined(ARCH_ESP32) && defined(NICHE_HAS_FASTEPD)
+
+#include "./EInk.h"
+
+#include
+#include
+#include
+
+class FASTEPD;
+
+namespace NicheGraphics::Drivers
+{
+
+class EInkParallel : public EInk
+{
+ public:
+ EInkParallel(uint16_t width, uint16_t height, uint32_t panelType, uint32_t panelClock = 28000000);
+ ~EInkParallel();
+
+ // SPI parameters are unused for parallel panels.
+ void begin(SPIClass *spi, uint8_t pin_dc, uint8_t pin_cs, uint8_t pin_busy, uint8_t pin_rst = -1) override;
+ void update(uint8_t *imageData, UpdateTypes type) override;
+
+ FASTEPD *fastEpd() { return epaper; }
+
+ protected:
+ bool isUpdateDone() override;
+ void finalizeUpdate() override;
+
+ // Hook for boards that need to bring up GPIO expanders / power pins after FastEPD::initPanel.
+ virtual void postPanelInit() {}
+
+ private:
+ void copyImageInverted(const uint8_t *src);
+ static void asyncFullTask(void *param);
+
+ FASTEPD *epaper = nullptr;
+ uint32_t panelType;
+ uint32_t panelClock;
+
+ UpdateTypes pendingType = UpdateTypes::UNSPECIFIED;
+ std::atomic asyncRunning{false};
+ TaskHandle_t asyncTaskHandle = nullptr;
+
+ uint8_t fastRefreshCount = 0;
+ static constexpr uint8_t FULL_SLOW_PERIOD = 100;
+};
+
+} // namespace NicheGraphics::Drivers
+
+#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS && ARCH_ESP32 && NICHE_HAS_FASTEPD
diff --git a/src/graphics/eink/Drivers/GDEH0122T61.cpp b/src/graphics/eink/Drivers/GDEH0122T61.cpp
new file mode 100644
index 000000000..38aac91fc
--- /dev/null
+++ b/src/graphics/eink/Drivers/GDEH0122T61.cpp
@@ -0,0 +1,49 @@
+#include "./GDEH0122T61.h"
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+using namespace NicheGraphics::Drivers;
+
+void GDEH0122T61::configScanning()
+{
+ sendCommand(0x01);
+ sendData(0xAF); // Scan until gate 175 (176px vertical resolution, low byte)
+ sendData(0x00); // high byte
+ sendData(0x00);
+}
+
+void GDEH0122T61::configWaveform()
+{
+ sendCommand(0x3C);
+ sendData(0x05);
+
+ sendCommand(0x18);
+ sendData(0x80);
+}
+
+void GDEH0122T61::configUpdateSequence()
+{
+ switch (updateType) {
+ case FAST:
+ sendCommand(0x22);
+ sendData(0xFF);
+ break;
+ case FULL:
+ default:
+ sendCommand(0x22);
+ sendData(0xF7);
+ break;
+ }
+}
+
+void GDEH0122T61::detachFromUpdate()
+{
+ switch (updateType) {
+ case FAST:
+ return beginPolling(50, 250);
+ case FULL:
+ default:
+ return beginPolling(100, 1500);
+ }
+}
+#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
diff --git a/src/graphics/eink/Drivers/GDEH0122T61.h b/src/graphics/eink/Drivers/GDEH0122T61.h
new file mode 100644
index 000000000..42c585d0e
--- /dev/null
+++ b/src/graphics/eink/Drivers/GDEH0122T61.h
@@ -0,0 +1,43 @@
+/*
+
+E-Ink display driver
+ - GDEH0122T61
+ - Manufacturer: Good Display
+ - Size: 1.22 inch
+ - Resolution: 192px x 176px
+ - Controller IC: SSD1681 (operating in a sub-200x200 window)
+
+ Used by: t-echo-lite.
+
+*/
+
+#pragma once
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+#include "configuration.h"
+
+#include "./SSD16XX.h"
+
+namespace NicheGraphics::Drivers
+{
+class GDEH0122T61 : public SSD16XX
+{
+ private:
+ static constexpr uint32_t width = 192;
+ static constexpr uint32_t height = 176;
+ static constexpr UpdateTypes supported = (UpdateTypes)(FULL | FAST);
+
+ public:
+ GDEH0122T61() : SSD16XX(width, height, supported) {}
+
+ protected:
+ void configScanning() override;
+ void configWaveform() override;
+ void configUpdateSequence() override;
+ void detachFromUpdate() override;
+};
+
+} // namespace NicheGraphics::Drivers
+
+#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
diff --git a/src/graphics/eink/Drivers/GDEQ031T10.cpp b/src/graphics/eink/Drivers/GDEQ031T10.cpp
new file mode 100644
index 000000000..f6ed66ee8
--- /dev/null
+++ b/src/graphics/eink/Drivers/GDEQ031T10.cpp
@@ -0,0 +1,57 @@
+#include "./GDEQ031T10.h"
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+using namespace NicheGraphics::Drivers;
+
+void GDEQ031T10::configScanning()
+{
+ sendCommand(0x01);
+ sendData(0x3F); // 319, low byte
+ sendData(0x01); // 319, high byte
+ sendData(0x00);
+}
+
+void GDEQ031T10::configWaveform()
+{
+ sendCommand(0x3C);
+ sendData(0x01);
+
+ sendCommand(0x18);
+ sendData(0x80);
+}
+
+void GDEQ031T10::configUpdateSequence()
+{
+ switch (updateType) {
+ case FAST:
+ sendCommand(0x21);
+ sendData(0x00);
+ sendData(0x00);
+
+ sendCommand(0x22);
+ sendData(0xFF);
+ break;
+ case FULL:
+ default:
+ sendCommand(0x21);
+ sendData(0x40);
+ sendData(0x00);
+
+ sendCommand(0x22);
+ sendData(0xF7);
+ break;
+ }
+}
+
+void GDEQ031T10::detachFromUpdate()
+{
+ switch (updateType) {
+ case FAST:
+ return beginPolling(50, 400);
+ case FULL:
+ default:
+ return beginPolling(100, 2500);
+ }
+}
+#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
diff --git a/src/graphics/eink/Drivers/GDEQ031T10.h b/src/graphics/eink/Drivers/GDEQ031T10.h
new file mode 100644
index 000000000..9d26156fe
--- /dev/null
+++ b/src/graphics/eink/Drivers/GDEQ031T10.h
@@ -0,0 +1,43 @@
+/*
+
+E-Ink display driver
+ - GDEQ031T10
+ - Manufacturer: Good Display
+ - Size: 3.1 inch
+ - Resolution: 240px x 320px
+ - Controller IC: SSD1677 (SSD16XX-family, larger memory range)
+
+ Used by: t-deck-pro.
+
+*/
+
+#pragma once
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+#include "configuration.h"
+
+#include "./SSD16XX.h"
+
+namespace NicheGraphics::Drivers
+{
+class GDEQ031T10 : public SSD16XX
+{
+ private:
+ static constexpr uint32_t width = 240;
+ static constexpr uint32_t height = 320;
+ static constexpr UpdateTypes supported = (UpdateTypes)(FULL | FAST);
+
+ public:
+ GDEQ031T10() : SSD16XX(width, height, supported) {}
+
+ protected:
+ void configScanning() override;
+ void configWaveform() override;
+ void configUpdateSequence() override;
+ void detachFromUpdate() override;
+};
+
+} // namespace NicheGraphics::Drivers
+
+#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
diff --git a/src/graphics/niche/Drivers/EInk/GDEW0102T4.cpp b/src/graphics/eink/Drivers/GDEW0102T4.cpp
similarity index 100%
rename from src/graphics/niche/Drivers/EInk/GDEW0102T4.cpp
rename to src/graphics/eink/Drivers/GDEW0102T4.cpp
diff --git a/src/graphics/niche/Drivers/EInk/GDEW0102T4.h b/src/graphics/eink/Drivers/GDEW0102T4.h
similarity index 100%
rename from src/graphics/niche/Drivers/EInk/GDEW0102T4.h
rename to src/graphics/eink/Drivers/GDEW0102T4.h
diff --git a/src/graphics/niche/Drivers/EInk/GDEY0154D67.cpp b/src/graphics/eink/Drivers/GDEY0154D67.cpp
similarity index 100%
rename from src/graphics/niche/Drivers/EInk/GDEY0154D67.cpp
rename to src/graphics/eink/Drivers/GDEY0154D67.cpp
diff --git a/src/graphics/niche/Drivers/EInk/GDEY0154D67.h b/src/graphics/eink/Drivers/GDEY0154D67.h
similarity index 100%
rename from src/graphics/niche/Drivers/EInk/GDEY0154D67.h
rename to src/graphics/eink/Drivers/GDEY0154D67.h
diff --git a/src/graphics/niche/Drivers/EInk/GDEY0213B74.cpp b/src/graphics/eink/Drivers/GDEY0213B74.cpp
similarity index 100%
rename from src/graphics/niche/Drivers/EInk/GDEY0213B74.cpp
rename to src/graphics/eink/Drivers/GDEY0213B74.cpp
diff --git a/src/graphics/niche/Drivers/EInk/GDEY0213B74.h b/src/graphics/eink/Drivers/GDEY0213B74.h
similarity index 100%
rename from src/graphics/niche/Drivers/EInk/GDEY0213B74.h
rename to src/graphics/eink/Drivers/GDEY0213B74.h
diff --git a/src/graphics/eink/Drivers/GDEY029T94.cpp b/src/graphics/eink/Drivers/GDEY029T94.cpp
new file mode 100644
index 000000000..ec2e801db
--- /dev/null
+++ b/src/graphics/eink/Drivers/GDEY029T94.cpp
@@ -0,0 +1,49 @@
+#include "./GDEY029T94.h"
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+using namespace NicheGraphics::Drivers;
+
+void GDEY029T94::configScanning()
+{
+ sendCommand(0x01);
+ sendData(0x27); // 295, low byte
+ sendData(0x01); // 295, high byte
+ sendData(0x00);
+}
+
+void GDEY029T94::configWaveform()
+{
+ sendCommand(0x3C);
+ sendData(0x05);
+
+ sendCommand(0x18);
+ sendData(0x80);
+}
+
+void GDEY029T94::configUpdateSequence()
+{
+ switch (updateType) {
+ case FAST:
+ sendCommand(0x22);
+ sendData(0xFF);
+ break;
+ case FULL:
+ default:
+ sendCommand(0x22);
+ sendData(0xF7);
+ break;
+ }
+}
+
+void GDEY029T94::detachFromUpdate()
+{
+ switch (updateType) {
+ case FAST:
+ return beginPolling(50, 300);
+ case FULL:
+ default:
+ return beginPolling(100, 2000);
+ }
+}
+#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
diff --git a/src/graphics/eink/Drivers/GDEY029T94.h b/src/graphics/eink/Drivers/GDEY029T94.h
new file mode 100644
index 000000000..732fb185c
--- /dev/null
+++ b/src/graphics/eink/Drivers/GDEY029T94.h
@@ -0,0 +1,43 @@
+/*
+
+E-Ink display driver
+ - GDEY029T94 (also sold as GDEY029T94-V2)
+ - Manufacturer: Good Display
+ - Size: 2.9 inch
+ - Resolution: 128px x 296px
+ - Controller IC: SSD1680
+
+ Used by: esp32-s3-pico, crowpanel-esp32s3-2-epaper.
+
+*/
+
+#pragma once
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+#include "configuration.h"
+
+#include "./SSD16XX.h"
+
+namespace NicheGraphics::Drivers
+{
+class GDEY029T94 : public SSD16XX
+{
+ private:
+ static constexpr uint32_t width = 128;
+ static constexpr uint32_t height = 296;
+ static constexpr UpdateTypes supported = (UpdateTypes)(FULL | FAST);
+
+ public:
+ GDEY029T94() : SSD16XX(width, height, supported) {}
+
+ protected:
+ void configScanning() override;
+ void configWaveform() override;
+ void configUpdateSequence() override;
+ void detachFromUpdate() override;
+};
+
+} // namespace NicheGraphics::Drivers
+
+#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
diff --git a/src/graphics/eink/Drivers/GDEY042T81.cpp b/src/graphics/eink/Drivers/GDEY042T81.cpp
new file mode 100644
index 000000000..4f19dcc9e
--- /dev/null
+++ b/src/graphics/eink/Drivers/GDEY042T81.cpp
@@ -0,0 +1,49 @@
+#include "./GDEY042T81.h"
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+using namespace NicheGraphics::Drivers;
+
+void GDEY042T81::configWaveform()
+{
+ sendCommand(0x3C);
+ sendData(0x01);
+
+ sendCommand(0x18);
+ sendData(0x80);
+}
+
+void GDEY042T81::configUpdateSequence()
+{
+ switch (updateType) {
+ case FAST:
+ sendCommand(0x21);
+ sendData(0x00);
+ sendData(0x00);
+
+ sendCommand(0x22);
+ sendData(0xFF);
+ break;
+ case FULL:
+ default:
+ sendCommand(0x21);
+ sendData(0x40);
+ sendData(0x00);
+
+ sendCommand(0x22);
+ sendData(0xF7);
+ break;
+ }
+}
+
+void GDEY042T81::detachFromUpdate()
+{
+ switch (updateType) {
+ case FAST:
+ return beginPolling(50, 1000);
+ case FULL:
+ default:
+ return beginPolling(100, 3500);
+ }
+}
+#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
diff --git a/src/graphics/eink/Drivers/GDEY042T81.h b/src/graphics/eink/Drivers/GDEY042T81.h
new file mode 100644
index 000000000..166b45266
--- /dev/null
+++ b/src/graphics/eink/Drivers/GDEY042T81.h
@@ -0,0 +1,42 @@
+/*
+
+E-Ink display driver
+ - GDEY042T81
+ - Manufacturer: Good Display
+ - Size: 4.2 inch
+ - Resolution: 400px x 300px
+ - Controller IC: SSD1683
+
+ Used by: ME25LS01-4Y10TD_e-ink.
+
+*/
+
+#pragma once
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+#include "configuration.h"
+
+#include "./SSD16XX.h"
+
+namespace NicheGraphics::Drivers
+{
+class GDEY042T81 : public SSD16XX
+{
+ private:
+ static constexpr uint32_t width = 400;
+ static constexpr uint32_t height = 300;
+ static constexpr UpdateTypes supported = (UpdateTypes)(FULL | FAST);
+
+ public:
+ GDEY042T81() : SSD16XX(width, height, supported) {}
+
+ protected:
+ void configWaveform() override;
+ void configUpdateSequence() override;
+ void detachFromUpdate() override;
+};
+
+} // namespace NicheGraphics::Drivers
+
+#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
diff --git a/src/graphics/eink/Drivers/GDEY0579T93.cpp b/src/graphics/eink/Drivers/GDEY0579T93.cpp
new file mode 100644
index 000000000..f076108fb
--- /dev/null
+++ b/src/graphics/eink/Drivers/GDEY0579T93.cpp
@@ -0,0 +1,57 @@
+#include "./GDEY0579T93.h"
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+using namespace NicheGraphics::Drivers;
+
+void GDEY0579T93::configScanning()
+{
+ sendCommand(0x01);
+ sendData(0x0F); // 271, low byte
+ sendData(0x01); // 271, high byte
+ sendData(0x00);
+}
+
+void GDEY0579T93::configWaveform()
+{
+ sendCommand(0x3C);
+ sendData(0x01);
+
+ sendCommand(0x18);
+ sendData(0x80);
+}
+
+void GDEY0579T93::configUpdateSequence()
+{
+ switch (updateType) {
+ case FAST:
+ sendCommand(0x21);
+ sendData(0x00);
+ sendData(0x00);
+
+ sendCommand(0x22);
+ sendData(0xFF);
+ break;
+ case FULL:
+ default:
+ sendCommand(0x21);
+ sendData(0x40);
+ sendData(0x00);
+
+ sendCommand(0x22);
+ sendData(0xF7);
+ break;
+ }
+}
+
+void GDEY0579T93::detachFromUpdate()
+{
+ switch (updateType) {
+ case FAST:
+ return beginPolling(100, 2000);
+ case FULL:
+ default:
+ return beginPolling(150, 5000);
+ }
+}
+#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
diff --git a/src/graphics/eink/Drivers/GDEY0579T93.h b/src/graphics/eink/Drivers/GDEY0579T93.h
new file mode 100644
index 000000000..05956405c
--- /dev/null
+++ b/src/graphics/eink/Drivers/GDEY0579T93.h
@@ -0,0 +1,43 @@
+/*
+
+E-Ink display driver
+ - GDEY0579T93
+ - Manufacturer: Good Display
+ - Size: 5.79 inch
+ - Resolution: 792px x 272px
+ - Controller IC: SSD1683 (extended memory range)
+
+ Used by: crowpanel-esp32s3-5-epaper.
+
+*/
+
+#pragma once
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+#include "configuration.h"
+
+#include "./SSD16XX.h"
+
+namespace NicheGraphics::Drivers
+{
+class GDEY0579T93 : public SSD16XX
+{
+ private:
+ static constexpr uint32_t width = 792;
+ static constexpr uint32_t height = 272;
+ static constexpr UpdateTypes supported = (UpdateTypes)(FULL | FAST);
+
+ public:
+ GDEY0579T93() : SSD16XX(width, height, supported) {}
+
+ protected:
+ void configScanning() override;
+ void configWaveform() override;
+ void configUpdateSequence() override;
+ void detachFromUpdate() override;
+};
+
+} // namespace NicheGraphics::Drivers
+
+#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
diff --git a/src/graphics/niche/Drivers/EInk/HINK_E0213A289.cpp b/src/graphics/eink/Drivers/HINK_E0213A289.cpp
similarity index 100%
rename from src/graphics/niche/Drivers/EInk/HINK_E0213A289.cpp
rename to src/graphics/eink/Drivers/HINK_E0213A289.cpp
diff --git a/src/graphics/niche/Drivers/EInk/HINK_E0213A289.h b/src/graphics/eink/Drivers/HINK_E0213A289.h
similarity index 100%
rename from src/graphics/niche/Drivers/EInk/HINK_E0213A289.h
rename to src/graphics/eink/Drivers/HINK_E0213A289.h
diff --git a/src/graphics/niche/Drivers/EInk/HINK_E042A87.cpp b/src/graphics/eink/Drivers/HINK_E042A87.cpp
similarity index 100%
rename from src/graphics/niche/Drivers/EInk/HINK_E042A87.cpp
rename to src/graphics/eink/Drivers/HINK_E042A87.cpp
diff --git a/src/graphics/niche/Drivers/EInk/HINK_E042A87.h b/src/graphics/eink/Drivers/HINK_E042A87.h
similarity index 100%
rename from src/graphics/niche/Drivers/EInk/HINK_E042A87.h
rename to src/graphics/eink/Drivers/HINK_E042A87.h
diff --git a/src/graphics/niche/Drivers/EInk/LCMEN2R13ECC1.cpp b/src/graphics/eink/Drivers/LCMEN2R13ECC1.cpp
similarity index 100%
rename from src/graphics/niche/Drivers/EInk/LCMEN2R13ECC1.cpp
rename to src/graphics/eink/Drivers/LCMEN2R13ECC1.cpp
diff --git a/src/graphics/niche/Drivers/EInk/LCMEN2R13ECC1.h b/src/graphics/eink/Drivers/LCMEN2R13ECC1.h
similarity index 100%
rename from src/graphics/niche/Drivers/EInk/LCMEN2R13ECC1.h
rename to src/graphics/eink/Drivers/LCMEN2R13ECC1.h
diff --git a/src/graphics/niche/Drivers/EInk/LCMEN2R13EFC1.cpp b/src/graphics/eink/Drivers/LCMEN2R13EFC1.cpp
similarity index 100%
rename from src/graphics/niche/Drivers/EInk/LCMEN2R13EFC1.cpp
rename to src/graphics/eink/Drivers/LCMEN2R13EFC1.cpp
diff --git a/src/graphics/niche/Drivers/EInk/LCMEN2R13EFC1.h b/src/graphics/eink/Drivers/LCMEN2R13EFC1.h
similarity index 100%
rename from src/graphics/niche/Drivers/EInk/LCMEN2R13EFC1.h
rename to src/graphics/eink/Drivers/LCMEN2R13EFC1.h
diff --git a/src/graphics/niche/Drivers/README.md b/src/graphics/eink/Drivers/README-legacy.md
similarity index 100%
rename from src/graphics/niche/Drivers/README.md
rename to src/graphics/eink/Drivers/README-legacy.md
diff --git a/src/graphics/niche/Drivers/EInk/README.md b/src/graphics/eink/Drivers/README.md
similarity index 100%
rename from src/graphics/niche/Drivers/EInk/README.md
rename to src/graphics/eink/Drivers/README.md
diff --git a/src/graphics/niche/Drivers/EInk/SSD1682.cpp b/src/graphics/eink/Drivers/SSD1682.cpp
similarity index 100%
rename from src/graphics/niche/Drivers/EInk/SSD1682.cpp
rename to src/graphics/eink/Drivers/SSD1682.cpp
diff --git a/src/graphics/niche/Drivers/EInk/SSD1682.h b/src/graphics/eink/Drivers/SSD1682.h
similarity index 100%
rename from src/graphics/niche/Drivers/EInk/SSD1682.h
rename to src/graphics/eink/Drivers/SSD1682.h
diff --git a/src/graphics/niche/Drivers/EInk/SSD16XX.cpp b/src/graphics/eink/Drivers/SSD16XX.cpp
similarity index 100%
rename from src/graphics/niche/Drivers/EInk/SSD16XX.cpp
rename to src/graphics/eink/Drivers/SSD16XX.cpp
diff --git a/src/graphics/niche/Drivers/EInk/SSD16XX.h b/src/graphics/eink/Drivers/SSD16XX.h
similarity index 100%
rename from src/graphics/niche/Drivers/EInk/SSD16XX.h
rename to src/graphics/eink/Drivers/SSD16XX.h
diff --git a/src/graphics/niche/Drivers/EInk/UC8175.cpp b/src/graphics/eink/Drivers/UC8175.cpp
similarity index 100%
rename from src/graphics/niche/Drivers/EInk/UC8175.cpp
rename to src/graphics/eink/Drivers/UC8175.cpp
diff --git a/src/graphics/niche/Drivers/EInk/UC8175.h b/src/graphics/eink/Drivers/UC8175.h
similarity index 100%
rename from src/graphics/niche/Drivers/EInk/UC8175.h
rename to src/graphics/eink/Drivers/UC8175.h
diff --git a/src/graphics/niche/Drivers/EInk/ZJY122250_0213BAAMFGN.cpp b/src/graphics/eink/Drivers/ZJY122250_0213BAAMFGN.cpp
similarity index 100%
rename from src/graphics/niche/Drivers/EInk/ZJY122250_0213BAAMFGN.cpp
rename to src/graphics/eink/Drivers/ZJY122250_0213BAAMFGN.cpp
diff --git a/src/graphics/niche/Drivers/EInk/ZJY122250_0213BAAMFGN.h b/src/graphics/eink/Drivers/ZJY122250_0213BAAMFGN.h
similarity index 100%
rename from src/graphics/niche/Drivers/EInk/ZJY122250_0213BAAMFGN.h
rename to src/graphics/eink/Drivers/ZJY122250_0213BAAMFGN.h
diff --git a/src/graphics/niche/Drivers/EInk/ZJY128296_029EAAMFGN.cpp b/src/graphics/eink/Drivers/ZJY128296_029EAAMFGN.cpp
similarity index 100%
rename from src/graphics/niche/Drivers/EInk/ZJY128296_029EAAMFGN.cpp
rename to src/graphics/eink/Drivers/ZJY128296_029EAAMFGN.cpp
diff --git a/src/graphics/niche/Drivers/EInk/ZJY128296_029EAAMFGN.h b/src/graphics/eink/Drivers/ZJY128296_029EAAMFGN.h
similarity index 100%
rename from src/graphics/niche/Drivers/EInk/ZJY128296_029EAAMFGN.h
rename to src/graphics/eink/Drivers/ZJY128296_029EAAMFGN.h
diff --git a/src/graphics/niche/Drivers/EInk/ZJY200200_0154DAAMFGN.h b/src/graphics/eink/Drivers/ZJY200200_0154DAAMFGN.h
similarity index 100%
rename from src/graphics/niche/Drivers/EInk/ZJY200200_0154DAAMFGN.h
rename to src/graphics/eink/Drivers/ZJY200200_0154DAAMFGN.h
diff --git a/src/graphics/eink/Panels/DEPG0213BNS800.h b/src/graphics/eink/Panels/DEPG0213BNS800.h
new file mode 100644
index 000000000..9ea40b2fb
--- /dev/null
+++ b/src/graphics/eink/Panels/DEPG0213BNS800.h
@@ -0,0 +1,25 @@
+#pragma once
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+#include "./PanelProfile.h"
+#include "graphics/eink/Drivers/DEPG0213BNS800.h"
+
+namespace NicheGraphics::Panels
+{
+class DEPG0213BNS800 : public PanelProfile
+{
+ public:
+ NicheGraphics::Drivers::EInk *create() override
+ {
+ prePowerOn();
+ SPIClass *spi = beginSpi();
+ auto *drv = new NicheGraphics::Drivers::DEPG0213BNS800();
+ drv->begin(spi, pinDC(), pinCS(), pinBusy(), pinReset());
+ return drv;
+ }
+ uint8_t rotation() const override { return 3; }
+};
+} // namespace NicheGraphics::Panels
+
+#endif
diff --git a/src/graphics/eink/Panels/DEPG0290BNS800.h b/src/graphics/eink/Panels/DEPG0290BNS800.h
new file mode 100644
index 000000000..3ad4e0ac3
--- /dev/null
+++ b/src/graphics/eink/Panels/DEPG0290BNS800.h
@@ -0,0 +1,25 @@
+#pragma once
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+#include "./PanelProfile.h"
+#include "graphics/eink/Drivers/DEPG0290BNS800.h"
+
+namespace NicheGraphics::Panels
+{
+class DEPG0290BNS800 : public PanelProfile
+{
+ public:
+ NicheGraphics::Drivers::EInk *create() override
+ {
+ prePowerOn();
+ SPIClass *spi = beginSpi();
+ auto *drv = new NicheGraphics::Drivers::DEPG0290BNS800();
+ drv->begin(spi, pinDC(), pinCS(), pinBusy(), pinReset());
+ return drv;
+ }
+ uint8_t rotation() const override { return 1; }
+};
+} // namespace NicheGraphics::Panels
+
+#endif
diff --git a/src/graphics/eink/Panels/E0213A367.h b/src/graphics/eink/Panels/E0213A367.h
new file mode 100644
index 000000000..6637063c4
--- /dev/null
+++ b/src/graphics/eink/Panels/E0213A367.h
@@ -0,0 +1,25 @@
+#pragma once
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+#include "./PanelProfile.h"
+#include "graphics/eink/Drivers/E0213A367.h"
+
+namespace NicheGraphics::Panels
+{
+class E0213A367 : public PanelProfile
+{
+ public:
+ NicheGraphics::Drivers::EInk *create() override
+ {
+ prePowerOn();
+ SPIClass *spi = beginSpi();
+ auto *drv = new NicheGraphics::Drivers::E0213A367();
+ drv->begin(spi, pinDC(), pinCS(), pinBusy(), pinReset());
+ return drv;
+ }
+ uint8_t rotation() const override { return 3; }
+};
+} // namespace NicheGraphics::Panels
+
+#endif
diff --git a/src/graphics/eink/Panels/GDEH0122T61.h b/src/graphics/eink/Panels/GDEH0122T61.h
new file mode 100644
index 000000000..8a09f30dd
--- /dev/null
+++ b/src/graphics/eink/Panels/GDEH0122T61.h
@@ -0,0 +1,24 @@
+#pragma once
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+#include "./PanelProfile.h"
+#include "graphics/eink/Drivers/GDEH0122T61.h"
+
+namespace NicheGraphics::Panels
+{
+class GDEH0122T61 : public PanelProfile
+{
+ public:
+ NicheGraphics::Drivers::EInk *create() override
+ {
+ prePowerOn();
+ SPIClass *spi = beginSpi();
+ auto *drv = new NicheGraphics::Drivers::GDEH0122T61();
+ drv->begin(spi, pinDC(), pinCS(), pinBusy(), pinReset());
+ return drv;
+ }
+};
+} // namespace NicheGraphics::Panels
+
+#endif
diff --git a/src/graphics/eink/Panels/GDEQ031T10.h b/src/graphics/eink/Panels/GDEQ031T10.h
new file mode 100644
index 000000000..f1d0dfbe7
--- /dev/null
+++ b/src/graphics/eink/Panels/GDEQ031T10.h
@@ -0,0 +1,24 @@
+#pragma once
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+#include "./PanelProfile.h"
+#include "graphics/eink/Drivers/GDEQ031T10.h"
+
+namespace NicheGraphics::Panels
+{
+class GDEQ031T10 : public PanelProfile
+{
+ public:
+ NicheGraphics::Drivers::EInk *create() override
+ {
+ prePowerOn();
+ SPIClass *spi = beginSpi();
+ auto *drv = new NicheGraphics::Drivers::GDEQ031T10();
+ drv->begin(spi, pinDC(), pinCS(), pinBusy(), pinReset());
+ return drv;
+ }
+};
+} // namespace NicheGraphics::Panels
+
+#endif
diff --git a/src/graphics/eink/Panels/GDEW0102T4.h b/src/graphics/eink/Panels/GDEW0102T4.h
new file mode 100644
index 000000000..68eb62d2c
--- /dev/null
+++ b/src/graphics/eink/Panels/GDEW0102T4.h
@@ -0,0 +1,25 @@
+#pragma once
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+#include "./PanelProfile.h"
+#include "graphics/eink/Drivers/GDEW0102T4.h"
+
+namespace NicheGraphics::Panels
+{
+class GDEW0102T4 : public PanelProfile
+{
+ public:
+ NicheGraphics::Drivers::EInk *create() override
+ {
+ prePowerOn();
+ SPIClass *spi = beginSpi();
+ auto *drv = new NicheGraphics::Drivers::GDEW0102T4();
+ drv->begin(spi, pinDC(), pinCS(), pinBusy(), pinReset());
+ return drv;
+ }
+ uint8_t rotation() const override { return 3; }
+};
+} // namespace NicheGraphics::Panels
+
+#endif
diff --git a/src/graphics/eink/Panels/GDEY0154D67.h b/src/graphics/eink/Panels/GDEY0154D67.h
new file mode 100644
index 000000000..940bf7c8e
--- /dev/null
+++ b/src/graphics/eink/Panels/GDEY0154D67.h
@@ -0,0 +1,24 @@
+#pragma once
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+#include "./PanelProfile.h"
+#include "graphics/eink/Drivers/GDEY0154D67.h"
+
+namespace NicheGraphics::Panels
+{
+class GDEY0154D67 : public PanelProfile
+{
+ public:
+ NicheGraphics::Drivers::EInk *create() override
+ {
+ prePowerOn();
+ SPIClass *spi = beginSpi();
+ auto *drv = new NicheGraphics::Drivers::GDEY0154D67();
+ drv->begin(spi, pinDC(), pinCS(), pinBusy(), pinReset());
+ return drv;
+ }
+};
+} // namespace NicheGraphics::Panels
+
+#endif
diff --git a/src/graphics/eink/Panels/GDEY0213B74.h b/src/graphics/eink/Panels/GDEY0213B74.h
new file mode 100644
index 000000000..f743d477d
--- /dev/null
+++ b/src/graphics/eink/Panels/GDEY0213B74.h
@@ -0,0 +1,25 @@
+#pragma once
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+#include "./PanelProfile.h"
+#include "graphics/eink/Drivers/GDEY0213B74.h"
+
+namespace NicheGraphics::Panels
+{
+class GDEY0213B74 : public PanelProfile
+{
+ public:
+ NicheGraphics::Drivers::EInk *create() override
+ {
+ prePowerOn();
+ SPIClass *spi = beginSpi();
+ auto *drv = new NicheGraphics::Drivers::GDEY0213B74();
+ drv->begin(spi, pinDC(), pinCS(), pinBusy(), pinReset());
+ return drv;
+ }
+ uint8_t rotation() const override { return 3; }
+};
+} // namespace NicheGraphics::Panels
+
+#endif
diff --git a/src/graphics/eink/Panels/GDEY029T94.h b/src/graphics/eink/Panels/GDEY029T94.h
new file mode 100644
index 000000000..18cad37b2
--- /dev/null
+++ b/src/graphics/eink/Panels/GDEY029T94.h
@@ -0,0 +1,25 @@
+#pragma once
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+#include "./PanelProfile.h"
+#include "graphics/eink/Drivers/GDEY029T94.h"
+
+namespace NicheGraphics::Panels
+{
+class GDEY029T94 : public PanelProfile
+{
+ public:
+ NicheGraphics::Drivers::EInk *create() override
+ {
+ prePowerOn();
+ SPIClass *spi = beginSpi();
+ auto *drv = new NicheGraphics::Drivers::GDEY029T94();
+ drv->begin(spi, pinDC(), pinCS(), pinBusy(), pinReset());
+ return drv;
+ }
+ uint8_t rotation() const override { return 1; }
+};
+} // namespace NicheGraphics::Panels
+
+#endif
diff --git a/src/graphics/eink/Panels/GDEY042T81.h b/src/graphics/eink/Panels/GDEY042T81.h
new file mode 100644
index 000000000..bba0253f8
--- /dev/null
+++ b/src/graphics/eink/Panels/GDEY042T81.h
@@ -0,0 +1,24 @@
+#pragma once
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+#include "./PanelProfile.h"
+#include "graphics/eink/Drivers/GDEY042T81.h"
+
+namespace NicheGraphics::Panels
+{
+class GDEY042T81 : public PanelProfile
+{
+ public:
+ NicheGraphics::Drivers::EInk *create() override
+ {
+ prePowerOn();
+ SPIClass *spi = beginSpi();
+ auto *drv = new NicheGraphics::Drivers::GDEY042T81();
+ drv->begin(spi, pinDC(), pinCS(), pinBusy(), pinReset());
+ return drv;
+ }
+};
+} // namespace NicheGraphics::Panels
+
+#endif
diff --git a/src/graphics/eink/Panels/GDEY0579T93.h b/src/graphics/eink/Panels/GDEY0579T93.h
new file mode 100644
index 000000000..5c3d98f55
--- /dev/null
+++ b/src/graphics/eink/Panels/GDEY0579T93.h
@@ -0,0 +1,24 @@
+#pragma once
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+#include "./PanelProfile.h"
+#include "graphics/eink/Drivers/GDEY0579T93.h"
+
+namespace NicheGraphics::Panels
+{
+class GDEY0579T93 : public PanelProfile
+{
+ public:
+ NicheGraphics::Drivers::EInk *create() override
+ {
+ prePowerOn();
+ SPIClass *spi = beginSpi();
+ auto *drv = new NicheGraphics::Drivers::GDEY0579T93();
+ drv->begin(spi, pinDC(), pinCS(), pinBusy(), pinReset());
+ return drv;
+ }
+};
+} // namespace NicheGraphics::Panels
+
+#endif
diff --git a/src/graphics/eink/Panels/HINK_E042A87.h b/src/graphics/eink/Panels/HINK_E042A87.h
new file mode 100644
index 000000000..2b6ae446d
--- /dev/null
+++ b/src/graphics/eink/Panels/HINK_E042A87.h
@@ -0,0 +1,24 @@
+#pragma once
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+#include "./PanelProfile.h"
+#include "graphics/eink/Drivers/HINK_E042A87.h"
+
+namespace NicheGraphics::Panels
+{
+class HINK_E042A87 : public PanelProfile
+{
+ public:
+ NicheGraphics::Drivers::EInk *create() override
+ {
+ prePowerOn();
+ SPIClass *spi = beginSpi();
+ auto *drv = new NicheGraphics::Drivers::HINK_E042A87();
+ drv->begin(spi, pinDC(), pinCS(), pinBusy(), pinReset());
+ return drv;
+ }
+};
+} // namespace NicheGraphics::Panels
+
+#endif
diff --git a/src/graphics/eink/Panels/LCMEN213EFC1.h b/src/graphics/eink/Panels/LCMEN213EFC1.h
new file mode 100644
index 000000000..7a2dc6e2a
--- /dev/null
+++ b/src/graphics/eink/Panels/LCMEN213EFC1.h
@@ -0,0 +1,25 @@
+#pragma once
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+#include "./PanelProfile.h"
+#include "graphics/eink/Drivers/LCMEN2R13EFC1.h"
+
+namespace NicheGraphics::Panels
+{
+class LCMEN213EFC1 : public PanelProfile
+{
+ public:
+ NicheGraphics::Drivers::EInk *create() override
+ {
+ prePowerOn();
+ SPIClass *spi = beginSpi();
+ auto *drv = new NicheGraphics::Drivers::LCMEN213EFC1();
+ drv->begin(spi, pinDC(), pinCS(), pinBusy(), pinReset());
+ return drv;
+ }
+ uint8_t rotation() const override { return 3; }
+};
+} // namespace NicheGraphics::Panels
+
+#endif
diff --git a/src/graphics/eink/Panels/LCMEN2R13ECC1.h b/src/graphics/eink/Panels/LCMEN2R13ECC1.h
new file mode 100644
index 000000000..18c6adcf0
--- /dev/null
+++ b/src/graphics/eink/Panels/LCMEN2R13ECC1.h
@@ -0,0 +1,25 @@
+#pragma once
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+#include "./PanelProfile.h"
+#include "graphics/eink/Drivers/LCMEN2R13ECC1.h"
+
+namespace NicheGraphics::Panels
+{
+class LCMEN2R13ECC1 : public PanelProfile
+{
+ public:
+ NicheGraphics::Drivers::EInk *create() override
+ {
+ prePowerOn();
+ SPIClass *spi = beginSpi();
+ auto *drv = new NicheGraphics::Drivers::LCMEN2R13ECC1();
+ drv->begin(spi, pinDC(), pinCS(), pinBusy(), pinReset());
+ return drv;
+ }
+ uint8_t rotation() const override { return 3; }
+};
+} // namespace NicheGraphics::Panels
+
+#endif
diff --git a/src/graphics/eink/Panels/PanelProfile.cpp b/src/graphics/eink/Panels/PanelProfile.cpp
new file mode 100644
index 000000000..3ec35e34c
--- /dev/null
+++ b/src/graphics/eink/Panels/PanelProfile.cpp
@@ -0,0 +1,70 @@
+#include "./PanelProfile.h"
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+using namespace NicheGraphics::Panels;
+
+SPIClass *PanelProfile::beginSpi()
+{
+#if defined(ARCH_ESP32)
+ auto *spi = new SPIClass(HSPI);
+#if defined(PIN_EINK_SCLK) && defined(PIN_EINK_MOSI) && defined(PIN_EINK_CS)
+ spi->begin(PIN_EINK_SCLK, -1, PIN_EINK_MOSI, PIN_EINK_CS);
+#else
+ spi->begin();
+#endif
+ return spi;
+#elif defined(ARCH_NRF52)
+ SPI1.begin();
+ return &SPI1;
+#else
+ return &SPI;
+#endif
+}
+
+int8_t PanelProfile::backlightPin() const
+{
+#ifdef PIN_EINK_EN
+ return PIN_EINK_EN;
+#else
+ return -1;
+#endif
+}
+
+uint8_t PanelProfile::pinDC() const
+{
+#ifdef PIN_EINK_DC
+ return PIN_EINK_DC;
+#else
+ return 0xFF;
+#endif
+}
+
+uint8_t PanelProfile::pinCS() const
+{
+#ifdef PIN_EINK_CS
+ return PIN_EINK_CS;
+#else
+ return 0xFF;
+#endif
+}
+
+uint8_t PanelProfile::pinBusy() const
+{
+#ifdef PIN_EINK_BUSY
+ return PIN_EINK_BUSY;
+#else
+ return 0xFF;
+#endif
+}
+
+int8_t PanelProfile::pinReset() const
+{
+#ifdef PIN_EINK_RES
+ return PIN_EINK_RES;
+#else
+ return -1;
+#endif
+}
+
+#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
diff --git a/src/graphics/eink/Panels/PanelProfile.h b/src/graphics/eink/Panels/PanelProfile.h
new file mode 100644
index 000000000..81d599efa
--- /dev/null
+++ b/src/graphics/eink/Panels/PanelProfile.h
@@ -0,0 +1,52 @@
+/*
+
+Panel profile: single source of truth for how a specific E-Ink panel is wired and brought up.
+Variants subclass a per-panel profile only to override differences (SPI bus, pins, rotation, backlight pin,
+power-up quirks). The profile's create() constructs and begins the underlying
+NicheGraphics::Drivers::EInk subclass exactly once.
+
+*/
+
+#pragma once
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+#include "configuration.h"
+
+#include "graphics/eink/Drivers/EInk.h"
+
+#include
+
+namespace NicheGraphics::Panels
+{
+
+class PanelProfile
+{
+ public:
+ virtual ~PanelProfile() = default;
+
+ // Produce and begin() the underlying E-Ink driver. Called once per boot.
+ virtual NicheGraphics::Drivers::EInk *create() = 0;
+
+ // Public, variant-overridable metadata
+ virtual uint8_t rotation() const { return 0; }
+ virtual int8_t backlightPin() const;
+
+ protected:
+ // Default SPI bring-up. ESP32 uses HSPI with PIN_EINK_SCLK/MOSI; nRF52 uses SPI1 (pins from variant.h).
+ // Variants override when using a non-default bus or pin set.
+ virtual SPIClass *beginSpi();
+
+ // Pin defaults read the variant's PIN_EINK_* macros. Variants override if mapping differs.
+ virtual uint8_t pinDC() const;
+ virtual uint8_t pinCS() const;
+ virtual uint8_t pinBusy() const;
+ virtual int8_t pinReset() const;
+
+ // Hook for variants that need to raise a power rail / observe settle time before SPI traffic.
+ virtual void prePowerOn() {}
+};
+
+} // namespace NicheGraphics::Panels
+
+#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
diff --git a/src/graphics/eink/Panels/T5Epaper.h b/src/graphics/eink/Panels/T5Epaper.h
new file mode 100644
index 000000000..05091f3f1
--- /dev/null
+++ b/src/graphics/eink/Panels/T5Epaper.h
@@ -0,0 +1,38 @@
+/*
+
+Panel profile base for the LILYGO T5 ePaper Pro family (ED047TC1, 960x540, 8-bit parallel via FastEPD).
+
+V1 and V2 use different FastEPD panel IDs and V2 also needs GPIO-expander pins raised.
+Variants subclass to provide a Drivers::EInkParallel subclass that implements
+postPanelInit() if needed.
+
+*/
+
+#pragma once
+
+#include "configuration.h"
+
+#if defined(MESHTASTIC_INCLUDE_NICHE_GRAPHICS) && defined(ARCH_ESP32) && defined(NICHE_HAS_FASTEPD)
+
+#include "./PanelProfile.h"
+#include "graphics/eink/Drivers/EInkParallel.h"
+
+namespace NicheGraphics::Panels
+{
+class T5EpaperPanel : public PanelProfile
+{
+ public:
+ NicheGraphics::Drivers::EInk *create() override
+ {
+ auto *drv = makeDriver();
+ drv->begin(nullptr, 0, 0, 0); // SPI args ignored
+ return drv;
+ }
+
+ protected:
+ // Variant returns a Drivers::EInkParallel subclass configured for its specific panel/init.
+ virtual NicheGraphics::Drivers::EInkParallel *makeDriver() = 0;
+};
+} // namespace NicheGraphics::Panels
+
+#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS && ARCH_ESP32 && NICHE_HAS_FASTEPD
diff --git a/src/graphics/eink/Panels/ZJY122250_0213BAAMFGN.h b/src/graphics/eink/Panels/ZJY122250_0213BAAMFGN.h
new file mode 100644
index 000000000..d84c4d7eb
--- /dev/null
+++ b/src/graphics/eink/Panels/ZJY122250_0213BAAMFGN.h
@@ -0,0 +1,25 @@
+#pragma once
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+#include "./PanelProfile.h"
+#include "graphics/eink/Drivers/ZJY122250_0213BAAMFGN.h"
+
+namespace NicheGraphics::Panels
+{
+class ZJY122250_0213BAAMFGN : public PanelProfile
+{
+ public:
+ NicheGraphics::Drivers::EInk *create() override
+ {
+ prePowerOn();
+ SPIClass *spi = beginSpi();
+ auto *drv = new NicheGraphics::Drivers::ZJY122250_0213BAAMFGN();
+ drv->begin(spi, pinDC(), pinCS(), pinBusy(), pinReset());
+ return drv;
+ }
+ uint8_t rotation() const override { return 1; }
+};
+} // namespace NicheGraphics::Panels
+
+#endif
diff --git a/src/graphics/niche/InkHUD/Applet.cpp b/src/graphics/niche/Applet.cpp
similarity index 99%
rename from src/graphics/niche/InkHUD/Applet.cpp
rename to src/graphics/niche/Applet.cpp
index 0a9cd3add..c017591bb 100644
--- a/src/graphics/niche/InkHUD/Applet.cpp
+++ b/src/graphics/niche/Applet.cpp
@@ -1,4 +1,4 @@
-#include "graphics/niche/InkHUD/Tile.h"
+#include "graphics/niche/Tile.h"
#include
#ifdef MESHTASTIC_INCLUDE_INKHUD
diff --git a/src/graphics/niche/InkHUD/Applet.h b/src/graphics/niche/Applet.h
similarity index 99%
rename from src/graphics/niche/InkHUD/Applet.h
rename to src/graphics/niche/Applet.h
index 3c14c2607..9fa36b194 100644
--- a/src/graphics/niche/InkHUD/Applet.h
+++ b/src/graphics/niche/Applet.h
@@ -23,7 +23,7 @@
#include "./InkHUD.h"
#include "./Persistence.h"
#include "./Tile.h"
-#include "graphics/niche/Drivers/EInk/EInk.h"
+#include "graphics/eink/Drivers/EInk.h"
namespace NicheGraphics::InkHUD
{
diff --git a/src/graphics/niche/InkHUD/AppletFont.cpp b/src/graphics/niche/AppletFont.cpp
similarity index 100%
rename from src/graphics/niche/InkHUD/AppletFont.cpp
rename to src/graphics/niche/AppletFont.cpp
diff --git a/src/graphics/niche/InkHUD/AppletFont.h b/src/graphics/niche/AppletFont.h
similarity index 100%
rename from src/graphics/niche/InkHUD/AppletFont.h
rename to src/graphics/niche/AppletFont.h
diff --git a/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.cpp b/src/graphics/niche/Applets/Bases/Map/MapApplet.cpp
similarity index 100%
rename from src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.cpp
rename to src/graphics/niche/Applets/Bases/Map/MapApplet.cpp
diff --git a/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.h b/src/graphics/niche/Applets/Bases/Map/MapApplet.h
similarity index 97%
rename from src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.h
rename to src/graphics/niche/Applets/Bases/Map/MapApplet.h
index 11dfb39d9..c3b4c886a 100644
--- a/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.h
+++ b/src/graphics/niche/Applets/Bases/Map/MapApplet.h
@@ -16,7 +16,7 @@ The base applet doesn't handle any events; this is left to the derived applets.
#include "configuration.h"
-#include "graphics/niche/InkHUD/Applet.h"
+#include "graphics/niche/Applet.h"
#include "MeshModule.h"
#include "gps/GeoCoord.h"
diff --git a/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.cpp b/src/graphics/niche/Applets/Bases/NodeList/NodeListApplet.cpp
similarity index 100%
rename from src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.cpp
rename to src/graphics/niche/Applets/Bases/NodeList/NodeListApplet.cpp
diff --git a/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.h b/src/graphics/niche/Applets/Bases/NodeList/NodeListApplet.h
similarity index 98%
rename from src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.h
rename to src/graphics/niche/Applets/Bases/NodeList/NodeListApplet.h
index baee3f0f4..d73b3ab3a 100644
--- a/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.h
+++ b/src/graphics/niche/Applets/Bases/NodeList/NodeListApplet.h
@@ -21,7 +21,7 @@ Used by the "Recents" and "Heard" applets. Possibly more in future?
#include "configuration.h"
-#include "graphics/niche/InkHUD/Applet.h"
+#include "graphics/niche/Applet.h"
#include "main.h"
diff --git a/src/graphics/niche/InkHUD/Applets/Examples/BasicExample/BasicExampleApplet.cpp b/src/graphics/niche/Applets/Examples/BasicExample/BasicExampleApplet.cpp
similarity index 100%
rename from src/graphics/niche/InkHUD/Applets/Examples/BasicExample/BasicExampleApplet.cpp
rename to src/graphics/niche/Applets/Examples/BasicExample/BasicExampleApplet.cpp
diff --git a/src/graphics/niche/InkHUD/Applets/Examples/BasicExample/BasicExampleApplet.h b/src/graphics/niche/Applets/Examples/BasicExample/BasicExampleApplet.h
similarity index 93%
rename from src/graphics/niche/InkHUD/Applets/Examples/BasicExample/BasicExampleApplet.h
rename to src/graphics/niche/Applets/Examples/BasicExample/BasicExampleApplet.h
index a36f6e8d5..1ebb6a5a5 100644
--- a/src/graphics/niche/InkHUD/Applets/Examples/BasicExample/BasicExampleApplet.h
+++ b/src/graphics/niche/Applets/Examples/BasicExample/BasicExampleApplet.h
@@ -17,7 +17,7 @@ In variants//nicheGraphics.h:
#include "configuration.h"
-#include "graphics/niche/InkHUD/Applet.h"
+#include "graphics/niche/Applet.h"
namespace NicheGraphics::InkHUD
{
diff --git a/src/graphics/niche/InkHUD/Applets/Examples/NewMsgExample/NewMsgExampleApplet.cpp b/src/graphics/niche/Applets/Examples/NewMsgExample/NewMsgExampleApplet.cpp
similarity index 100%
rename from src/graphics/niche/InkHUD/Applets/Examples/NewMsgExample/NewMsgExampleApplet.cpp
rename to src/graphics/niche/Applets/Examples/NewMsgExample/NewMsgExampleApplet.cpp
diff --git a/src/graphics/niche/InkHUD/Applets/Examples/NewMsgExample/NewMsgExampleApplet.h b/src/graphics/niche/Applets/Examples/NewMsgExample/NewMsgExampleApplet.h
similarity index 97%
rename from src/graphics/niche/InkHUD/Applets/Examples/NewMsgExample/NewMsgExampleApplet.h
rename to src/graphics/niche/Applets/Examples/NewMsgExample/NewMsgExampleApplet.h
index 599f08a7a..04aafc7f4 100644
--- a/src/graphics/niche/InkHUD/Applets/Examples/NewMsgExample/NewMsgExampleApplet.h
+++ b/src/graphics/niche/Applets/Examples/NewMsgExample/NewMsgExampleApplet.h
@@ -20,7 +20,7 @@ In variants//nicheGraphics.h:
#include "configuration.h"
-#include "graphics/niche/InkHUD/Applet.h"
+#include "graphics/niche/Applet.h"
#include "mesh/SinglePortModule.h"
diff --git a/src/graphics/niche/InkHUD/Applets/Examples/UserAppletInputExample/UserAppletInputExample.cpp b/src/graphics/niche/Applets/Examples/UserAppletInputExample/UserAppletInputExample.cpp
similarity index 100%
rename from src/graphics/niche/InkHUD/Applets/Examples/UserAppletInputExample/UserAppletInputExample.cpp
rename to src/graphics/niche/Applets/Examples/UserAppletInputExample/UserAppletInputExample.cpp
diff --git a/src/graphics/niche/InkHUD/Applets/Examples/UserAppletInputExample/UserAppletInputExample.h b/src/graphics/niche/Applets/Examples/UserAppletInputExample/UserAppletInputExample.h
similarity index 94%
rename from src/graphics/niche/InkHUD/Applets/Examples/UserAppletInputExample/UserAppletInputExample.h
rename to src/graphics/niche/Applets/Examples/UserAppletInputExample/UserAppletInputExample.h
index a99dec00c..4ea8aa530 100644
--- a/src/graphics/niche/InkHUD/Applets/Examples/UserAppletInputExample/UserAppletInputExample.h
+++ b/src/graphics/niche/Applets/Examples/UserAppletInputExample/UserAppletInputExample.h
@@ -4,7 +4,7 @@
#include "configuration.h"
-#include "graphics/niche/InkHUD/Applet.h"
+#include "graphics/niche/Applet.h"
namespace NicheGraphics::InkHUD
{
diff --git a/src/graphics/niche/InkHUD/Applets/System/AlignStick/AlignStickApplet.cpp b/src/graphics/niche/Applets/System/AlignStick/AlignStickApplet.cpp
similarity index 100%
rename from src/graphics/niche/InkHUD/Applets/System/AlignStick/AlignStickApplet.cpp
rename to src/graphics/niche/Applets/System/AlignStick/AlignStickApplet.cpp
diff --git a/src/graphics/niche/InkHUD/Applets/System/AlignStick/AlignStickApplet.h b/src/graphics/niche/Applets/System/AlignStick/AlignStickApplet.h
similarity index 95%
rename from src/graphics/niche/InkHUD/Applets/System/AlignStick/AlignStickApplet.h
rename to src/graphics/niche/Applets/System/AlignStick/AlignStickApplet.h
index 7c8d00155..ea56f125f 100644
--- a/src/graphics/niche/InkHUD/Applets/System/AlignStick/AlignStickApplet.h
+++ b/src/graphics/niche/Applets/System/AlignStick/AlignStickApplet.h
@@ -13,7 +13,7 @@ and not aligned to the screen
#include "configuration.h"
-#include "graphics/niche/InkHUD/SystemApplet.h"
+#include "graphics/niche/SystemApplet.h"
namespace NicheGraphics::InkHUD
{
diff --git a/src/graphics/niche/InkHUD/Applets/System/BatteryIcon/BatteryIconApplet.cpp b/src/graphics/niche/Applets/System/BatteryIcon/BatteryIconApplet.cpp
similarity index 100%
rename from src/graphics/niche/InkHUD/Applets/System/BatteryIcon/BatteryIconApplet.cpp
rename to src/graphics/niche/Applets/System/BatteryIcon/BatteryIconApplet.cpp
diff --git a/src/graphics/niche/InkHUD/Applets/System/BatteryIcon/BatteryIconApplet.h b/src/graphics/niche/Applets/System/BatteryIcon/BatteryIconApplet.h
similarity index 95%
rename from src/graphics/niche/InkHUD/Applets/System/BatteryIcon/BatteryIconApplet.h
rename to src/graphics/niche/Applets/System/BatteryIcon/BatteryIconApplet.h
index ceaf88d7f..9c3d2b4d0 100644
--- a/src/graphics/niche/InkHUD/Applets/System/BatteryIcon/BatteryIconApplet.h
+++ b/src/graphics/niche/Applets/System/BatteryIcon/BatteryIconApplet.h
@@ -11,7 +11,7 @@ It should be optional, enabled by the on-screen menu
#include "configuration.h"
-#include "graphics/niche/InkHUD/SystemApplet.h"
+#include "graphics/niche/SystemApplet.h"
#include "PowerStatus.h"
diff --git a/src/graphics/niche/InkHUD/Applets/System/Keyboard/KeyboardApplet.cpp b/src/graphics/niche/Applets/System/Keyboard/KeyboardApplet.cpp
similarity index 100%
rename from src/graphics/niche/InkHUD/Applets/System/Keyboard/KeyboardApplet.cpp
rename to src/graphics/niche/Applets/System/Keyboard/KeyboardApplet.cpp
diff --git a/src/graphics/niche/InkHUD/Applets/System/Keyboard/KeyboardApplet.h b/src/graphics/niche/Applets/System/Keyboard/KeyboardApplet.h
similarity index 95%
rename from src/graphics/niche/InkHUD/Applets/System/Keyboard/KeyboardApplet.h
rename to src/graphics/niche/Applets/System/Keyboard/KeyboardApplet.h
index 0ae181a2c..e63af2e6f 100644
--- a/src/graphics/niche/InkHUD/Applets/System/Keyboard/KeyboardApplet.h
+++ b/src/graphics/niche/Applets/System/Keyboard/KeyboardApplet.h
@@ -9,8 +9,8 @@ System Applet to render an on-screen keyboard
#pragma once
#include "configuration.h"
-#include "graphics/niche/InkHUD/InkHUD.h"
-#include "graphics/niche/InkHUD/SystemApplet.h"
+#include "graphics/niche/InkHUD.h"
+#include "graphics/niche/SystemApplet.h"
#include
namespace NicheGraphics::InkHUD
{
diff --git a/src/graphics/niche/InkHUD/Applets/System/Logo/LogoApplet.cpp b/src/graphics/niche/Applets/System/Logo/LogoApplet.cpp
similarity index 100%
rename from src/graphics/niche/InkHUD/Applets/System/Logo/LogoApplet.cpp
rename to src/graphics/niche/Applets/System/Logo/LogoApplet.cpp
diff --git a/src/graphics/niche/InkHUD/Applets/System/Logo/LogoApplet.h b/src/graphics/niche/Applets/System/Logo/LogoApplet.h
similarity index 94%
rename from src/graphics/niche/InkHUD/Applets/System/Logo/LogoApplet.h
rename to src/graphics/niche/Applets/System/Logo/LogoApplet.h
index d70dcc7b2..648851da7 100644
--- a/src/graphics/niche/InkHUD/Applets/System/Logo/LogoApplet.h
+++ b/src/graphics/niche/Applets/System/Logo/LogoApplet.h
@@ -12,7 +12,7 @@
#include "configuration.h"
#include "concurrency/OSThread.h"
-#include "graphics/niche/InkHUD/SystemApplet.h"
+#include "graphics/niche/SystemApplet.h"
namespace NicheGraphics::InkHUD
{
diff --git a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuAction.h b/src/graphics/niche/Applets/System/Menu/MenuAction.h
similarity index 100%
rename from src/graphics/niche/InkHUD/Applets/System/Menu/MenuAction.h
rename to src/graphics/niche/Applets/System/Menu/MenuAction.h
diff --git a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp b/src/graphics/niche/Applets/System/Menu/MenuApplet.cpp
similarity index 100%
rename from src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp
rename to src/graphics/niche/Applets/System/Menu/MenuApplet.cpp
diff --git a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.h b/src/graphics/niche/Applets/System/Menu/MenuApplet.h
similarity index 95%
rename from src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.h
rename to src/graphics/niche/Applets/System/Menu/MenuApplet.h
index b5c1c86e4..8c3603afd 100644
--- a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.h
+++ b/src/graphics/niche/Applets/System/Menu/MenuApplet.h
@@ -2,11 +2,11 @@
#include "configuration.h"
-#include "graphics/niche/Drivers/Backlight/LatchingBacklight.h"
-#include "graphics/niche/InkHUD/InkHUD.h"
-#include "graphics/niche/InkHUD/Persistence.h"
-#include "graphics/niche/InkHUD/SystemApplet.h"
-#include "graphics/niche/Utils/CannedMessageStore.h"
+#include "graphics/eink/Backlight/LatchingBacklight.h"
+#include "graphics/niche/CannedMessageStore.h"
+#include "graphics/niche/InkHUD.h"
+#include "graphics/niche/Persistence.h"
+#include "graphics/niche/SystemApplet.h"
#include "./MenuItem.h"
#include "./MenuPage.h"
diff --git a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuItem.h b/src/graphics/niche/Applets/System/Menu/MenuItem.h
similarity index 100%
rename from src/graphics/niche/InkHUD/Applets/System/Menu/MenuItem.h
rename to src/graphics/niche/Applets/System/Menu/MenuItem.h
diff --git a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuPage.h b/src/graphics/niche/Applets/System/Menu/MenuPage.h
similarity index 100%
rename from src/graphics/niche/InkHUD/Applets/System/Menu/MenuPage.h
rename to src/graphics/niche/Applets/System/Menu/MenuPage.h
diff --git a/src/graphics/niche/InkHUD/Applets/System/Notification/Notification.h b/src/graphics/niche/Applets/System/Notification/Notification.h
similarity index 100%
rename from src/graphics/niche/InkHUD/Applets/System/Notification/Notification.h
rename to src/graphics/niche/Applets/System/Notification/Notification.h
diff --git a/src/graphics/niche/InkHUD/Applets/System/Notification/NotificationApplet.cpp b/src/graphics/niche/Applets/System/Notification/NotificationApplet.cpp
similarity index 99%
rename from src/graphics/niche/InkHUD/Applets/System/Notification/NotificationApplet.cpp
rename to src/graphics/niche/Applets/System/Notification/NotificationApplet.cpp
index 6c8069c8b..5626de2f1 100644
--- a/src/graphics/niche/InkHUD/Applets/System/Notification/NotificationApplet.cpp
+++ b/src/graphics/niche/Applets/System/Notification/NotificationApplet.cpp
@@ -3,7 +3,7 @@
#include "./NotificationApplet.h"
#include "./Notification.h"
-#include "graphics/niche/InkHUD/Persistence.h"
+#include "graphics/niche/Persistence.h"
#include "meshUtils.h"
#include "modules/TextMessageModule.h"
diff --git a/src/graphics/niche/InkHUD/Applets/System/Notification/NotificationApplet.h b/src/graphics/niche/Applets/System/Notification/NotificationApplet.h
similarity index 97%
rename from src/graphics/niche/InkHUD/Applets/System/Notification/NotificationApplet.h
rename to src/graphics/niche/Applets/System/Notification/NotificationApplet.h
index d398a36f3..524d9c2b0 100644
--- a/src/graphics/niche/InkHUD/Applets/System/Notification/NotificationApplet.h
+++ b/src/graphics/niche/Applets/System/Notification/NotificationApplet.h
@@ -16,7 +16,7 @@ Feature should be optional; enable disable via on-screen menu
#include "concurrency/OSThread.h"
-#include "graphics/niche/InkHUD/SystemApplet.h"
+#include "graphics/niche/SystemApplet.h"
namespace NicheGraphics::InkHUD
{
diff --git a/src/graphics/niche/InkHUD/Applets/System/Pairing/PairingApplet.cpp b/src/graphics/niche/Applets/System/Pairing/PairingApplet.cpp
similarity index 100%
rename from src/graphics/niche/InkHUD/Applets/System/Pairing/PairingApplet.cpp
rename to src/graphics/niche/Applets/System/Pairing/PairingApplet.cpp
diff --git a/src/graphics/niche/InkHUD/Applets/System/Pairing/PairingApplet.h b/src/graphics/niche/Applets/System/Pairing/PairingApplet.h
similarity index 94%
rename from src/graphics/niche/InkHUD/Applets/System/Pairing/PairingApplet.h
rename to src/graphics/niche/Applets/System/Pairing/PairingApplet.h
index 4c2e95321..b89fe62bb 100644
--- a/src/graphics/niche/InkHUD/Applets/System/Pairing/PairingApplet.h
+++ b/src/graphics/niche/Applets/System/Pairing/PairingApplet.h
@@ -10,7 +10,7 @@
#include "configuration.h"
-#include "graphics/niche/InkHUD/SystemApplet.h"
+#include "graphics/niche/SystemApplet.h"
#include "main.h"
diff --git a/src/graphics/niche/InkHUD/Applets/System/Placeholder/PlaceholderApplet.cpp b/src/graphics/niche/Applets/System/Placeholder/PlaceholderApplet.cpp
similarity index 100%
rename from src/graphics/niche/InkHUD/Applets/System/Placeholder/PlaceholderApplet.cpp
rename to src/graphics/niche/Applets/System/Placeholder/PlaceholderApplet.cpp
diff --git a/src/graphics/niche/InkHUD/Applets/System/Placeholder/PlaceholderApplet.h b/src/graphics/niche/Applets/System/Placeholder/PlaceholderApplet.h
similarity index 92%
rename from src/graphics/niche/InkHUD/Applets/System/Placeholder/PlaceholderApplet.h
rename to src/graphics/niche/Applets/System/Placeholder/PlaceholderApplet.h
index fa40913e0..b22529680 100644
--- a/src/graphics/niche/InkHUD/Applets/System/Placeholder/PlaceholderApplet.h
+++ b/src/graphics/niche/Applets/System/Placeholder/PlaceholderApplet.h
@@ -9,7 +9,7 @@ Fills the area with diagonal lines
#include "configuration.h"
-#include "graphics/niche/InkHUD/SystemApplet.h"
+#include "graphics/niche/SystemApplet.h"
namespace NicheGraphics::InkHUD
{
diff --git a/src/graphics/niche/InkHUD/Applets/System/Tips/TipsApplet.cpp b/src/graphics/niche/Applets/System/Tips/TipsApplet.cpp
similarity index 99%
rename from src/graphics/niche/InkHUD/Applets/System/Tips/TipsApplet.cpp
rename to src/graphics/niche/Applets/System/Tips/TipsApplet.cpp
index a45e8d9b5..e65ee86b6 100644
--- a/src/graphics/niche/InkHUD/Applets/System/Tips/TipsApplet.cpp
+++ b/src/graphics/niche/Applets/System/Tips/TipsApplet.cpp
@@ -2,7 +2,7 @@
#include "./TipsApplet.h"
-#include "graphics/niche/InkHUD/Persistence.h"
+#include "graphics/niche/Persistence.h"
#include "main.h"
diff --git a/src/graphics/niche/InkHUD/Applets/System/Tips/TipsApplet.h b/src/graphics/niche/Applets/System/Tips/TipsApplet.h
similarity index 95%
rename from src/graphics/niche/InkHUD/Applets/System/Tips/TipsApplet.h
rename to src/graphics/niche/Applets/System/Tips/TipsApplet.h
index 2e81d678b..2042d5ac4 100644
--- a/src/graphics/niche/InkHUD/Applets/System/Tips/TipsApplet.h
+++ b/src/graphics/niche/Applets/System/Tips/TipsApplet.h
@@ -12,7 +12,7 @@
#include "configuration.h"
-#include "graphics/niche/InkHUD/SystemApplet.h"
+#include "graphics/niche/SystemApplet.h"
namespace NicheGraphics::InkHUD
{
diff --git a/src/graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.cpp b/src/graphics/niche/Applets/User/AllMessage/AllMessageApplet.cpp
similarity index 100%
rename from src/graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.cpp
rename to src/graphics/niche/Applets/User/AllMessage/AllMessageApplet.cpp
diff --git a/src/graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h b/src/graphics/niche/Applets/User/AllMessage/AllMessageApplet.h
similarity index 97%
rename from src/graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h
rename to src/graphics/niche/Applets/User/AllMessage/AllMessageApplet.h
index 4aa97e4f1..e084f2b37 100644
--- a/src/graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h
+++ b/src/graphics/niche/Applets/User/AllMessage/AllMessageApplet.h
@@ -18,7 +18,7 @@ to know when a new message has arrived, and trigger the update.
#include "configuration.h"
-#include "graphics/niche/InkHUD/Applet.h"
+#include "graphics/niche/Applet.h"
#include "modules/TextMessageModule.h"
diff --git a/src/graphics/niche/InkHUD/Applets/User/DM/DMApplet.cpp b/src/graphics/niche/Applets/User/DM/DMApplet.cpp
similarity index 100%
rename from src/graphics/niche/InkHUD/Applets/User/DM/DMApplet.cpp
rename to src/graphics/niche/Applets/User/DM/DMApplet.cpp
diff --git a/src/graphics/niche/InkHUD/Applets/User/DM/DMApplet.h b/src/graphics/niche/Applets/User/DM/DMApplet.h
similarity index 97%
rename from src/graphics/niche/InkHUD/Applets/User/DM/DMApplet.h
rename to src/graphics/niche/Applets/User/DM/DMApplet.h
index 4eb0ec704..c667b17c0 100644
--- a/src/graphics/niche/InkHUD/Applets/User/DM/DMApplet.h
+++ b/src/graphics/niche/Applets/User/DM/DMApplet.h
@@ -18,7 +18,7 @@ to know when a new message has arrived, and trigger the update.
#include "configuration.h"
-#include "graphics/niche/InkHUD/Applet.h"
+#include "graphics/niche/Applet.h"
#include "modules/TextMessageModule.h"
diff --git a/src/graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.cpp b/src/graphics/niche/Applets/User/FavoritesMap/FavoritesMapApplet.cpp
similarity index 100%
rename from src/graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.cpp
rename to src/graphics/niche/Applets/User/FavoritesMap/FavoritesMapApplet.cpp
diff --git a/src/graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.h b/src/graphics/niche/Applets/User/FavoritesMap/FavoritesMapApplet.h
similarity index 94%
rename from src/graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.h
rename to src/graphics/niche/Applets/User/FavoritesMap/FavoritesMapApplet.h
index da5fb0dc3..825af64a8 100644
--- a/src/graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.h
+++ b/src/graphics/niche/Applets/User/FavoritesMap/FavoritesMapApplet.h
@@ -13,7 +13,7 @@ The favorite node which most recently sent a position will be labeled.
#include "configuration.h"
-#include "graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.h"
+#include "graphics/niche/Applets/Bases/Map/MapApplet.h"
#include "SinglePortModule.h"
diff --git a/src/graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.cpp b/src/graphics/niche/Applets/User/Heard/HeardApplet.cpp
similarity index 100%
rename from src/graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.cpp
rename to src/graphics/niche/Applets/User/Heard/HeardApplet.cpp
diff --git a/src/graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.h b/src/graphics/niche/Applets/User/Heard/HeardApplet.h
similarity index 91%
rename from src/graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.h
rename to src/graphics/niche/Applets/User/Heard/HeardApplet.h
index 932b5a75e..a09ce573b 100644
--- a/src/graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.h
+++ b/src/graphics/niche/Applets/User/Heard/HeardApplet.h
@@ -11,7 +11,7 @@ Most of the work is done by the InkHUD::NodeListApplet base class
#include "configuration.h"
-#include "graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.h"
+#include "graphics/niche/Applets/Bases/NodeList/NodeListApplet.h"
namespace NicheGraphics::InkHUD
{
diff --git a/src/graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.cpp b/src/graphics/niche/Applets/User/Positions/PositionsApplet.cpp
similarity index 100%
rename from src/graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.cpp
rename to src/graphics/niche/Applets/User/Positions/PositionsApplet.cpp
diff --git a/src/graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.h b/src/graphics/niche/Applets/User/Positions/PositionsApplet.h
similarity index 94%
rename from src/graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.h
rename to src/graphics/niche/Applets/User/Positions/PositionsApplet.h
index d0d3e5f07..3d31518c6 100644
--- a/src/graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.h
+++ b/src/graphics/niche/Applets/User/Positions/PositionsApplet.h
@@ -13,7 +13,7 @@ The node which has most recently sent a position will be labeled.
#include "configuration.h"
-#include "graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.h"
+#include "graphics/niche/Applets/Bases/Map/MapApplet.h"
#include "SinglePortModule.h"
diff --git a/src/graphics/niche/InkHUD/Applets/User/RecentsList/RecentsListApplet.cpp b/src/graphics/niche/Applets/User/RecentsList/RecentsListApplet.cpp
similarity index 100%
rename from src/graphics/niche/InkHUD/Applets/User/RecentsList/RecentsListApplet.cpp
rename to src/graphics/niche/Applets/User/RecentsList/RecentsListApplet.cpp
diff --git a/src/graphics/niche/InkHUD/Applets/User/RecentsList/RecentsListApplet.h b/src/graphics/niche/Applets/User/RecentsList/RecentsListApplet.h
similarity index 95%
rename from src/graphics/niche/InkHUD/Applets/User/RecentsList/RecentsListApplet.h
rename to src/graphics/niche/Applets/User/RecentsList/RecentsListApplet.h
index 74f5f3e57..0395570ff 100644
--- a/src/graphics/niche/InkHUD/Applets/User/RecentsList/RecentsListApplet.h
+++ b/src/graphics/niche/Applets/User/RecentsList/RecentsListApplet.h
@@ -13,7 +13,7 @@ Most of the work is done by the shared InkHUD::NodeListApplet base class
#include "configuration.h"
-#include "graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.h"
+#include "graphics/niche/Applets/Bases/NodeList/NodeListApplet.h"
namespace NicheGraphics::InkHUD
{
diff --git a/src/graphics/niche/InkHUD/Applets/User/ThreadedMessage/ThreadedMessageApplet.cpp b/src/graphics/niche/Applets/User/ThreadedMessage/ThreadedMessageApplet.cpp
similarity index 100%
rename from src/graphics/niche/InkHUD/Applets/User/ThreadedMessage/ThreadedMessageApplet.cpp
rename to src/graphics/niche/Applets/User/ThreadedMessage/ThreadedMessageApplet.cpp
diff --git a/src/graphics/niche/InkHUD/Applets/User/ThreadedMessage/ThreadedMessageApplet.h b/src/graphics/niche/Applets/User/ThreadedMessage/ThreadedMessageApplet.h
similarity index 94%
rename from src/graphics/niche/InkHUD/Applets/User/ThreadedMessage/ThreadedMessageApplet.h
rename to src/graphics/niche/Applets/User/ThreadedMessage/ThreadedMessageApplet.h
index 2cd2c4163..7a03b1885 100644
--- a/src/graphics/niche/InkHUD/Applets/User/ThreadedMessage/ThreadedMessageApplet.h
+++ b/src/graphics/niche/Applets/User/ThreadedMessage/ThreadedMessageApplet.h
@@ -20,8 +20,8 @@ Suggest a max of two channel, to minimize fs usage?
#include "configuration.h"
-#include "graphics/niche/InkHUD/Applet.h"
-#include "graphics/niche/InkHUD/MessageStore.h"
+#include "graphics/niche/Applet.h"
+#include "graphics/niche/MessageStore.h"
#include "modules/TextMessageModule.h"
diff --git a/src/graphics/niche/Utils/CannedMessageStore.cpp b/src/graphics/niche/CannedMessageStore.cpp
similarity index 100%
rename from src/graphics/niche/Utils/CannedMessageStore.cpp
rename to src/graphics/niche/CannedMessageStore.cpp
diff --git a/src/graphics/niche/Utils/CannedMessageStore.h b/src/graphics/niche/CannedMessageStore.h
similarity index 100%
rename from src/graphics/niche/Utils/CannedMessageStore.h
rename to src/graphics/niche/CannedMessageStore.h
diff --git a/src/graphics/niche/InkHUD/DisplayHealth.cpp b/src/graphics/niche/DisplayHealth.cpp
similarity index 100%
rename from src/graphics/niche/InkHUD/DisplayHealth.cpp
rename to src/graphics/niche/DisplayHealth.cpp
diff --git a/src/graphics/niche/InkHUD/DisplayHealth.h b/src/graphics/niche/DisplayHealth.h
similarity index 97%
rename from src/graphics/niche/InkHUD/DisplayHealth.h
rename to src/graphics/niche/DisplayHealth.h
index 2bd887f9d..f11dcf0fe 100644
--- a/src/graphics/niche/InkHUD/DisplayHealth.h
+++ b/src/graphics/niche/DisplayHealth.h
@@ -16,7 +16,7 @@ Responsible for maintaining display health, by optimizing the ratio of FAST vs F
#include "InkHUD.h"
-#include "graphics/niche/Drivers/EInk/EInk.h"
+#include "graphics/eink/Drivers/EInk.h"
namespace NicheGraphics::InkHUD
{
diff --git a/src/graphics/niche/InkHUD/Events.cpp b/src/graphics/niche/Events.cpp
similarity index 99%
rename from src/graphics/niche/InkHUD/Events.cpp
rename to src/graphics/niche/Events.cpp
index 577a773bb..6576b52bb 100644
--- a/src/graphics/niche/InkHUD/Events.cpp
+++ b/src/graphics/niche/Events.cpp
@@ -10,7 +10,7 @@
#include "./Applet.h"
#include "./SystemApplet.h"
-#include "graphics/niche/Utils/FlashData.h"
+#include "graphics/niche/FlashData.h"
using namespace NicheGraphics;
diff --git a/src/graphics/niche/InkHUD/Events.h b/src/graphics/niche/Events.h
similarity index 100%
rename from src/graphics/niche/InkHUD/Events.h
rename to src/graphics/niche/Events.h
diff --git a/src/graphics/niche/Utils/FlashData.h b/src/graphics/niche/FlashData.h
similarity index 100%
rename from src/graphics/niche/Utils/FlashData.h
rename to src/graphics/niche/FlashData.h
diff --git a/src/graphics/niche/InkHUD/InkHUD.cpp b/src/graphics/niche/InkHUD.cpp
similarity index 100%
rename from src/graphics/niche/InkHUD/InkHUD.cpp
rename to src/graphics/niche/InkHUD.cpp
diff --git a/src/graphics/niche/InkHUD/InkHUD.h b/src/graphics/niche/InkHUD.h
similarity index 98%
rename from src/graphics/niche/InkHUD/InkHUD.h
rename to src/graphics/niche/InkHUD.h
index abd53951a..ebefef87f 100644
--- a/src/graphics/niche/InkHUD/InkHUD.h
+++ b/src/graphics/niche/InkHUD.h
@@ -12,7 +12,7 @@
#include "configuration.h"
-#include "graphics/niche/Drivers/EInk/EInk.h"
+#include "graphics/eink/Drivers/EInk.h"
#include "./AppletFont.h"
diff --git a/src/graphics/niche/InkHUD/PlatformioConfig.ini b/src/graphics/niche/InkHUD/PlatformioConfig.ini
deleted file mode 100644
index 67ad5098f..000000000
--- a/src/graphics/niche/InkHUD/PlatformioConfig.ini
+++ /dev/null
@@ -1,12 +0,0 @@
-[inkhud]
-build_src_filter =
- +; Include the nicheGraphics directory
-build_flags =
- -D MESHTASTIC_INCLUDE_NICHE_GRAPHICS ; Use NicheGraphics
- -D MESHTASTIC_INCLUDE_INKHUD ; Use InkHUD (a NicheGraphics UI)
- -D MESHTASTIC_EXCLUDE_SCREEN ; Suppress default Screen class
- -D MESHTASTIC_EXCLUDE_INPUTBROKER ; Suppress default input handling
- -D HAS_BUTTON=0 ; Suppress default ButtonThread
-lib_deps =
- # renovate: datasource=github-tags depName=GFX_Root packageName=ZinggJM/GFX_Root
- https://github.com/ZinggJM/GFX_Root/archive/3195764e352a0d2567c8d277ac408ca7293a99b0.zip ; Used by InkHUD as a "slimmer" version of AdafruitGFX
diff --git a/src/graphics/niche/InkHUD/MessageStore.cpp b/src/graphics/niche/MessageStore.cpp
similarity index 100%
rename from src/graphics/niche/InkHUD/MessageStore.cpp
rename to src/graphics/niche/MessageStore.cpp
diff --git a/src/graphics/niche/InkHUD/MessageStore.h b/src/graphics/niche/MessageStore.h
similarity index 100%
rename from src/graphics/niche/InkHUD/MessageStore.h
rename to src/graphics/niche/MessageStore.h
diff --git a/src/graphics/niche/InkHUD/Persistence.cpp b/src/graphics/niche/Persistence.cpp
similarity index 100%
rename from src/graphics/niche/InkHUD/Persistence.cpp
rename to src/graphics/niche/Persistence.cpp
diff --git a/src/graphics/niche/InkHUD/Persistence.h b/src/graphics/niche/Persistence.h
similarity index 98%
rename from src/graphics/niche/InkHUD/Persistence.h
rename to src/graphics/niche/Persistence.h
index 5054b7234..d3a42ec86 100644
--- a/src/graphics/niche/InkHUD/Persistence.h
+++ b/src/graphics/niche/Persistence.h
@@ -15,8 +15,8 @@ The save / load mechanism is a shared NicheGraphics feature.
#include "configuration.h"
#include "./InkHUD.h"
-#include "graphics/niche/InkHUD/MessageStore.h"
-#include "graphics/niche/Utils/FlashData.h"
+#include "graphics/niche/FlashData.h"
+#include "graphics/niche/MessageStore.h"
namespace NicheGraphics::InkHUD
{
diff --git a/src/graphics/niche/InkHUD/Renderer.cpp b/src/graphics/niche/Renderer.cpp
similarity index 100%
rename from src/graphics/niche/InkHUD/Renderer.cpp
rename to src/graphics/niche/Renderer.cpp
diff --git a/src/graphics/niche/InkHUD/Renderer.h b/src/graphics/niche/Renderer.h
similarity index 98%
rename from src/graphics/niche/InkHUD/Renderer.h
rename to src/graphics/niche/Renderer.h
index 1ab94b70b..607177747 100644
--- a/src/graphics/niche/InkHUD/Renderer.h
+++ b/src/graphics/niche/Renderer.h
@@ -17,7 +17,7 @@ Orchestrates updating of the display image
#include "./DisplayHealth.h"
#include "./InkHUD.h"
#include "./Persistence.h"
-#include "graphics/niche/Drivers/EInk/EInk.h"
+#include "graphics/eink/Drivers/EInk.h"
namespace NicheGraphics::InkHUD
{
diff --git a/src/graphics/niche/InkHUD/SystemApplet.h b/src/graphics/niche/SystemApplet.h
similarity index 100%
rename from src/graphics/niche/InkHUD/SystemApplet.h
rename to src/graphics/niche/SystemApplet.h
diff --git a/src/graphics/niche/InkHUD/Tile.cpp b/src/graphics/niche/Tile.cpp
similarity index 100%
rename from src/graphics/niche/InkHUD/Tile.cpp
rename to src/graphics/niche/Tile.cpp
diff --git a/src/graphics/niche/InkHUD/Tile.h b/src/graphics/niche/Tile.h
similarity index 100%
rename from src/graphics/niche/InkHUD/Tile.h
rename to src/graphics/niche/Tile.h
diff --git a/src/graphics/niche/InkHUD/WindowManager.cpp b/src/graphics/niche/WindowManager.cpp
similarity index 100%
rename from src/graphics/niche/InkHUD/WindowManager.cpp
rename to src/graphics/niche/WindowManager.cpp
diff --git a/src/graphics/niche/InkHUD/WindowManager.h b/src/graphics/niche/WindowManager.h
similarity index 100%
rename from src/graphics/niche/InkHUD/WindowManager.h
rename to src/graphics/niche/WindowManager.h
diff --git a/src/graphics/niche/InkHUD/docs/README.md b/src/graphics/niche/docs/README.md
similarity index 97%
rename from src/graphics/niche/InkHUD/docs/README.md
rename to src/graphics/niche/docs/README.md
index 7cd468d73..e154f1bb2 100644
--- a/src/graphics/niche/InkHUD/docs/README.md
+++ b/src/graphics/niche/docs/README.md
@@ -51,8 +51,8 @@ It is intended to supplement a connected client app.
### Self-Contained
-- Keep InkHUD code within `/src/graphics/niche/InkHUD`.
-- Place reusable components within `/src/graphics/niche`, for other UIs to take advantage of.
+- Keep InkHUD code within `/src/graphics/niche`.
+- Reusable E-Ink driver, panel, backlight, and BaseUI-adapter code lives in `/src/graphics/eink` so other UIs (including the default BaseUI) can use the same hardware layer without pulling InkHUD.
- Interact with the firmware code using the **Module API**, **Observables**, and other similarly non-intrusive hooks.
### Static
@@ -138,7 +138,7 @@ Hypothetical examples: E-Ink, 1602 LCDs, tiny OLEDs, smart watches, etc
A NicheGraphics UI:
- Is self-contained
-- Makes use of the loose collection of resources (drivers, input methods, etc) gathered in the `/src/graphics/niche` folder.
+- Makes use of shared E-Ink hardware resources (drivers, panels, backlight, BaseUI adapter) in `/src/graphics/eink`, plus InkHUD-specific resources (input helpers, fonts, applets, storage) in `/src/graphics/niche`.
- Implements a `setupNicheGraphics()` method.
### NicheGraphics E-Ink Drivers
@@ -149,7 +149,7 @@ Interacting with the drivers is straightforward. InkHUD generates a frame of 1-b
`driver->update(uint8_t* buffer, EInk::UpdateTypes::FULL)`
-For more information, see the documentation in `src/graphics/niche/Drivers/EInk`
+For more information, see the documentation in `src/graphics/eink/Drivers`
### InkHUD Applets
@@ -161,7 +161,7 @@ Consider: `DMApplet.h` (displays most recent direct message) and `RecentsList.h`
- Applets use responsive design. They should scale for different screens / layouts / fonts.
- Applets decide when to update. They use the Module API, Observers, etc, to retrieve information, and request a display update when they have something interesting to show.
-See `src/graphics/niche/InkHUD/Applets/Examples` for example code.
+See `src/graphics/niche/Applets/Examples` for example code.
#### Writing an Applet
@@ -252,7 +252,7 @@ You will need to add these lines to any variants which will use your applet.
#### Applet Bases
-If you need to create several similar applets, it might make sense to create a reusable base class. Several of these already exist in `src/graphics/niche/InkHUD/Applets/Bases`, but use these with caution, as they may be modified in future.
+If you need to create several similar applets, it might make sense to create a reusable base class. Several of these already exist in `src/graphics/niche/Applets/Bases`, but use these with caution, as they may be modified in future.
#### System Applets
@@ -394,7 +394,7 @@ See [Encoding](#encoding) for details on using an extended-ASCII font.
### `InkHUD::InkHUD`
-_`src/graphics/niche/InkHUD/InkHUD.h`_
+_`src/graphics/niche/InkHUD.h`_
- singleton
- mediator between other InkHUD components
@@ -415,7 +415,7 @@ For convenience, many InkHUD components call this on `begin`, and store it as `I
### `InkHUD::Persistence`
-_`src/graphics/niche/InkHUD/Persistence.h`_
+_`src/graphics/niche/Persistence.h`_
Stores InkHUD data in flash
@@ -430,7 +430,7 @@ Data saved only on shutdown / reboot. Not saved if power is removed unexpectedly
### `InkHUD::Persistence::Settings`
-_`src/graphics/niche/InkHUD/Persistence.h`_
+_`src/graphics/niche/Persistence.h`_
Settings which relate to InkHUD. Mostly user's customization, but some values record the UI's state (e.g. `tips.safeShutdownSeen`)
@@ -455,7 +455,7 @@ By modifying the values at this point, they will be used if we fail to load prev
### `InkHUD::Persistence::LatestMessage`
-_`src/graphics/niche/InkHUD/Persistence.h`_
+_`src/graphics/niche/Persistence.h`_
Most recently received text message
@@ -483,7 +483,7 @@ The hack is:
### `InkHUD::WindowManager`
-_`src/graphics/niche/InkHUD/WindowManager.h`_
+_`src/graphics/niche/WindowManager.h`_
Manages which applets are shown, and their size / position (by manipulating the "tiles")
@@ -502,7 +502,7 @@ Note: Some of the methods (incl. `changeLayout`, `changeActivatedApplets`) don't
### `InkHUD::Renderer`
-_`src/graphics/niche/InkHUD/Renderer.h`_
+_`src/graphics/niche/Renderer.h`_
Get pixel output from applets (via a tile), combine, and pass to the driver.
@@ -534,7 +534,7 @@ The exact size / position / rotation of InkHUD applets is configurable by the us
### `InkHUD::Renderer::DisplayHealth`
-_`src/graphics/niche/InkHUD/DisplayHealth.h`_
+_`src/graphics/niche/DisplayHealth.h`_
Responsible for maintaining display health, by optimizing the ratio of FAST vs FULL refreshes
diff --git a/src/graphics/niche/InkHUD/docs/appletfont.png b/src/graphics/niche/docs/appletfont.png
similarity index 100%
rename from src/graphics/niche/InkHUD/docs/appletfont.png
rename to src/graphics/niche/docs/appletfont.png
diff --git a/src/graphics/niche/InkHUD/docs/disclaimer.jpg b/src/graphics/niche/docs/disclaimer.jpg
similarity index 100%
rename from src/graphics/niche/InkHUD/docs/disclaimer.jpg
rename to src/graphics/niche/docs/disclaimer.jpg
diff --git a/src/graphics/niche/InkHUD/docs/rendering.gif b/src/graphics/niche/docs/rendering.gif
similarity index 100%
rename from src/graphics/niche/InkHUD/docs/rendering.gif
rename to src/graphics/niche/docs/rendering.gif
diff --git a/src/graphics/niche/InkHUD/docs/tile_translation.png b/src/graphics/niche/docs/tile_translation.png
similarity index 100%
rename from src/graphics/niche/InkHUD/docs/tile_translation.png
rename to src/graphics/niche/docs/tile_translation.png
diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp
index 65e903134..e0469511e 100644
--- a/src/modules/CannedMessageModule.cpp
+++ b/src/modules/CannedMessageModule.cpp
@@ -33,8 +33,8 @@ extern MessageStore messageStore;
#if !MESHTASTIC_EXCLUDE_GPS
#include "GPS.h"
#endif
-#if defined(USE_EINK) && defined(USE_EINK_DYNAMICDISPLAY)
-#include "graphics/EInkDynamicDisplay.h" // To select between full and fast refresh on E-Ink displays
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+#include "graphics/BaseUIEInkDisplay.h"
#endif
#ifndef INPUTBROKER_MATRIX_TYPE
@@ -1915,9 +1915,8 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st
// Free Text Input Screen
if (this->runState == CANNED_MESSAGE_RUN_STATE_FREETEXT) {
requestFocus();
-#if defined(USE_EINK) && defined(USE_EINK_DYNAMICDISPLAY)
- EInkDynamicDisplay *einkDisplay = static_cast(display);
- einkDisplay->enableUnlimitedFastMode();
+#if defined(USE_EINK) && defined(MESHTASTIC_INCLUDE_NICHE_GRAPHICS)
+ static_cast(display)->enableUnlimitedFastMode();
#endif
#if defined(USE_VIRTUAL_KEYBOARD)
drawKeyboard(display, state, 0, 0);
diff --git a/variants/esp32/m5stack_coreink/nicheGraphics.h b/variants/esp32/m5stack_coreink/nicheGraphics.h
new file mode 100644
index 000000000..6c0675a65
--- /dev/null
+++ b/variants/esp32/m5stack_coreink/nicheGraphics.h
@@ -0,0 +1,44 @@
+/*
+
+NicheGraphics setup for M5Stack CoreInk (1.54" 200x200 GDEW0154M09).
+
+The GDEW0154M09 uses an SSD1681-class controller, so the GDEY0154D67 panel
+profile (same controller family, same resolution) drives it directly.
+
+LoRa and the e-ink panel share the default SPI bus (SCLK=18, MOSI=23);
+use the board's default SPI instance rather than spinning up a separate
+HSPI peripheral on the same pins.
+
+*/
+
+#pragma once
+
+#include "configuration.h"
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+#include "graphics/BaseUIEInkDisplay.h"
+#include "graphics/eink/Panels/GDEY0154D67.h"
+
+class M5CoreInkPanel : public NicheGraphics::Panels::GDEY0154D67
+{
+ protected:
+ SPIClass *beginSpi() override
+ {
+ SPI.begin();
+ return &SPI;
+ }
+};
+
+void setupNicheGraphics() {}
+
+NicheGraphics::BaseUIEInkDisplay *setupNicheGraphicsBaseUI()
+{
+ auto *panel = new M5CoreInkPanel();
+ NicheGraphics::Drivers::EInk *driver = panel->create();
+ auto *display = new NicheGraphics::BaseUIEInkDisplay(driver, panel->rotation());
+ display->setDisplayResilience(10, 2.0f);
+ return display;
+}
+
+#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
diff --git a/variants/esp32/m5stack_coreink/platformio.ini b/variants/esp32/m5stack_coreink/platformio.ini
index e107bd893..177f5e08a 100644
--- a/variants/esp32/m5stack_coreink/platformio.ini
+++ b/variants/esp32/m5stack_coreink/platformio.ini
@@ -1,25 +1,22 @@
[env:m5stack-coreink]
-extends = esp32_base
+extends = esp32_base, niche
board = m5stack-coreink
board_check = true
-build_src_filter =
+build_src_filter =
${esp32_base.build_src_filter}
-build_flags =
+ ${niche.build_src_filter}
+build_flags =
${esp32_base.build_flags}
+ ${niche.build_flags}
-I variants/esp32/m5stack_coreink
;-D RADIOLIB_VERBOSE
-Ofast
-D__MCUXPRESSO
- -DEINK_DISPLAY_MODEL=GxEPD2_154_M09
- -DEINK_WIDTH=200
- -DEINK_HEIGHT=200
-DUSER_SETUP_LOADED
- -DM5_COREINK
+ -DM5_COREINK
-DM5STACK
-lib_deps =
+lib_deps =
${esp32_base.lib_deps}
- # renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2
- zinggjm/GxEPD2@1.6.8
# renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib
lewisxhe/SensorLib@0.3.4
lib_ignore =
diff --git a/variants/esp32s3/ELECROW-ThinkNode-M5/nicheGraphics.h b/variants/esp32s3/ELECROW-ThinkNode-M5/nicheGraphics.h
new file mode 100644
index 000000000..42cf6665a
--- /dev/null
+++ b/variants/esp32s3/ELECROW-ThinkNode-M5/nicheGraphics.h
@@ -0,0 +1,42 @@
+/*
+
+NicheGraphics setup for Elecrow ThinkNode-M5 (GDEY0154D67 1.54" on HSPI).
+
+*/
+
+#pragma once
+
+#include "configuration.h"
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+#include "graphics/BaseUIEInkDisplay.h"
+#include "graphics/eink/Panels/GDEY0154D67.h"
+
+class ThinkNodeM5Panel : public NicheGraphics::Panels::GDEY0154D67
+{
+ protected:
+ SPIClass *beginSpi() override
+ {
+ auto *hspi = new SPIClass(HSPI);
+ hspi->begin(PIN_EINK_SCLK, -1, PIN_EINK_MOSI, PIN_EINK_CS);
+ return hspi;
+ }
+ // Old GxEPD2 path used setRotation(4) (= 0° + mirror). BaseUIEInkDisplay does not implement
+ // mirroring; expose 0 here and rely on hardware orientation. Adjust if testing shows otherwise.
+ public:
+ uint8_t rotation() const override { return 0; }
+};
+
+void setupNicheGraphics() {}
+
+NicheGraphics::BaseUIEInkDisplay *setupNicheGraphicsBaseUI()
+{
+ auto *panel = new ThinkNodeM5Panel();
+ NicheGraphics::Drivers::EInk *driver = panel->create();
+ auto *display = new NicheGraphics::BaseUIEInkDisplay(driver, panel->rotation());
+ display->setDisplayResilience(10, 1.5f);
+ return display;
+}
+
+#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
diff --git a/variants/esp32s3/ELECROW-ThinkNode-M5/platformio.ini b/variants/esp32s3/ELECROW-ThinkNode-M5/platformio.ini
index 9f8c3a871..2394d2348 100644
--- a/variants/esp32s3/ELECROW-ThinkNode-M5/platformio.ini
+++ b/variants/esp32s3/ELECROW-ThinkNode-M5/platformio.ini
@@ -9,29 +9,20 @@ custom_meshtastic_images = thinknode_m1.svg
custom_meshtastic_tags = Elecrow
custom_meshtastic_requires_dfu = false
-extends = esp32s3_base
+extends = esp32s3_base, niche
board = ESP32-S3-WROOM-1-N4
build_src_filter =
${esp32s3_base.build_src_filter}
+<../variants/esp32s3/ELECROW-ThinkNode-M5>
+ ${niche.build_src_filter}
build_flags =
${esp32s3_base.build_flags}
+ ${niche.build_flags}
-D ELECROW_ThinkNode_M5
-I variants/esp32s3/ELECROW-ThinkNode-M5
- -DEINK_DISPLAY_MODEL=GxEPD2_154_D67
- -DEINK_WIDTH=200
- -DEINK_HEIGHT=200
- -DUSE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk
- -DEINK_LIMIT_FASTREFRESH=10 ; How many consecutive fast-refreshes are permitted //20
- -DEINK_LIMIT_RATE_BACKGROUND_SEC=10 ; Minimum interval between BACKGROUND updates //30
- -DEINK_LIMIT_RATE_RESPONSIVE_SEC=1 ; Minimum interval between RESPONSIVE updates
-; -DEINK_LIMIT_GHOSTING_PX=2000 ; (Optional) How much image ghosting is tolerated
- -DEINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached.
lib_deps = ${esp32s3_base.lib_deps}
- # renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master
- https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip
# renovate: datasource=custom.pio depName=PCA9557-arduino packageName=maxpromer/library/PCA9557-arduino
maxpromer/PCA9557-arduino@1.0.0
# renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib
diff --git a/variants/esp32s3/crowpanel-esp32s3-5-epaper/nicheGraphics.h b/variants/esp32s3/crowpanel-esp32s3-5-epaper/nicheGraphics.h
new file mode 100644
index 000000000..3c854adaf
--- /dev/null
+++ b/variants/esp32s3/crowpanel-esp32s3-5-epaper/nicheGraphics.h
@@ -0,0 +1,61 @@
+/*
+
+NicheGraphics setup for the CrowPanel ESP32-S3 ePaper boards.
+
+This directory is shared by three envs that each pick a different panel via
+CROWPANEL_ESP32S3_{2,4,5}_EPAPER macros in platformio.ini.
+
+*/
+
+#pragma once
+
+#include "configuration.h"
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+#include "graphics/BaseUIEInkDisplay.h"
+
+#if defined(CROWPANEL_ESP32S3_2_EPAPER)
+#include "graphics/eink/Panels/GDEY029T94.h"
+using CrowpanelBasePanel = NicheGraphics::Panels::GDEY029T94;
+#elif defined(CROWPANEL_ESP32S3_4_EPAPER)
+#include "graphics/eink/Panels/HINK_E042A87.h"
+using CrowpanelBasePanel = NicheGraphics::Panels::HINK_E042A87;
+#elif defined(CROWPANEL_ESP32S3_5_EPAPER)
+#include "graphics/eink/Panels/GDEY0579T93.h"
+using CrowpanelBasePanel = NicheGraphics::Panels::GDEY0579T93;
+#else
+#error "No CROWPANEL_ESP32S3_*_EPAPER macro defined for this env"
+#endif
+
+class CrowpanelPanel : public CrowpanelBasePanel
+{
+ protected:
+ SPIClass *beginSpi() override
+ {
+ auto *hspi = new SPIClass(HSPI);
+ hspi->begin(PIN_EINK_SCLK, -1, PIN_EINK_MOSI, PIN_EINK_CS);
+ return hspi;
+ }
+ void prePowerOn() override
+ {
+#ifdef VEXT_ENABLE
+ pinMode(VEXT_ENABLE, OUTPUT);
+ digitalWrite(VEXT_ENABLE, VEXT_ON_VALUE);
+ delay(10);
+#endif
+ }
+};
+
+void setupNicheGraphics() {}
+
+NicheGraphics::BaseUIEInkDisplay *setupNicheGraphicsBaseUI()
+{
+ auto *panel = new CrowpanelPanel();
+ NicheGraphics::Drivers::EInk *driver = panel->create();
+ auto *display = new NicheGraphics::BaseUIEInkDisplay(driver, panel->rotation());
+ display->setDisplayResilience(20, 1.5f);
+ return display;
+}
+
+#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
diff --git a/variants/esp32s3/crowpanel-esp32s3-5-epaper/platformio.ini b/variants/esp32s3/crowpanel-esp32s3-5-epaper/platformio.ini
index 7e37a0eb4..415b7bcfc 100644
--- a/variants/esp32s3/crowpanel-esp32s3-5-epaper/platformio.ini
+++ b/variants/esp32s3/crowpanel-esp32s3-5-epaper/platformio.ini
@@ -1,5 +1,5 @@
-[env:crowpanel-esp32s3-5-epaper]
-extends = esp32s3_base
+[crowpanel_epaper_base]
+extends = esp32s3_base, niche
board_build.arduino.memory_type = qio_opi
board_build.flash_mode = qio
board_build.psram_type = opi
@@ -10,80 +10,32 @@ board = esp32-s3-devkitc-1
;upload_port = /dev/ttyUSB0
board_level = extra
upload_protocol = esptool
-build_flags =
+build_src_filter =
+ ${esp32s3_base.build_src_filter}
+ ${niche.build_src_filter}
+build_flags =
${esp32s3_base.build_flags}
- -D CROWPANEL_ESP32S3_5_EPAPER
+ ${niche.build_flags}
-I variants/esp32s3/crowpanel-esp32s3-5-epaper
-D PRIVATE_HW
-DBOARD_HAS_PSRAM
- -DEINK_DISPLAY_MODEL=GxEPD2_579_GDEY0579T93 ;https://www.good-display.com/product/439.html
- -DEINK_WIDTH=792
- -DEINK_HEIGHT=272
- -DUSE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk
- -DEINK_LIMIT_FASTREFRESH=100 ; How many consecutive fast-refreshes are permitted
- ;-DEINK_LIMIT_RATE_BACKGROUND_SEC=30 ; Minimum interval between BACKGROUND updates
- ;-DEINK_LIMIT_RATE_RESPONSIVE_SEC=1
lib_deps =
${esp32s3_base.lib_deps}
- # renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master
- https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip
+
+[env:crowpanel-esp32s3-5-epaper]
+extends = crowpanel_epaper_base
+build_flags =
+ ${crowpanel_epaper_base.build_flags}
+ -D CROWPANEL_ESP32S3_5_EPAPER
[env:crowpanel-esp32s3-4-epaper]
-extends = esp32s3_base
-board_build.arduino.memory_type = qio_opi
-board_build.flash_mode = qio
-board_build.psram_type = opi
-board_upload.flash_size = 8MB
-board_upload.maximum_size = 8388608
-board_build.partitions = default_8MB.csv
-board = esp32-s3-devkitc-1
-;upload_port = /dev/ttyUSB0
-board_level = extra
-upload_protocol = esptool
-build_flags =
- ${esp32s3_base.build_flags}
+extends = crowpanel_epaper_base
+build_flags =
+ ${crowpanel_epaper_base.build_flags}
-D CROWPANEL_ESP32S3_4_EPAPER
- -I variants/esp32s3/crowpanel-esp32s3-5-epaper
- -D PRIVATE_HW
- -DBOARD_HAS_PSRAM
- -DEINK_DISPLAY_MODEL=GxEPD2_420_GYE042A87 ; similar Panel: GDEY042T81 : https://www.good-display.com/product/386.html
- -DEINK_WIDTH=400
- -DEINK_HEIGHT=300
- -DUSE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk
- -DEINK_LIMIT_FASTREFRESH=100 ; How many consecutive fast-refreshes are permitted
- ;-DEINK_LIMIT_RATE_BACKGROUND_SEC=30 ; Minimum interval between BACKGROUND updates
- ;-DEINK_LIMIT_RATE_RESPONSIVE_SEC=1
-lib_deps =
- ${esp32s3_base.lib_deps}
- # renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master
- https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip
[env:crowpanel-esp32s3-2-epaper]
-extends = esp32s3_base
-board_build.arduino.memory_type = qio_opi
-board_build.flash_mode = qio
-board_build.psram_type = opi
-board_upload.flash_size = 8MB
-board_upload.maximum_size = 8388608
-board_build.partitions = default_8MB.csv
-board = esp32-s3-devkitc-1
-;upload_port = /dev/ttyUSB0
-board_level = extra
-upload_protocol = esptool
-build_flags =
- ${esp32s3_base.build_flags}
+extends = crowpanel_epaper_base
+build_flags =
+ ${crowpanel_epaper_base.build_flags}
-D CROWPANEL_ESP32S3_2_EPAPER
- -I variants/esp32s3/crowpanel-esp32s3-5-epaper
- -D PRIVATE_HW
- -DBOARD_HAS_PSRAM
- -DEINK_DISPLAY_MODEL=GxEPD2_290_GDEY029T94 ;https://www.good-display.com/product/389.html
- -DEINK_WIDTH=296
- -DEINK_HEIGHT=128
- -DUSE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk
- -DEINK_LIMIT_FASTREFRESH=100 ; How many consecutive fast-refreshes are permitted
- ;-DEINK_LIMIT_RATE_BACKGROUND_SEC=30 ; Minimum interval between BACKGROUND updates
- ;-DEINK_LIMIT_RATE_RESPONSIVE_SEC=1
-lib_deps =
- ${esp32s3_base.lib_deps}
- # renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master
- https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip
diff --git a/variants/esp32s3/diy/my_esp32s3_diy_eink/nicheGraphics.h b/variants/esp32s3/diy/my_esp32s3_diy_eink/nicheGraphics.h
new file mode 100644
index 000000000..b9661d67f
--- /dev/null
+++ b/variants/esp32s3/diy/my_esp32s3_diy_eink/nicheGraphics.h
@@ -0,0 +1,45 @@
+/*
+
+NicheGraphics setup for the DIY ESP32-S3 2.9" e-ink reference board
+(as shipped: GDEW029T5D, 128x296, UC8151D controller).
+
+The UC8151D init/command set is close enough to SSD1680 that GDEY029T94
+(same 128x296 geometry, SSD1680-based) is used as the panel profile.
+If the specific UC8151D panel behaves oddly (ghosting, inverted colours,
+wrong refresh sequence), swap this panel profile for a UC8151D one.
+
+Uses the default SPI bus, which is shared with the SX1280 LoRa module.
+
+*/
+
+#pragma once
+
+#include "configuration.h"
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+#include "graphics/BaseUIEInkDisplay.h"
+#include "graphics/eink/Panels/GDEY029T94.h"
+
+class DiyEsp32S3EinkPanel : public NicheGraphics::Panels::GDEY029T94
+{
+ protected:
+ SPIClass *beginSpi() override
+ {
+ SPI.begin();
+ return &SPI;
+ }
+};
+
+void setupNicheGraphics() {}
+
+NicheGraphics::BaseUIEInkDisplay *setupNicheGraphicsBaseUI()
+{
+ auto *panel = new DiyEsp32S3EinkPanel();
+ NicheGraphics::Drivers::EInk *driver = panel->create();
+ auto *display = new NicheGraphics::BaseUIEInkDisplay(driver, panel->rotation());
+ display->setDisplayResilience(7, 1.5f);
+ return display;
+}
+
+#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
diff --git a/variants/esp32s3/diy/my_esp32s3_diy_eink/platformio.ini b/variants/esp32s3/diy/my_esp32s3_diy_eink/platformio.ini
index 90e4910f4..f2332492f 100644
--- a/variants/esp32s3/diy/my_esp32s3_diy_eink/platformio.ini
+++ b/variants/esp32s3/diy/my_esp32s3_diy_eink/platformio.ini
@@ -1,6 +1,6 @@
[env:my-esp32s3-diy-eink]
board_level = extra
-extends = esp32s3_base
+extends = esp32s3_base, niche
board = my_esp32s3_diy_eink
board_build.arduino.memory_type = dio_opi
board_build.mcu = esp32s3
@@ -10,19 +10,18 @@ upload_protocol = esptool
upload_speed = 921600
lib_deps =
${esp32s3_base.lib_deps}
- # renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2
- zinggjm/GxEPD2@1.6.8
build_unflags =
${esp32s3_base.build_unflags}
-DARDUINO_USB_MODE=1
-build_flags =
+build_src_filter =
+ ${esp32s3_base.build_src_filter}
+ ${niche.build_src_filter}
+build_flags =
${esp32s3_base.build_flags}
+ ${niche.build_flags}
-D PRIVATE_HW
-I variants/esp32s3/diy/my_esp32s3_diy_eink
-Dmy
- -DEINK_DISPLAY_MODEL=GxEPD2_290_T5D
- -DEINK_WIDTH=296
- -DEINK_HEIGHT=128
-DBOARD_HAS_PSRAM
-mfix-esp32-psram-cache-issue
-DARDUINO_USB_MODE=0
diff --git a/variants/esp32s3/esp32-s3-pico/nicheGraphics.h b/variants/esp32s3/esp32-s3-pico/nicheGraphics.h
new file mode 100644
index 000000000..fa4c983cd
--- /dev/null
+++ b/variants/esp32s3/esp32-s3-pico/nicheGraphics.h
@@ -0,0 +1,38 @@
+/*
+
+NicheGraphics setup for the ESP32-S3-Pico board (GDEY029T94 2.9" on the default SPI bus).
+
+*/
+
+#pragma once
+
+#include "configuration.h"
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+#include "graphics/BaseUIEInkDisplay.h"
+#include "graphics/eink/Panels/GDEY029T94.h"
+
+class Esp32S3PicoPanel : public NicheGraphics::Panels::GDEY029T94
+{
+ protected:
+ SPIClass *beginSpi() override
+ {
+ // Old GxEPD2 path used the default SPI bus; preserve that to avoid SPI conflicts.
+ SPI.begin();
+ return &SPI;
+ }
+};
+
+void setupNicheGraphics() {}
+
+NicheGraphics::BaseUIEInkDisplay *setupNicheGraphicsBaseUI()
+{
+ auto *panel = new Esp32S3PicoPanel();
+ NicheGraphics::Drivers::EInk *driver = panel->create();
+ auto *display = new NicheGraphics::BaseUIEInkDisplay(driver, panel->rotation());
+ display->setDisplayResilience(7, 1.5f);
+ return display;
+}
+
+#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
diff --git a/variants/esp32s3/esp32-s3-pico/platformio.ini b/variants/esp32s3/esp32-s3-pico/platformio.ini
index 64f50f80e..983549afe 100644
--- a/variants/esp32s3/esp32-s3-pico/platformio.ini
+++ b/variants/esp32s3/esp32-s3-pico/platformio.ini
@@ -1,7 +1,7 @@
[env:ESP32-S3-Pico]
board_level = extra
-extends = esp32s3_base
+extends = esp32s3_base, niche
upload_protocol = esptool
board = esp32-s3-pico
board_build.partitions = default_16MB.csv
@@ -12,15 +12,13 @@ board_upload.require_upload_port = yes
;upload_port = /dev/ttyACM0
+build_src_filter = ${esp32s3_base.build_src_filter}
+ ${niche.build_src_filter}
build_flags = ${esp32s3_base.build_flags}
+ ${niche.build_flags}
-DESP32_S3_PICO
;-DPRIVATE_HW
-Ivariants/esp32s3/esp32-s3-pico
-DBOARD_HAS_PSRAM
- -DEINK_DISPLAY_MODEL=GxEPD2_290_T94_V2
- -DEINK_WIDTH=296
- -DEINK_HEIGHT=128
lib_deps = ${esp32s3_base.lib_deps}
- # renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2
- zinggjm/GxEPD2@1.6.8
diff --git a/variants/esp32s3/heltec_vision_master_e213/nicheGraphics.h b/variants/esp32s3/heltec_vision_master_e213/nicheGraphics.h
index fb0744bc3..8edae09f7 100644
--- a/variants/esp32s3/heltec_vision_master_e213/nicheGraphics.h
+++ b/variants/esp32s3/heltec_vision_master_e213/nicheGraphics.h
@@ -1,119 +1,130 @@
+/*
+
+NicheGraphics setup for Heltec Vision Master E-213 (HSPI; runtime-detected 2.13" panel).
+
+Panel choice depends on detectEInk() output:
+ - LCMEN213EFC1 (V1, unmarked)
+ - E0213A367 (V1.1, V1.2)
+
+Both panels share the same SPI wiring; the difference is the controller/waveform.
+
+*/
+
#pragma once
#include "configuration.h"
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
-// InkHUD-specific components
-// ---------------------------
-#include "graphics/niche/InkHUD/InkHUD.h"
+#include "graphics/BaseUIEInkDisplay.h"
+#include "graphics/eink/Panels/E0213A367.h"
+#include "graphics/eink/Panels/LCMEN213EFC1.h"
-// Applets
-#include "graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/DM/DMApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/RecentsList/RecentsListApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/ThreadedMessage/ThreadedMessageApplet.h"
+#include "buzz.h"
+#include "einkDetect.h"
-// Shared NicheGraphics components
-// --------------------------------
-#include "graphics/niche/Drivers/EInk/E0213A367.h"
-#include "graphics/niche/Drivers/EInk/LCMEN2R13EFC1.h"
+#ifdef MESHTASTIC_INCLUDE_INKHUD
+#include "graphics/niche/Applets/User/AllMessage/AllMessageApplet.h"
+#include "graphics/niche/Applets/User/DM/DMApplet.h"
+#include "graphics/niche/Applets/User/FavoritesMap/FavoritesMapApplet.h"
+#include "graphics/niche/Applets/User/Heard/HeardApplet.h"
+#include "graphics/niche/Applets/User/Positions/PositionsApplet.h"
+#include "graphics/niche/Applets/User/RecentsList/RecentsListApplet.h"
+#include "graphics/niche/Applets/User/ThreadedMessage/ThreadedMessageApplet.h"
+#include "graphics/niche/InkHUD.h"
#include "graphics/niche/Inputs/TwoButton.h"
+#endif
-#include "buzz.h" // Button feedback
-#include "einkDetect.h" // Detect display model at runtime
+template class VMe213Panel : public Base
+{
+ protected:
+ SPIClass *beginSpi() override
+ {
+ auto *hspi = new SPIClass(HSPI);
+ hspi->begin(PIN_EINK_SCLK, -1, PIN_EINK_MOSI, PIN_EINK_CS);
+ return hspi;
+ }
+};
+struct VMe213DetectedDriver {
+ NicheGraphics::Drivers::EInk *driver;
+ uint8_t fastPerFull;
+ float stress;
+ uint8_t rotation;
+};
+
+static VMe213DetectedDriver detectAndCreateVMe213Driver()
+{
+ EInkDetectionResult model = detectEInk();
+ VMe213DetectedDriver out{nullptr, 10, 1.5f, 3};
+ if (model == EInkDetectionResult::LCMEN213EFC1) {
+ auto *p = new VMe213Panel();
+ out.driver = p->create();
+ out.rotation = p->rotation();
+ out.fastPerFull = 10;
+ out.stress = 1.5f;
+ } else { // E0213A367 (V1.1, V1.2)
+ auto *p = new VMe213Panel();
+ out.driver = p->create();
+ out.rotation = p->rotation();
+ out.fastPerFull = 15;
+ out.stress = 3.0f;
+ }
+ return out;
+}
+
+#ifdef MESHTASTIC_INCLUDE_INKHUD
void setupNicheGraphics()
{
using namespace NicheGraphics;
- // Detect E-Ink Model
- // -------------------
-
- EInkDetectionResult displayModel = detectEInk();
-
- // SPI
- // -----------------------------
-
- // Display is connected to HSPI
- SPIClass *hspi = new SPIClass(HSPI);
- hspi->begin(PIN_EINK_SCLK, -1, PIN_EINK_MOSI, PIN_EINK_CS);
-
- // E-Ink Driver
- // -----------------------------
-
- Drivers::EInk *driver;
-
- if (displayModel == EInkDetectionResult::LCMEN213EFC1) // V1 (unmarked)
- driver = new Drivers::LCMEN213EFC1;
- else if (displayModel == EInkDetectionResult::E0213A367) // V1.1
- driver = new Drivers::E0213A367;
-
- driver->begin(hspi, PIN_EINK_DC, PIN_EINK_CS, PIN_EINK_BUSY, PIN_EINK_RES);
-
- // InkHUD
- // ----------------------------
+ VMe213DetectedDriver detected = detectAndCreateVMe213Driver();
InkHUD::InkHUD *inkhud = InkHUD::InkHUD::getInstance();
+ inkhud->setDriver(detected.driver);
+ inkhud->setDisplayResilience(detected.fastPerFull, detected.stress);
- // Set the E-Ink driver
- inkhud->setDriver(driver);
-
- // Set how many FAST updates per FULL update
- // Set how unhealthy additional FAST updates beyond this number are
-
- if (displayModel == EInkDetectionResult::LCMEN213EFC1) // V1 (unmarked)
- inkhud->setDisplayResilience(10, 1.5);
- else if (displayModel == EInkDetectionResult::E0213A367) // V1.1
- inkhud->setDisplayResilience(15, 3);
-
- // Select fonts
InkHUD::Applet::fontLarge = FREESANS_12PT_WIN1252;
InkHUD::Applet::fontMedium = FREESANS_9PT_WIN1252;
InkHUD::Applet::fontSmall = FREESANS_6PT_WIN1252;
- // Customize default settings
- inkhud->persistence->settings.userTiles.maxCount = 2; // How many tiles can the display handle?
- inkhud->persistence->settings.rotation = 3; // 270 degrees clockwise
- inkhud->persistence->settings.userTiles.count = 1; // One tile only by default, keep things simple for new users
- inkhud->persistence->settings.optionalMenuItems.nextTile = false; // Behavior handled by aux button instead
+ inkhud->persistence->settings.userTiles.maxCount = 2;
+ inkhud->persistence->settings.rotation = 3;
+ inkhud->persistence->settings.userTiles.count = 1;
+ inkhud->persistence->settings.optionalMenuItems.nextTile = false;
- // Pick applets
- // Note: order of applets determines priority of "auto-show" feature
- inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet, true, true); // Activated, autoshown
- inkhud->addApplet("DMs", new InkHUD::DMApplet); // -
- inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0)); // -
- inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1)); // -
- inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true); // Activated
- inkhud->addApplet("Favorites Map", new InkHUD::FavoritesMapApplet); // -
- inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet); // -
- inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0); // Activated, not autoshown, default on tile 0
+ inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet, true, true);
+ inkhud->addApplet("DMs", new InkHUD::DMApplet);
+ inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0));
+ inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1));
+ inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true);
+ inkhud->addApplet("Favorites Map", new InkHUD::FavoritesMapApplet);
+ inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet);
+ inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0);
- // Start running InkHUD
inkhud->begin();
- // Buttons
- // --------------------------
-
- Inputs::TwoButton *buttons = Inputs::TwoButton::getInstance(); // Shared NicheGraphics component
-
- // #0: Main User Button
+ Inputs::TwoButton *buttons = Inputs::TwoButton::getInstance();
buttons->setWiring(0, Inputs::TwoButton::getUserButtonPin());
buttons->setHandlerShortPress(0, [inkhud]() { inkhud->shortpress(); });
buttons->setHandlerLongPress(0, [inkhud]() { inkhud->longpress(); });
-
- // #1: Aux Button
buttons->setWiring(1, PIN_BUTTON2);
buttons->setHandlerShortPress(1, [inkhud]() {
inkhud->nextTile();
playChirp();
});
-
- // Begin handling button events
buttons->start();
}
+#else
+void setupNicheGraphics() {}
+NicheGraphics::BaseUIEInkDisplay *setupNicheGraphicsBaseUI()
+{
+ VMe213DetectedDriver detected = detectAndCreateVMe213Driver();
+ auto *display = new NicheGraphics::BaseUIEInkDisplay(detected.driver, detected.rotation);
+ display->setDisplayResilience(detected.fastPerFull, detected.stress);
+ return display;
+}
#endif
+
+#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
diff --git a/variants/esp32s3/heltec_vision_master_e213/platformio.ini b/variants/esp32s3/heltec_vision_master_e213/platformio.ini
index bd1b73d2b..b3444806b 100644
--- a/variants/esp32s3/heltec_vision_master_e213/platformio.ini
+++ b/variants/esp32s3/heltec_vision_master_e213/platformio.ini
@@ -11,26 +11,20 @@ custom_meshtastic_requires_dfu = true
custom_meshtastic_partition_scheme = 8MB
custom_meshtastic_has_ink_hud = true
-extends = esp32s3_base
+extends = esp32s3_base, niche
board = heltec_vision_master_e213
board_build.partitions = default_8MB.csv
-build_flags =
+build_src_filter =
+ ${esp32s3_base.build_src_filter}
+ ${niche.build_src_filter}
+build_flags =
${esp32s3_base.build_flags}
- -Ivariants/esp32s3/heltec_vision_master_e213
+ ${niche.build_flags}
+ -Ivariants/esp32s3/heltec_vision_master_e213
-DHELTEC_VISION_MASTER_E213
-DUSE_EINK
- -DGXEPD2_DRIVER_0=GxEPD2_213_FC1
- -DGXEPD2_DRIVER_1=GxEPD2_213_E0213A367
- -DEINK_WIDTH=250
- -DEINK_HEIGHT=122
- -DUSE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk
- -DEINK_LIMIT_FASTREFRESH=10 ; How many consecutive fast-refreshes are permitted
- -DEINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached.
- -DEINK_HASQUIRK_GHOSTING ; Display model is identified as "prone to ghosting"
lib_deps =
${esp32s3_base.lib_deps}
- # renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master
- https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip
upload_speed = 115200
[env:heltec-vision-master-e213-inkhud]
@@ -41,7 +35,7 @@ board_build.partitions = default_8MB.csv
build_src_filter =
${esp32s3_base.build_src_filter}
${inkhud.build_src_filter}
-build_flags =
+build_flags =
${esp32s3_base.build_flags}
${inkhud.build_flags}
-I variants/esp32s3/heltec_vision_master_e213
diff --git a/variants/esp32s3/heltec_vision_master_e290/nicheGraphics.h b/variants/esp32s3/heltec_vision_master_e290/nicheGraphics.h
index a90500b15..0d01fa311 100644
--- a/variants/esp32s3/heltec_vision_master_e290/nicheGraphics.h
+++ b/variants/esp32s3/heltec_vision_master_e290/nicheGraphics.h
@@ -1,13 +1,14 @@
/*
-Most of the Meshtastic firmware uses preprocessor macros throughout the code to support different hardware variants.
-NicheGraphics attempts a different approach:
+NicheGraphics setup for Heltec Vision Master E-290.
-Per-device config takes place in this setupNicheGraphics() method
-(And a small amount in platformio.ini)
+Supports two UI builds that share this file:
+ - InkHUD build (MESHTASTIC_INCLUDE_INKHUD defined)
+ - BaseUI build (MESHTASTIC_INCLUDE_INKHUD not defined)
-This file sets up InkHUD for Heltec VM-E290.
-Different NicheGraphics UIs and different hardware variants will each have their own setup procedure.
+The panel itself (DEPG0290BNS800, SSD1680, 128x296) is defined once in
+ graphics/eink/Panels/DEPG0290BNS800.h
+The variant only overrides what is specific to this board — here, that's the HSPI pin bring-up.
*/
@@ -17,100 +18,111 @@ Different NicheGraphics UIs and different hardware variants will each have their
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
-// InkHUD-specific components
-// ---------------------------
-#include "graphics/niche/InkHUD/InkHUD.h"
+#include "graphics/BaseUIEInkDisplay.h"
+#include "graphics/eink/Panels/DEPG0290BNS800.h"
-// Applets
-#include "graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/DM/DMApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/RecentsList/RecentsListApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/ThreadedMessage/ThreadedMessageApplet.h"
-
-// Shared NicheGraphics components
-// --------------------------------
-#include "graphics/niche/Drivers/EInk/DEPG0290BNS800.h"
-#include "graphics/niche/Inputs/TwoButton.h"
-
-// Button feedback
#include "buzz.h"
+#ifdef MESHTASTIC_INCLUDE_INKHUD
+#include "graphics/niche/Applets/User/AllMessage/AllMessageApplet.h"
+#include "graphics/niche/Applets/User/DM/DMApplet.h"
+#include "graphics/niche/Applets/User/FavoritesMap/FavoritesMapApplet.h"
+#include "graphics/niche/Applets/User/Heard/HeardApplet.h"
+#include "graphics/niche/Applets/User/Positions/PositionsApplet.h"
+#include "graphics/niche/Applets/User/RecentsList/RecentsListApplet.h"
+#include "graphics/niche/Applets/User/ThreadedMessage/ThreadedMessageApplet.h"
+#include "graphics/niche/InkHUD.h"
+#include "graphics/niche/Inputs/TwoButton.h"
+#endif
+
+// Board-specific: display is wired to HSPI with explicit pins.
+class VMe290Panel : public NicheGraphics::Panels::DEPG0290BNS800
+{
+ protected:
+ SPIClass *beginSpi() override
+ {
+ auto *hspi = new SPIClass(HSPI);
+ hspi->begin(PIN_EINK_SCLK, -1, PIN_EINK_MOSI, PIN_EINK_CS);
+ return hspi;
+ }
+};
+
+static NicheGraphics::Drivers::EInk *createVMe290Driver(uint8_t *outRotation)
+{
+ auto *panel = new VMe290Panel();
+ *outRotation = panel->rotation();
+ return panel->create();
+}
+
+#ifdef MESHTASTIC_INCLUDE_INKHUD
+static void setupVMe290Buttons(std::function shortPress, std::function longPress,
+ std::function auxShortPress)
+{
+ using namespace NicheGraphics;
+ auto *buttons = Inputs::TwoButton::getInstance();
+
+ buttons->setWiring(0, Inputs::TwoButton::getUserButtonPin());
+ buttons->setHandlerShortPress(0, shortPress);
+ buttons->setHandlerLongPress(0, longPress);
+
+ buttons->setWiring(1, PIN_BUTTON2);
+ buttons->setHandlerShortPress(1, auxShortPress);
+
+ buttons->start();
+}
+
void setupNicheGraphics()
{
using namespace NicheGraphics;
- // SPI
- // -----------------------------
-
- // Display is connected to HSPI
- SPIClass *hspi = new SPIClass(HSPI);
- hspi->begin(PIN_EINK_SCLK, -1, PIN_EINK_MOSI, PIN_EINK_CS);
-
- // E-Ink Driver
- // -----------------------------
-
- Drivers::EInk *driver = new Drivers::DEPG0290BNS800;
- driver->begin(hspi, PIN_EINK_DC, PIN_EINK_CS, PIN_EINK_BUSY);
-
- // InkHUD
- // ----------------------------
+ uint8_t panelRotation;
+ Drivers::EInk *driver = createVMe290Driver(&panelRotation);
+ (void)panelRotation; // InkHUD uses its own settings.rotation below
InkHUD::InkHUD *inkhud = InkHUD::InkHUD::getInstance();
-
- // Set the E-Ink driver
inkhud->setDriver(driver);
-
- // Set how many FAST updates per FULL update
- // Set how unhealthy additional FAST updates beyond this number are
inkhud->setDisplayResilience(7, 1.5);
- // Select fonts
InkHUD::Applet::fontLarge = FREESANS_12PT_WIN1252;
InkHUD::Applet::fontMedium = FREESANS_9PT_WIN1252;
InkHUD::Applet::fontSmall = FREESANS_6PT_WIN1252;
- // Customize default settings
- inkhud->persistence->settings.userTiles.maxCount = 2; // How many tiles can the display handle?
- inkhud->persistence->settings.rotation = 1; // 90 degrees clockwise
- inkhud->persistence->settings.userTiles.count = 1; // One tile only by default, keep things simple for new users
- inkhud->persistence->settings.optionalMenuItems.nextTile = false; // Behavior handled by aux button instead
+ inkhud->persistence->settings.userTiles.maxCount = 2;
+ inkhud->persistence->settings.rotation = 1;
+ inkhud->persistence->settings.userTiles.count = 1;
+ inkhud->persistence->settings.optionalMenuItems.nextTile = false;
- // Pick applets
- // Note: order of applets determines priority of "auto-show" feature
- inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet, true, true); // Activated, autoshown
- inkhud->addApplet("DMs", new InkHUD::DMApplet); // -
- inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0)); // -
- inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1)); // -
- inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true); // Activated
- inkhud->addApplet("Favorites Map", new InkHUD::FavoritesMapApplet); // -
- inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet); // -
- inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0); // Activated, not autoshown, default on tile 0
+ inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet, true, true);
+ inkhud->addApplet("DMs", new InkHUD::DMApplet);
+ inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0));
+ inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1));
+ inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true);
+ inkhud->addApplet("Favorites Map", new InkHUD::FavoritesMapApplet);
+ inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet);
+ inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0);
- // Start running InkHUD
inkhud->begin();
- // Buttons
- // --------------------------
-
- Inputs::TwoButton *buttons = Inputs::TwoButton::getInstance(); // A shared NicheGraphics component
-
- // #0: Main User Button
- buttons->setWiring(0, Inputs::TwoButton::getUserButtonPin());
- buttons->setHandlerShortPress(0, [inkhud]() { inkhud->shortpress(); });
- buttons->setHandlerLongPress(0, [inkhud]() { inkhud->longpress(); });
-
- // #1: Aux Button
- buttons->setWiring(1, PIN_BUTTON2);
- buttons->setHandlerShortPress(1, [inkhud]() {
- inkhud->nextTile();
- playChirp();
- });
-
- // Begin handling button events
- buttons->start();
+ setupVMe290Buttons([inkhud]() { inkhud->shortpress(); }, [inkhud]() { inkhud->longpress(); },
+ [inkhud]() {
+ inkhud->nextTile();
+ playChirp();
+ });
+}
+#else // BaseUI path
+void setupNicheGraphics()
+{
+ // BaseUI handles its own input via the default ButtonThread. Nothing extra to set up here.
}
-#endif
+NicheGraphics::BaseUIEInkDisplay *setupNicheGraphicsBaseUI()
+{
+ uint8_t panelRotation;
+ NicheGraphics::Drivers::EInk *driver = createVMe290Driver(&panelRotation);
+ auto *display = new NicheGraphics::BaseUIEInkDisplay(driver, panelRotation);
+ display->setDisplayResilience(7, 1.5f);
+ return display;
+}
+#endif // MESHTASTIC_INCLUDE_INKHUD
+
+#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
diff --git a/variants/esp32s3/heltec_vision_master_e290/platformio.ini b/variants/esp32s3/heltec_vision_master_e290/platformio.ini
index 9fdb023de..804bd557a 100644
--- a/variants/esp32s3/heltec_vision_master_e290/platformio.ini
+++ b/variants/esp32s3/heltec_vision_master_e290/platformio.ini
@@ -1,4 +1,4 @@
-; Using the original screen class
+; BaseUI build, using NicheGraphics drivers (no GxEPD2).
[env:heltec-vision-master-e290]
custom_meshtastic_hw_model = 68
custom_meshtastic_hw_model_slug = HELTEC_VISION_MASTER_E290
@@ -12,38 +12,32 @@ custom_meshtastic_requires_dfu = true
custom_meshtastic_partition_scheme = 8MB
custom_meshtastic_has_ink_hud = true
-extends = esp32s3_base
+extends = esp32s3_base, niche
board = heltec_vision_master_e290
board_build.partitions = default_8MB.csv
-build_flags =
- ${esp32s3_base.build_flags}
+build_src_filter =
+ ${esp32s3_base.build_src_filter}
+ ${niche.build_src_filter}
+build_flags =
+ ${esp32s3_base.build_flags}
+ ${niche.build_flags}
-I variants/esp32s3/heltec_vision_master_e290
-D DISPLAY_FLIP_SCREEN ; Orient so the LoRa antenna faces up
-D HELTEC_VISION_MASTER_E290
-D BUTTON_CLICK_MS=200
- -D EINK_DISPLAY_MODEL=GxEPD2_290_BN8
- -D EINK_WIDTH=296
- -D EINK_HEIGHT=128
-D USE_EINK
- -D USE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk
- -D EINK_LIMIT_FASTREFRESH=10 ; How many consecutive fast-refreshes are permitted
- -D EINK_HASQUIRK_GHOSTING ; Display model is identified as "prone to ghosting"
-
-
lib_deps =
${esp32s3_base.lib_deps}
- # renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master
- https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip
upload_speed = 115200
[env:heltec-vision-master-e290-inkhud]
extends = esp32s3_base, inkhud
board = heltec_vision_master_e290
board_build.partitions = default_8MB.csv
-build_src_filter =
+build_src_filter =
${esp32s3_base.build_src_filter}
${inkhud.build_src_filter}
-build_flags =
+build_flags =
${esp32s3_base.build_flags}
${inkhud.build_flags}
-I variants/esp32s3/heltec_vision_master_e290
diff --git a/variants/esp32s3/heltec_wireless_paper/nicheGraphics.h b/variants/esp32s3/heltec_wireless_paper/nicheGraphics.h
index 9e84a541e..d46abf69e 100644
--- a/variants/esp32s3/heltec_wireless_paper/nicheGraphics.h
+++ b/variants/esp32s3/heltec_wireless_paper/nicheGraphics.h
@@ -1,112 +1,121 @@
+/*
+
+NicheGraphics setup for Heltec Wireless Paper (HSPI; runtime-detected 2.13" panel).
+
+Panel choice depends on detectEInk() output:
+ - LCMEN213EFC1 (V1.1)
+ - E0213A367 (V1.1.1, V1.2)
+
+*/
+
#pragma once
#include "configuration.h"
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
-// InkHUD-specific components
-// ---------------------------
-#include "graphics/niche/InkHUD/InkHUD.h"
+#include "graphics/BaseUIEInkDisplay.h"
+#include "graphics/eink/Panels/E0213A367.h"
+#include "graphics/eink/Panels/LCMEN213EFC1.h"
-// Applets
-#include "graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/DM/DMApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/RecentsList/RecentsListApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/ThreadedMessage/ThreadedMessageApplet.h"
+#include "einkDetect.h"
-// Shared NicheGraphics components
-// --------------------------------
-#include "graphics/niche/Drivers/EInk/E0213A367.h"
-#include "graphics/niche/Drivers/EInk/LCMEN2R13EFC1.h"
+#ifdef MESHTASTIC_INCLUDE_INKHUD
+#include "graphics/niche/Applets/User/AllMessage/AllMessageApplet.h"
+#include "graphics/niche/Applets/User/DM/DMApplet.h"
+#include "graphics/niche/Applets/User/FavoritesMap/FavoritesMapApplet.h"
+#include "graphics/niche/Applets/User/Heard/HeardApplet.h"
+#include "graphics/niche/Applets/User/Positions/PositionsApplet.h"
+#include "graphics/niche/Applets/User/RecentsList/RecentsListApplet.h"
+#include "graphics/niche/Applets/User/ThreadedMessage/ThreadedMessageApplet.h"
+#include "graphics/niche/InkHUD.h"
#include "graphics/niche/Inputs/TwoButton.h"
+#endif
-#include "einkDetect.h" // Detect display model at runtime
+template class HeltecWPPanel : public Base
+{
+ protected:
+ SPIClass *beginSpi() override
+ {
+ auto *hspi = new SPIClass(HSPI);
+ hspi->begin(PIN_EINK_SCLK, -1, PIN_EINK_MOSI, PIN_EINK_CS);
+ return hspi;
+ }
+};
+struct HeltecWPDetectedDriver {
+ NicheGraphics::Drivers::EInk *driver;
+ uint8_t fastPerFull;
+ float stress;
+ uint8_t rotation;
+};
+
+static HeltecWPDetectedDriver detectAndCreateHeltecWPDriver()
+{
+ EInkDetectionResult model = detectEInk();
+ HeltecWPDetectedDriver out{nullptr, 10, 1.5f, 3};
+ if (model == EInkDetectionResult::LCMEN213EFC1) {
+ auto *p = new HeltecWPPanel();
+ out.driver = p->create();
+ out.rotation = p->rotation();
+ out.fastPerFull = 10;
+ out.stress = 1.5f;
+ } else {
+ auto *p = new HeltecWPPanel();
+ out.driver = p->create();
+ out.rotation = p->rotation();
+ out.fastPerFull = 15;
+ out.stress = 3.0f;
+ }
+ return out;
+}
+
+#ifdef MESHTASTIC_INCLUDE_INKHUD
void setupNicheGraphics()
{
using namespace NicheGraphics;
- // Detect E-Ink Model
- // -------------------
-
- EInkDetectionResult displayModel = detectEInk();
-
- // SPI
- // -----------------------------
-
- // Display is connected to HSPI
- SPIClass *hspi = new SPIClass(HSPI);
- hspi->begin(PIN_EINK_SCLK, -1, PIN_EINK_MOSI, PIN_EINK_CS);
-
- // E-Ink Driver
- // -----------------------------
-
- Drivers::EInk *driver;
-
- if (displayModel == EInkDetectionResult::LCMEN213EFC1) // V1.1
- driver = new Drivers::LCMEN213EFC1;
- else if (displayModel == EInkDetectionResult::E0213A367) // V1.1.1, V1.2
- driver = new Drivers::E0213A367;
-
- driver->begin(hspi, PIN_EINK_DC, PIN_EINK_CS, PIN_EINK_BUSY, PIN_EINK_RES);
-
- // InkHUD
- // ----------------------------
+ HeltecWPDetectedDriver detected = detectAndCreateHeltecWPDriver();
InkHUD::InkHUD *inkhud = InkHUD::InkHUD::getInstance();
+ inkhud->setDriver(detected.driver);
+ inkhud->setDisplayResilience(detected.fastPerFull, detected.stress);
- // Set the E-Ink driver
- inkhud->setDriver(driver);
-
- // Set how many FAST updates per FULL update
- // Set how unhealthy additional FAST updates beyond this number are
-
- if (displayModel == EInkDetectionResult::LCMEN213EFC1) // V1.1 (unmarked)
- inkhud->setDisplayResilience(10, 1.5);
- else if (displayModel == EInkDetectionResult::E0213A367) // V1.1.1, V1.2
- inkhud->setDisplayResilience(15, 3);
-
- // Select fonts
InkHUD::Applet::fontLarge = FREESANS_12PT_WIN1252;
InkHUD::Applet::fontMedium = FREESANS_9PT_WIN1252;
InkHUD::Applet::fontSmall = FREESANS_6PT_WIN1252;
- // Customize default settings
- inkhud->persistence->settings.userTiles.maxCount = 2; // How many tiles can the display handle?
- inkhud->persistence->settings.rotation = 3; // 270 degrees clockwise
- inkhud->persistence->settings.userTiles.count = 1; // One tile only by default, keep things simple for new users
+ inkhud->persistence->settings.userTiles.maxCount = 2;
+ inkhud->persistence->settings.rotation = 3;
+ inkhud->persistence->settings.userTiles.count = 1;
- // Pick applets
- // Note: order of applets determines priority of "auto-show" feature
- inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet, true, true); // Activated, autoshown
- inkhud->addApplet("DMs", new InkHUD::DMApplet); // -
- inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0)); // -
- inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1)); // -
- inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true); // Activated
- inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet); // -
- inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0); // Activated, not autoshown, default on tile 0
- inkhud->addApplet("Favorites Map", new InkHUD::FavoritesMapApplet, false, false); // -
+ inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet, true, true);
+ inkhud->addApplet("DMs", new InkHUD::DMApplet);
+ inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0));
+ inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1));
+ inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true);
+ inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet);
+ inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0);
+ inkhud->addApplet("Favorites Map", new InkHUD::FavoritesMapApplet, false, false);
- // Start running InkHUD
inkhud->begin();
- // Buttons
- // --------------------------
-
- Inputs::TwoButton *buttons = Inputs::TwoButton::getInstance(); // Shared NicheGraphics component
-
- // #0: Main User Button
+ Inputs::TwoButton *buttons = Inputs::TwoButton::getInstance();
buttons->setWiring(0, Inputs::TwoButton::getUserButtonPin());
buttons->setHandlerShortPress(0, [inkhud]() { inkhud->shortpress(); });
buttons->setHandlerLongPress(0, [inkhud]() { inkhud->longpress(); });
-
- // No aux button on this board
-
- // Begin handling button events
buttons->start();
}
+#else
+void setupNicheGraphics() {}
+NicheGraphics::BaseUIEInkDisplay *setupNicheGraphicsBaseUI()
+{
+ HeltecWPDetectedDriver detected = detectAndCreateHeltecWPDriver();
+ auto *display = new NicheGraphics::BaseUIEInkDisplay(detected.driver, detected.rotation);
+ display->setDisplayResilience(detected.fastPerFull, detected.stress);
+ return display;
+}
#endif
+
+#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
diff --git a/variants/esp32s3/heltec_wireless_paper/platformio.ini b/variants/esp32s3/heltec_wireless_paper/platformio.ini
index acd619c48..f84538e2b 100644
--- a/variants/esp32s3/heltec_wireless_paper/platformio.ini
+++ b/variants/esp32s3/heltec_wireless_paper/platformio.ini
@@ -1,4 +1,3 @@
-; Using the original screen class
[env:heltec-wireless-paper]
custom_meshtastic_hw_model = 49
custom_meshtastic_hw_model_slug = HELTEC_WIRELESS_PAPER
@@ -11,26 +10,20 @@ custom_meshtastic_tags = Heltec
custom_meshtastic_partition_scheme = 8MB
custom_meshtastic_has_ink_hud = true
-extends = esp32s3_base
+extends = esp32s3_base, niche
board = heltec_wifi_lora_32_V3
board_build.partitions = default_8MB.csv
-build_flags =
- ${esp32s3_base.build_flags}
+build_src_filter =
+ ${esp32s3_base.build_src_filter}
+ ${niche.build_src_filter}
+build_flags =
+ ${esp32s3_base.build_flags}
+ ${niche.build_flags}
-I variants/esp32s3/heltec_wireless_paper
- -D HELTEC_WIRELESS_PAPER
- -D GXEPD2_DRIVER_0=GxEPD2_213_FC1
- -D GXEPD2_DRIVER_1=GxEPD2_213_E0213A367
- -D EINK_WIDTH=250
- -D EINK_HEIGHT=122
+ -D HELTEC_WIRELESS_PAPER
-D USE_EINK
- -D USE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk
- -D EINK_LIMIT_FASTREFRESH=10 ; How many consecutive fast-refreshes are permitted
- -D EINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached.
- -D EINK_HASQUIRK_GHOSTING ; Display model is identified as "prone to ghosting"
lib_deps =
${esp32s3_base.lib_deps}
- # renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master
- https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip
upload_speed = 115200
[env:heltec-wireless-paper-inkhud]
diff --git a/variants/esp32s3/heltec_wireless_paper_v1/nicheGraphics.h b/variants/esp32s3/heltec_wireless_paper_v1/nicheGraphics.h
new file mode 100644
index 000000000..bfd65c94a
--- /dev/null
+++ b/variants/esp32s3/heltec_wireless_paper_v1/nicheGraphics.h
@@ -0,0 +1,38 @@
+/*
+
+NicheGraphics setup for Heltec Wireless Paper V1.0 (DEPG0213BNS800 on HSPI).
+
+*/
+
+#pragma once
+
+#include "configuration.h"
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+#include "graphics/BaseUIEInkDisplay.h"
+#include "graphics/eink/Panels/DEPG0213BNS800.h"
+
+class HeltecWirelessPaperV1Panel : public NicheGraphics::Panels::DEPG0213BNS800
+{
+ protected:
+ SPIClass *beginSpi() override
+ {
+ auto *hspi = new SPIClass(HSPI);
+ hspi->begin(PIN_EINK_SCLK, -1, PIN_EINK_MOSI, PIN_EINK_CS);
+ return hspi;
+ }
+};
+
+void setupNicheGraphics() {}
+
+NicheGraphics::BaseUIEInkDisplay *setupNicheGraphicsBaseUI()
+{
+ auto *panel = new HeltecWirelessPaperV1Panel();
+ NicheGraphics::Drivers::EInk *driver = panel->create();
+ auto *display = new NicheGraphics::BaseUIEInkDisplay(driver, panel->rotation());
+ display->setDisplayResilience(5, 1.5f);
+ return display;
+}
+
+#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
diff --git a/variants/esp32s3/heltec_wireless_paper_v1/platformio.ini b/variants/esp32s3/heltec_wireless_paper_v1/platformio.ini
index b34adfb17..3c6538c34 100644
--- a/variants/esp32s3/heltec_wireless_paper_v1/platformio.ini
+++ b/variants/esp32s3/heltec_wireless_paper_v1/platformio.ini
@@ -9,22 +9,18 @@ custom_meshtastic_images = heltec-wireless-paper-v1_0.svg
custom_meshtastic_tags = Heltec
custom_meshtastic_partition_scheme = 8MB
-extends = esp32s3_base
+extends = esp32s3_base, niche
board_level = extra
board = heltec_wifi_lora_32_V3
board_build.partitions = default_8MB.csv
-build_flags =
- ${esp32s3_base.build_flags}
- -I variants/esp32s3/heltec_wireless_paper_v1
+build_src_filter =
+ ${esp32s3_base.build_src_filter}
+ ${niche.build_src_filter}
+build_flags =
+ ${esp32s3_base.build_flags}
+ ${niche.build_flags}
+ -I variants/esp32s3/heltec_wireless_paper_v1
-D HELTEC_WIRELESS_PAPER_V1_0
- -D EINK_DISPLAY_MODEL=GxEPD2_213_BN
- -D EINK_WIDTH=250
- -D EINK_HEIGHT=122
- -D USE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk
- -D EINK_LIMIT_FASTREFRESH=5 ; How many consecutive fast-refreshes are permitted
- -D EINK_LIMIT_GHOSTING_PX=2000 ; (Optional) How much image ghosting is tolerated
lib_deps =
${esp32s3_base.lib_deps}
- # renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master
- https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip
upload_speed = 115200
diff --git a/variants/esp32s3/mini-epaper-s3/nicheGraphics.h b/variants/esp32s3/mini-epaper-s3/nicheGraphics.h
index 0cbd6a192..538530563 100644
--- a/variants/esp32s3/mini-epaper-s3/nicheGraphics.h
+++ b/variants/esp32s3/mini-epaper-s3/nicheGraphics.h
@@ -1,58 +1,79 @@
+/*
+
+NicheGraphics setup for LILYGO Mini ePaper S3 (GDEW0102T4 1.02" on HSPI).
+Shared by both BaseUI and InkHUD envs.
+
+*/
+
#pragma once
#include "configuration.h"
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
-// InkHUD-specific components
-#include "graphics/niche/InkHUD/InkHUD.h"
+#include "graphics/BaseUIEInkDisplay.h"
+#include "graphics/eink/Drivers/GDEW0102T4.h"
+#include "graphics/eink/Panels/GDEW0102T4.h"
-// Applets
-#include "graphics/niche/InkHUD/Applet.h"
-#include "graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/DM/DMApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/RecentsList/RecentsListApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/ThreadedMessage/ThreadedMessageApplet.h"
-#include "graphics/niche/InkHUD/SystemApplet.h"
-
-// Shared NicheGraphics components
-#include "graphics/niche/Drivers/EInk/GDEW0102T4.h"
+#ifdef MESHTASTIC_INCLUDE_INKHUD
+#include "graphics/niche/Applet.h"
+#include "graphics/niche/Applets/User/AllMessage/AllMessageApplet.h"
+#include "graphics/niche/Applets/User/DM/DMApplet.h"
+#include "graphics/niche/Applets/User/FavoritesMap/FavoritesMapApplet.h"
+#include "graphics/niche/Applets/User/Heard/HeardApplet.h"
+#include "graphics/niche/Applets/User/Positions/PositionsApplet.h"
+#include "graphics/niche/Applets/User/RecentsList/RecentsListApplet.h"
+#include "graphics/niche/Applets/User/ThreadedMessage/ThreadedMessageApplet.h"
+#include "graphics/niche/InkHUD.h"
#include "graphics/niche/Inputs/TwoButtonExtended.h"
+#include "graphics/niche/SystemApplet.h"
+#endif
+class MiniEpaperS3Panel : public NicheGraphics::Panels::GDEW0102T4
+{
+ public:
+ uint8_t rotation() const override { return 0; }
+
+ protected:
+ void prePowerOn() override
+ {
+ pinMode(PIN_EINK_EN, OUTPUT);
+ digitalWrite(PIN_EINK_EN, HIGH);
+ delay(10);
+ }
+ SPIClass *beginSpi() override
+ {
+ auto *hspi = new SPIClass(HSPI);
+ hspi->begin(PIN_EINK_SCLK, -1, PIN_EINK_MOSI, PIN_EINK_CS);
+ return hspi;
+ }
+};
+
+static NicheGraphics::Drivers::EInk *makeMiniEpaperS3Driver()
+{
+ auto *panel = new MiniEpaperS3Panel();
+ auto *driver = panel->create();
+ // Tuned fast-refresh values: reg30 reg50 reg82 lutW2 lutB2 = 11 F2 04 11 0D
+ static_cast(driver)->setFastConfig({0x11, 0xF2, 0x04, 0x11, 0x0D});
+ return driver;
+}
+
+#ifdef MESHTASTIC_INCLUDE_INKHUD
void setupNicheGraphics()
{
using namespace NicheGraphics;
- // Power-enable the E-Ink panel on this board before any SPI traffic.
- pinMode(PIN_EINK_EN, OUTPUT);
- digitalWrite(PIN_EINK_EN, HIGH);
- delay(10);
-
- // Display uses HSPI on this board
- SPIClass *hspi = new SPIClass(HSPI);
- hspi->begin(PIN_EINK_SCLK, -1, PIN_EINK_MOSI, PIN_EINK_CS);
-
- Drivers::GDEW0102T4 *displayDriver = new Drivers::GDEW0102T4;
- displayDriver->begin(hspi, PIN_EINK_DC, PIN_EINK_CS, PIN_EINK_BUSY, PIN_EINK_RES);
- // Tuned fast-refresh values reg30 reg50 reg82 lutW2 lutB2 = 11 F2 04 11 0D
- displayDriver->setFastConfig({0x11, 0xF2, 0x04, 0x11, 0x0D});
- Drivers::EInk *driver = displayDriver;
+ Drivers::EInk *driver = makeMiniEpaperS3Driver();
InkHUD::InkHUD *inkhud = InkHUD::InkHUD::getInstance();
inkhud->setDriver(driver);
- // Slightly stricter FAST/FULL
inkhud->setDisplayResilience(5, 1.5);
inkhud->twoWayRocker = true;
- // Fonts
InkHUD::Applet::fontLarge = FREESANS_9PT_WIN1252;
InkHUD::Applet::fontMedium = FREESANS_6PT_WIN1252;
InkHUD::Applet::fontSmall = FREESANS_6PT_WIN1252;
- // Small display defaults
inkhud->persistence->settings.rotation = 0;
inkhud->persistence->settings.userTiles.maxCount = 1;
inkhud->persistence->settings.userTiles.count = 1;
@@ -60,41 +81,28 @@ void setupNicheGraphics()
inkhud->persistence->settings.joystick.aligned = true;
inkhud->persistence->settings.optionalMenuItems.nextTile = false;
- // Pick applets
- // Note: order of applets determines priority of "auto-show" feature
- inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet, false, false); // -
- inkhud->addApplet("DMs", new InkHUD::DMApplet, true, false); // Activated, not autoshown
- inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0), true, true); // Activated, Autoshown
- inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1), false, false); // -
- inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true); // Activated
- inkhud->addApplet("Favorites Map", new InkHUD::FavoritesMapApplet, false, false); // -
- inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet, false, false); // -
- inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0); // Activated, not autoshown, default on tile 0
- // Start running InkHUD
+ inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet, false, false);
+ inkhud->addApplet("DMs", new InkHUD::DMApplet, true, false);
+ inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0), true, true);
+ inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1), false, false);
+ inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true);
+ inkhud->addApplet("Favorites Map", new InkHUD::FavoritesMapApplet, false, false);
+ inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet, false, false);
+ inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0);
inkhud->begin();
- // Enforce two-way rocker behavior regardless of persisted settings.
inkhud->persistence->settings.joystick.enabled = true;
inkhud->persistence->settings.joystick.aligned = true;
inkhud->persistence->settings.optionalMenuItems.nextTile = false;
- // Inputs
Inputs::TwoButtonExtended *buttons = Inputs::TwoButtonExtended::getInstance();
-
- // Center press (boot button)
buttons->setWiring(0, INPUTDRIVER_TWO_WAY_ROCKER_BTN, true);
- // Match baseUI encoder long-press feel.
buttons->setTiming(0, 75, 300);
buttons->setHandlerShortPress(0, [inkhud]() { inkhud->shortpress(); });
buttons->setHandlerLongPress(0, [inkhud]() { inkhud->longpress(); });
- // LEFT rocker pin is IO4; RIGHT rocker pin is IO3.
buttons->setTwoWayRockerWiring(INPUTDRIVER_TWO_WAY_ROCKER_LEFT, INPUTDRIVER_TWO_WAY_ROCKER_RIGHT, true);
buttons->setJoystickDebounce(50);
-
- // Two-way rocker behavior:
- // - when a system applet is handling input (menu, tips, etc): LEFT=up, RIGHT=down
- // - otherwise: LEFT=previous applet, RIGHT=next applet
buttons->setTwoWayRockerPressHandlers(
[inkhud]() {
bool systemHandlingInput = false;
@@ -104,7 +112,6 @@ void setupNicheGraphics()
break;
}
}
-
if (systemHandlingInput)
inkhud->navUp();
else
@@ -118,7 +125,6 @@ void setupNicheGraphics()
break;
}
}
-
if (systemHandlingInput)
inkhud->navDown();
else
@@ -127,5 +133,16 @@ void setupNicheGraphics()
buttons->start();
}
+#else
+void setupNicheGraphics() {}
+NicheGraphics::BaseUIEInkDisplay *setupNicheGraphicsBaseUI()
+{
+ NicheGraphics::Drivers::EInk *driver = makeMiniEpaperS3Driver();
+ auto *display = new NicheGraphics::BaseUIEInkDisplay(driver, 0);
+ display->setDisplayResilience(3, 1.5f);
+ return display;
+}
#endif
+
+#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
diff --git a/variants/esp32s3/mini-epaper-s3/platformio.ini b/variants/esp32s3/mini-epaper-s3/platformio.ini
index 3e4802319..103358db7 100644
--- a/variants/esp32s3/mini-epaper-s3/platformio.ini
+++ b/variants/esp32s3/mini-epaper-s3/platformio.ini
@@ -10,28 +10,23 @@ custom_meshtastic_tags = LilyGo
custom_meshtastic_requires_dfu = no
custom_meshtastic_has_mui = false
-extends = esp32s3_base
+extends = esp32s3_base, niche
board = mini-epaper-s3
board_check = true
upload_protocol = esptool
-build_flags =
+build_src_filter =
+ ${esp32s3_base.build_src_filter}
+ ${niche.build_src_filter}
+build_flags =
${esp32s3_base.build_flags}
+ ${niche.build_flags}
-I variants/esp32s3/mini-epaper-s3
-D MINI_EPAPER_S3
-D USE_EINK
- -D EINK_DISPLAY_MODEL=GxEPD2_102
- -D EINK_WIDTH=128
- -D EINK_HEIGHT=80
- -D USE_EINK_DYNAMICDISPLAY
- -D EINK_LIMIT_FASTREFRESH=3
- -D EINK_BACKGROUND_USES_FAST
- -D EINK_HASQUIRK_GHOSTING
lib_deps =
${esp32s3_base.lib_deps}
- # renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master
- https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip
# renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib
lewisxhe/SensorLib@0.3.4
diff --git a/variants/esp32s3/t-deck-pro/nicheGraphics.h b/variants/esp32s3/t-deck-pro/nicheGraphics.h
new file mode 100644
index 000000000..f897f6151
--- /dev/null
+++ b/variants/esp32s3/t-deck-pro/nicheGraphics.h
@@ -0,0 +1,39 @@
+/*
+
+NicheGraphics setup for LILYGO T-Deck Pro (GDEQ031T10 3.1" 240x320 on the default SPI bus).
+
+*/
+
+#pragma once
+
+#include "configuration.h"
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+#include "graphics/BaseUIEInkDisplay.h"
+#include "graphics/eink/Panels/GDEQ031T10.h"
+
+class TDeckProPanel : public NicheGraphics::Panels::GDEQ031T10
+{
+ protected:
+ SPIClass *beginSpi() override
+ {
+ // Old GxEPD2 path used the global SPI bus on this board.
+ SPI.begin();
+ return &SPI;
+ }
+};
+
+void setupNicheGraphics() {}
+
+NicheGraphics::BaseUIEInkDisplay *setupNicheGraphicsBaseUI()
+{
+ auto *panel = new TDeckProPanel();
+ NicheGraphics::Drivers::EInk *driver = panel->create();
+ auto *display = new NicheGraphics::BaseUIEInkDisplay(driver, panel->rotation());
+ // T-Deck Pro types frequently; allow more FAST refreshes between FULLs than the default.
+ display->setDisplayResilience(15, 1.5f);
+ return display;
+}
+
+#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
diff --git a/variants/esp32s3/t-deck-pro/platformio.ini b/variants/esp32s3/t-deck-pro/platformio.ini
index 93ef8babf..94b0de6d4 100644
--- a/variants/esp32s3/t-deck-pro/platformio.ini
+++ b/variants/esp32s3/t-deck-pro/platformio.ini
@@ -11,7 +11,7 @@ custom_meshtastic_requires_dfu = true
custom_meshtastic_partition_scheme = 16MB
custom_meshtastic_has_mui = false
-extends = esp32s3_base
+extends = esp32s3_base, niche
board = t-deck-pro
board_check = true
upload_protocol = esptool
@@ -19,22 +19,18 @@ upload_protocol = esptool
build_src_filter =
${esp32s3_base.build_src_filter}
+<../variants/esp32s3/t-deck-pro>
+ ${niche.build_src_filter}
-build_flags =
+build_flags =
${esp32s3_base.build_flags} -I variants/esp32s3/t-deck-pro
+ ${niche.build_flags}
-D T_DECK_PRO
-D USE_EINK
- -D EINK_DISPLAY_MODEL=GxEPD2_310_GDEQ031T10
-D EINK_WIDTH=240
-D EINK_HEIGHT=320
- ;-D USE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk
- -D EINK_LIMIT_FASTREFRESH=10 ; How many consecutive fast-refreshes are permitted
- -D EINK_LIMIT_GHOSTING_PX=2000 ; (Optional) How much image ghosting is tolerated
lib_deps =
${esp32s3_base.lib_deps}
- # renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2
- zinggjm/GxEPD2@1.6.8
# renovate: datasource=git-refs depName=CSE_Touch packageName=https://github.com/CIRCUITSTATE/CSE_Touch gitBranch=main
https://github.com/CIRCUITSTATE/CSE_Touch/archive/b44f23b6f870b848f1fbe453c190879bc6cfaafa.zip
# renovate: datasource=github-tags depName=CSE_CST328 packageName=CIRCUITSTATE/CSE_CST328
diff --git a/variants/esp32s3/t5s3_epaper/nicheGraphics.h b/variants/esp32s3/t5s3_epaper/nicheGraphics.h
index 699a82de0..1c362686a 100644
--- a/variants/esp32s3/t5s3_epaper/nicheGraphics.h
+++ b/variants/esp32s3/t5s3_epaper/nicheGraphics.h
@@ -1,123 +1,117 @@
/*
-Most of the Meshtastic firmware uses preprocessor macros throughout the code to support different hardware variants.
-NicheGraphics attempts a different approach:
+NicheGraphics setup for LILYGO T5 ePaper Pro (ED047TC1, 960x540, parallel via FastEPD).
-Per-device config takes place in this setupNicheGraphics() method
-(And a small amount in platformio.ini)
+Three envs share this directory:
+ - t5s3-epaper-v1 (BaseUI, BB_PANEL_LILYGO_T5PRO)
+ - t5s3-epaper-v2 (BaseUI, BB_PANEL_LILYGO_T5PRO_V2 + GPIO-expander setup)
+ - t5s3_epaper_inkhud (InkHUD on V2)
-This file sets up InkHUD for Heltec VM-E290.
-Different NicheGraphics UIs and different hardware variants will each have their own setup procedure.
+The active panel is selected by the T5_S3_EPAPER_PRO_V1 / T5_S3_EPAPER_PRO_V2 macros from platformio.ini.
*/
#pragma once
#include "configuration.h"
-#include "mesh/MeshModule.h"
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
-// InkHUD-specific components
-// ---------------------------
-// #include "graphics/niche/InkHUD/InkHUD.h"
-#include "graphics/niche/InkHUD/WindowManager.h"
+#include "FastEPD.h"
-// Applets
-#include "graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/DM/DMApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/RecentsList/RecentsListApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/ThreadedMessage/ThreadedMessageApplet.h"
+#include "graphics/BaseUIEInkDisplay.h"
+#include "graphics/eink/Drivers/EInkParallel.h"
+#include "graphics/eink/Panels/T5Epaper.h"
-// Shared NicheGraphics components
-// --------------------------------
-#include "graphics/niche/Drivers/Backlight/LatchingBacklight.h"
-#include "graphics/niche/Drivers/EInk/DEPG0290BNS800.h"
-#include "graphics/niche/Inputs/TwoButton.h"
+#ifdef MESHTASTIC_INCLUDE_INKHUD
+#include "graphics/niche/Applets/User/AllMessage/AllMessageApplet.h"
+#include "graphics/niche/Applets/User/DM/DMApplet.h"
+#include "graphics/niche/Applets/User/Heard/HeardApplet.h"
+#include "graphics/niche/Applets/User/Positions/PositionsApplet.h"
+#include "graphics/niche/Applets/User/RecentsList/RecentsListApplet.h"
+#include "graphics/niche/Applets/User/ThreadedMessage/ThreadedMessageApplet.h"
+#include "graphics/niche/InkHUD.h"
+#endif
+#if defined(T5_S3_EPAPER_PRO_V2)
+class T5V2Driver : public NicheGraphics::Drivers::EInkParallel
+{
+ public:
+ T5V2Driver() : EInkParallel(960, 540, BB_PANEL_LILYGO_T5PRO_V2, 28000000) {}
+
+ protected:
+ void postPanelInit() override
+ {
+ // V2 uses a GPIO expander; raise port-0 pins 0..7 high (as the legacy driver did).
+ FASTEPD *epd = fastEpd();
+ for (int i = 0; i < 8; i++) {
+ epd->ioPinMode(i, OUTPUT);
+ epd->ioWrite(i, HIGH);
+ }
+ }
+};
+#elif defined(T5_S3_EPAPER_PRO_V1)
+class T5V1Driver : public NicheGraphics::Drivers::EInkParallel
+{
+ public:
+ T5V1Driver() : EInkParallel(960, 540, BB_PANEL_LILYGO_T5PRO, 28000000) {}
+};
+#else
+#error "t5s3_epaper requires T5_S3_EPAPER_PRO_V1 or T5_S3_EPAPER_PRO_V2"
+#endif
+
+class T5EpaperPanel : public NicheGraphics::Panels::T5EpaperPanel
+{
+ protected:
+ NicheGraphics::Drivers::EInkParallel *makeDriver() override
+ {
+#if defined(T5_S3_EPAPER_PRO_V2)
+ return new T5V2Driver();
+#else
+ return new T5V1Driver();
+#endif
+ }
+};
+
+#ifdef MESHTASTIC_INCLUDE_INKHUD
void setupNicheGraphics()
{
using namespace NicheGraphics;
- // SPI
- // -----------------------------
-
- // Display is connected to HSPI
- SPIClass *hspi = new SPIClass(HSPI);
- hspi->begin(PIN_EINK_SCLK, -1, PIN_EINK_MOSI, PIN_EINK_CS);
-
- // E-Ink Driver
- // -----------------------------
-
- // Use E-Ink driver
- Drivers::EInk *driver = new Drivers::DEPG0290BNS800;
- driver->begin(hspi, PIN_EINK_DC, PIN_EINK_CS, PIN_EINK_BUSY);
-
- // InkHUD
- // ----------------------------
+ auto *panel = new T5EpaperPanel();
+ Drivers::EInk *driver = panel->create();
InkHUD::InkHUD *inkhud = InkHUD::InkHUD::getInstance();
-
- // Set the driver
inkhud->setDriver(driver);
+ inkhud->setDisplayResilience(20, 1.5);
- // Set how many FAST updates per FULL update
- // Set how unhealthy additional FAST updates beyond this number are
- inkhud->setDisplayResilience(7, 1.5);
-
- // Prepare fonts
- InkHUD::Applet::fontLarge = FREESANS_9PT_WIN1252;
+ InkHUD::Applet::fontLarge = FREESANS_12PT_WIN1252;
+ InkHUD::Applet::fontMedium = FREESANS_9PT_WIN1252;
InkHUD::Applet::fontSmall = FREESANS_6PT_WIN1252;
- // Init settings, and customize defaults
- inkhud->persistence->settings.userTiles.maxCount = 2; // How many tiles can the display handle?
- inkhud->persistence->settings.rotation = 1; // 90 degrees clockwise
- inkhud->persistence->settings.userTiles.count = 1; // One tile only by default, keep things simple for new users
- inkhud->persistence->settings.optionalMenuItems.nextTile = false; // Behavior handled by aux button instead
- inkhud->persistence->settings.optionalFeatures.batteryIcon = true; // Device definitely has a battery
+ inkhud->persistence->settings.userTiles.maxCount = 4;
+ inkhud->persistence->settings.userTiles.count = 1;
- // Setup backlight
- // Note: AUX button behavior configured further down
- Drivers::LatchingBacklight *backlight = Drivers::LatchingBacklight::getInstance();
- backlight->setPin(PIN_EINK_EN);
-
- // Pick applets
- // Note: order of applets determines priority of "auto-show" feature
- // Optional arguments for defaults:
- // - is activated?
- // - is autoshown?
- // - is foreground on a specific tile (index)?
- inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet, true, true); // Activated, autoshown
+ inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet, true, true);
inkhud->addApplet("DMs", new InkHUD::DMApplet);
inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0));
- inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1));
- inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true); // Activated
+ inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true);
inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet);
- inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0); // Activated, not autoshown, default on tile 0
- // inkhud->addApplet("Basic", new InkHUD::BasicExampleApplet);
- // inkhud->addApplet("NewMsg", new InkHUD::NewMsgExampleApplet);
+ inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0);
- // Start running InkHUD
inkhud->begin();
-
- // Buttons
- // --------------------------
-
- Inputs::TwoButton *buttons = Inputs::TwoButton::getInstance(); // A shared NicheGraphics component
-
- // Setup the main user button (0)
- buttons->setWiring(0, BUTTON_PIN);
- buttons->setHandlerShortPress(0, []() { InkHUD::InkHUD::getInstance()->shortpress(); });
- buttons->setHandlerLongPress(0, []() { InkHUD::InkHUD::getInstance()->longpress(); });
-
- // Setup the aux button (1)
- // Bonus feature of VME290
- buttons->setWiring(1, BUTTON_PIN_SECONDARY);
- buttons->setHandlerShortPress(1, []() { InkHUD::InkHUD::getInstance()->nextTile(); });
-
- buttons->start();
}
+#else
+void setupNicheGraphics() {}
-#endif
\ No newline at end of file
+NicheGraphics::BaseUIEInkDisplay *setupNicheGraphicsBaseUI()
+{
+ auto *panel = new T5EpaperPanel();
+ NicheGraphics::Drivers::EInk *driver = panel->create();
+ auto *display = new NicheGraphics::BaseUIEInkDisplay(driver, 0);
+ display->setDisplayResilience(20, 1.5f);
+ return display;
+}
+#endif
+
+#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
diff --git a/variants/esp32s3/t5s3_epaper/platformio.ini b/variants/esp32s3/t5s3_epaper/platformio.ini
index bad36706c..da0ee467f 100644
--- a/variants/esp32s3/t5s3_epaper/platformio.ini
+++ b/variants/esp32s3/t5s3_epaper/platformio.ini
@@ -1,26 +1,25 @@
[t5s3_epaper_base]
-extends = esp32s3_base
+extends = esp32s3_base, niche
board = t5-epaper-s3
board_build.partition = default_16MB.csv
board_check = true
upload_protocol = esptool
build_flags = -fno-strict-aliasing
- ${esp32_base.build_flags}
+ ${esp32_base.build_flags}
+ ${niche.build_flags}
-I variants/esp32s3/t5s3_epaper
-D T5_S3_EPAPER_PRO
-D USE_EINK
- -D USE_EINK_PARALLELDISPLAY
+ -D NICHE_HAS_FASTEPD ; activate NicheGraphics parallel-EPD driver (FastEPD)
-D PRIVATE_HW
-D TOUCH_THRESHOLD_X=60
-D TOUCH_THRESHOLD_Y=40
-D TIME_LONG_PRESS=500
-; -D EINK_LIMIT_GHOSTING_PX=5000
- -D EPD_FULLSLOW_PERIOD=100
- -D FAST_EPD_PARTIAL_UPDATE_BUG ; use rect area update instead of partial
-build_src_filter =
+build_src_filter =
${esp32s3_base.build_src_filter}
+<../variants/esp32s3/t5s3_epaper>
+ ${niche.build_src_filter}
lib_deps =
${esp32s3_base.lib_deps}
# renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib
diff --git a/variants/esp32s3/tlora_t3s3_epaper/nicheGraphics.h b/variants/esp32s3/tlora_t3s3_epaper/nicheGraphics.h
index 73cc2e235..271cdaf14 100644
--- a/variants/esp32s3/tlora_t3s3_epaper/nicheGraphics.h
+++ b/variants/esp32s3/tlora_t3s3_epaper/nicheGraphics.h
@@ -1,91 +1,90 @@
+/*
+
+NicheGraphics setup for LILYGO T-LoRa T3-S3 ePaper (DEPG0213BNS800 on HSPI).
+Shared by both BaseUI and InkHUD envs.
+
+*/
+
#pragma once
#include "configuration.h"
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
-// InkHUD-specific components
-// ---------------------------
-#include "graphics/niche/InkHUD/InkHUD.h"
+#include "graphics/BaseUIEInkDisplay.h"
+#include "graphics/eink/Panels/DEPG0213BNS800.h"
-// Applets
-#include "graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/DM/DMApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/RecentsList/RecentsListApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/ThreadedMessage/ThreadedMessageApplet.h"
-
-// Shared NicheGraphics components
-// --------------------------------
-#include "graphics/niche/Drivers/EInk/DEPG0213BNS800.h"
+#ifdef MESHTASTIC_INCLUDE_INKHUD
+#include "graphics/niche/Applets/User/AllMessage/AllMessageApplet.h"
+#include "graphics/niche/Applets/User/DM/DMApplet.h"
+#include "graphics/niche/Applets/User/FavoritesMap/FavoritesMapApplet.h"
+#include "graphics/niche/Applets/User/Heard/HeardApplet.h"
+#include "graphics/niche/Applets/User/Positions/PositionsApplet.h"
+#include "graphics/niche/Applets/User/RecentsList/RecentsListApplet.h"
+#include "graphics/niche/Applets/User/ThreadedMessage/ThreadedMessageApplet.h"
+#include "graphics/niche/InkHUD.h"
#include "graphics/niche/Inputs/TwoButton.h"
+#endif
+class TLoraT3S3Panel : public NicheGraphics::Panels::DEPG0213BNS800
+{
+ protected:
+ SPIClass *beginSpi() override
+ {
+ auto *hspi = new SPIClass(HSPI);
+ hspi->begin(PIN_EINK_SCLK, -1, PIN_EINK_MOSI, PIN_EINK_CS);
+ return hspi;
+ }
+};
+
+#ifdef MESHTASTIC_INCLUDE_INKHUD
void setupNicheGraphics()
{
using namespace NicheGraphics;
- // SPI
- // -----------------------------
-
- // Display is connected to HSPI
- SPIClass *hspi = new SPIClass(HSPI);
- hspi->begin(PIN_EINK_SCLK, -1, PIN_EINK_MOSI, PIN_EINK_CS);
-
- // E-Ink Driver
- // -----------------------------
-
- Drivers::EInk *driver = new Drivers::DEPG0213BNS800;
- driver->begin(hspi, PIN_EINK_DC, PIN_EINK_CS, PIN_EINK_BUSY, PIN_EINK_RES);
-
- // InkHUD
- // ----------------------------
+ auto *panel = new TLoraT3S3Panel();
+ Drivers::EInk *driver = panel->create();
InkHUD::InkHUD *inkhud = InkHUD::InkHUD::getInstance();
-
- // Set the driver
inkhud->setDriver(driver);
-
- // Set how many FAST updates per FULL update
- // Set how unhealthy additional FAST updates beyond this number are
inkhud->setDisplayResilience(15, 1.5);
- // Select fonts
InkHUD::Applet::fontLarge = FREESANS_12PT_WIN1252;
InkHUD::Applet::fontMedium = FREESANS_9PT_WIN1252;
InkHUD::Applet::fontSmall = FREESANS_6PT_WIN1252;
- // Customize default settings
- inkhud->persistence->settings.userTiles.maxCount = 2; // How many tiles can the display handle?
- inkhud->persistence->settings.rotation = 3; // 270 degrees clockwise
- inkhud->persistence->settings.userTiles.count = 1; // One tile only by default, keep things simple for new users
+ inkhud->persistence->settings.userTiles.maxCount = 2;
+ inkhud->persistence->settings.rotation = 3;
+ inkhud->persistence->settings.userTiles.count = 1;
- // Pick applets
- // Note: order of applets determines priority of "auto-show" feature
- inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet, true, true); // Activated, autoshown
- inkhud->addApplet("DMs", new InkHUD::DMApplet); // -
- inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0)); // -
- inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1)); // -
- inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true); // Activated
- inkhud->addApplet("Favorites Map", new InkHUD::FavoritesMapApplet); // -
- inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet); // -
- inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0); // Activated, not autoshown, default on tile 0
+ inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet, true, true);
+ inkhud->addApplet("DMs", new InkHUD::DMApplet);
+ inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0));
+ inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1));
+ inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true);
+ inkhud->addApplet("Favorites Map", new InkHUD::FavoritesMapApplet);
+ inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet);
+ inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0);
- // Start running InkHUD
inkhud->begin();
- // Buttons
- // --------------------------
-
- Inputs::TwoButton *buttons = Inputs::TwoButton::getInstance(); // Shared NicheGraphics component
-
- // Setup the main user button
+ Inputs::TwoButton *buttons = Inputs::TwoButton::getInstance();
buttons->setWiring(0, Inputs::TwoButton::getUserButtonPin(), true);
buttons->setHandlerShortPress(0, [inkhud]() { inkhud->shortpress(); });
buttons->setHandlerLongPress(0, [inkhud]() { inkhud->longpress(); });
-
buttons->start();
}
+#else
+void setupNicheGraphics() {}
+NicheGraphics::BaseUIEInkDisplay *setupNicheGraphicsBaseUI()
+{
+ auto *panel = new TLoraT3S3Panel();
+ NicheGraphics::Drivers::EInk *driver = panel->create();
+ auto *display = new NicheGraphics::BaseUIEInkDisplay(driver, panel->rotation());
+ display->setDisplayResilience(15, 1.5f);
+ return display;
+}
#endif
+
+#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
diff --git a/variants/esp32s3/tlora_t3s3_epaper/platformio.ini b/variants/esp32s3/tlora_t3s3_epaper/platformio.ini
index 8f764bbe6..156824ea4 100644
--- a/variants/esp32s3/tlora_t3s3_epaper/platformio.ini
+++ b/variants/esp32s3/tlora_t3s3_epaper/platformio.ini
@@ -10,29 +10,23 @@ custom_meshtastic_tags = LilyGo
custom_meshtastic_requires_dfu = true
custom_meshtastic_has_ink_hud = true
-extends = esp32s3_base
+extends = esp32s3_base, niche
board = tlora-t3s3-v1
board_check = true
upload_protocol = esptool
-build_flags =
+build_src_filter =
+ ${esp32s3_base.build_src_filter}
+ ${niche.build_src_filter}
+build_flags =
${esp32s3_base.build_flags}
+ ${niche.build_flags}
-D TLORA_T3S3_EPAPER
-I variants/esp32s3/tlora_t3s3_epaper
-DUSE_EINK
- -DEINK_DISPLAY_MODEL=GxEPD2_213_BN
- -DEINK_WIDTH=250
- -DEINK_HEIGHT=122
- -DUSE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk
- -DEINK_LIMIT_FASTREFRESH=20 ; How many consecutive fast-refreshes are permitted //20
- -DEINK_LIMIT_RATE_BACKGROUND_SEC=30 ; Minimum interval between BACKGROUND updates //30
- -DEINK_LIMIT_RATE_RESPONSIVE_SEC=1 ; Minimum interval between RESPONSIVE updates
- -DEINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached.
lib_deps =
${esp32s3_base.lib_deps}
- # renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master
- https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip
[env:tlora-t3s3-epaper-inkhud]
extends = esp32s3_base, inkhud
diff --git a/variants/nrf52840/Dongle_nRF52840-pca10059-v1/nicheGraphics.h b/variants/nrf52840/Dongle_nRF52840-pca10059-v1/nicheGraphics.h
new file mode 100644
index 000000000..5be58443f
--- /dev/null
+++ b/variants/nrf52840/Dongle_nRF52840-pca10059-v1/nicheGraphics.h
@@ -0,0 +1,35 @@
+/*
+
+NicheGraphics setup for the Nordic pca10059 dongle + 4.2" e-ink DIY build
+(as shipped: GDEW042M01, 400x300, UC8276 controller).
+
+No UC8276 driver exists in the NicheGraphics tree, so this uses the
+HINK_E042A87 profile (same 400x300 geometry, SSD1680-based). The command
+sets differ; if the panel misbehaves, drop in a UC8276 profile here.
+
+Uses SPI1 per variant.h (SCLK=P0.09, MOSI=P0.10), which is the default
+for nRF52 panel profiles.
+
+*/
+
+#pragma once
+
+#include "configuration.h"
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+#include "graphics/BaseUIEInkDisplay.h"
+#include "graphics/eink/Panels/HINK_E042A87.h"
+
+void setupNicheGraphics() {}
+
+NicheGraphics::BaseUIEInkDisplay *setupNicheGraphicsBaseUI()
+{
+ auto *panel = new NicheGraphics::Panels::HINK_E042A87();
+ NicheGraphics::Drivers::EInk *driver = panel->create();
+ auto *display = new NicheGraphics::BaseUIEInkDisplay(driver, panel->rotation());
+ display->setDisplayResilience(10, 2.0f);
+ return display;
+}
+
+#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
diff --git a/variants/nrf52840/Dongle_nRF52840-pca10059-v1/platformio.ini b/variants/nrf52840/Dongle_nRF52840-pca10059-v1/platformio.ini
index fd159a6d2..2ce3c0f65 100644
--- a/variants/nrf52840/Dongle_nRF52840-pca10059-v1/platformio.ini
+++ b/variants/nrf52840/Dongle_nRF52840-pca10059-v1/platformio.ini
@@ -1,16 +1,12 @@
[env:pca10059_diy_eink]
board_level = extra
-extends = nrf52840_base
+extends = nrf52840_base, niche
board = nordic_pca10059
build_flags = ${nrf52840_base.build_flags}
+ ${niche.build_flags}
-Ivariants/nrf52840/Dongle_nRF52840-pca10059-v1
-D NORDIC_PCA10059
- -DEINK_DISPLAY_MODEL=GxEPD2_420_M01
- -DEINK_WIDTH=300
- -DEINK_HEIGHT=400
-build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/Dongle_nRF52840-pca10059-v1>
-lib_deps =
+build_src_filter = ${nrf52_base.build_src_filter} ${niche.build_src_filter} +<../variants/nrf52840/Dongle_nRF52840-pca10059-v1>
+lib_deps =
${nrf52840_base.lib_deps}
- # renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2
- zinggjm/GxEPD2@1.6.8
debug_tool = jlink
diff --git a/variants/nrf52840/ELECROW-ThinkNode-M1/nicheGraphics.h b/variants/nrf52840/ELECROW-ThinkNode-M1/nicheGraphics.h
index 242e5ae49..6390c25c2 100644
--- a/variants/nrf52840/ELECROW-ThinkNode-M1/nicheGraphics.h
+++ b/variants/nrf52840/ELECROW-ThinkNode-M1/nicheGraphics.h
@@ -1,120 +1,93 @@
+/*
+
+NicheGraphics setup for Elecrow ThinkNode-M1 (GDEY0154D67 1.54" on SPI1).
+Shared by both BaseUI and InkHUD envs.
+
+*/
+
#pragma once
#include "configuration.h"
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
-// InkHUD-specific components
-// ---------------------------
-#include "graphics/niche/InkHUD/InkHUD.h"
+#include "graphics/BaseUIEInkDisplay.h"
+#include "graphics/eink/Backlight/LatchingBacklight.h"
+#include "graphics/eink/Panels/GDEY0154D67.h"
-// Applets
-#include "graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/DM/DMApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/RecentsList/RecentsListApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/ThreadedMessage/ThreadedMessageApplet.h"
-
-// Shared NicheGraphics components
-// --------------------------------
-#include "graphics/niche/Drivers/Backlight/LatchingBacklight.h"
-#include "graphics/niche/Drivers/EInk/GDEY0154D67.h"
-#include "graphics/niche/Inputs/TwoButton.h"
-
-// Button feedback
#include "buzz.h"
+#ifdef MESHTASTIC_INCLUDE_INKHUD
+#include "graphics/niche/Applets/User/AllMessage/AllMessageApplet.h"
+#include "graphics/niche/Applets/User/DM/DMApplet.h"
+#include "graphics/niche/Applets/User/FavoritesMap/FavoritesMapApplet.h"
+#include "graphics/niche/Applets/User/Heard/HeardApplet.h"
+#include "graphics/niche/Applets/User/Positions/PositionsApplet.h"
+#include "graphics/niche/Applets/User/RecentsList/RecentsListApplet.h"
+#include "graphics/niche/Applets/User/ThreadedMessage/ThreadedMessageApplet.h"
+#include "graphics/niche/InkHUD.h"
+#include "graphics/niche/Inputs/TwoButton.h"
+#endif
+
+#ifdef MESHTASTIC_INCLUDE_INKHUD
void setupNicheGraphics()
{
using namespace NicheGraphics;
- // SPI
- // -----------------------------
-
- // For NRF52 platforms, SPI pins are defined in variant.h
- SPI1.begin();
-
- // E-Ink Driver
- // -----------------------------
-
- Drivers::EInk *driver = new Drivers::GDEY0154D67;
- driver->begin(&SPI1, PIN_EINK_DC, PIN_EINK_CS, PIN_EINK_BUSY, PIN_EINK_RES);
-
- // InkHUD
- // ----------------------------
+ auto *panel = new Panels::GDEY0154D67();
+ Drivers::EInk *driver = panel->create();
InkHUD::InkHUD *inkhud = InkHUD::InkHUD::getInstance();
-
- // Set the E-Ink driver
inkhud->setDriver(driver);
-
- // Set how many FAST updates per FULL update
- // Set how unhealthy additional FAST updates beyond this number are
- // Todo: observe the display's performance in-person and adjust accordingly.
- // Currently set to the values given by Elecrow for EInkDynamicDisplay.
inkhud->setDisplayResilience(10, 1.5);
- // Select fonts
InkHUD::Applet::fontLarge = FREESANS_12PT_WIN1252;
InkHUD::Applet::fontMedium = FREESANS_9PT_WIN1252;
InkHUD::Applet::fontSmall = FREESANS_6PT_WIN1252;
- // Customize default settings
- inkhud->persistence->settings.userTiles.maxCount = 2; // Two applets side-by-side
- inkhud->persistence->settings.optionalFeatures.batteryIcon = true; // Device definitely has a battery
+ inkhud->persistence->settings.userTiles.maxCount = 2;
+ inkhud->persistence->settings.optionalFeatures.batteryIcon = true;
- // Setup backlight controller
- // Note: button is attached further down
Drivers::LatchingBacklight *backlight = Drivers::LatchingBacklight::getInstance();
backlight->setPin(PIN_EINK_EN);
- // Pick applets
- // Note: order of applets determines priority of "auto-show" feature
- inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet, true, true); // Activated, autoshown
- inkhud->addApplet("DMs", new InkHUD::DMApplet); // -
- inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0)); // -
- inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1)); // -
- inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true); // Activated
- inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet); // -
- inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0); // Activated, no autoshow, default on tile 0
- inkhud->addApplet("Favorites Map", new InkHUD::FavoritesMapApplet, false, false); // -
-
- // Start running InkHUD
+ inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet, true, true);
+ inkhud->addApplet("DMs", new InkHUD::DMApplet);
+ inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0));
+ inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1));
+ inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true);
+ inkhud->addApplet("Favorites Map", new InkHUD::FavoritesMapApplet);
+ inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet);
+ inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0);
inkhud->begin();
- // Buttons
- // --------------------------
-
- Inputs::TwoButton *buttons = Inputs::TwoButton::getInstance(); // Shared NicheGraphics component
-
- // Elecrow diagram: https://www.elecrow.com/download/product/CIL12901M/ThinkNode-M1_User_Manual.pdf
-
- // #0: Main User Button
- // Labeled "Page Turn Button" by manual
- buttons->setWiring(0, PIN_BUTTON2);
- buttons->setTiming(0, 50, 500); // Todo: confirm 50ms is adequate debounce
+ Inputs::TwoButton *buttons = Inputs::TwoButton::getInstance();
+ buttons->setWiring(0, Inputs::TwoButton::getUserButtonPin());
buttons->setHandlerShortPress(0, [inkhud]() { inkhud->shortpress(); });
buttons->setHandlerLongPress(0, [inkhud]() { inkhud->longpress(); });
-
- // #1: Aux Button
- // Labeled "Function Button" by manual
- // Todo: additional features
- buttons->setWiring(1, PIN_BUTTON1);
- buttons->setTiming(1, 50, 500); // 500ms before latch
- buttons->setHandlerDown(1, [backlight]() { backlight->peek(); });
- buttons->setHandlerLongPress(1, [backlight]() {
- backlight->latch();
- playBoop();
- });
- buttons->setHandlerShortPress(1, [backlight]() {
- backlight->off();
- playChirp();
- });
-
- // Begin handling button events
+#ifdef PIN_BUTTON2
+ buttons->setWiring(1, PIN_BUTTON2);
+ buttons->setHandlerShortPress(1, [backlight]() { backlight->latch(); });
+ buttons->setHandlerLongPress(1, [backlight]() { backlight->off(); });
+#endif
buttons->start();
}
+#else
+void setupNicheGraphics()
+{
+ // Backlight on by default for the original BaseUI build.
+ pinMode(PIN_EINK_EN, OUTPUT);
+ digitalWrite(PIN_EINK_EN, HIGH);
+}
+NicheGraphics::BaseUIEInkDisplay *setupNicheGraphicsBaseUI()
+{
+ auto *panel = new NicheGraphics::Panels::GDEY0154D67();
+ NicheGraphics::Drivers::EInk *driver = panel->create();
+ auto *display = new NicheGraphics::BaseUIEInkDisplay(driver, panel->rotation());
+ display->setDisplayResilience(10, 1.5f);
+ return display;
+}
#endif
+
+#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
diff --git a/variants/nrf52840/ELECROW-ThinkNode-M1/platformio.ini b/variants/nrf52840/ELECROW-ThinkNode-M1/platformio.ini
index c169de8f7..b2a8abd91 100644
--- a/variants/nrf52840/ELECROW-ThinkNode-M1/platformio.ini
+++ b/variants/nrf52840/ELECROW-ThinkNode-M1/platformio.ini
@@ -10,31 +10,20 @@ custom_meshtastic_images = thinknode_m1.svg
custom_meshtastic_tags = Elecrow
custom_meshtastic_has_ink_hud = true
-extends = nrf52840_base
+extends = nrf52840_base, niche
board = ThinkNode-M1
board_check = true
debug_tool = jlink
-# add -DCFG_SYSVIEW if you want to use the Segger systemview tool for OS profiling.
build_flags = ${nrf52840_base.build_flags}
+ ${niche.build_flags}
-Ivariants/nrf52840/ELECROW-ThinkNode-M1
-DELECROW_ThinkNode_M1
-DUSE_EINK
- -DEINK_DISPLAY_MODEL=GxEPD2_154_D67
- -DEINK_WIDTH=200
- -DEINK_HEIGHT=200
- -DUSE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk
- -DEINK_LIMIT_FASTREFRESH=10 ; How many consecutive fast-refreshes are permitted //20
- -DEINK_LIMIT_RATE_BACKGROUND_SEC=10 ; Minimum interval between BACKGROUND updates //30
- -DEINK_LIMIT_RATE_RESPONSIVE_SEC=1 ; Minimum interval between RESPONSIVE updates
-; -DEINK_LIMIT_GHOSTING_PX=2000 ; (Optional) How much image ghosting is tolerated
- -DEINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached.
-build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/ELECROW-ThinkNode-M1>
-lib_deps =
+build_src_filter = ${nrf52_base.build_src_filter} ${niche.build_src_filter} +<../variants/nrf52840/ELECROW-ThinkNode-M1>
+lib_deps =
${nrf52840_base.lib_deps}
- # renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master
- https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip
# renovate: datasource=custom.pio depName=nRF52_PWM packageName=khoih-prog/library/nRF52_PWM
khoih-prog/nRF52_PWM@1.0.1
;upload_protocol = fs
@@ -44,15 +33,15 @@ extends = nrf52840_base, inkhud
board = ThinkNode-M1
board_check = true
debug_tool = jlink
-build_flags =
+build_flags =
${nrf52840_base.build_flags}
${inkhud.build_flags}
-I variants/nrf52840/ELECROW-ThinkNode-M1
-D ELECROW_ThinkNode_M1
-build_src_filter =
- ${nrf52_base.build_src_filter}
+build_src_filter =
+ ${nrf52_base.build_src_filter}
${inkhud.build_src_filter}
+<../variants/nrf52840/ELECROW-ThinkNode-M1>
-lib_deps =
+lib_deps =
${inkhud.lib_deps} ; InkHUD libs first, so we get GFXRoot instead of AdafruitGFX
${nrf52840_base.lib_deps}
diff --git a/variants/nrf52840/ME25LS01-4Y10TD_e-ink/nicheGraphics.h b/variants/nrf52840/ME25LS01-4Y10TD_e-ink/nicheGraphics.h
new file mode 100644
index 000000000..49961eedd
--- /dev/null
+++ b/variants/nrf52840/ME25LS01-4Y10TD_e-ink/nicheGraphics.h
@@ -0,0 +1,27 @@
+/*
+
+NicheGraphics setup for ME25LS01-4Y10TD e-ink board (GDEY042T81 4.2" on SPI1).
+
+*/
+
+#pragma once
+
+#include "configuration.h"
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+#include "graphics/BaseUIEInkDisplay.h"
+#include "graphics/eink/Panels/GDEY042T81.h"
+
+void setupNicheGraphics() {}
+
+NicheGraphics::BaseUIEInkDisplay *setupNicheGraphicsBaseUI()
+{
+ auto *panel = new NicheGraphics::Panels::GDEY042T81();
+ NicheGraphics::Drivers::EInk *driver = panel->create();
+ auto *display = new NicheGraphics::BaseUIEInkDisplay(driver, panel->rotation());
+ display->setDisplayResilience(10, 2.0f);
+ return display;
+}
+
+#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
diff --git a/variants/nrf52840/ME25LS01-4Y10TD_e-ink/platformio.ini b/variants/nrf52840/ME25LS01-4Y10TD_e-ink/platformio.ini
index 39b5dfbd4..dc68ef55b 100644
--- a/variants/nrf52840/ME25LS01-4Y10TD_e-ink/platformio.ini
+++ b/variants/nrf52840/ME25LS01-4Y10TD_e-ink/platformio.ini
@@ -1,22 +1,18 @@
[env:ME25LS01-4Y10TD_e-ink]
-extends = nrf52840_base
+extends = nrf52840_base, niche
board = me25ls01-4y10td
board_level = extra
; platform = https://github.com/maxgerhardt/platform-nordicnrf52#cac6fcf943a41accd2aeb4f3659ae297a73f422e
build_flags = ${nrf52840_base.build_flags}
+ ${niche.build_flags}
-Ivariants/nrf52840/ME25LS01-4Y10TD_e-ink
-Isrc/platform/nrf52/softdevice
-Isrc/platform/nrf52/softdevice/nrf52
-DME25LS01_4Y10TD
- -DEINK_DISPLAY_MODEL=GxEPD2_420_GDEY042T81
- -DEINK_WIDTH=400
- -DEINK_HEIGHT=300
board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld
-build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/ME25LS01-4Y10TD_e-ink>
-lib_deps =
+build_src_filter = ${nrf52_base.build_src_filter} ${niche.build_src_filter} +<../variants/nrf52840/ME25LS01-4Y10TD_e-ink>
+lib_deps =
${nrf52840_base.lib_deps}
- # renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2
- zinggjm/GxEPD2@1.6.8
; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm)
upload_protocol = nrfutil
;upload_port = /dev/ttyACM1
diff --git a/variants/nrf52840/MakePython_nRF52840_eink/nicheGraphics.h b/variants/nrf52840/MakePython_nRF52840_eink/nicheGraphics.h
new file mode 100644
index 000000000..74bd253ef
--- /dev/null
+++ b/variants/nrf52840/MakePython_nRF52840_eink/nicheGraphics.h
@@ -0,0 +1,35 @@
+/*
+
+NicheGraphics setup for the MakerPython nRF52840 + SX1280 DIY e-ink board
+(as shipped: GDEW029T5D, 128x296, UC8151D controller).
+
+The UC8151D init/command set is close enough to SSD1680 that GDEY029T94
+(same 128x296 geometry, SSD1680-based) is used as the panel profile.
+If the panel misbehaves, drop in a UC8151D profile here.
+
+Uses SPI1 per variant.h (SCLK=P0.02, MOSI=P0.28), which is the default
+for nRF52 panel profiles.
+
+*/
+
+#pragma once
+
+#include "configuration.h"
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+#include "graphics/BaseUIEInkDisplay.h"
+#include "graphics/eink/Panels/GDEY029T94.h"
+
+void setupNicheGraphics() {}
+
+NicheGraphics::BaseUIEInkDisplay *setupNicheGraphicsBaseUI()
+{
+ auto *panel = new NicheGraphics::Panels::GDEY029T94();
+ NicheGraphics::Drivers::EInk *driver = panel->create();
+ auto *display = new NicheGraphics::BaseUIEInkDisplay(driver, panel->rotation());
+ display->setDisplayResilience(7, 1.5f);
+ return display;
+}
+
+#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
diff --git a/variants/nrf52840/MakePython_nRF52840_eink/platformio.ini b/variants/nrf52840/MakePython_nRF52840_eink/platformio.ini
index ebea1ce97..db98a47d4 100644
--- a/variants/nrf52840/MakePython_nRF52840_eink/platformio.ini
+++ b/variants/nrf52840/MakePython_nRF52840_eink/platformio.ini
@@ -1,20 +1,16 @@
[env:makerpython_nrf52840_sx1280_eink]
board_level = extra
-extends = nrf52840_base
+extends = nrf52840_base, niche
board = nordic_pca10059
build_flags = ${nrf52840_base.build_flags}
+ ${niche.build_flags}
-Ivariants/nrf52840/MakePython_nRF52840_eink
-D PRIVATE_HW
-D PIN_EINK_EN
- -DEINK_DISPLAY_MODEL=GxEPD2_290_T5D
- -DEINK_WIDTH=296
- -DEINK_HEIGHT=128
-build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/MakePython_nRF52840_eink>
-lib_deps =
+build_src_filter = ${nrf52_base.build_src_filter} ${niche.build_src_filter} +<../variants/nrf52840/MakePython_nRF52840_eink>
+lib_deps =
${nrf52840_base.lib_deps}
# renovate: datasource=git-refs depName=meshtastic-ESP32_Codec2 packageName=https://github.com/meshtastic/ESP32_Codec2 gitBranch=master
https://github.com/meshtastic/ESP32_Codec2/archive/633326c78ac251c059ab3a8c430fcdf25b41672f.zip
- # renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2
- zinggjm/GxEPD2@1.6.8
debug_tool = jlink
;upload_port = /dev/ttyACM4
\ No newline at end of file
diff --git a/variants/nrf52840/TWC_mesh_v4/platformio.ini b/variants/nrf52840/TWC_mesh_v4/platformio.ini
index c529caa0b..4d0ec7aaa 100644
--- a/variants/nrf52840/TWC_mesh_v4/platformio.ini
+++ b/variants/nrf52840/TWC_mesh_v4/platformio.ini
@@ -6,8 +6,6 @@ build_flags = ${nrf52840_base.build_flags}
-I variants/nrf52840/TWC_mesh_v4
-D TWC_mesh_v4
build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/TWC_mesh_v4>
-lib_deps =
+lib_deps =
${nrf52840_base.lib_deps}
- # renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2
- zinggjm/GxEPD2@1.6.8
debug_tool = jlink
diff --git a/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/nicheGraphics.h b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/nicheGraphics.h
index 0a01b613e..50fc723d2 100644
--- a/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/nicheGraphics.h
+++ b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/nicheGraphics.h
@@ -6,25 +6,24 @@
// InkHUD-specific components
// ---------------------------
-#include "graphics/niche/InkHUD/InkHUD.h"
+#include "graphics/niche/InkHUD.h"
+#include "graphics/niche/Inputs/TwoButton.h"
// Applets
-#include "graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/DM/DMApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/RecentsList/RecentsListApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/ThreadedMessage/ThreadedMessageApplet.h"
+#include "graphics/niche/Applets/User/AllMessage/AllMessageApplet.h"
+#include "graphics/niche/Applets/User/DM/DMApplet.h"
+#include "graphics/niche/Applets/User/FavoritesMap/FavoritesMapApplet.h"
+#include "graphics/niche/Applets/User/Heard/HeardApplet.h"
+#include "graphics/niche/Applets/User/Positions/PositionsApplet.h"
+#include "graphics/niche/Applets/User/RecentsList/RecentsListApplet.h"
+#include "graphics/niche/Applets/User/ThreadedMessage/ThreadedMessageApplet.h"
// Shared NicheGraphics components
// --------------------------------
-#include "graphics/niche/Drivers/EInk/HINK_E0213A289.h" // WeAct 2.13"
-#include "graphics/niche/Drivers/EInk/HINK_E042A87.h" // WeAct 4.2"
-#include "graphics/niche/Drivers/EInk/ZJY128296_029EAAMFGN.h" // WeAct 2.9"
-#include "graphics/niche/Drivers/EInk/ZJY200200_0154DAAMFGN.h" // WeACt 1.54"
-
-#include "graphics/niche/Inputs/TwoButton.h"
+#include "graphics/eink/Drivers/HINK_E0213A289.h" // WeAct 2.13"
+#include "graphics/eink/Drivers/HINK_E042A87.h" // WeAct 4.2"
+#include "graphics/eink/Drivers/ZJY128296_029EAAMFGN.h" // WeAct 2.9"
+#include "graphics/eink/Drivers/ZJY200200_0154DAAMFGN.h" // WeACt 1.54"
#if !defined(INKHUD_BUILDCONF_DRIVER) || !defined(INKHUD_BUILDCONF_DISPLAYRESILIENCE)
#error If not using a DIY preset, display model and resilience must be set manually
diff --git a/variants/nrf52840/heltec_mesh_node_t114-inkhud/nicheGraphics.h b/variants/nrf52840/heltec_mesh_node_t114-inkhud/nicheGraphics.h
index ad17e7457..17865996b 100644
--- a/variants/nrf52840/heltec_mesh_node_t114-inkhud/nicheGraphics.h
+++ b/variants/nrf52840/heltec_mesh_node_t114-inkhud/nicheGraphics.h
@@ -6,25 +6,24 @@
// InkHUD-specific components
// ---------------------------
-#include "graphics/niche/InkHUD/InkHUD.h"
+#include "graphics/niche/InkHUD.h"
+#include "graphics/niche/Inputs/TwoButton.h"
// Applets
-#include "graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/DM/DMApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/RecentsList/RecentsListApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/ThreadedMessage/ThreadedMessageApplet.h"
+#include "graphics/niche/Applets/User/AllMessage/AllMessageApplet.h"
+#include "graphics/niche/Applets/User/DM/DMApplet.h"
+#include "graphics/niche/Applets/User/FavoritesMap/FavoritesMapApplet.h"
+#include "graphics/niche/Applets/User/Heard/HeardApplet.h"
+#include "graphics/niche/Applets/User/Positions/PositionsApplet.h"
+#include "graphics/niche/Applets/User/RecentsList/RecentsListApplet.h"
+#include "graphics/niche/Applets/User/ThreadedMessage/ThreadedMessageApplet.h"
// Shared NicheGraphics components
// --------------------------------
-#include "graphics/niche/Drivers/EInk/HINK_E0213A289.h" // WeAct 2.13"
-#include "graphics/niche/Drivers/EInk/HINK_E042A87.h" // WeAct 4.2"
-#include "graphics/niche/Drivers/EInk/ZJY128296_029EAAMFGN.h" // WeAct 2.9"
-#include "graphics/niche/Drivers/EInk/ZJY200200_0154DAAMFGN.h" // WeACt 1.54"
-
-#include "graphics/niche/Inputs/TwoButton.h"
+#include "graphics/eink/Drivers/HINK_E0213A289.h" // WeAct 2.13"
+#include "graphics/eink/Drivers/HINK_E042A87.h" // WeAct 4.2"
+#include "graphics/eink/Drivers/ZJY128296_029EAAMFGN.h" // WeAct 2.9"
+#include "graphics/eink/Drivers/ZJY200200_0154DAAMFGN.h" // WeACt 1.54"
#if !defined(INKHUD_BUILDCONF_DRIVER) || !defined(INKHUD_BUILDCONF_DISPLAYRESILIENCE)
// cppcheck-suppress preprocessorErrorDirective
diff --git a/variants/nrf52840/heltec_mesh_pocket/nicheGraphics.h b/variants/nrf52840/heltec_mesh_pocket/nicheGraphics.h
index 187022ea7..5d9ee9b15 100644
--- a/variants/nrf52840/heltec_mesh_pocket/nicheGraphics.h
+++ b/variants/nrf52840/heltec_mesh_pocket/nicheGraphics.h
@@ -1,92 +1,80 @@
+/*
+
+NicheGraphics setup for Heltec Mesh Pocket (LCMEN2R13ECC1 2.13" on SPI1).
+Shared by both BaseUI and InkHUD envs (5000 and 10000 mAh variants).
+
+*/
+
#pragma once
#include "configuration.h"
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
-// InkHUD-specific components
-// ---------------------------
-#include "graphics/niche/InkHUD/InkHUD.h"
+#include "graphics/BaseUIEInkDisplay.h"
+#include "graphics/eink/Panels/LCMEN2R13ECC1.h"
-// Applets
-#include "graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/DM/DMApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/RecentsList/RecentsListApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/ThreadedMessage/ThreadedMessageApplet.h"
-
-// Shared NicheGraphics components
-// --------------------------------
-#include "graphics/niche/Drivers/EInk/LCMEN2R13ECC1.h"
+#ifdef MESHTASTIC_INCLUDE_INKHUD
+#include "graphics/niche/Applets/User/AllMessage/AllMessageApplet.h"
+#include "graphics/niche/Applets/User/DM/DMApplet.h"
+#include "graphics/niche/Applets/User/FavoritesMap/FavoritesMapApplet.h"
+#include "graphics/niche/Applets/User/Heard/HeardApplet.h"
+#include "graphics/niche/Applets/User/Positions/PositionsApplet.h"
+#include "graphics/niche/Applets/User/RecentsList/RecentsListApplet.h"
+#include "graphics/niche/Applets/User/ThreadedMessage/ThreadedMessageApplet.h"
+#include "graphics/niche/InkHUD.h"
#include "graphics/niche/Inputs/TwoButton.h"
+#endif
+#ifdef MESHTASTIC_INCLUDE_INKHUD
void setupNicheGraphics()
{
using namespace NicheGraphics;
- // SPI
- // -----------------------------
-
- // For NRF52 platforms, SPI pins are defined in variant.h
- SPI1.begin();
-
- // E-Ink Driver
- // -----------------------------
-
- Drivers::EInk *driver = new Drivers::LCMEN2R13ECC1;
- driver->begin(&SPI1, PIN_EINK_DC, PIN_EINK_CS, PIN_EINK_BUSY, PIN_EINK_RES);
-
- // InkHUD
- // ----------------------------
+ auto *panel = new Panels::LCMEN2R13ECC1();
+ Drivers::EInk *driver = panel->create();
InkHUD::InkHUD *inkhud = InkHUD::InkHUD::getInstance();
-
- // Set the E-Ink driver
inkhud->setDriver(driver);
-
- // Set how many FAST updates per FULL update
- // Set how unhealthy additional FAST updates beyond this number are
inkhud->setDisplayResilience(10, 1.5);
- // Select fonts
InkHUD::Applet::fontLarge = FREESANS_12PT_WIN1253;
InkHUD::Applet::fontMedium = FREESANS_9PT_WIN1253;
InkHUD::Applet::fontSmall = FREESANS_6PT_WIN1253;
- // Customize default settings
- inkhud->persistence->settings.userTiles.maxCount = 2; // How many tiles can the display handle?
- inkhud->persistence->settings.rotation = 3; // 270 degrees clockwise
- inkhud->persistence->settings.userTiles.count = 1; // One tile only by default, keep things simple for new users
+ inkhud->persistence->settings.userTiles.maxCount = 2;
+ inkhud->persistence->settings.rotation = 3;
+ inkhud->persistence->settings.userTiles.count = 1;
inkhud->persistence->settings.optionalMenuItems.nextTile = true;
- // Pick applets
- // Note: order of applets determines priority of "auto-show" feature
- inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet, true, true); // Activated, autoshown
- inkhud->addApplet("DMs", new InkHUD::DMApplet); // -
- inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0)); // -
- inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1)); // -
- inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true); // Activated
- inkhud->addApplet("Favorites Map", new InkHUD::FavoritesMapApplet); // -
- inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet); // -
- inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0); // Activated, no autoshow, default on tile 0
+ inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet, true, true);
+ inkhud->addApplet("DMs", new InkHUD::DMApplet);
+ inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0));
+ inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1));
+ inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true);
+ inkhud->addApplet("Favorites Map", new InkHUD::FavoritesMapApplet);
+ inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet);
+ inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0);
- // Start running InkHUD
inkhud->begin();
- // Buttons
- // --------------------------
-
- Inputs::TwoButton *buttons = Inputs::TwoButton::getInstance(); // Shared NicheGraphics component
-
- // #0: Main User Button
+ Inputs::TwoButton *buttons = Inputs::TwoButton::getInstance();
buttons->setWiring(0, Inputs::TwoButton::getUserButtonPin());
buttons->setHandlerShortPress(0, [inkhud]() { inkhud->shortpress(); });
buttons->setHandlerLongPress(0, [inkhud]() { inkhud->longpress(); });
-
- // Begin handling button events
buttons->start();
}
+#else
+void setupNicheGraphics() {}
+NicheGraphics::BaseUIEInkDisplay *setupNicheGraphicsBaseUI()
+{
+ auto *panel = new NicheGraphics::Panels::LCMEN2R13ECC1();
+ NicheGraphics::Drivers::EInk *driver = panel->create();
+ auto *display = new NicheGraphics::BaseUIEInkDisplay(driver, panel->rotation());
+ display->setDisplayResilience(10, 1.5f);
+ return display;
+}
#endif
+
+#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
diff --git a/variants/nrf52840/heltec_mesh_pocket/platformio.ini b/variants/nrf52840/heltec_mesh_pocket/platformio.ini
index c9b599382..7cd6d00dc 100644
--- a/variants/nrf52840/heltec_mesh_pocket/platformio.ini
+++ b/variants/nrf52840/heltec_mesh_pocket/platformio.ini
@@ -4,7 +4,7 @@ custom_meshtastic_support_level = 1
custom_meshtastic_images = heltec_mesh_pocket.svg
custom_meshtastic_tags = Heltec
-extends = nrf52840_base
+extends = nrf52840_base, niche
board = heltec_mesh_pocket
debug_tool = jlink
custom_device_hw_model = 94
@@ -17,29 +17,16 @@ custom_meshtastic_variant = 5000mAh
custom_meshtastic_key = heltec_mesh_pocket
custom_meshtastic_has_ink_hud = true
-# add -DCFG_SYSVIEW if you want to use the Segger systemview tool for OS profiling.
build_flags = ${nrf52840_base.build_flags}
+ ${niche.build_flags}
-Ivariants/nrf52840/heltec_mesh_pocket
-DHELTEC_MESH_POCKET
-DHELTEC_MESH_POCKET_BATTERY_5000
-DUSE_EINK
- -DEINK_DISPLAY_MODEL=GxEPD2_213_B74
- -DEINK_WIDTH=250
- -DEINK_HEIGHT=122
- -DUSE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk
- -DEINK_LIMIT_FASTREFRESH=10 ; How many consecutive fast-refreshes are permitted
- -DEINK_LIMIT_RATE_BACKGROUND_SEC=30 ; Minimum interval between BACKGROUND updates
- -DEINK_LIMIT_RATE_RESPONSIVE_SEC=1 ; Minimum interval between RESPONSIVE updates
-; -D EINK_LIMIT_GHOSTING_PX=2000 ; (Optional) How much image ghosting is tolerated
- -DEINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached.
- -DEINK_HASQUIRK_GHOSTING ; Display model is identified as "prone to ghosting"
- -DEINK_HASQUIRK_WEAKFASTREFRESH ; Pixels set with fast-refresh are easy to clear, disrupted by sunlight
-build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/heltec_mesh_pocket>
-lib_deps =
+build_src_filter = ${nrf52_base.build_src_filter} ${niche.build_src_filter} +<../variants/nrf52840/heltec_mesh_pocket>
+lib_deps =
${nrf52840_base.lib_deps}
- # renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master
- https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip
[env:heltec-mesh-pocket-5000-inkhud]
extends = nrf52840_base, inkhud
@@ -52,9 +39,9 @@ custom_meshtastic_actively_supported = true
custom_meshtastic_variant = 5000mAh InkHUD
custom_meshtastic_key = heltec_mesh_pocket
build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/heltec_mesh_pocket> ${inkhud.build_src_filter}
-build_flags =
+build_flags =
${inkhud.build_flags}
- ${nrf52840_base.build_flags}
+ ${nrf52840_base.build_flags}
-I variants/nrf52840/heltec_mesh_pocket
-D HELTEC_MESH_POCKET
-D HELTEC_MESH_POCKET_BATTERY_5000
@@ -69,7 +56,7 @@ custom_meshtastic_support_level = 1
custom_meshtastic_images = heltec_mesh_pocket.svg
custom_meshtastic_tags = Heltec
-extends = nrf52840_base
+extends = nrf52840_base, niche
board = heltec_mesh_pocket
debug_tool = jlink
custom_meshtastic_hw_model = 94
@@ -81,29 +68,16 @@ custom_meshtastic_variant = 10000mAh
custom_meshtastic_key = heltec_mesh_pocket
custom_meshtastic_has_ink_hud = true
-# add -DCFG_SYSVIEW if you want to use the Segger systemview tool for OS profiling.
build_flags = ${nrf52840_base.build_flags}
+ ${niche.build_flags}
-Ivariants/nrf52840/heltec_mesh_pocket
-DHELTEC_MESH_POCKET
-DHELTEC_MESH_POCKET_BATTERY_10000
-DUSE_EINK
- -DEINK_DISPLAY_MODEL=GxEPD2_213_B74
- -DEINK_WIDTH=250
- -DEINK_HEIGHT=122
- -DUSE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk
- -DEINK_LIMIT_FASTREFRESH=10 ; How many consecutive fast-refreshes are permitted
- -DEINK_LIMIT_RATE_BACKGROUND_SEC=30 ; Minimum interval between BACKGROUND updates
- -DEINK_LIMIT_RATE_RESPONSIVE_SEC=1 ; Minimum interval between RESPONSIVE updates
-; -D EINK_LIMIT_GHOSTING_PX=2000 ; (Optional) How much image ghosting is tolerated
- -DEINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached.
- -DEINK_HASQUIRK_GHOSTING ; Display model is identified as "prone to ghosting"
- -DEINK_HASQUIRK_WEAKFASTREFRESH ; Pixels set with fast-refresh are easy to clear, disrupted by sunlight
-build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/heltec_mesh_pocket>
-lib_deps =
+build_src_filter = ${nrf52_base.build_src_filter} ${niche.build_src_filter} +<../variants/nrf52840/heltec_mesh_pocket>
+lib_deps =
${nrf52840_base.lib_deps}
- # renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master
- https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip
[env:heltec-mesh-pocket-10000-inkhud]
extends = nrf52840_base, inkhud
@@ -116,9 +90,9 @@ custom_meshtastic_actively_supported = true
custom_meshtastic_variant = 10000mAh InkHUD
custom_meshtastic_key = heltec_mesh_pocket
build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/heltec_mesh_pocket> ${inkhud.build_src_filter}
-build_flags =
+build_flags =
${inkhud.build_flags}
- ${nrf52840_base.build_flags}
+ ${nrf52840_base.build_flags}
-I variants/nrf52840/heltec_mesh_pocket
-D HELTEC_MESH_POCKET
-D HELTEC_MESH_POCKET_BATTERY_10000
diff --git a/variants/nrf52840/heltec_mesh_solar/nicheGraphics.h b/variants/nrf52840/heltec_mesh_solar/nicheGraphics.h
index 0f4131916..acb8e2892 100644
--- a/variants/nrf52840/heltec_mesh_solar/nicheGraphics.h
+++ b/variants/nrf52840/heltec_mesh_solar/nicheGraphics.h
@@ -1,92 +1,93 @@
+/*
+
+NicheGraphics setup for Heltec MeshSolar E-Ink (E0213A367 2.13" on SPI1).
+Shared by both BaseUI (heltec-mesh-solar-eink) and InkHUD (heltec-mesh-solar-inkhud).
+
+*/
+
#pragma once
#include "configuration.h"
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
-// InkHUD-specific components
-// ---------------------------
-#include "graphics/niche/InkHUD/InkHUD.h"
+#include "graphics/BaseUIEInkDisplay.h"
+#include "graphics/eink/Panels/E0213A367.h"
-// Applets
-#include "graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/DM/DMApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/RecentsList/RecentsListApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/ThreadedMessage/ThreadedMessageApplet.h"
-
-// Shared NicheGraphics components
-// --------------------------------
-#include "graphics/niche/Drivers/EInk/E0213A367.h"
+#ifdef MESHTASTIC_INCLUDE_INKHUD
+#include "graphics/niche/Applets/User/AllMessage/AllMessageApplet.h"
+#include "graphics/niche/Applets/User/DM/DMApplet.h"
+#include "graphics/niche/Applets/User/FavoritesMap/FavoritesMapApplet.h"
+#include "graphics/niche/Applets/User/Heard/HeardApplet.h"
+#include "graphics/niche/Applets/User/Positions/PositionsApplet.h"
+#include "graphics/niche/Applets/User/RecentsList/RecentsListApplet.h"
+#include "graphics/niche/Applets/User/ThreadedMessage/ThreadedMessageApplet.h"
+#include "graphics/niche/InkHUD.h"
#include "graphics/niche/Inputs/TwoButton.h"
+#endif
+class HeltecMeshSolarPanel : public NicheGraphics::Panels::E0213A367
+{
+ protected:
+ void prePowerOn() override
+ {
+#ifdef PIN_SCREEN_VDD_CTL
+ pinMode(PIN_SCREEN_VDD_CTL, OUTPUT);
+ digitalWrite(PIN_SCREEN_VDD_CTL, HIGH);
+ delay(10);
+#endif
+ }
+};
+
+#ifdef MESHTASTIC_INCLUDE_INKHUD
void setupNicheGraphics()
{
using namespace NicheGraphics;
- // SPI
- // -----------------------------
-
- // For NRF52 platforms, SPI pins are defined in variant.h
- SPI1.begin();
-
- // E-Ink Driver
- // -----------------------------
-
- Drivers::EInk *driver = new Drivers::E0213A367;
- driver->begin(&SPI1, PIN_EINK_DC, PIN_EINK_CS, PIN_EINK_BUSY, PIN_EINK_RES);
-
- // InkHUD
- // ----------------------------
+ auto *panel = new HeltecMeshSolarPanel();
+ Drivers::EInk *driver = panel->create();
InkHUD::InkHUD *inkhud = InkHUD::InkHUD::getInstance();
-
- // Set the E-Ink driver
inkhud->setDriver(driver);
-
- // Set how many FAST updates per FULL update
- // Set how unhealthy additional FAST updates beyond this number are
inkhud->setDisplayResilience(10, 1.5);
- // Select fonts
InkHUD::Applet::fontLarge = FREESANS_12PT_WIN1252;
InkHUD::Applet::fontMedium = FREESANS_9PT_WIN1252;
InkHUD::Applet::fontSmall = FREESANS_6PT_WIN1252;
- // Customize default settings
- inkhud->persistence->settings.userTiles.maxCount = 2; // How many tiles can the display handle?
- inkhud->persistence->settings.rotation = 3; // 270 degrees clockwise
- inkhud->persistence->settings.userTiles.count = 1; // One tile only by default, keep things simple for new users
+ inkhud->persistence->settings.userTiles.maxCount = 2;
+ inkhud->persistence->settings.rotation = 3;
+ inkhud->persistence->settings.userTiles.count = 1;
inkhud->persistence->settings.optionalMenuItems.nextTile = true;
- // Pick applets
- // Note: order of applets determines priority of "auto-show" feature
- inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet, true, true); // Activated, autoshown
- inkhud->addApplet("DMs", new InkHUD::DMApplet); // -
- inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0)); // -
- inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1)); // -
- inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true); // Activated
- inkhud->addApplet("Favorites Map", new InkHUD::FavoritesMapApplet); // -
- inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet); // -
- inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0); // Activated, no autoshow, default on tile 0
+ inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet, true, true);
+ inkhud->addApplet("DMs", new InkHUD::DMApplet);
+ inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0));
+ inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1));
+ inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true);
+ inkhud->addApplet("Favorites Map", new InkHUD::FavoritesMapApplet);
+ inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet);
+ inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0);
- // Start running InkHUD
inkhud->begin();
- // Buttons
- // --------------------------
-
- Inputs::TwoButton *buttons = Inputs::TwoButton::getInstance(); // Shared NicheGraphics component
-
- // #0: Main User Button
+ Inputs::TwoButton *buttons = Inputs::TwoButton::getInstance();
buttons->setWiring(0, Inputs::TwoButton::getUserButtonPin());
buttons->setHandlerShortPress(0, [inkhud]() { inkhud->shortpress(); });
buttons->setHandlerLongPress(0, [inkhud]() { inkhud->longpress(); });
-
- // Begin handling button events
buttons->start();
}
+#else
+void setupNicheGraphics() {}
+NicheGraphics::BaseUIEInkDisplay *setupNicheGraphicsBaseUI()
+{
+ auto *panel = new HeltecMeshSolarPanel();
+ NicheGraphics::Drivers::EInk *driver = panel->create();
+ auto *display = new NicheGraphics::BaseUIEInkDisplay(driver, panel->rotation());
+ display->setDisplayResilience(10, 1.5f);
+ return display;
+}
#endif
+
+#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
diff --git a/variants/nrf52840/heltec_mesh_solar/platformio.ini b/variants/nrf52840/heltec_mesh_solar/platformio.ini
index 1b6f59a68..eaed69d1a 100644
--- a/variants/nrf52840/heltec_mesh_solar/platformio.ini
+++ b/variants/nrf52840/heltec_mesh_solar/platformio.ini
@@ -11,7 +11,7 @@ build_flags = ${nrf52840_base.build_flags}
-DHELTEC_MESH_SOLAR
build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/heltec_mesh_solar>
-lib_deps =
+lib_deps =
${nrf52840_base.lib_deps}
# renovate: datasource=git-refs depName=NMIoT-meshsolar packageName=https://github.com/NMIoT/meshsolar gitBranch=main
https://github.com/NMIoT/meshsolar/archive/dfc5330dad443982e6cdd37a61d33fc7252f468b.zip
@@ -33,8 +33,10 @@ build_flags = ${heltec_mesh_solar_base.build_flags}
-DSPI_INTERFACES_COUNT=1
[env:heltec-mesh-solar-eink]
-extends = heltec_mesh_solar_base
+extends = heltec_mesh_solar_base, niche
+build_src_filter = ${heltec_mesh_solar_base.build_src_filter} ${niche.build_src_filter}
build_flags = ${heltec_mesh_solar_base.build_flags}
+ ${niche.build_flags}
-DHELTEC_MESH_SOLAR_EINK
-DSPI_INTERFACES_COUNT=2
-DUSE_EINK
@@ -48,15 +50,6 @@ build_flags = ${heltec_mesh_solar_base.build_flags}
-DPIN_SPI1_MISO=-1
-DPIN_SPI1_MOSI=PIN_EINK_MOSI
-DPIN_SPI1_SCK=PIN_EINK_SCLK
- -DEINK_DISPLAY_MODEL=GxEPD2_213_E0213A367
- -DEINK_WIDTH=250
- -DEINK_HEIGHT=122
- -DUSE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk
- -DEINK_LIMIT_FASTREFRESH=10 ; How many consecutive fast-refreshes are permitted
- -DEINK_LIMIT_RATE_BACKGROUND_SEC=30 ; Minimum interval between BACKGROUND updates
- -DEINK_LIMIT_RATE_RESPONSIVE_SEC=1 ; Minimum interval between RESPONSIVE updates
- -DEINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached.
- -DEINK_HASQUIRK_GHOSTING ; Display model is identified as "prone to ghosting"
-DENABLE_MESSAGE_PERSISTENCE=0 ; Disable flash persistence for space-limited build
-DMESHTASTIC_EXCLUDE_WIFI=1
-DMESHTASTIC_EXCLUDE_EXTERNALNOTIFICATION=1
@@ -67,8 +60,6 @@ build_flags = ${heltec_mesh_solar_base.build_flags}
-DMESHTASTIC_EXCLUDE_WAYPOINT=1
lib_deps =
${heltec_mesh_solar_base.lib_deps}
- # renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master
- https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip
[env:heltec-mesh-solar-inkhud]
extends = heltec_mesh_solar_base, inkhud
diff --git a/variants/nrf52840/meshlink/nicheGraphics.h b/variants/nrf52840/meshlink/nicheGraphics.h
new file mode 100644
index 000000000..1db0de9fa
--- /dev/null
+++ b/variants/nrf52840/meshlink/nicheGraphics.h
@@ -0,0 +1,28 @@
+/*
+
+NicheGraphics setup for MeshLink (LoraItalia) ePaper build (GDEY0213B74 2.13" on SPI1).
+Only used by env:meshlink_eink (the env:meshlink build has no display).
+
+*/
+
+#pragma once
+
+#include "configuration.h"
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+#include "graphics/BaseUIEInkDisplay.h"
+#include "graphics/eink/Panels/GDEY0213B74.h"
+
+void setupNicheGraphics() {}
+
+NicheGraphics::BaseUIEInkDisplay *setupNicheGraphicsBaseUI()
+{
+ auto *panel = new NicheGraphics::Panels::GDEY0213B74();
+ NicheGraphics::Drivers::EInk *driver = panel->create();
+ auto *display = new NicheGraphics::BaseUIEInkDisplay(driver, panel->rotation());
+ display->setDisplayResilience(5, 1.5f);
+ return display;
+}
+
+#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
diff --git a/variants/nrf52840/meshlink/platformio.ini b/variants/nrf52840/meshlink/platformio.ini
index f3dc6185c..5d2770d14 100644
--- a/variants/nrf52840/meshlink/platformio.ini
+++ b/variants/nrf52840/meshlink/platformio.ini
@@ -21,11 +21,12 @@ debug_tool = jlink
;upload_protocol = jlink
[env:meshlink_eink]
-extends = nrf52840_base
+extends = nrf52840_base, niche
board = meshlink
board_level = extra
;board_check = true
build_flags = ${nrf52840_base.build_flags}
+ ${niche.build_flags}
-I variants/nrf52840/meshlink
-D MESHLINK
-DRADIOLIB_EXCLUDE_SX128X=1
@@ -33,23 +34,10 @@ build_flags = ${nrf52840_base.build_flags}
-DRADIOLIB_EXCLUDE_LR11X0=1
-DRADIOLIB_EXCLUDE_LR2021=1
-D USE_EINK
- -D EINK_DISPLAY_MODEL=GxEPD2_213_B74
- -D EINK_WIDTH=250
- -D EINK_HEIGHT=122
- -D USE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk
- -D EINK_LIMIT_FASTREFRESH=5 ; How many consecutive fast-refreshes are permitted
- -D EINK_LIMIT_RATE_BACKGROUND_SEC=30 ; Minimum interval between BACKGROUND updates
- -D EINK_LIMIT_RATE_RESPONSIVE_SEC=1 ; Minimum interval between RESPONSIVE updates
- -D EINK_LIMIT_GHOSTING_PX=2000 ; (Optional) How much image ghosting is tolerated
- -D EINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached.
- -D EINK_HASQUIRK_VICIOUSFASTREFRESH ; Identify that pixels drawn by fast-refresh are harder to clear
-
-build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/meshlink>
-lib_deps =
+build_src_filter = ${nrf52_base.build_src_filter} ${niche.build_src_filter} +<../variants/nrf52840/meshlink>
+lib_deps =
${nrf52840_base.lib_deps}
- # renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master
- https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip
debug_tool = jlink
; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm)
; Note: as of 6/2013 the serial/bootloader based programming takes approximately 30 seconds
diff --git a/variants/nrf52840/rak4631_epaper/nicheGraphics.h b/variants/nrf52840/rak4631_epaper/nicheGraphics.h
new file mode 100644
index 000000000..125b588ca
--- /dev/null
+++ b/variants/nrf52840/rak4631_epaper/nicheGraphics.h
@@ -0,0 +1,43 @@
+/*
+
+NicheGraphics setup for RAK4631 + RAK14000 ePaper module (2.13" BN, DEPG0213BNS800 on SPI1).
+BaseUI build only - no InkHUD env.
+
+*/
+
+#pragma once
+
+#include "configuration.h"
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+#include "graphics/BaseUIEInkDisplay.h"
+#include "graphics/eink/Panels/DEPG0213BNS800.h"
+
+class RAK4631EpaperPanel : public NicheGraphics::Panels::DEPG0213BNS800
+{
+ protected:
+ SPIClass *beginSpi() override
+ {
+ // RAK14000 is wired to SPI1 via pins declared in variant.h.
+ // Init settings cloned from the legacy GxEPD2 path on this board.
+ SPI1.begin();
+ return &SPI1;
+ }
+};
+
+void setupNicheGraphics()
+{
+ // BaseUI handles buttons through its default ButtonThread. Nothing extra here.
+}
+
+NicheGraphics::BaseUIEInkDisplay *setupNicheGraphicsBaseUI()
+{
+ auto *panel = new RAK4631EpaperPanel();
+ NicheGraphics::Drivers::EInk *driver = panel->create();
+ auto *display = new NicheGraphics::BaseUIEInkDisplay(driver, panel->rotation());
+ display->setDisplayResilience(7, 1.5f);
+ return display;
+}
+
+#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
diff --git a/variants/nrf52840/rak4631_epaper/platformio.ini b/variants/nrf52840/rak4631_epaper/platformio.ini
index f71fb6301..e9a60e352 100644
--- a/variants/nrf52840/rak4631_epaper/platformio.ini
+++ b/variants/nrf52840/rak4631_epaper/platformio.ini
@@ -1,22 +1,18 @@
; The very slick RAK wireless RAK 4631 / 4630 board - Firmware for 5005 with the RAK 14000 ePaper
[env:rak4631_eink]
-extends = nrf52840_base
+extends = nrf52840_base, niche
board = wiscore_rak4631
build_flags = ${nrf52840_base.build_flags}
+ ${niche.build_flags}
-I variants/nrf52840/rak4631_epaper
-D RAK_4631
- -DEINK_DISPLAY_MODEL=GxEPD2_213_BN
- -DEINK_WIDTH=250
- -DEINK_HEIGHT=122
-DRADIOLIB_EXCLUDE_SX128X=1
-DRADIOLIB_EXCLUDE_SX127X=1
-DRADIOLIB_EXCLUDE_LR11X0=1
-DRADIOLIB_EXCLUDE_LR2021=1
-build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/rak4631_epaper>
-lib_deps =
+build_src_filter = ${nrf52_base.build_src_filter} ${niche.build_src_filter} +<../variants/nrf52840/rak4631_epaper>
+lib_deps =
${nrf52840_base.lib_deps}
- # renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2
- zinggjm/GxEPD2@1.6.8
# renovate: datasource=custom.pio depName=Melopero RV3028 packageName=melopero/library/Melopero RV3028
melopero/Melopero RV3028@1.2.0
# renovate: datasource=custom.pio depName=RAK NCP5623 RGB LED packageName=rakwireless/library/RAKwireless NCP5623 RGB LED library
diff --git a/variants/nrf52840/rak4631_epaper_onrxtx/platformio.ini b/variants/nrf52840/rak4631_epaper_onrxtx/platformio.ini
index 670b2c415..b3f400f99 100644
--- a/variants/nrf52840/rak4631_epaper_onrxtx/platformio.ini
+++ b/variants/nrf52840/rak4631_epaper_onrxtx/platformio.ini
@@ -1,24 +1,20 @@
; The very slick RAK wireless RAK 4631 / 4630 board - Firmware for 5005 with the RAK 14000 ePaper
[env:rak4631_eink_onrxtx]
board_level = extra
-extends = nrf52840_base
+extends = nrf52840_base, niche
board = wiscore_rak4631
build_flags = ${nrf52840_base.build_flags}
+ ${niche.build_flags}
-I variants/nrf52840/rak4631_epaper
-D RAK_4631
-D PIN_EINK_EN=34
- -D EINK_DISPLAY_MODEL=GxEPD2_213_BN
- -D EINK_WIDTH=250
- -D EINK_HEIGHT=122
-D RADIOLIB_EXCLUDE_SX128X=1
-D RADIOLIB_EXCLUDE_SX127X=1
-D RADIOLIB_EXCLUDE_LR11X0=1
-D RADIOLIB_EXCLUDE_LR2021=1
-build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/rak4631_epaper_onrxtx>
-lib_deps =
+build_src_filter = ${nrf52_base.build_src_filter} ${niche.build_src_filter} +<../variants/nrf52840/rak4631_epaper_onrxtx>
+lib_deps =
${nrf52840_base.lib_deps}
- # renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2
- zinggjm/GxEPD2@1.6.8
# renovate: datasource=custom.pio depName=Melopero RV3028 packageName=melopero/library/Melopero RV3028
melopero/Melopero RV3028@1.2.0
# renovate: datasource=custom.pio depName=RAK NCP5623 RGB LED packageName=rakwireless/library/RAKwireless NCP5623 RGB LED library
diff --git a/variants/nrf52840/rak4631_eth_gw/platformio.ini b/variants/nrf52840/rak4631_eth_gw/platformio.ini
index 0fded96f4..381bdb40e 100644
--- a/variants/nrf52840/rak4631_eth_gw/platformio.ini
+++ b/variants/nrf52840/rak4631_eth_gw/platformio.ini
@@ -7,9 +7,6 @@ build_flags = ${nrf52840_base.build_flags}
-I variants/nrf52840/rak4631_eth_gw
-D RAK_4631
-DHAS_UDP_MULTICAST=1
- -DEINK_DISPLAY_MODEL=GxEPD2_213_BN
- -DEINK_WIDTH=250
- -DEINK_HEIGHT=122
-DNRF52_USE_JSON=1
-DMESHTASTIC_EXCLUDE_WIFI=1
-DMESHTASTIC_EXCLUDE_SCREEN=1
diff --git a/variants/nrf52840/rak4631_nomadstar_meteor_pro/platformio.ini b/variants/nrf52840/rak4631_nomadstar_meteor_pro/platformio.ini
index f1641e7e4..634f0ab19 100644
--- a/variants/nrf52840/rak4631_nomadstar_meteor_pro/platformio.ini
+++ b/variants/nrf52840/rak4631_nomadstar_meteor_pro/platformio.ini
@@ -15,9 +15,6 @@ board_check = true
build_flags = ${nrf52840_base.build_flags}
-I variants/nrf52840/rak4631_nomadstar_meteor_pro
-D NOMADSTAR_METEOR_PRO
- -DEINK_DISPLAY_MODEL=GxEPD2_213_BN
- -DEINK_WIDTH=250
- -DEINK_HEIGHT=122
-DRADIOLIB_EXCLUDE_SX128X=1
-DRADIOLIB_EXCLUDE_SX127X=1
-DRADIOLIB_EXCLUDE_LR11X0=1
diff --git a/variants/nrf52840/rak_wismeshtap/platformio.ini b/variants/nrf52840/rak_wismeshtap/platformio.ini
index f058d9153..999b6323f 100644
--- a/variants/nrf52840/rak_wismeshtap/platformio.ini
+++ b/variants/nrf52840/rak_wismeshtap/platformio.ini
@@ -15,9 +15,6 @@ build_flags = ${nrf52840_base.build_flags}
-Ivariants/nrf52840/rak_wismeshtap
-DWISMESH_TAP
-DRAK_4631
- -DEINK_DISPLAY_MODEL=GxEPD2_213_BN
- -DEINK_WIDTH=250
- -DEINK_HEIGHT=122
-DMESHTASTIC_EXCLUDE_WIFI=1
-DMESHTASTIC_EXCLUDE_DETECTIONSENSOR=1
-DMESHTASTIC_EXCLUDE_STOREFORWARD=1
diff --git a/variants/nrf52840/seeed_wio_tracker_L1_eink/nicheGraphics.h b/variants/nrf52840/seeed_wio_tracker_L1_eink/nicheGraphics.h
index 2a2967f5e..f6624b621 100644
--- a/variants/nrf52840/seeed_wio_tracker_L1_eink/nicheGraphics.h
+++ b/variants/nrf52840/seeed_wio_tracker_L1_eink/nicheGraphics.h
@@ -1,119 +1,105 @@
+/*
+
+NicheGraphics setup for Seeed Wio Tracker L1 E-Ink (ZJY122250_0213BAAMFGN 2.13" on SPI1).
+Shared by both BaseUI and InkHUD envs.
+
+*/
+
#pragma once
#include "configuration.h"
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
-// InkHUD-specific components
-// ---------------------------
-#include "graphics/niche/InkHUD/InkHUD.h"
+#include "graphics/BaseUIEInkDisplay.h"
+#include "graphics/eink/Panels/ZJY122250_0213BAAMFGN.h"
-// Applets
-#include "graphics/niche/InkHUD/Applets/Examples/UserAppletInputExample/UserAppletInputExample.h"
-#include "graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/DM/DMApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/RecentsList/RecentsListApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/ThreadedMessage/ThreadedMessageApplet.h"
-
-// Shared NicheGraphics components
-// --------------------------------
-#include "graphics/niche/Drivers/EInk/ZJY122250_0213BAAMFGN.h"
+#ifdef MESHTASTIC_INCLUDE_INKHUD
+#include "graphics/niche/Applets/Examples/UserAppletInputExample/UserAppletInputExample.h"
+#include "graphics/niche/Applets/User/AllMessage/AllMessageApplet.h"
+#include "graphics/niche/Applets/User/DM/DMApplet.h"
+#include "graphics/niche/Applets/User/FavoritesMap/FavoritesMapApplet.h"
+#include "graphics/niche/Applets/User/Heard/HeardApplet.h"
+#include "graphics/niche/Applets/User/Positions/PositionsApplet.h"
+#include "graphics/niche/Applets/User/RecentsList/RecentsListApplet.h"
+#include "graphics/niche/Applets/User/ThreadedMessage/ThreadedMessageApplet.h"
+#include "graphics/niche/InkHUD.h"
#include "graphics/niche/Inputs/TwoButtonExtended.h"
+#endif
+#ifdef MESHTASTIC_INCLUDE_INKHUD
void setupNicheGraphics()
{
using namespace NicheGraphics;
- // SPI
- // -----------------------------
-
- // For NRF52 platforms, SPI pins are defined in variant.h
- SPI1.begin();
-
- // E-Ink Driver
- // -----------------------------
-
- Drivers::EInk *driver = new Drivers::ZJY122250_0213BAAMFGN;
- driver->begin(&SPI1, PIN_EINK_DC, PIN_EINK_CS, PIN_EINK_BUSY, PIN_EINK_RES);
-
- // InkHUD
- // ----------------------------
+ auto *panel = new Panels::ZJY122250_0213BAAMFGN();
+ Drivers::EInk *driver = panel->create();
InkHUD::InkHUD *inkhud = InkHUD::InkHUD::getInstance();
-
- // Set the E-Ink driver
inkhud->setDriver(driver);
-
- // Set how many FAST updates per FULL update
inkhud->setDisplayResilience(15);
-
- // Select fonts
InkHUD::Applet::fontLarge = FREESANS_12PT_WIN1252;
InkHUD::Applet::fontMedium = FREESANS_9PT_WIN1252;
InkHUD::Applet::fontSmall = FREESANS_6PT_WIN1252;
- // Customize default settings
- inkhud->persistence->settings.rotation = 1; // 90 degrees clockwise
+ inkhud->persistence->settings.rotation = 1;
#if HAS_TRACKBALL
- inkhud->persistence->settings.joystick.enabled = true; // Device uses a joystick
- inkhud->persistence->settings.joystick.alignment = 3; // 270 degrees
- inkhud->persistence->settings.optionalMenuItems.nextTile = false; // Use joystick instead
+ inkhud->persistence->settings.joystick.enabled = true;
+ inkhud->persistence->settings.joystick.alignment = 3;
+ inkhud->persistence->settings.optionalMenuItems.nextTile = false;
#endif
- inkhud->persistence->settings.optionalFeatures.batteryIcon = true; // Device definitely has a battery
- inkhud->persistence->settings.userTiles.count = 1; // One tile only by default, keep things simple for new users
- inkhud->persistence->settings.userTiles.maxCount = 2; // Two applets side-by-side
inkhud->persistence->settings.optionalFeatures.batteryIcon = true;
+ inkhud->persistence->settings.userTiles.count = 1;
+ inkhud->persistence->settings.userTiles.maxCount = 2;
- // Pick applets
- // Note: order of applets determines priority of "auto-show" feature
- inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet, true, true); // Activated, autoshown
- inkhud->addApplet("DMs", new InkHUD::DMApplet); // -
- inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0)); // -
- inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1)); // -
- inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true); // Activated
- inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet); // -
- inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0); // Activated, no autoshow, default on tile 0
+ inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet, true, true);
+ inkhud->addApplet("DMs", new InkHUD::DMApplet);
+ inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0));
+ inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1));
+ inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true);
+ inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet);
+ inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0);
+ inkhud->addApplet("Favorites Map", new InkHUD::FavoritesMapApplet, false, false);
- inkhud->addApplet("Favorites Map", new InkHUD::FavoritesMapApplet, false, false); // -
- // Start running InkHUD
inkhud->begin();
- // Buttons
- // --------------------------
-
- Inputs::TwoButtonExtended *buttons = Inputs::TwoButtonExtended::getInstance(); // Shared NicheGraphics component
+ Inputs::TwoButtonExtended *buttons = Inputs::TwoButtonExtended::getInstance();
#if HAS_TRACKBALL
- // #0: Exit Button
buttons->setWiring(0, Inputs::TwoButtonExtended::getUserButtonPin());
buttons->setTiming(0, 75, 500);
buttons->setHandlerShortPress(0, [inkhud]() { inkhud->exitShort(); });
buttons->setHandlerLongPress(0, [inkhud]() { inkhud->exitLong(); });
- // #1: Joystick Center
buttons->setWiring(1, TB_PRESS);
buttons->setTiming(1, 75, 500);
buttons->setHandlerShortPress(1, [inkhud]() { inkhud->shortpress(); });
buttons->setHandlerLongPress(1, [inkhud]() { inkhud->longpress(); });
- // Joystick Directions
buttons->setJoystickWiring(TB_UP, TB_DOWN, TB_LEFT, TB_RIGHT);
buttons->setJoystickDebounce(50);
buttons->setJoystickPressHandlers([inkhud]() { inkhud->navUp(); }, [inkhud]() { inkhud->navDown(); },
[inkhud]() { inkhud->navLeft(); }, [inkhud]() { inkhud->navRight(); });
#else
- // #0: User Button
buttons->setWiring(0, Inputs::TwoButtonExtended::getUserButtonPin());
buttons->setTiming(0, 75, 500);
buttons->setHandlerShortPress(0, [inkhud]() { inkhud->shortpress(); });
buttons->setHandlerLongPress(0, [inkhud]() { inkhud->longpress(); });
#endif
- // Begin handling button events
buttons->start();
}
+#else
+void setupNicheGraphics() {}
+NicheGraphics::BaseUIEInkDisplay *setupNicheGraphicsBaseUI()
+{
+ auto *panel = new NicheGraphics::Panels::ZJY122250_0213BAAMFGN();
+ NicheGraphics::Drivers::EInk *driver = panel->create();
+ auto *display = new NicheGraphics::BaseUIEInkDisplay(driver, panel->rotation());
+ display->setDisplayResilience(10, 1.5f);
+ return display;
+}
#endif
+
+#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
diff --git a/variants/nrf52840/seeed_wio_tracker_L1_eink/platformio.ini b/variants/nrf52840/seeed_wio_tracker_L1_eink/platformio.ini
index 26f4de565..067769de8 100644
--- a/variants/nrf52840/seeed_wio_tracker_L1_eink/platformio.ini
+++ b/variants/nrf52840/seeed_wio_tracker_L1_eink/platformio.ini
@@ -10,45 +10,33 @@ custom_meshtastic_tags = Seeed
custom_meshtastic_has_ink_hud = true
board = seeed_wio_tracker_L1
-extends = nrf52840_base
+extends = nrf52840_base, niche
;board_level = extra
build_flags = ${nrf52840_base.build_flags}
+ ${niche.build_flags}
-I variants/nrf52840/seeed_wio_tracker_L1_eink
-D SEEED_WIO_TRACKER_L1_EINK
-D SEEED_WIO_TRACKER_L1
-I src/platform/nrf52/softdevice
-I src/platform/nrf52/softdevice/nrf52
-DUSE_EINK
- -DEINK_DISPLAY_MODEL=GxEPD2_213_B74
- -DEINK_WIDTH=250
- -DEINK_HEIGHT=122
- -DUSE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk
- -DEINK_LIMIT_FASTREFRESH=10 ; How many consecutive fast-refreshes are permitted
- -DEINK_LIMIT_RATE_BACKGROUND_SEC=30 ; Minimum interval between BACKGROUND updates
- -DEINK_LIMIT_RATE_RESPONSIVE_SEC=1 ; Minimum interval between RESPONSIVE updates
-; -D EINK_LIMIT_GHOSTING_PX=2000 ; (Optional) How much image ghosting is tolerated
- -DEINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached.
- -DEINK_HASQUIRK_GHOSTING ; Display model is identified as "prone to ghosting"
- -DEINK_HASQUIRK_WEAKFASTREFRESH ; Pixels set with fast-refresh are easy to clear, disrupted by sunlight
board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld
-build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/seeed_wio_tracker_L1_eink>
+build_src_filter = ${nrf52_base.build_src_filter} ${niche.build_src_filter} +<../variants/nrf52840/seeed_wio_tracker_L1_eink>
lib_deps =
${nrf52840_base.lib_deps}
- # renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master
- https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip
debug_tool = jlink
[env:seeed_wio_tracker_L1_eink-inkhud]
board = seeed_wio_tracker_L1
extends = nrf52840_base, inkhud
-build_flags =
+build_flags =
${nrf52840_base.build_flags}
${inkhud.build_flags}
-I variants/nrf52840/seeed_wio_tracker_L1_eink
-D SEEED_WIO_TRACKER_L1
-D BUTTON_PIN=D13
board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld
-build_src_filter =
+build_src_filter =
${nrf52_base.build_src_filter}
${inkhud.build_src_filter}
+<../variants/nrf52840/seeed_wio_tracker_L1_eink>
diff --git a/variants/nrf52840/t-echo-lite/nicheGraphics.h b/variants/nrf52840/t-echo-lite/nicheGraphics.h
new file mode 100644
index 000000000..f904702c4
--- /dev/null
+++ b/variants/nrf52840/t-echo-lite/nicheGraphics.h
@@ -0,0 +1,27 @@
+/*
+
+NicheGraphics setup for LILYGO T-Echo Lite (GDEH0122T61 1.22" on SPI1).
+
+*/
+
+#pragma once
+
+#include "configuration.h"
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+#include "graphics/BaseUIEInkDisplay.h"
+#include "graphics/eink/Panels/GDEH0122T61.h"
+
+void setupNicheGraphics() {}
+
+NicheGraphics::BaseUIEInkDisplay *setupNicheGraphicsBaseUI()
+{
+ auto *panel = new NicheGraphics::Panels::GDEH0122T61();
+ NicheGraphics::Drivers::EInk *driver = panel->create();
+ auto *display = new NicheGraphics::BaseUIEInkDisplay(driver, panel->rotation());
+ display->setDisplayResilience(20, 1.5f);
+ return display;
+}
+
+#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
diff --git a/variants/nrf52840/t-echo-lite/platformio.ini b/variants/nrf52840/t-echo-lite/platformio.ini
index 1b725815e..e81272037 100644
--- a/variants/nrf52840/t-echo-lite/platformio.ini
+++ b/variants/nrf52840/t-echo-lite/platformio.ini
@@ -9,26 +9,18 @@ custom_meshtastic_display_name = LILYGO T-Echo Lite
custom_meshtastic_images = techo_lite.svg
custom_meshtastic_tags = LilyGo
-extends = nrf52840_base
+extends = nrf52840_base, niche
board = t-echo
board_check = true
debug_tool = jlink
-# add -DCFG_SYSVIEW if you want to use the Segger systemview tool for OS profiling.
build_flags = ${nrf52840_base.build_flags}
+ ${niche.build_flags}
-Ivariants/nrf52840/t-echo-lite
-D T_ECHO_LITE
- -D EINK_DISPLAY_MODEL=GxEPD2_122_T61
- -D EINK_WIDTH=192
- -D EINK_HEIGHT=176
-D USE_EINK
- -D USE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk
- -D EINK_LIMIT_FASTREFRESH=20 ; How many consecutive fast-refreshes are permitted
- -D EINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached.
-build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/t-echo-lite>
-lib_deps =
+build_src_filter = ${nrf52_base.build_src_filter} ${niche.build_src_filter} +<../variants/nrf52840/t-echo-lite>
+lib_deps =
${nrf52840_base.lib_deps}
- # renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master
- https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip
;upload_protocol = fs
diff --git a/variants/nrf52840/t-echo-plus/nicheGraphics.h b/variants/nrf52840/t-echo-plus/nicheGraphics.h
index 73067d7a7..733feebb8 100644
--- a/variants/nrf52840/t-echo-plus/nicheGraphics.h
+++ b/variants/nrf52840/t-echo-plus/nicheGraphics.h
@@ -1,29 +1,39 @@
+/*
+
+NicheGraphics setup for LILYGO T-Echo Plus (GDEY0154D67 1.54" on SPI1).
+Shared by both BaseUI and InkHUD envs.
+
+*/
+
#pragma once
#include "configuration.h"
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
-#include "graphics/niche/Drivers/Backlight/LatchingBacklight.h"
-#include "graphics/niche/Drivers/EInk/GDEY0154D67.h"
-#include "graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/DM/DMApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/RecentsList/RecentsListApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/ThreadedMessage/ThreadedMessageApplet.h"
-#include "graphics/niche/InkHUD/InkHUD.h"
-#include "graphics/niche/Inputs/TwoButton.h"
+#include "graphics/BaseUIEInkDisplay.h"
+#include "graphics/eink/Backlight/LatchingBacklight.h"
+#include "graphics/eink/Panels/GDEY0154D67.h"
+#ifdef MESHTASTIC_INCLUDE_INKHUD
+#include "graphics/niche/Applets/User/AllMessage/AllMessageApplet.h"
+#include "graphics/niche/Applets/User/DM/DMApplet.h"
+#include "graphics/niche/Applets/User/FavoritesMap/FavoritesMapApplet.h"
+#include "graphics/niche/Applets/User/Heard/HeardApplet.h"
+#include "graphics/niche/Applets/User/Positions/PositionsApplet.h"
+#include "graphics/niche/Applets/User/RecentsList/RecentsListApplet.h"
+#include "graphics/niche/Applets/User/ThreadedMessage/ThreadedMessageApplet.h"
+#include "graphics/niche/InkHUD.h"
+#include "graphics/niche/Inputs/TwoButton.h"
+#endif
+
+#ifdef MESHTASTIC_INCLUDE_INKHUD
void setupNicheGraphics()
{
using namespace NicheGraphics;
- SPI1.begin();
-
- Drivers::EInk *driver = new Drivers::GDEY0154D67;
- driver->begin(&SPI1, PIN_EINK_DC, PIN_EINK_CS, PIN_EINK_BUSY, PIN_EINK_RES);
+ auto *panel = new Panels::GDEY0154D67();
+ Drivers::EInk *driver = panel->create();
InkHUD::InkHUD *inkhud = InkHUD::InkHUD::getInstance();
inkhud->setDriver(driver);
@@ -51,12 +61,12 @@ void setupNicheGraphics()
inkhud->begin();
Inputs::TwoButton *buttons = Inputs::TwoButton::getInstance();
-
buttons->setWiring(0, Inputs::TwoButton::getUserButtonPin());
buttons->setTiming(0, 75, 500);
buttons->setHandlerShortPress(0, [inkhud]() { inkhud->shortpress(); });
buttons->setHandlerLongPress(0, [inkhud]() { inkhud->longpress(); });
+#ifdef PIN_BUTTON_TOUCH
buttons->setWiring(1, PIN_BUTTON_TOUCH);
buttons->setTiming(1, 50, 5000);
buttons->setHandlerDown(1, [inkhud, backlight]() {
@@ -65,8 +75,21 @@ void setupNicheGraphics()
});
buttons->setHandlerLongPress(1, [backlight]() { backlight->latch(); });
buttons->setHandlerShortPress(1, [backlight]() { backlight->off(); });
+#endif
buttons->start();
}
+#else
+void setupNicheGraphics() {}
+NicheGraphics::BaseUIEInkDisplay *setupNicheGraphicsBaseUI()
+{
+ auto *panel = new NicheGraphics::Panels::GDEY0154D67();
+ NicheGraphics::Drivers::EInk *driver = panel->create();
+ auto *display = new NicheGraphics::BaseUIEInkDisplay(driver, panel->rotation());
+ display->setDisplayResilience(20, 1.5f);
+ return display;
+}
#endif
+
+#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
diff --git a/variants/nrf52840/t-echo-plus/platformio.ini b/variants/nrf52840/t-echo-plus/platformio.ini
index 313eceadc..c4df9d05d 100644
--- a/variants/nrf52840/t-echo-plus/platformio.ini
+++ b/variants/nrf52840/t-echo-plus/platformio.ini
@@ -10,29 +10,23 @@ custom_meshtastic_tags = LilyGo
custom_meshtastic_requires_dfu = true
custom_meshtastic_has_ink_hud = true
-extends = nrf52840_base
+extends = nrf52840_base, niche
board = t-echo
board_level = pr
board_check = true
debug_tool = jlink
build_flags = ${nrf52840_base.build_flags}
+ ${niche.build_flags}
-DTTGO_T_ECHO_PLUS
-Ivariants/nrf52840/t-echo-plus
- -DEINK_DISPLAY_MODEL=GxEPD2_154_D67
- -DEINK_WIDTH=200
- -DEINK_HEIGHT=200
-DUSE_EINK
- -DUSE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk
- -DEINK_LIMIT_FASTREFRESH=20 ; How many consecutive fast-refreshes are permitted
- -DEINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached.
-DI2C_NO_RESCAN
-build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/t-echo-plus>
+build_src_filter = ${nrf52_base.build_src_filter} ${niche.build_src_filter} +<../variants/nrf52840/t-echo-plus>
lib_deps =
${nrf52840_base.lib_deps}
- https://github.com/meshtastic/GxEPD2/archive/55f618961db45a23eff0233546430f1e5a80f63a.zip
# renovate: datasource=custom.pio depName=PCF8563 packageName=lewisxhe/library/PCF8563_Library
lewisxhe/PCF8563_Library@1.0.1
# renovate: datasource=custom.pio depName=Adafruit DRV2605 packageName=adafruit/library/Adafruit DRV2605 Library
diff --git a/variants/nrf52840/t-echo/nicheGraphics.h b/variants/nrf52840/t-echo/nicheGraphics.h
index c0b24dea7..3a3b4fce8 100644
--- a/variants/nrf52840/t-echo/nicheGraphics.h
+++ b/variants/nrf52840/t-echo/nicheGraphics.h
@@ -1,128 +1,99 @@
+/*
+
+NicheGraphics setup for LILYGO T-Echo (GDEY0154D67 1.54" on SPI1).
+Shared by both BaseUI (env:t-echo) and InkHUD (env:t-echo-inkhud).
+
+*/
+
#pragma once
#include "configuration.h"
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
-// InkHUD-specific components
-// ---------------------------
-#include "graphics/niche/InkHUD/InkHUD.h"
+#include "graphics/BaseUIEInkDisplay.h"
+#include "graphics/eink/Backlight/LatchingBacklight.h"
+#include "graphics/eink/Panels/GDEY0154D67.h"
-// Applets
-#include "graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/DM/DMApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/RecentsList/RecentsListApplet.h"
-#include "graphics/niche/InkHUD/Applets/User/ThreadedMessage/ThreadedMessageApplet.h"
-
-// Shared NicheGraphics components
-// --------------------------------
-#include "graphics/niche/Drivers/Backlight/LatchingBacklight.h"
-#include "graphics/niche/Drivers/EInk/GDEY0154D67.h"
+#ifdef MESHTASTIC_INCLUDE_INKHUD
+#include "graphics/niche/Applets/User/AllMessage/AllMessageApplet.h"
+#include "graphics/niche/Applets/User/DM/DMApplet.h"
+#include "graphics/niche/Applets/User/FavoritesMap/FavoritesMapApplet.h"
+#include "graphics/niche/Applets/User/Heard/HeardApplet.h"
+#include "graphics/niche/Applets/User/Positions/PositionsApplet.h"
+#include "graphics/niche/Applets/User/RecentsList/RecentsListApplet.h"
+#include "graphics/niche/Applets/User/ThreadedMessage/ThreadedMessageApplet.h"
+#include "graphics/niche/InkHUD.h"
#include "graphics/niche/Inputs/TwoButton.h"
-// Special case - fix T-Echo's touch button
-// ----------------------------------------
-// On a handful of T-Echos, LoRa TX triggers the capacitive touch
-// To avoid this, we lockout the button during TX
#include "mesh/RadioLibInterface.h"
+#endif
+#ifdef MESHTASTIC_INCLUDE_INKHUD
void setupNicheGraphics()
{
using namespace NicheGraphics;
- // SPI
- // -----------------------------
-
- // For NRF52 platforms, SPI pins are defined in variant.h
- SPI1.begin();
-
- // E-Ink Driver
- // -----------------------------
-
- Drivers::EInk *driver = new Drivers::GDEY0154D67;
- driver->begin(&SPI1, PIN_EINK_DC, PIN_EINK_CS, PIN_EINK_BUSY, PIN_EINK_RES);
-
- // InkHUD
- // ----------------------------
+ auto *panel = new Panels::GDEY0154D67();
+ Drivers::EInk *driver = panel->create();
InkHUD::InkHUD *inkhud = InkHUD::InkHUD::getInstance();
-
- // Set the E-Ink driver
inkhud->setDriver(driver);
-
- // Set how many FAST updates per FULL update
- // Set how unhealthy additional FAST updates beyond this number are
inkhud->setDisplayResilience(20, 1.5);
- // Select fonts
InkHUD::Applet::fontLarge = FREESANS_12PT_WIN1252;
InkHUD::Applet::fontMedium = FREESANS_9PT_WIN1252;
InkHUD::Applet::fontSmall = FREESANS_6PT_WIN1252;
- // Customize default settings
- inkhud->persistence->settings.userTiles.maxCount = 2; // Two applets side-by-side
- inkhud->persistence->settings.rotation = 3; // 270 degrees clockwise
- inkhud->persistence->settings.optionalFeatures.batteryIcon = true; // Device definitely has a battery
- inkhud->persistence->settings.optionalMenuItems.backlight = true; // Until proves capacitive button works by touching it
+ inkhud->persistence->settings.userTiles.maxCount = 2;
+ inkhud->persistence->settings.rotation = 3;
+ inkhud->persistence->settings.optionalFeatures.batteryIcon = true;
+ inkhud->persistence->settings.optionalMenuItems.backlight = true;
- // Setup backlight controller
- // Note: AUX button attached further down
Drivers::LatchingBacklight *backlight = Drivers::LatchingBacklight::getInstance();
backlight->setPin(PIN_EINK_EN);
- // Pick applets
- // Note: order of applets determines priority of "auto-show" feature
- inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet, true, true); // Activated, autoshown
- inkhud->addApplet("DMs", new InkHUD::DMApplet); // -
- inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0)); // -
- inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1)); // -
- inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true); // Activated
- inkhud->addApplet("Favorites Map", new InkHUD::FavoritesMapApplet); // -
- inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet); // -
- inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0); // Activated, no autoshow, default on tile 0
+ inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet, true, true);
+ inkhud->addApplet("DMs", new InkHUD::DMApplet);
+ inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0));
+ inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1));
+ inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true);
+ inkhud->addApplet("Favorites Map", new InkHUD::FavoritesMapApplet);
+ inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet);
+ inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0);
- // Start running InkHUD
inkhud->begin();
- // Buttons
- // --------------------------
-
- Inputs::TwoButton *buttons = Inputs::TwoButton::getInstance(); // Shared NicheGraphics component
-
- // #0: Main User Button
+ Inputs::TwoButton *buttons = Inputs::TwoButton::getInstance();
buttons->setWiring(0, Inputs::TwoButton::getUserButtonPin());
buttons->setTiming(0, 75, 500);
buttons->setHandlerShortPress(0, [inkhud]() { inkhud->shortpress(); });
buttons->setHandlerLongPress(0, [inkhud]() { inkhud->longpress(); });
- // #1: Aux Button (Capacitive Touch Button)
- // - short: momentary backlight
- // - long: latch backlight on
buttons->setWiring(1, PIN_BUTTON_TOUCH);
- buttons->setTiming(1, 50, 5000); // 5 seconds before latch - limited by T-Echo's capacitive touch IC
-
+ buttons->setTiming(1, 50, 5000);
buttons->setHandlerDown(1, [inkhud, backlight]() {
- // Discard the button press if radio is active
- // Rare hardware fault: LoRa activity triggers touch button
if (!RadioLibInterface::instance || RadioLibInterface::instance->isSending())
return;
-
- // Backlight on (while held)
backlight->peek();
-
- // Handler has run, which confirms touch button wasn't removed as part of DIY build.
- // No longer need the fallback backlight toggle in menu.
inkhud->persistence->settings.optionalMenuItems.backlight = false;
});
-
buttons->setHandlerLongPress(1, [backlight]() { backlight->latch(); });
buttons->setHandlerShortPress(1, [backlight]() { backlight->off(); });
- // Begin handling button events
buttons->start();
}
+#else
+void setupNicheGraphics() {}
+NicheGraphics::BaseUIEInkDisplay *setupNicheGraphicsBaseUI()
+{
+ auto *panel = new NicheGraphics::Panels::GDEY0154D67();
+ NicheGraphics::Drivers::EInk *driver = panel->create();
+ auto *display = new NicheGraphics::BaseUIEInkDisplay(driver, panel->rotation());
+ display->setDisplayResilience(20, 1.5f);
+ return display;
+}
#endif
+
+#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
diff --git a/variants/nrf52840/t-echo/platformio.ini b/variants/nrf52840/t-echo/platformio.ini
index 58ad029ae..138d21d58 100644
--- a/variants/nrf52840/t-echo/platformio.ini
+++ b/variants/nrf52840/t-echo/platformio.ini
@@ -1,4 +1,3 @@
-; Using original screen class
[env:t-echo]
custom_meshtastic_hw_model = 7
custom_meshtastic_hw_model_slug = T_ECHO
@@ -10,28 +9,20 @@ custom_meshtastic_images = t-echo.svg
custom_meshtastic_tags = LilyGo
custom_meshtastic_has_ink_hud = true
-extends = nrf52840_base
+extends = nrf52840_base, niche
board = t-echo
board_level = pr
board_check = true
debug_tool = jlink
-# add -DCFG_SYSVIEW if you want to use the Segger systemview tool for OS profiling.
build_flags = ${nrf52840_base.build_flags}
+ ${niche.build_flags}
-Ivariants/nrf52840/t-echo
- -DEINK_DISPLAY_MODEL=GxEPD2_154_D67
- -DEINK_WIDTH=200
- -DEINK_HEIGHT=200
-DUSE_EINK
- -DUSE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk
- -DEINK_LIMIT_FASTREFRESH=20 ; How many consecutive fast-refreshes are permitted
- -DEINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached.
-build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/t-echo>
-lib_deps =
+build_src_filter = ${nrf52_base.build_src_filter} ${niche.build_src_filter} +<../variants/nrf52840/t-echo>
+lib_deps =
${nrf52840_base.lib_deps}
- # renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master
- https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip
# renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib
lewisxhe/SensorLib@0.3.4
;upload_protocol = fs
@@ -42,15 +33,15 @@ board = t-echo
board_level = pr
board_check = true
debug_tool = jlink
-build_flags =
+build_flags =
${nrf52840_base.build_flags}
${inkhud.build_flags}
-I variants/nrf52840/t-echo
-build_src_filter =
- ${nrf52_base.build_src_filter}
+build_src_filter =
+ ${nrf52_base.build_src_filter}
${inkhud.build_src_filter}
+<../variants/nrf52840/t-echo>
-lib_deps =
+lib_deps =
${inkhud.lib_deps} ; InkHUD libs first, so we get GFXRoot instead of AdafruitGFX
${nrf52840_base.lib_deps}
# renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib