Files
MeshCore-Solo/src/helpers/ui/GxEPDDisplay.h
T
JakubandClaude Opus 4.8 f5b3213f80 Merge upstream/main (v1.16.0) into wio-unified
Merge 254 upstream commits. Highlights relevant to the Wio fork: native
NRF52 companion power-saving (sleep when !hasPendingWork), preamble 16->32
for SF<9, generic sensor-registration model, CMD_SEND_RAW_PACKET, isEink()
display abstraction, KEEP_DISPLAY_ON_USB, contacts-sync/transient fixes.

Conflicts resolved (11 files):
- buzzer.h/.cpp: keep NRF52 include guard + our begin()->startup(); fold in
  upstream doc comment.
- platformio (wio + eink): keep our slim env layout, add upstream kiss_modem
  env (+ debug_tool=stlink on eink ble).
- GxEPDDisplay.h: take upstream isEink() override.
- EnvironmentSensorManager.h: take upstream's wider #if guard.
- DataStore.cpp: take upstream saveContacts(filter) signature, keep our
  ::openWrite (member openWrite would shadow the global 2-arg overload).
- main.cpp: keep our watchdog init + add board.onBootComplete(); adopt
  upstream's `if(!hasPendingWork()) sleep` loop.
- MyMesh.h: version -> v1.16-solo.0, build date 6 Jun 2026.
- MyMesh.cpp: keep both CMD codes (SCREENSHOT 66 + RAW_PACKET 65), both field
  inits, both handlers, our screenshot fns + upstream saveContacts/save_filter,
  upstream hasPendingWork() + our MyMeshBot include.
