Files
MeshCore-Solo/src/helpers/ui/GxEPDDisplay.h
Jakub 7d9d5f847d feat(screenshot): extend e-ink screenshot support via GxEPD2 patch
- Add lib/GxEPD2-patch/src/GxEPD2_BW.h: local patched copy of GxEPD2_BW.h
  that exposes getBuffer()/getBufferSize() under ENABLE_SCREENSHOT guard.
  Include guard (_GxEPD2_BW_H_) prevents double-inclusion of the installed
  library version.

- GxEPDDisplay.h: use patched header via relative include when
  ENABLE_SCREENSHOT so the patch takes precedence over the installed lib
  (PlatformIO adds library paths before -I build_flags).

- DisplayDriver.h: add virtual getBuffer()/getBufferSize()/getDisplayType()
  defaults (nullptr/0/0) under ENABLE_SCREENSHOT.

- SH1106Display.h / SSD1306Display.h / GxEPDDisplay.h: add concrete overrides;
  getDisplayType() returns 0 (OLED) or 1 (e-ink).

- MyMesh.cpp/h: replace fragile C-cast with virtual dispatch in
  handleScreenshotRequest(); extend 5-byte header to 6 bytes by appending
  display_type so the host tool can decode the correct pixel layout.

- tools/screenshot.py: parse 6-byte header; add eink_buffer_to_image()
  that decodes the row-major MSB-first GxEPD2 buffer with DISPLAY_ROTATION=1
  (phys_x = 127-ly, phys_y = lx); dispatch on display_type.

- variants/wio-tracker-l1-eink/platformio.ini: add
  [env:WioTrackerL1Eink_companion_dual_dev] with ENABLE_SCREENSHOT.
- variants/wio-tracker-l1/platformio.ini: unchanged (OLED env already exists).

Builds verified: WioTrackerL1Eink_companion_dual_dev SUCCESS,
                 WioTrackerL1Eink_companion_radio_ble SUCCESS (unaffected).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 01:03:47 +02:00

138 lines
4.6 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
#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)
// Size 1 scales with orientation (portrait 1×, landscape 2×); size 2 is always 12×16;
// size 3 is FreeSans18pt (~17×28). Landscape = width >= height.
int getCharWidth() const override {
int sc = 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 == 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; }
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;
};