Repository: meshtastic/firmware
Pull request #6533: Add new hardware: Heltec MeshPocket
Author: login: Heltec-Aaron-Lee
Created: 2025-04-09T10:49:54Z | Merged: 2025-04-15T03:34:31Z

[MeshPocket](https://heltec.org/project/meshpocket/) is not only a Qi2 magnetic power bank but also an emergency communications device. Withch includes:
- Bluetooth (nRF52840)
- LoRa(SX1262)
- 2.13" e-ink display
- 5000/10000 mAh battery.

It can be used for daily work like a normal magnetic power bank and also has full Meshtastic support.

**What was added in this commit:**
- MeshPocket 5000mAh version;
- MeshPocket 10000mAh version;
The only difference between the two is the battery capacity, and the other hardware configurations are exactly the same.

**What are the main features of firmware changes:**
- Add Meshtastic 2.6 InkHUD support;
- Added 5000/10000mAh discharge curve data to improve the accuracy of the power display icon.


Diff:
diff --git a/boards/heltec_mesh_pocket.json b/boards/heltec_mesh_pocket.json
new file mode 100644
index 00000000000..a353878573b
--- /dev/null
+++ b/boards/heltec_mesh_pocket.json
@@ -0,0 +1,54 @@
+{
+    "build": {
+      "arduino": {
+        "ldscript": "nrf52840_s140_v6.ld"
+      },
+      "core": "nRF5",
+      "cpu": "cortex-m4",
+      "extra_flags": "-DNRF52840_XXAA",
+      "f_cpu": "64000000L",
+      "hwids": [
+        ["0x239A", "0x4405"],
+        ["0x239A", "0x0029"],
+        ["0x239A", "0x002A"]
+      ],
+      "usb_product": "HT-n5262",
+      "mcu": "nrf52840",
+      "variant": "heltec_mesh_pocket",
+      "variants_dir": "variants",
+      "bsp": {
+        "name": "adafruit"
+      },
+      "softdevice": {
+        "sd_flags": "-DS140",
+        "sd_name": "s140",
+        "sd_version": "6.1.1",
+        "sd_fwid": "0x00B6"
+      },
+      "bootloader": {
+        "settings_addr": "0xFF000"
+      }
+    },
+    "connectivity": ["bluetooth"],
+    "debug": {
+      "jlink_device": "nRF52840_xxAA",
+      "onboard_tools": ["jlink"],
+      "svd_path": "nrf52840.svd",
+      "openocd_target": "nrf52840-mdk-rs"
+    },
+    "frameworks": ["arduino"],
+    "name": "Heltec nrf (Adafruit BSP)",
+    "upload": {
+      "maximum_ram_size": 248832,
+      "maximum_size": 815104,
+      "speed": 115200,
+      "protocol": "nrfutil",
+      "protocols": ["jlink", "nrfjprog", "nrfutil", "stlink"],
+      "use_1200bps_touch": true,
+      "require_upload_port": true,
+      "wait_for_upload_port": true
+    },
+    "url": "https://heltec.org/project/meshpocket/",
+    "vendor": "Heltec"
+  }
+  
\ No newline at end of file
diff --git a/src/graphics/EInkDisplay2.cpp b/src/graphics/EInkDisplay2.cpp
index d2d373d2491..737fcc3f0a8 100644
--- a/src/graphics/EInkDisplay2.cpp
+++ b/src/graphics/EInkDisplay2.cpp
@@ -181,7 +181,6 @@ bool EInkDisplay::connect()
         // 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()
 
@@ -218,6 +217,21 @@ bool EInkDisplay::connect()
         adafruitDisplay->setRotation(1);
         adafruitDisplay->setPartialWindow(0, 0, EINK_WIDTH, EINK_HEIGHT);
     }
+#elif defined(HELTEC_MESH_POCKET)
+    {
+        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<EINK_DISPLAY_MODEL, EINK_DISPLAY_MODEL::HEIGHT>(*lowLevel);
+
+        // Init GxEPD2
+        adafruitDisplay->init();
+        adafruitDisplay->setRotation(3);
+    }
 #endif
 
     return true;
diff --git a/src/graphics/EInkDisplay2.h b/src/graphics/EInkDisplay2.h
index 9c1c8d18e90..93be197b004 100644
--- a/src/graphics/EInkDisplay2.h
+++ b/src/graphics/EInkDisplay2.h
@@ -73,6 +73,10 @@ class EInkDisplay : public OLEDDisplay
     SPIClass *hspi = NULL;
 #endif
 
+#if defined(HELTEC_MESH_POCKET)
+    SPIClass *spi1 = NULL;
+#endif
+
   private:
     // FIXME quick hack to limit drawing to a very slow rate
     uint32_t lastDrawMsec = 0;
diff --git a/src/graphics/niche/Drivers/EInk/LCMEN2R13ECC1.cpp b/src/graphics/niche/Drivers/EInk/LCMEN2R13ECC1.cpp
new file mode 100644
index 00000000000..5e21c00f65f
--- /dev/null
+++ b/src/graphics/niche/Drivers/EInk/LCMEN2R13ECC1.cpp
@@ -0,0 +1,68 @@
+#include "./LCMEN2R13ECC1.h"
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+using namespace NicheGraphics::Drivers;
+
+// Map the display controller IC's output to the connected panel
+void LCMEN2R13ECC1::configScanning()
+{
+    // "Driver output control"
+    sendCommand(0x01);
+    sendData(0xF9);
+    sendData(0x00);
+    sendData(0x00);
+
+    // To-do: delete this method?
+    // Values set here might be redundant: F9, 00, 00 seems to be default
+}
+
+// Specify which information is used to control the sequence of voltages applied to move the pixels
+// - For this display, configUpdateSequence() specifies that a suitable LUT will be loaded from
+//   the controller IC's OTP memory, when the update procedure begins.
+void LCMEN2R13ECC1::configWaveform()
+{
+    switch (updateType) {
+        case FAST:
+            sendCommand(0x3C); // Border waveform:
+            sendData(0x85);
+            break;
+    
+        case FULL:
+        default:
+            // From OTP memory
+            break;
+        }
+}
+
+void LCMEN2R13ECC1::configUpdateSequence()
+{
+    switch (updateType) {
+    case FAST:
+        sendCommand(0x22); // Set "update sequence"
+        sendData(0xFF);    // Will load LUT from OTP memory, Display mode 2 "differential refresh"
+        break;
+
+    case FULL:
+    default:
+        sendCommand(0x22); // Set "update sequence"
+        sendData(0xF7);    // Will load LUT from OTP memory
+        break;
+    }
+}
+
+// Once the refresh operation has been started,
+// begin periodically polling the display to check for completion, using the normal Meshtastic threading code
+// Only used when refresh is "async"
+void LCMEN2R13ECC1::detachFromUpdate()
+{
+    switch (updateType) {
+    case FAST:
+        return beginPolling(50, 800); // At least 500ms for fast refresh
+    case FULL:
+    default:
+        return beginPolling(100, 2500); // At least 2 seconds for full refresh
+    }
+}
+
+#endif   // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
\ No newline at end of file
diff --git a/src/graphics/niche/Drivers/EInk/LCMEN2R13ECC1.h b/src/graphics/niche/Drivers/EInk/LCMEN2R13ECC1.h
new file mode 100644
index 00000000000..7b0aed282a7
--- /dev/null
+++ b/src/graphics/niche/Drivers/EInk/LCMEN2R13ECC1.h
@@ -0,0 +1,41 @@
+/*
+
+E-Ink display driver
+    - SSD1680
+    - Manufacturer: WISEVAST
+    - Size: 2.13 inch
+    - Resolution: 122px x 255px
+    - Flex connector marking: Soldering connector, no connector is needed
+
+*/
+
+#pragma once
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+#include "configuration.h"
+
+#include "./SSD16XX.h"
+
+namespace NicheGraphics::Drivers
+{
+class LCMEN2R13ECC1 : public SSD16XX
+{
+    // Display properties
+  private:
+    static constexpr uint32_t width = 122;
+    static constexpr uint32_t height = 250;
+    static constexpr UpdateTypes supported = (UpdateTypes)(FULL | FAST);
+
+  public:
+  LCMEN2R13ECC1() : SSD16XX(width, height, supported, 1) {} // Note: left edge of this display is offset by 1 byte
+
+  protected:
+  virtual void configScanning() override;
+  virtual void configWaveform() override;
+  virtual void configUpdateSequence() override;
+  void detachFromUpdate() override;
+};
+
+} // namespace NicheGraphics::Drivers
+#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
\ No newline at end of file
diff --git a/src/platform/nrf52/architecture.h b/src/platform/nrf52/architecture.h
index 4e882306390..21296c3fc9b 100644
--- a/src/platform/nrf52/architecture.h
+++ b/src/platform/nrf52/architecture.h
@@ -81,6 +81,8 @@
 #define HW_VENDOR meshtastic_HardwareModel_MESHLINK
 #elif defined(SEEED_XIAO_NRF52840_KIT)
 #define HW_VENDOR meshtastic_HardwareModel_XIAO_NRF52_KIT