- UITask.cpp: keep our solo splash/clock page/snprintf/buzzer init; merge
  auto-off (+ opt-in KEEP_DISPLAY_ON_USB); merge low-batt shutdown (our EMA +
  prefs threshold + upstream's !isExternalPowered() guard and eink-aware delay).

Post-merge drift fixes (upstream API/architecture changes, not conflicts):
- applyTxPower(): radio_set_tx_power() removed upstream -> radio_driver.setTxPower().
- Sensor refactor dropped the per-sensor *_initialized flags our
  getAvailableLPPTypes() relied on; removed that override and the SENSORS page
  now derives available LPP types from the populated sensors_lpp buffer.

README kept ours via .gitattributes merge=ours. Builds verified on all 5 Wio
envs (dual, eink dual, radio_ble, dual_dev, eink_dual_dev). Not yet
hardware-verified.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 17:21:41 +02:00

149 lines
5.4 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#pragma once
#include <SPI.h>
#include <Wire.h>
#define ENABLE_GxEPD2_GFX 0
// When ENABLE_SCREENSHOT is active, use the patched copy of GxEPD2_BW.h from
// lib/GxEPD2-patch/src/ that exposes getBuffer()/getBufferSize(). The include
// guard (_GxEPD2_BW_H_) then prevents the installed library version from
// being compiled again. Non-screenshot builds use the installed library as-is.
#ifdef ENABLE_SCREENSHOT
#include "../../../lib/GxEPD2-patch/src/GxEPD2_BW.h"
#else
#include <GxEPD2_BW.h>
#endif
#include <GxEPD2_3C.h>
#include <GxEPD2_4C.h>
#include <GxEPD2_7C.h>
#include <Fonts/FreeSans9pt7b.h>
#include <Fonts/FreeSansBold12pt7b.h>
#include <Fonts/FreeSans18pt7b.h>
#include <CRC32.h>
#include "DisplayDriver.h"
#include "LemonFont.h"
#ifndef DISPLAY_ROTATION
#define DISPLAY_ROTATION 0
#endif
// Panel native dimensions before rotation. Derived from the model class when
// EINK_DISPLAY_MODEL is set; override with EINK_PANEL_W/H for other panels.
#if defined(EINK_DISPLAY_MODEL)
#ifndef EINK_PANEL_W
#define EINK_PANEL_W EINK_DISPLAY_MODEL::WIDTH
#endif
#ifndef EINK_PANEL_H
#define EINK_PANEL_H EINK_DISPLAY_MODEL::HEIGHT
#endif
#else
#ifndef EINK_PANEL_W
#define EINK_PANEL_W 200
#define EINK_PANEL_H 200
#endif
#endif
// Odd rotations (1, 3) swap width and height.
#define EINK_DISP_W ((DISPLAY_ROTATION & 1) ? EINK_PANEL_H : EINK_PANEL_W)
#define EINK_DISP_H ((DISPLAY_ROTATION & 1) ? EINK_PANEL_W : EINK_PANEL_H)
class GxEPDDisplay : public DisplayDriver {
#if defined(EINK_DISPLAY_MODEL)
GxEPD2_BW<EINK_DISPLAY_MODEL, EINK_DISPLAY_MODEL::HEIGHT> display;
#else
GxEPD2_BW<GxEPD2_150_BN, 200> display;
#endif
bool _init = false;
bool _isOn = false;
bool _use_lemon = false;
uint16_t _curr_color;
CRC32 display_crc;
int last_display_crc_value = 0;
int _text_sz = 1;
uint8_t _full_refresh_interval = 0;
uint8_t _partial_count = 0;
int16_t drawLemonChar(int16_t x, int16_t y, uint32_t cp, int sc);
uint8_t lemonXAdvance(uint32_t cp, int sc);
int scale() const { return (width() >= height()) ? 2 : 1; }
public:
#if defined(EINK_DISPLAY_MODEL)
GxEPDDisplay() : DisplayDriver(EINK_DISP_W, EINK_DISP_H),
display(EINK_DISPLAY_MODEL(PIN_DISPLAY_CS, PIN_DISPLAY_DC, PIN_DISPLAY_RST, PIN_DISPLAY_BUSY)) {}
#else
GxEPDDisplay() : DisplayDriver(EINK_DISP_W, EINK_DISP_H),
display(GxEPD2_150_BN(DISP_CS, DISP_DC, DISP_RST, DISP_BUSY)) {}
#endif
#ifdef ENABLE_SCREENSHOT
const uint8_t* getBuffer() override { return display.getBuffer(); }
uint16_t getBufferSize() override { return display.getBufferSize(); }
uint8_t getDisplayType() override { return 1; } // 1 = e-ink
// Return GxEPD2's own reported dimensions (uses WIDTH_VISIBLE, not the full physical WIDTH
// stored in DisplayDriver). After setRotation(1): width()=HEIGHT, height()=WIDTH_VISIBLE.
int screenshotWidth() override { return (int)display.width(); }
int screenshotHeight() override { return (int)display.height(); }
uint8_t screenshotRotation() override { return (uint8_t)display.getRotation(); }
#endif
// Line height and approx. char width for each font size:
// 1 = FreeSans9pt (lineH=16, charW≈9)
// 2 = FreeSansBold12pt (lineH=20, charW≈12)
// 3 = FreeSans18pt (lineH=28, charW≈17)
// Built-in font scale used for size 4 (huge clock digits): 6×8 cell × 7 = 42×56.
static const int BIG_TEXT_SCALE = 7;
// Size 1 scales with orientation (portrait 1×, landscape 2×); size 2 is always 12×16;
// size 3 is FreeSans18pt (~17×28); size 4 is the built-in font at 7× (~42×56),
// used only for the stacked HH/MM clock on portrait e-ink. Landscape = width >= height.
int getCharWidth() const override {
int sc = scale();
if (_text_sz == 4) return 6 * BIG_TEXT_SCALE;
if (_text_sz == 3) return 17;
if (_text_sz == 2) return 12 * sc;
return (_use_lemon ? 5 : 6) * sc;
}
int getLineHeight() const override {
int sc = scale();
if (_text_sz == 4) return 8 * BIG_TEXT_SCALE;
if (_text_sz == 3) return 28;
if (_text_sz == 2) return 16 * sc;
return (_use_lemon ? 10 : 8) * sc;
}
void setLemonFont(bool enabled) override { _use_lemon = enabled; _vw_dirty = true; }
bool isLemonFont() const override { return _use_lemon; }
void translateUTF8ToBlocks(char* dest, const char* src, size_t dest_size) override {
if (_use_lemon) {
strncpy(dest, src, dest_size - 1);
dest[dest_size - 1] = '\0';
} else {
translateUTF8Static(dest, src, dest_size);
}
}
bool begin();
bool isOn() override { return _isOn; }
bool isEink() override { return true; }
void turnOn() override;
void turnOff() override;
void clear() override;
void startFrame(Color bkg = DARK) override;
void setTextSize(int sz) override;
void setColor(Color c) override;
void setCursor(int x, int y) override;
void print(const char* str) override;
void fillRect(int x, int y, int w, int h) override;
void drawRect(int x, int y, int w, int h) override;
void drawXbm(int x, int y, const uint8_t* bits, int w, int h) override;
uint16_t getTextWidth(const char* str) override;
uint16_t getCodepointWidth(uint32_t cp) override {
if (_use_lemon && _text_sz == 1) return lemonXAdvance(cp, scale());
return DisplayDriver::getCodepointWidth(cp);
}
void setDisplayRotation(uint8_t rot) override;
void setFullRefreshInterval(uint8_t n) override { _full_refresh_interval = n; _partial_count = 0; }
void endFrame() override;
};