Files
Paperwall/display/epd4in0e_driver.py
Honey 61348be674 Landscape orientation, redesigned web UI, rotation on/off + minutes, weather by city name
- Panel is mounted sideways: render landscape (600x400), rotate to the
  panel's native portrait orientation right before writing to hardware.
- Web UI: card-based layout, dark mode, active nav highlighting, save
  confirmation toasts, error banners.
- Rotation: separate enabled/disabled toggle (off pins to the first screen
  in the list) and the interval is now in minutes instead of raw seconds.
  Migrates existing config.json from the old interval_seconds field.
- Weather: type a city name instead of hunting down coordinates, resolved
  via Open-Meteo's free geocoding API. Manual lat/long still available as
  a fallback.
2026-07-21 22:51:16 +02:00

66 lines
2.3 KiB
Python

"""Driver for the Waveshare epd4in0e (4", 6-color Spectra) panel.
Thin wrapper around the vendored `waveshare_epd.epd4in0e` module -- the exact
driver already proven working under the InkyPi install on this Pi. That
driver only exposes `Clear()` and `display()`, both of which do a full
`TurnOnDisplay()` flash cycle; there is no partial-refresh path for this
panel at the hardware/SDK level, confirmed by reading the driver itself.
"""
import logging
from display import Display
logger = logging.getLogger(__name__)
# The panel is physically native portrait (400x600), but mounted sideways --
# screens render in this landscape resolution, and show() rotates the frame
# to match the panel's native orientation right before sending it over.
RESOLUTION = (600, 400)
PANEL_NATIVE_RESOLUTION = (400, 600)
class EPD4in0eDisplay(Display):
def __init__(self):
self.resolution = RESOLUTION
self._epd = None
def init(self) -> None:
from display.waveshare_epd import epd4in0e
self._epd = epd4in0e.EPD()
self._epd.init()
logger.info("epd4in0e initialized")
def show(self, image) -> None:
if self._epd is None:
self.init()
width, height = self.resolution
if image.size != (width, height):
raise ValueError(
f"Image size {image.size} does not match panel resolution {self.resolution}"
)
# Re-init: the panel goes to sleep after every show(), and needs a
# fresh init() before it will accept commands again.
self._epd.init()
# Clear first to avoid ghosting from the previous image -- also a
# full flash, there's no way around it on this panel.
self._epd.Clear()
# Rotate landscape -> the panel's native portrait orientation. If this
# comes out upside-down for how it's physically mounted, flip the
# sign here (rotate(-90) instead of rotate(90)).
rotated = image.convert("RGB").rotate(90, expand=True)
if rotated.size != PANEL_NATIVE_RESOLUTION:
raise ValueError(f"Rotated image {rotated.size} doesn't match panel {PANEL_NATIVE_RESOLUTION}")
self._epd.display(self._epd.getbuffer(rotated))
def sleep(self) -> None:
if self._epd is not None:
self._epd.sleep()
logger.info("epd4in0e put to sleep")