+#elif defined(HELTEC_MESH_POCKET)
+#define HW_VENDOR meshtastic_HardwareModel_HELTEC_MESH_POCKET
 #else
 #define HW_VENDOR meshtastic_HardwareModel_NRF52_UNKNOWN
 #endif
diff --git a/src/power.h b/src/power.h
index e9c0deb7c33..a21f7d1646e 100644
--- a/src/power.h
+++ b/src/power.h
@@ -26,6 +26,10 @@
 #define OCV_ARRAY 2700, 2560, 2540, 2520, 2500, 2460, 2420, 2400, 2380, 2320, 1500
 #elif defined(TRACKER_T1000_E)
 #define OCV_ARRAY 4190, 4078, 4017, 3969, 3887, 3818, 3798, 3791, 3766, 3712, 3100
+#elif defined(HELTEC_MESH_POCKET_BATTERY_5000)
+#define OCV_ARRAY 4300, 4240, 4120, 4000, 3888, 3800, 3740, 3698, 3655, 3580, 3400
+#elif defined(HELTEC_MESH_POCKET_BATTERY_10000)
+#define OCV_ARRAY 4100, 4060, 3960, 3840, 3729, 3625, 3550, 3500, 3420, 3345, 3100
 #else // LiIon
 #define OCV_ARRAY 4190, 4050, 3990, 3890, 3800, 3720, 3630, 3530, 3420, 3300, 3100
 #endif
diff --git a/variants/heltec_mesh_pocket/nicheGraphics.h b/variants/heltec_mesh_pocket/nicheGraphics.h
new file mode 100644
index 00000000000..352a9bc476c
--- /dev/null
+++ b/variants/heltec_mesh_pocket/nicheGraphics.h
@@ -0,0 +1,107 @@
+#pragma once
+
+#include "configuration.h"
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+// InkHUD-specific components
+// ---------------------------
+#include "graphics/niche/InkHUD/InkHUD.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/niche/InkHUD/Applets/Examples/BasicExample/BasicExampleApplet.h"
+// #include "graphics/niche/InkHUD/Applets/Examples/NewMsgExample/NewMsgExampleApplet.h"
+
+// Shared NicheGraphics components
+// --------------------------------
+#include "graphics/niche/Drivers/EInk/LCMEN2R13ECC1.h"
+#include "graphics/niche/Inputs/TwoButton.h"
+
+#include "graphics/niche/Fonts/FreeSans6pt7b.h"
+#include "graphics/niche/Fonts/FreeSans6pt8bCyrillic.h"
+#include <Fonts/FreeSans9pt7b.h>
+
+void setupNicheGraphics()
+{
+    using namespace NicheGraphics;
+
+    // SPI
+    // -----------------------------
+    SPIClass *spi1=&SPI1;
+    spi1->begin();
+    // Display is connected to SPI1
+
+    // E-Ink Driver
+    // -----------------------------
+    // Use 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
+    // ----------------------------
+
+    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(10, 1.5);
+
+    // Prepare fonts
+    InkHUD::Applet::fontLarge = InkHUD::AppletFont(FreeSans9pt7b);
+    InkHUD::Applet::fontSmall = InkHUD::AppletFont(FreeSans6pt7b);
+    /*
+    // Font localization demo: Cyrillic
+    InkHUD::Applet::fontSmall = InkHUD::AppletFont(FreeSans6pt8bCyrillic);
+    InkHUD::Applet::fontSmall.addSubstitutionsWin1251();
+    */
+
+    // 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 = true;
+
+    // Pick applets
+    inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet, true, true); // Activated, autoshown
+    inkhud->addApplet("DMs", new InkHUD::DMApplet);                              // Inactive
+    inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0));        // Inactive
+    inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1));        // Inactive
+    inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true);           // Activated
+    inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet);            // Inactive
+    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);
+
+    // Start running InkHUD
+    inkhud->begin();
+
+    // Buttons
+    // --------------------------
+
+    Inputs::TwoButton *buttons = Inputs::TwoButton::getInstance(); // Shared NicheGraphics component
+    constexpr uint8_t MAIN_BUTTON = 0;
+    // constexpr uint8_t AUX_BUTTON = 1;
+
+    // Setup the main user button
+    buttons->setWiring(MAIN_BUTTON, Inputs::TwoButton::getUserButtonPin());
+    buttons->setHandlerShortPress(MAIN_BUTTON, []() { InkHUD::InkHUD::getInstance()->shortpress(); });
+    buttons->setHandlerLongPress(MAIN_BUTTON, []() { InkHUD::InkHUD::getInstance()->longpress(); });
+
+    // Setup the aux button
+    // Bonus feature of VME213
+    // buttons->setWiring(AUX_BUTTON, BUTTON_PIN_SECONDARY);
+    // buttons->setHandlerShortPress(AUX_BUTTON, []() { InkHUD::InkHUD::getInstance()->nextTile(); });
+    buttons->start();
+}
+
+#endif
\ No newline at end of file
diff --git a/variants/heltec_mesh_pocket/platformio.ini b/variants/heltec_mesh_pocket/platformio.ini
new file mode 100644
index 00000000000..53f56e97354
--- /dev/null
+++ b/variants/heltec_mesh_pocket/platformio.ini
@@ -0,0 +1,92 @@
+; First prototype nrf52840/sx1262 device
+[env:heltec-mesh-pocket-5000]
+extends = nrf52840_base
+board = heltec_mesh_pocket
+debug_tool = jlink
+
+# add -DCFG_SYSVIEW if you want to use the Segger systemview tool for OS profiling.
+build_flags = ${nrf52840_base.build_flags} -Ivariants/heltec_mesh_pocket
+        -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard"
+  -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/heltec_mesh_pocket> 
+lib_deps = 
+  ${nrf52840_base.lib_deps}
+  lewisxhe/PCF8563_Library@^1.0.1
+  https://github.com/meshtastic/GxEPD2#b202ebfec6a4821e098cf7a625ba0f6f2400292d
+
+
+[env:heltec-mesh-pocket-inkhud-5000]
+extends = nrf52840_base, inkhud
+board = heltec_mesh_pocket
+build_src_filter = ${nrf52_base.build_src_filter} +<../variants/heltec_mesh_pocket> ${inkhud.build_src_filter}
+build_flags = 
+  ${inkhud.build_flags}
+  ${nrf52840_base.build_flags} 
+  -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard"
+  -I variants/heltec_mesh_pocket
+  -D HELTEC_MESH_POCKET
+  -D HELTEC_MESH_POCKET_BATTERY_5000
+lib_deps =
+  ${inkhud.lib_deps} ; InkHUD libs first, so we get GFXRoot instead of AdafruitGFX
+  ${nrf52840_base.lib_deps}
+
+
+; First prototype nrf52840/sx1262 device
+[env:heltec-mesh-pocket-10000]
+extends = nrf52840_base
+board = heltec_mesh_pocket
+debug_tool = jlink
+
+# add -DCFG_SYSVIEW if you want to use the Segger systemview tool for OS profiling.
+build_flags = ${nrf52840_base.build_flags} -Ivariants/heltec_mesh_pocket
+        -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard"
+  -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/heltec_mesh_pocket> 
+lib_deps = 
+  ${nrf52840_base.lib_deps}
+  lewisxhe/PCF8563_Library@^1.0.1
+  https://github.com/meshtastic/GxEPD2#b202ebfec6a4821e098cf7a625ba0f6f2400292d
+
+
+[env:heltec-mesh-pocket-inkhud-10000]
+extends = nrf52840_base, inkhud
+board = heltec_mesh_pocket
+build_src_filter = ${nrf52_base.build_src_filter} +<../variants/heltec_mesh_pocket> ${inkhud.build_src_filter}
+build_flags = 
+  ${inkhud.build_flags}
+  ${nrf52840_base.build_flags} 
+  -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard"
+  -I variants/heltec_mesh_pocket
+  -D HELTEC_MESH_POCKET
+  -D HELTEC_MESH_POCKET_BATTERY_10000
+lib_deps =
+  ${inkhud.lib_deps} ; InkHUD libs first, so we get GFXRoot instead of AdafruitGFX
+  ${nrf52840_base.lib_deps}
diff --git a/variants/heltec_mesh_pocket/variant.cpp b/variants/heltec_mesh_pocket/variant.cpp
new file mode 100644
index 00000000000..20ba5f2ae1d
--- /dev/null
+++ b/variants/heltec_mesh_pocket/variant.cpp
@@ -0,0 +1,13 @@
+#include "variant.h"
+#include "nrf.h"
+#include "wiring_constants.h"
+#include "wiring_digital.h"
+
+const uint32_t g_ADigitalPinMap[] = {
+    // P0 - pins 0 and 1 are hardwired for xtal and should never be enabled
+    0xff, 0xff, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
+
+    // P1
+    32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47};
+
+
diff --git a/variants/heltec_mesh_pocket/variant.h b/variants/heltec_mesh_pocket/variant.h
new file mode 100644
index 00000000000..89f06f35865
--- /dev/null
+++ b/variants/heltec_mesh_pocket/variant.h
@@ -0,0 +1,135 @@
+#ifndef _VARIANT_HELTEC_NRF_
+#define _VARIANT_HELTEC_NRF_
+/** Master clock frequency */
+#define VARIANT_MCK (64000000ul)
+
+#define USE_LFXO // Board uses 32khz crystal for LF
+
+/*----------------------------------------------------------------------------
+ *        Headers
+ *----------------------------------------------------------------------------*/
+
+#include "WVariant.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif // __cplusplus
+
+// Number of pins defined in PinDescription array
+#define PINS_COUNT (48)
+#define NUM_DIGITAL_PINS (48)
+#define NUM_ANALOG_INPUTS (1)
+#define NUM_ANALOG_OUTPUTS (0)
+
+// LEDs
+#define PIN_LED1 (13) // 13 red (confirmed on 1.0 board)
+#define LED_RED PIN_LED1
+#define LED_BLUE PIN_LED1
+#define LED_GREEN PIN_LED1
+#define LED_BUILTIN LED_BLUE
+#define LED_CONN LED_BLUE
+#define LED_STATE_ON 0 // State when LED is lit
+
+/*
+ * Buttons
+ */
+#define PIN_BUTTON1 (32 + 10)
+// #define PIN_BUTTON2 (0 + 18)      // 0.18 is labeled on the board as RESET but we configure it in the bootloader as a regular
+// GPIO
+
+/*
+No longer populated on PCB
+*/
+#define PIN_SERIAL2_RX (0 + 7)
+#define PIN_SERIAL2_TX (0 + 8)
+//  #define PIN_SERIAL2_EN (0 + 17)
+
+/**
+    Wire Interfaces
+    */
+#define WIRE_INTERFACES_COUNT 1
+
+#define PIN_WIRE_SDA (32+15)
+#define PIN_WIRE_SCL (32+13)
+
+/*
+ * Lora radio
+ */
+
+#define USE_SX1262
+#define SX126X_CS   (0 + 26) // FIXME - we really should define LORA_CS instead
+#define LORA_CS     (0 + 26)
+#define SX126X_DIO1 (0 + 16)
+// Note DIO2 is attached internally to the module to an analog switch for TX/RX switching
+// #define SX1262_DIO3 (0 + 21)
+// This is used as an *output* from the sx1262 and connected internally to power the tcxo, do not drive from the
+//    main
+// CPU?
+#define SX126X_BUSY (0 + 15)
+#define SX126X_RESET (0 + 12)
+// Not really an E22 but TTGO seems to be trying to clone that
+#define SX126X_DIO2_AS_RF_SWITCH
+#define SX126X_DIO3_TCXO_VOLTAGE 1.8
+
+// Display (E-Ink)
+#define PIN_EINK_CS   24
+#define PIN_EINK_BUSY 32+6
+#define PIN_EINK_DC   31
+#define PIN_EINK_RES  32+4
+#define PIN_EINK_SCLK 22
+#define PIN_EINK_MOSI 20
+
+
+#define PIN_SPI1_MISO -1
+#define PIN_SPI1_MOSI PIN_EINK_MOSI
+#define PIN_SPI1_SCK PIN_EINK_SCLK
+
+
+/*
+ * GPS pins
+ */
+
+#define PIN_SERIAL1_RX 32+5
+#define PIN_SERIAL1_TX 32+7
+
+/*
+ * SPI Interfaces
+ */
+#define SPI_INTERFACES_COUNT 2
+
+// For LORA, spi 0
+#define PIN_SPI_MISO (32 + 9)
+#define PIN_SPI_MOSI (0 + 5)
+#define PIN_SPI_SCK (0 + 4)
+
+// #define PIN_PWR_EN (0 + 6)
+
+// To debug via the segger JLINK console rather than the CDC-ACM serial device
+// #define USE_SEGGER
+
+// Battery
+// The battery sense is hooked to pin A0 (4)
+// it is defined in the anlaolgue pin section of this file
+// and has 12 bit resolution
+
+#define ADC_CTRL 32+2
+#define ADC_CTRL_ENABLED HIGH
+#define BATTERY_PIN 29
+#define ADC_RESOLUTION 14
+
+#define BATTERY_SENSE_RESOLUTION_BITS 12
+#define BATTERY_SENSE_RESOLUTION 4096.0
+#undef AREF_VOLTAGE
+#define AREF_VOLTAGE 3.0
+#define VBAT_AR_INTERNAL AR_INTERNAL_3_0
+#define ADC_MULTIPLIER (4.90F)
+
+#undef  HAS_GPS
+#define HAS_GPS 0
+#define HAS_RTC 0
+#ifdef __cplusplus
+}
+#endif
+
+
+#endif
\ No newline at end of file


Reviews:
user: login: caveman99
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-09T12:00:31Z
user: login: todd-herbert
body: Well done for setting up a new InkHUD variant and display driver! I think you might be the the first person to take on that challenge so far.

Do you know what the exact model of the display is?
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T12:48:01Z
user: login: Heltec-Aaron-Lee
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T07:19:05Z
user: login: Heltec-Aaron-Lee
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T07:50:30Z
user: login: Heltec-Aaron-Lee
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T07:58:43Z
user: login: Heltec-Aaron-Lee
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T07:58:59Z
user: login: Heltec-Aaron-Lee
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T08:00:01Z
user: login: Heltec-Aaron-Lee
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T08:01:25Z
user: login: Heltec-Aaron-Lee
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T08:02:43Z
user: login: Heltec-Aaron-Lee
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T08:04:06Z
user: login: todd-herbert
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T08:21:56Z
user: login: todd-herbert
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T08:50:27Z
user: login: Heltec-Aaron-Lee
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T09:12:30Z
user: login: todd-herbert
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T09:31:30Z
user: login: fifieldt
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-11T07:18:03Z
user: login: fifieldt
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-11T07:18:38Z
user: login: caveman99
body: If the 2 edits in the json file are made (they don't affect the actual working of the board' i think this is good to go.
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-11T11:12:32Z
user: login: Heltec-Aaron-Lee
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T03:08:55Z
user: login: fifieldt
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-15T03:33:58Z

Review comments:
path: src/graphics/niche/Drivers/EInk/MESHPOCKET_SSD1680.cpp
user: login: todd-herbert
body: Can we please rename this file / class to use the model name of the display? These drivers should target specific display models, not individual Meshtastic devices. 
created_at: 2025-04-09T11:49:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,106 @@
+#include "./MESHPOCKET_SSD1680.h"
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+using namespace NicheGraphics::Drivers;
+
+// Map the display controller IC's output to the connected panel
+void MESHPOCKET_SSD1680::configScanning()
+{
+    // "Driver output control"
+    sendCommand(0x01);
+    sendData(0xF9);
+    sendData(0x00);
+    sendData(0x00);
+
+    // To-do: delete this method?
+    // Values set here might be redundant: C7, 00, 00 seems to be default
+}
+
+// Specify which information is used to control the sequence of voltages applied to move the pixels
+// - For this display, configUpdateSequence() specifies that a suitable LUT will be loaded from
+//   the controller IC's OTP memory, when the update procedure begins.
+void MESHPOCKET_SSD1680::configWaveform()
+{
+    sendCommand(0x3C); // Border waveform:
+    sendData(0x85);    // Screen border should follow LUT1 waveform (actively drive pixels white)
+
+    sendCommand(0x18); // Temperature sensor:
+    sendData(0x80);    // Use internal temperature sensor to select an appropriate refresh waveform
+}
+
+void MESHPOCKET_SSD1680::configUpdateSequence()
+{
+    switch (updateType) {
+    case FAST:
+        sendCommand(0x22); // Set "update sequence"
+        sendData(0xFF);    // Will load LUT from OTP memory, Display mode 2 "differential refresh"
+        break;
+
+    case FULL:
+    default:
+        sendCommand(0x22); // Set "update sequence"
+        sendData(0xF7);    // Will load LUT from OTP memory
+        break;
+    }
+}
+
+// Once the refresh operation has been started,
+// begin periodically polling the display to check for completion, using the normal Meshtastic threading code
+// Only used when refresh is "async"
+void MESHPOCKET_SSD1680::detachFromUpdate()
+{
+    switch (updateType) {
+    case FAST:
+        return beginPolling(50, 500); // At least 500ms for fast refresh
+    case FULL:
+    default:
+        return beginPolling(100, 2000); // At least 2 seconds for full refresh
+    }
+}
+
+void MESHPOCKET_SSD1680::update(uint8_t *imageData, UpdateTypes type)
path: src/graphics/niche/Drivers/EInk/MESHPOCKET_SSD1680.cpp
user: login: todd-herbert
body: I don't think there's any need to override `SSD1680::update`. It looks like this code is identical to the base class.
created_at: 2025-04-09T11:58:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,106 @@
+#include "./MESHPOCKET_SSD1680.h"
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+using namespace NicheGraphics::Drivers;
+
+// Map the display controller IC's output to the connected panel
+void MESHPOCKET_SSD1680::configScanning()
+{
+    // "Driver output control"
+    sendCommand(0x01);
+    sendData(0xF9);
+    sendData(0x00);
+    sendData(0x00);
+
+    // To-do: delete this method?
+    // Values set here might be redundant: C7, 00, 00 seems to be default
+}
+
+// Specify which information is used to control the sequence of voltages applied to move the pixels
+// - For this display, configUpdateSequence() specifies that a suitable LUT will be loaded from
+//   the controller IC's OTP memory, when the update procedure begins.
+void MESHPOCKET_SSD1680::configWaveform()
+{
+    sendCommand(0x3C); // Border waveform:
+    sendData(0x85);    // Screen border should follow LUT1 waveform (actively drive pixels white)
path: src/graphics/niche/Drivers/EInk/MESHPOCKET_SSD1680.cpp
user: login: todd-herbert
body: Does `0x85` make sense here? It seems to select two conflicting options: 
![SSD1680 datasheet](https://github.com/user-attachments/assets/81804cfe-295d-44cd-9ded-eb005526ad7c)

Either `0x80` to hold the border at VCOM, or `0x05` to force the border towards white (I think..?)
created_at: 2025-04-09T12:22:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,106 @@
+#include "./MESHPOCKET_SSD1680.h"
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+using namespace NicheGraphics::Drivers;
+
+// Map the display controller IC's output to the connected panel
+void MESHPOCKET_SSD1680::configScanning()
+{
+    // "Driver output control"
+    sendCommand(0x01);
+    sendData(0xF9);
+    sendData(0x00);
+    sendData(0x00);
+
+    // To-do: delete this method?
+    // Values set here might be redundant: C7, 00, 00 seems to be default
+}
+
+// Specify which information is used to control the sequence of voltages applied to move the pixels
+// - For this display, configUpdateSequence() specifies that a suitable LUT will be loaded from
+//   the controller IC's OTP memory, when the update procedure begins.
+void MESHPOCKET_SSD1680::configWaveform()
+{
+    sendCommand(0x3C); // Border waveform:
+    sendData(0x85);    // Screen border should follow LUT1 waveform (actively drive pixels white)
+
+    sendCommand(0x18); // Temperature sensor:
+    sendData(0x80);    // Use internal temperature sensor to select an appropriate refresh waveform
+}
+
+void MESHPOCKET_SSD1680::configUpdateSequence()
+{
+    switch (updateType) {
+    case FAST:
+        sendCommand(0x22); // Set "update sequence"
+        sendData(0xFF);    // Will load LUT from OTP memory, Display mode 2 "differential refresh"
+        break;
+
+    case FULL:
+    default:
+        sendCommand(0x22); // Set "update sequence"
+        sendData(0xF7);    // Will load LUT from OTP memory
+        break;
+    }
+}
+
+// Once the refresh operation has been started,
+// begin periodically polling the display to check for completion, using the normal Meshtastic threading code
+// Only used when refresh is "async"
+void MESHPOCKET_SSD1680::detachFromUpdate()
+{
+    switch (updateType) {
+    case FAST:
+        return beginPolling(50, 500); // At least 500ms for fast refresh
path: src/graphics/niche/Drivers/EInk/MESHPOCKET_SSD1680.cpp
user: login: todd-herbert
body: I'm not sure whether you checked this already (sorry), but it would be good to measure whether the update usually takes longer than 500ms and 2000ms, just to avoid checking the busy pin lots.
created_at: 2025-04-09T12:22:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,106 @@
+#include "./MESHPOCKET_SSD1680.h"
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+using namespace NicheGraphics::Drivers;
+
+// Map the display controller IC's output to the connected panel
+void MESHPOCKET_SSD1680::configScanning()
+{
+    // "Driver output control"
+    sendCommand(0x01);
+    sendData(0xF9);
+    sendData(0x00);
+    sendData(0x00);
+
+    // To-do: delete this method?
+    // Values set here might be redundant: C7, 00, 00 seems to be default
+}
+
+// Specify which information is used to control the sequence of voltages applied to move the pixels
+// - For this display, configUpdateSequence() specifies that a suitable LUT will be loaded from
+//   the controller IC's OTP memory, when the update procedure begins.
+void MESHPOCKET_SSD1680::configWaveform()
+{
+    sendCommand(0x3C); // Border waveform:
+    sendData(0x85);    // Screen border should follow LUT1 waveform (actively drive pixels white)
+
+    sendCommand(0x18); // Temperature sensor:
+    sendData(0x80);    // Use internal temperature sensor to select an appropriate refresh waveform
+}
+
+void MESHPOCKET_SSD1680::configUpdateSequence()
+{
+    switch (updateType) {
+    case FAST:
+        sendCommand(0x22); // Set "update sequence"
+        sendData(0xFF);    // Will load LUT from OTP memory, Display mode 2 "differential refresh"
+        break;
+
+    case FULL:
+    default:
+        sendCommand(0x22); // Set "update sequence"
+        sendData(0xF7);    // Will load LUT from OTP memory
+        break;
+    }
+}
+
+// Once the refresh operation has been started,
+// begin periodically polling the display to check for completion, using the normal Meshtastic threading code
+// Only used when refresh is "async"
+void MESHPOCKET_SSD1680::detachFromUpdate()
+{
+    switch (updateType) {
+    case FAST:
+        return beginPolling(50, 500); // At least 500ms for fast refresh
+    case FULL:
+    default:
+        return beginPolling(100, 2000); // At least 2 seconds for full refresh
+    }
+}
+
+void MESHPOCKET_SSD1680::update(uint8_t *imageData, UpdateTypes type)
+{
+    this->updateType = type;
+    this->buffer = imageData;
+
+    reset();
+
+    configFullscreen();
+    configScanning(); // Virtual, unused by base class
+    configVoltages(); // Virtual, unused by base class
+    configWaveform(); // Virtual, unused by base class
+    wait();
+
+    if (updateType == FULL) {
+        sendCommand(0x12);  // Begin executing the update
+        wait();
+        configFullscreen();
+        wait();
+        writeNewImage();
+        writeOldImage();
+    } else {
+        writeNewImage();
+    }
+
+    configUpdateSequence();
+    sendCommand(0x20); // Begin executing the update
+
+    // Let the update run async, on display hardware. Base class will poll completion, then finalize.
+    // For a blocking update, call await after update
+    detachFromUpdate();
+}
+
+// void MESHPOCKET_SSD1680::finalizeUpdate()
path: src/graphics/niche/Drivers/EInk/MESHPOCKET_SSD1680.cpp
user: login: todd-herbert
body: Does skipping `writeNewImage` during `finalizeUpdate` cause problems for this display?  
created_at: 2025-04-09T12:30:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,42 @@
+/*
+
+E-Ink display driver
path: src/graphics/niche/Drivers/EInk/MESHPOCKET_SSD1680.h
user: login: todd-herbert
body: Are these details all correct? Manufacturer, controller IC, flex connector marking?
created_at: 2025-04-09T12:31:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,107 @@
+#pragma once
+
+#include "configuration.h"
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+// InkHUD-specific components
+// ---------------------------
+#include "graphics/niche/InkHUD/InkHUD.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/niche/InkHUD/Applets/Examples/BasicExample/BasicExampleApplet.h"
+// #include "graphics/niche/InkHUD/Applets/Examples/NewMsgExample/NewMsgExampleApplet.h"
+
+// Shared NicheGraphics components
+// --------------------------------
+#include "graphics/niche/Drivers/EInk/MESHPOCKET_SSD1680.h"
+#include "graphics/niche/Inputs/TwoButton.h"
+
+#include "graphics/niche/Fonts/FreeSans6pt7b.h"
+#include "graphics/niche/Fonts/FreeSans6pt8bCyrillic.h"
+#include <Fonts/FreeSans9pt7b.h>
+
+void setupNicheGraphics()
+{
+    using namespace NicheGraphics;
+
+    // SPI
+    // -----------------------------
+    SPIClass *hspi=&SPI1;
+    hspi->begin();
+    // Display is connected to HSPI
+
+    // E-Ink Driver
+    // -----------------------------
+    // Use E-Ink driver
+    Drivers::EInk *driver = new Drivers::MESHPOCKET_SSD1680;
+    driver->begin(hspi, PIN_EINK_DC, PIN_EINK_CS, PIN_EINK_BUSY, PIN_EINK_RES);
+
+    // InkHUD
+    // ----------------------------
+
+    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(10, 1.5);
path: variants/heltec_mesh_pocket/nicheGraphics.h
user: login: todd-herbert
body: Are you confident that the display handles 10 partial refreshes back-to-back? Should it be less? Could it be more?
created_at: 2025-04-09T12:36:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,107 @@
+#pragma once
+
+#include "configuration.h"
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+// InkHUD-specific components
+// ---------------------------
+#include "graphics/niche/InkHUD/InkHUD.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/niche/InkHUD/Applets/Examples/BasicExample/BasicExampleApplet.h"
+// #include "graphics/niche/InkHUD/Applets/Examples/NewMsgExample/NewMsgExampleApplet.h"
+
+// Shared NicheGraphics components
+// --------------------------------
+#include "graphics/niche/Drivers/EInk/MESHPOCKET_SSD1680.h"
+#include "graphics/niche/Inputs/TwoButton.h"
+
+#include "graphics/niche/Fonts/FreeSans6pt7b.h"
+#include "graphics/niche/Fonts/FreeSans6pt8bCyrillic.h"
+#include <Fonts/FreeSans9pt7b.h>
+
+void setupNicheGraphics()
+{
+    using namespace NicheGraphics;
+
+    // SPI
+    // -----------------------------
+    SPIClass *hspi=&SPI1;
+    hspi->begin();
+    // Display is connected to HSPI
+
+    // E-Ink Driver
+    // -----------------------------
+    // Use E-Ink driver
+    Drivers::EInk *driver = new Drivers::MESHPOCKET_SSD1680;
+    driver->begin(hspi, PIN_EINK_DC, PIN_EINK_CS, PIN_EINK_BUSY, PIN_EINK_RES);
+
+    // InkHUD
+    // ----------------------------
+
+    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(10, 1.5);
+
+    // Prepare fonts
+    InkHUD::Applet::fontLarge = InkHUD::AppletFont(FreeSans9pt7b);
+    InkHUD::Applet::fontSmall = InkHUD::AppletFont(FreeSans6pt7b);
+    /*
+    // Font localization demo: Cyrillic
+    InkHUD::Applet::fontSmall = InkHUD::AppletFont(FreeSans6pt8bCyrillic);
+    InkHUD::Applet::fontSmall.addSubstitutionsWin1251();
+    */
+
+    // 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
path: variants/heltec_mesh_pocket/nicheGraphics.h
user: login: todd-herbert
body: MeshPocket only has one button for user input, right? That means the only way to [switch between tiles](<https://meshtastic.org/docs/software/inkhud/#using-the-menu>) is with the "Next Tile" item in the menu. Deleting the line about `optionalMenuItems.nextTile` will re-enable this.
created_at: 2025-04-09T12:41:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,107 @@
+#pragma once
+
+#include "configuration.h"
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+// InkHUD-specific components
+// ---------------------------
+#include "graphics/niche/InkHUD/InkHUD.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/niche/InkHUD/Applets/Examples/BasicExample/BasicExampleApplet.h"
+// #include "graphics/niche/InkHUD/Applets/Examples/NewMsgExample/NewMsgExampleApplet.h"
+
+// Shared NicheGraphics components
+// --------------------------------
+#include "graphics/niche/Drivers/EInk/MESHPOCKET_SSD1680.h"
+#include "graphics/niche/Inputs/TwoButton.h"
+
+#include "graphics/niche/Fonts/FreeSans6pt7b.h"
+#include "graphics/niche/Fonts/FreeSans6pt8bCyrillic.h"
+#include <Fonts/FreeSans9pt7b.h>
+
+void setupNicheGraphics()
+{
+    using namespace NicheGraphics;
+
+    // SPI
+    // -----------------------------
+    SPIClass *hspi=&SPI1;
path: variants/heltec_mesh_pocket/nicheGraphics.h
user: login: todd-herbert
body: Let's not call NRF52's `SPI1` "HSPI".
created_at: 2025-04-09T12:41:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -176,12 +176,16 @@ bool EInkDisplay::connect()
 
 #elif defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_WIRELESS_PAPER) || 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(CROWPANEL_ESP32S3_4_EPAPER) || defined(CROWPANEL_ESP32S3_2_EPAPER) || defined(HELTEC_MESH_POCKET)
     {
+#if defined(HELTEC_MESH_POCKET)
path: src/graphics/EInkDisplay2.cpp
user: login: todd-herbert
body: Same here, let's not treat NRF52's `SPI1` as HSPI.
created_at: 2025-04-09T12:43:47Z
author_association: CONTRIBUTOR
path: src/graphics/niche/Drivers/EInk/MESHPOCKET_SSD1680.cpp
user: login: Heltec-Aaron-Lee
body: The display model name is [LCMEN2R13ECC1](https://resource.heltec.cn/download/MeshPocket/schematic/LCMEN2R13ECC1.pdf).
created_at: 2025-04-10T07:19:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,106 @@
+#include "./MESHPOCKET_SSD1680.h"
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+using namespace NicheGraphics::Drivers;
+
+// Map the display controller IC's output to the connected panel
+void MESHPOCKET_SSD1680::configScanning()
+{
+    // "Driver output control"
+    sendCommand(0x01);
+    sendData(0xF9);
+    sendData(0x00);
+    sendData(0x00);
+
+    // To-do: delete this method?
+    // Values set here might be redundant: C7, 00, 00 seems to be default
+}
+
+// Specify which information is used to control the sequence of voltages applied to move the pixels
+// - For this display, configUpdateSequence() specifies that a suitable LUT will be loaded from
+//   the controller IC's OTP memory, when the update procedure begins.
+void MESHPOCKET_SSD1680::configWaveform()
+{
+    sendCommand(0x3C); // Border waveform:
+    sendData(0x85);    // Screen border should follow LUT1 waveform (actively drive pixels white)
+
+    sendCommand(0x18); // Temperature sensor:
+    sendData(0x80);    // Use internal temperature sensor to select an appropriate refresh waveform
+}
+
+void MESHPOCKET_SSD1680::configUpdateSequence()
+{
+    switch (updateType) {
+    case FAST:
+        sendCommand(0x22); // Set "update sequence"
+        sendData(0xFF);    // Will load LUT from OTP memory, Display mode 2 "differential refresh"
+        break;
+
+    case FULL:
+    default:
+        sendCommand(0x22); // Set "update sequence"
+        sendData(0xF7);    // Will load LUT from OTP memory
+        break;
+    }
+}
+
+// Once the refresh operation has been started,
+// begin periodically polling the display to check for completion, using the normal Meshtastic threading code
+// Only used when refresh is "async"
+void MESHPOCKET_SSD1680::detachFromUpdate()
+{
+    switch (updateType) {
+    case FAST:
+        return beginPolling(50, 500); // At least 500ms for fast refresh
+    case FULL:
+    default:
+        return beginPolling(100, 2000); // At least 2 seconds for full refresh
+    }
+}
+
+void MESHPOCKET_SSD1680::update(uint8_t *imageData, UpdateTypes type)
path: src/graphics/niche/Drivers/EInk/MESHPOCKET_SSD1680.cpp
user: login: Heltec-Aaron-Lee
body: We hope the borders will also be refreshed so that there is a better visual effect. The default `SSD1680::update` will not refresh the border.
created_at: 2025-04-10T07:50:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,106 @@
+#include "./MESHPOCKET_SSD1680.h"
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+using namespace NicheGraphics::Drivers;
+
+// Map the display controller IC's output to the connected panel
+void MESHPOCKET_SSD1680::configScanning()
+{
+    // "Driver output control"
+    sendCommand(0x01);
+    sendData(0xF9);
+    sendData(0x00);
+    sendData(0x00);
+
+    // To-do: delete this method?
+    // Values set here might be redundant: C7, 00, 00 seems to be default
+}
+
+// Specify which information is used to control the sequence of voltages applied to move the pixels
+// - For this display, configUpdateSequence() specifies that a suitable LUT will be loaded from
+//   the controller IC's OTP memory, when the update procedure begins.
+void MESHPOCKET_SSD1680::configWaveform()
+{
+    sendCommand(0x3C); // Border waveform:
+    sendData(0x85);    // Screen border should follow LUT1 waveform (actively drive pixels white)
path: src/graphics/niche/Drivers/EInk/MESHPOCKET_SSD1680.cpp
user: login: Heltec-Aaron-Lee
body: ![image](https://github.com/user-attachments/assets/1f2bcfd9-c7a4-45ae-a0e6-44d5d36e2c59)
The display manufacturer told us that partial refresh uses 0x85.
created_at: 2025-04-10T07:58:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,106 @@
+#include "./MESHPOCKET_SSD1680.h"
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+using namespace NicheGraphics::Drivers;
+
+// Map the display controller IC's output to the connected panel
+void MESHPOCKET_SSD1680::configScanning()
+{
+    // "Driver output control"
+    sendCommand(0x01);
+    sendData(0xF9);
+    sendData(0x00);
+    sendData(0x00);
+
+    // To-do: delete this method?
+    // Values set here might be redundant: C7, 00, 00 seems to be default
+}
+
+// Specify which information is used to control the sequence of voltages applied to move the pixels
+// - For this display, configUpdateSequence() specifies that a suitable LUT will be loaded from
+//   the controller IC's OTP memory, when the update procedure begins.
+void MESHPOCKET_SSD1680::configWaveform()
+{
+    sendCommand(0x3C); // Border waveform:
+    sendData(0x85);    // Screen border should follow LUT1 waveform (actively drive pixels white)
+
+    sendCommand(0x18); // Temperature sensor:
+    sendData(0x80);    // Use internal temperature sensor to select an appropriate refresh waveform
+}
+
+void MESHPOCKET_SSD1680::configUpdateSequence()
+{
+    switch (updateType) {
+    case FAST:
+        sendCommand(0x22); // Set "update sequence"
+        sendData(0xFF);    // Will load LUT from OTP memory, Display mode 2 "differential refresh"
+        break;
+
+    case FULL:
+    default:
+        sendCommand(0x22); // Set "update sequence"
+        sendData(0xF7);    // Will load LUT from OTP memory
+        break;
+    }
+}
+
+// Once the refresh operation has been started,
+// begin periodically polling the display to check for completion, using the normal Meshtastic threading code
+// Only used when refresh is "async"
+void MESHPOCKET_SSD1680::detachFromUpdate()
+{
+    switch (updateType) {
+    case FAST:
+        return beginPolling(50, 500); // At least 500ms for fast refresh
path: src/graphics/niche/Drivers/EInk/MESHPOCKET_SSD1680.cpp
user: login: Heltec-Aaron-Lee
body: Updated
created_at: 2025-04-10T07:58:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,106 @@
+#include "./MESHPOCKET_SSD1680.h"
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+using namespace NicheGraphics::Drivers;
+
+// Map the display controller IC's output to the connected panel
+void MESHPOCKET_SSD1680::configScanning()
+{
+    // "Driver output control"
+    sendCommand(0x01);
+    sendData(0xF9);
+    sendData(0x00);
+    sendData(0x00);
+
+    // To-do: delete this method?
+    // Values set here might be redundant: C7, 00, 00 seems to be default
+}
+
+// Specify which information is used to control the sequence of voltages applied to move the pixels
+// - For this display, configUpdateSequence() specifies that a suitable LUT will be loaded from
+//   the controller IC's OTP memory, when the update procedure begins.
+void MESHPOCKET_SSD1680::configWaveform()
+{
+    sendCommand(0x3C); // Border waveform:
+    sendData(0x85);    // Screen border should follow LUT1 waveform (actively drive pixels white)
+
+    sendCommand(0x18); // Temperature sensor:
+    sendData(0x80);    // Use internal temperature sensor to select an appropriate refresh waveform
+}
+
+void MESHPOCKET_SSD1680::configUpdateSequence()
+{
+    switch (updateType) {
+    case FAST:
+        sendCommand(0x22); // Set "update sequence"
+        sendData(0xFF);    // Will load LUT from OTP memory, Display mode 2 "differential refresh"
+        break;
+
+    case FULL:
+    default:
+        sendCommand(0x22); // Set "update sequence"
+        sendData(0xF7);    // Will load LUT from OTP memory
+        break;
+    }
+}
+
+// Once the refresh operation has been started,
+// begin periodically polling the display to check for completion, using the normal Meshtastic threading code
+// Only used when refresh is "async"
+void MESHPOCKET_SSD1680::detachFromUpdate()
+{
+    switch (updateType) {
+    case FAST:
+        return beginPolling(50, 500); // At least 500ms for fast refresh
+    case FULL:
+    default:
+        return beginPolling(100, 2000); // At least 2 seconds for full refresh
+    }
+}
+
+void MESHPOCKET_SSD1680::update(uint8_t *imageData, UpdateTypes type)
+{
+    this->updateType = type;
+    this->buffer = imageData;
+
+    reset();
+
+    configFullscreen();
+    configScanning(); // Virtual, unused by base class
+    configVoltages(); // Virtual, unused by base class
+    configWaveform(); // Virtual, unused by base class
+    wait();
+
+    if (updateType == FULL) {
+        sendCommand(0x12);  // Begin executing the update
+        wait();
+        configFullscreen();
+        wait();
+        writeNewImage();
+        writeOldImage();
+    } else {
+        writeNewImage();
+    }
+
+    configUpdateSequence();
+    sendCommand(0x20); // Begin executing the update
+
+    // Let the update run async, on display hardware. Base class will poll completion, then finalize.
+    // For a blocking update, call await after update
+    detachFromUpdate();
+}
+
+// void MESHPOCKET_SSD1680::finalizeUpdate()
path: src/graphics/niche/Drivers/EInk/MESHPOCKET_SSD1680.cpp
user: login: Heltec-Aaron-Lee
body: The default function can meet our needs.
created_at: 2025-04-10T08:00:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,42 @@
+/*
+
+E-Ink display driver
path: src/graphics/niche/Drivers/EInk/MESHPOCKET_SSD1680.h
user: login: Heltec-Aaron-Lee
body: Updated. The Ink display uses a soldering connector, so no connector is needed.
created_at: 2025-04-10T08:01:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,107 @@
+#pragma once
+
+#include "configuration.h"
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+// InkHUD-specific components
+// ---------------------------
+#include "graphics/niche/InkHUD/InkHUD.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/niche/InkHUD/Applets/Examples/BasicExample/BasicExampleApplet.h"
+// #include "graphics/niche/InkHUD/Applets/Examples/NewMsgExample/NewMsgExampleApplet.h"
+
+// Shared NicheGraphics components
+// --------------------------------
+#include "graphics/niche/Drivers/EInk/MESHPOCKET_SSD1680.h"
+#include "graphics/niche/Inputs/TwoButton.h"
+
+#include "graphics/niche/Fonts/FreeSans6pt7b.h"
+#include "graphics/niche/Fonts/FreeSans6pt8bCyrillic.h"
+#include <Fonts/FreeSans9pt7b.h>
+
+void setupNicheGraphics()
+{
+    using namespace NicheGraphics;
+
+    // SPI
+    // -----------------------------
+    SPIClass *hspi=&SPI1;
+    hspi->begin();
+    // Display is connected to HSPI
+
+    // E-Ink Driver
+    // -----------------------------
+    // Use E-Ink driver
+    Drivers::EInk *driver = new Drivers::MESHPOCKET_SSD1680;
+    driver->begin(hspi, PIN_EINK_DC, PIN_EINK_CS, PIN_EINK_BUSY, PIN_EINK_RES);
+
+    // InkHUD
+    // ----------------------------
+
+    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(10, 1.5);
path: variants/heltec_mesh_pocket/nicheGraphics.h
user: login: Heltec-Aaron-Lee
body: 10 times has a good experience after testing.
created_at: 2025-04-10T08:02:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,107 @@
+#pragma once
+
+#include "configuration.h"
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+// InkHUD-specific components
+// ---------------------------
+#include "graphics/niche/InkHUD/InkHUD.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/niche/InkHUD/Applets/Examples/BasicExample/BasicExampleApplet.h"
+// #include "graphics/niche/InkHUD/Applets/Examples/NewMsgExample/NewMsgExampleApplet.h"
+
+// Shared NicheGraphics components
+// --------------------------------
+#include "graphics/niche/Drivers/EInk/MESHPOCKET_SSD1680.h"
+#include "graphics/niche/Inputs/TwoButton.h"
+
+#include "graphics/niche/Fonts/FreeSans6pt7b.h"
+#include "graphics/niche/Fonts/FreeSans6pt8bCyrillic.h"
+#include <Fonts/FreeSans9pt7b.h>
+
+void setupNicheGraphics()
+{
+    using namespace NicheGraphics;
+
+    // SPI
+    // -----------------------------
+    SPIClass *hspi=&SPI1;
+    hspi->begin();
+    // Display is connected to HSPI
+
+    // E-Ink Driver
+    // -----------------------------
+    // Use E-Ink driver
+    Drivers::EInk *driver = new Drivers::MESHPOCKET_SSD1680;
+    driver->begin(hspi, PIN_EINK_DC, PIN_EINK_CS, PIN_EINK_BUSY, PIN_EINK_RES);
+
+    // InkHUD
+    // ----------------------------
+
+    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(10, 1.5);
+
+    // Prepare fonts
+    InkHUD::Applet::fontLarge = InkHUD::AppletFont(FreeSans9pt7b);
+    InkHUD::Applet::fontSmall = InkHUD::AppletFont(FreeSans6pt7b);
+    /*
+    // Font localization demo: Cyrillic
+    InkHUD::Applet::fontSmall = InkHUD::AppletFont(FreeSans6pt8bCyrillic);
+    InkHUD::Applet::fontSmall.addSubstitutionsWin1251();
+    */
+
+    // 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
path: variants/heltec_mesh_pocket/nicheGraphics.h
user: login: Heltec-Aaron-Lee
body: Had set `optionalMenuItems.nextTile = true`
created_at: 2025-04-10T08:04:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,106 @@
+#include "./MESHPOCKET_SSD1680.h"
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+using namespace NicheGraphics::Drivers;
+
+// Map the display controller IC's output to the connected panel
+void MESHPOCKET_SSD1680::configScanning()
+{
+    // "Driver output control"
+    sendCommand(0x01);
+    sendData(0xF9);
+    sendData(0x00);
+    sendData(0x00);
+
+    // To-do: delete this method?
+    // Values set here might be redundant: C7, 00, 00 seems to be default
+}
+
+// Specify which information is used to control the sequence of voltages applied to move the pixels
+// - For this display, configUpdateSequence() specifies that a suitable LUT will be loaded from
+//   the controller IC's OTP memory, when the update procedure begins.
+void MESHPOCKET_SSD1680::configWaveform()
+{
+    sendCommand(0x3C); // Border waveform:
+    sendData(0x85);    // Screen border should follow LUT1 waveform (actively drive pixels white)
path: src/graphics/niche/Drivers/EInk/MESHPOCKET_SSD1680.cpp
user: login: todd-herbert
body: Hmm, I think this is probably identical to `0x80`, but I'm not 100% certain, so let's trust the manufacturer!
created_at: 2025-04-10T08:21:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,106 @@
+#include "./MESHPOCKET_SSD1680.h"
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+using namespace NicheGraphics::Drivers;
+
+// Map the display controller IC's output to the connected panel
+void MESHPOCKET_SSD1680::configScanning()
+{
+    // "Driver output control"
+    sendCommand(0x01);
+    sendData(0xF9);
+    sendData(0x00);
+    sendData(0x00);
+
+    // To-do: delete this method?
+    // Values set here might be redundant: C7, 00, 00 seems to be default
+}
+
+// Specify which information is used to control the sequence of voltages applied to move the pixels
+// - For this display, configUpdateSequence() specifies that a suitable LUT will be loaded from
+//   the controller IC's OTP memory, when the update procedure begins.
+void MESHPOCKET_SSD1680::configWaveform()
+{
+    sendCommand(0x3C); // Border waveform:
+    sendData(0x85);    // Screen border should follow LUT1 waveform (actively drive pixels white)
+
+    sendCommand(0x18); // Temperature sensor:
+    sendData(0x80);    // Use internal temperature sensor to select an appropriate refresh waveform
+}
+
+void MESHPOCKET_SSD1680::configUpdateSequence()
+{
+    switch (updateType) {
+    case FAST:
+        sendCommand(0x22); // Set "update sequence"
+        sendData(0xFF);    // Will load LUT from OTP memory, Display mode 2 "differential refresh"
+        break;
+
+    case FULL:
+    default:
+        sendCommand(0x22); // Set "update sequence"
+        sendData(0xF7);    // Will load LUT from OTP memory
+        break;
+    }
+}
+
+// Once the refresh operation has been started,
+// begin periodically polling the display to check for completion, using the normal Meshtastic threading code
+// Only used when refresh is "async"
+void MESHPOCKET_SSD1680::detachFromUpdate()
+{
+    switch (updateType) {
+    case FAST:
+        return beginPolling(50, 500); // At least 500ms for fast refresh
+    case FULL:
+    default:
+        return beginPolling(100, 2000); // At least 2 seconds for full refresh
+    }
+}
+
+void MESHPOCKET_SSD1680::update(uint8_t *imageData, UpdateTypes type)
path: src/graphics/niche/Drivers/EInk/MESHPOCKET_SSD1680.cpp
user: login: todd-herbert
body: Ah, now I understand! I think the problem is because of `configWaveform`. It should handle two different cases (like [`DEPG0290BNS800::configWaveform`](<https://github.com/meshtastic/firmware/blob/91f38797a8c770b693a30ee52f5be6d81bcf14c9/src/graphics/niche/Drivers/EInk/DEPG0290BNS800.cpp#L55-L71>)).

```cpp
void LCMEN2R13ECC1::configWaveform()
{
    switch (updateType) {
    case FAST:
        sendCommand(0x3C); // Border waveform:
        sendData(0x85); // VCOM? 0x85 is explicitly specified by Wisevast.
        break;

    case FULL:
    default:
        // From OTP memory
        break;
    }
}
```

I think with this change, the default `SSD1860::update` will probably work correctly and refresh the border during full update.
created_at: 2025-04-10T08:50:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,106 @@
+#include "./MESHPOCKET_SSD1680.h"
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+using namespace NicheGraphics::Drivers;
+
+// Map the display controller IC's output to the connected panel
+void MESHPOCKET_SSD1680::configScanning()
+{
+    // "Driver output control"
+    sendCommand(0x01);
+    sendData(0xF9);
+    sendData(0x00);
+    sendData(0x00);
+
+    // To-do: delete this method?
+    // Values set here might be redundant: C7, 00, 00 seems to be default
+}
+
+// Specify which information is used to control the sequence of voltages applied to move the pixels
+// - For this display, configUpdateSequence() specifies that a suitable LUT will be loaded from
+//   the controller IC's OTP memory, when the update procedure begins.
+void MESHPOCKET_SSD1680::configWaveform()
+{
+    sendCommand(0x3C); // Border waveform:
+    sendData(0x85);    // Screen border should follow LUT1 waveform (actively drive pixels white)
+
+    sendCommand(0x18); // Temperature sensor:
+    sendData(0x80);    // Use internal temperature sensor to select an appropriate refresh waveform
+}
+
+void MESHPOCKET_SSD1680::configUpdateSequence()
+{
+    switch (updateType) {
+    case FAST:
+        sendCommand(0x22); // Set "update sequence"
+        sendData(0xFF);    // Will load LUT from OTP memory, Display mode 2 "differential refresh"
+        break;
+
+    case FULL:
+    default:
+        sendCommand(0x22); // Set "update sequence"
+        sendData(0xF7);    // Will load LUT from OTP memory
+        break;
+    }
+}
+
+// Once the refresh operation has been started,
+// begin periodically polling the display to check for completion, using the normal Meshtastic threading code
+// Only used when refresh is "async"
+void MESHPOCKET_SSD1680::detachFromUpdate()
+{
+    switch (updateType) {
+    case FAST:
+        return beginPolling(50, 500); // At least 500ms for fast refresh
+    case FULL:
+    default:
+        return beginPolling(100, 2000); // At least 2 seconds for full refresh
+    }
+}
+
+void MESHPOCKET_SSD1680::update(uint8_t *imageData, UpdateTypes type)
path: src/graphics/niche/Drivers/EInk/MESHPOCKET_SSD1680.cpp
user: login: Heltec-Aaron-Lee
body: I tested it just a few minutes ago, and it worked as expected. 👍 
created_at: 2025-04-10T09:12:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,54 @@
+{
+    "build": {
+      "arduino": {
+        "ldscript": "nrf52840_s140_v6.ld"
+      },
+      "core": "nRF5",
+      "cpu": "cortex-m4",
+      "extra_flags": "-DNRF52840_XXAA",
+      "f_cpu": "64000000L",
+      "hwids": [
+        ["0x239A", "0x4405"],
+        ["0x239A", "0x0029"],
+        ["0x239A", "0x002A"]
+      ],
+      "usb_product": "HT-n5262",
+      "mcu": "nrf52840",
+      "variant": "heltec_mesh_node_t114",
path: boards/heltec_mesh_pocket.json
user: login: fifieldt
body: should this be "variant": "heltec_mesh_pocket" or similar?
created_at: 2025-04-11T07:18:02Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,54 @@
+{
+    "build": {
+      "arduino": {
+        "ldscript": "nrf52840_s140_v6.ld"
+      },
+      "core": "nRF5",
+      "cpu": "cortex-m4",
+      "extra_flags": "-DNRF52840_XXAA",
+      "f_cpu": "64000000L",
+      "hwids": [
+        ["0x239A", "0x4405"],
+        ["0x239A", "0x0029"],
+        ["0x239A", "0x002A"]
+      ],
+      "usb_product": "HT-n5262",
+      "mcu": "nrf52840",
+      "variant": "heltec_mesh_node_t114",
+      "variants_dir": "variants",
+      "bsp": {
+        "name": "adafruit"
+      },
+      "softdevice": {
+        "sd_flags": "-DS140",
+        "sd_name": "s140",
+        "sd_version": "6.1.1",
+        "sd_fwid": "0x00B6"
+      },
+      "bootloader": {
+        "settings_addr": "0xFF000"
+      }
+    },
+    "connectivity": ["bluetooth"],
+    "debug": {
+      "jlink_device": "nRF52840_xxAA",
+      "onboard_tools": ["jlink"],
+      "svd_path": "nrf52840.svd",
+      "openocd_target": "nrf52840-mdk-rs"
+    },
+    "frameworks": ["arduino"],
+    "name": "Heltec nrf (Adafruit BSP)",
+    "upload": {
+      "maximum_ram_size": 248832,
+      "maximum_size": 815104,
+      "speed": 115200,
+      "protocol": "nrfutil",
+      "protocols": ["jlink", "nrfjprog", "nrfutil", "stlink"],
+      "use_1200bps_touch": true,
+      "require_upload_port": true,
+      "wait_for_upload_port": true
+    },
+    "url": "FIXME",
path: boards/heltec_mesh_pocket.json
user: login: fifieldt
body: Maybe FIXME -->  https://heltec.org/project/meshpocket/ ?
created_at: 2025-04-11T07:18:38Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,54 @@
+{
+    "build": {
+      "arduino": {
+        "ldscript": "nrf52840_s140_v6.ld"
+      },
+      "core": "nRF5",
+      "cpu": "cortex-m4",
+      "extra_flags": "-DNRF52840_XXAA",
+      "f_cpu": "64000000L",
+      "hwids": [
+        ["0x239A", "0x4405"],
+        ["0x239A", "0x0029"],
+        ["0x239A", "0x002A"]
+      ],
+      "usb_product": "HT-n5262",
+      "mcu": "nrf52840",
+      "variant": "heltec_mesh_node_t114",
path: boards/heltec_mesh_pocket.json
user: login: Heltec-Aaron-Lee
body: Oh, I'm so sorry about this. It was a careless mistake that has already been fixed.
created_at: 2025-04-15T03:08:55Z
author_association: CONTRIBUTOR

PR comments:
user: login: Heltec-Aaron-Lee
created_at: 2025-04-15T03:10:03Z
body: Sorry about this careless mistake. I have already fixed them.
author_association: CONTRIBUTOR
user: login: fifieldt
created_at: 2025-04-15T03:41:02Z
body: Note for maintainers: going to fix the trunk bits in another patch.
author_association: MEMBER
user: login: fifieldt
created_at: 2025-04-15T04:39:42Z
body: Protobufs hadn't been merged yet, so build for these is failing: https://github.com/meshtastic/firmware/pull/6589
author_association: MEMBER
user: login: mrekin
created_at: 2025-11-06T14:24:29Z
body: > [MeshPocket](https://heltec.org/project/meshpocket/)
> * Added 5000/10000mAh discharge curve data to improve the accuracy of the power display icon.

Little question - does both (5 and 10 ) meshpocker variants have lihv batteries ? 
Want to understand why battary curves differs in max values (4200 vs 4300)

author_association: CONTRIBUTOR