feat: Add support for SSD168x ePaper panels
- Introduced EPaperPanels.ts to define configurations for various ePaper panels including dimensions, refresh rates, and controller details.
- Implemented SSD168xDecoder.ts to handle the decoding of SPI commands for the SSD168x family of ePaper displays.
- Created EPaperPart.ts to manage the simulation of ePaper panels, integrating with the existing simulator architecture and handling events.
- Added example sketches for 2.13", 2.9", 4.2", and 7.5" ePaper displays, demonstrating basic functionality and text rendering.
- Ensured compatibility with AVR, RP2040, and ESP32 platforms, with appropriate pin configurations for each.
2026-04-30 08:23:00 +07:00
|
|
|
|
"""ESP32 SPI slave state machines — runs inside the worker subprocess
|
|
|
|
|
|
synchronously, alongside the existing I2C slaves in
|
|
|
|
|
|
``esp32_i2c_slaves.py``.
|
|
|
|
|
|
|
2026-04-30 10:24:20 +07:00
|
|
|
|
Currently houses two ePaper decoders:
|
|
|
|
|
|
|
|
|
|
|
|
* :class:`Ssd168xEpaperSlave` — Solomon Systech SSD168x family
|
|
|
|
|
|
(mono + B/W/Red panels, 1.54"–7.5"). Latches on opcode 0x20.
|
|
|
|
|
|
* :class:`Uc8159cEpaperSlave` — UltraChip UC8159c (ACeP 7-colour 5.65"
|
|
|
|
|
|
GoodDisplay GDEP0565D90 / Waveshare). Latches on opcode 0x12.
|
|
|
|
|
|
|
|
|
|
|
|
Both classes have the same surface (``feed(byte, dc_high)``, ``reset()``,
|
|
|
|
|
|
``on_flush`` callback) so the worker dispatch in
|
|
|
|
|
|
``esp32_worker.py::_on_spi_event`` can route bytes by ``cs_low`` without
|
|
|
|
|
|
caring which controller is mounted.
|
feat: Add support for SSD168x ePaper panels
- Introduced EPaperPanels.ts to define configurations for various ePaper panels including dimensions, refresh rates, and controller details.
- Implemented SSD168xDecoder.ts to handle the decoding of SPI commands for the SSD168x family of ePaper displays.
- Created EPaperPart.ts to manage the simulation of ePaper panels, integrating with the existing simulator architecture and handling events.
- Added example sketches for 2.13", 2.9", 4.2", and 7.5" ePaper displays, demonstrating basic functionality and text rendering.
- Ensured compatibility with AVR, RP2040, and ESP32 platforms, with appropriate pin configurations for each.
2026-04-30 08:23:00 +07:00
|
|
|
|
"""
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
|
|
import base64
|
|
|
|
|
|
from dataclasses import dataclass, field
|
|
|
|
|
|
from typing import Callable, List, Optional
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ── Command opcodes (SSD1681; SSD1675/1680/1683 share these) ─────────────────
|
|
|
|
|
|
|
|
|
|
|
|
CMD_DRIVER_OUTPUT_CTRL = 0x01
|
|
|
|
|
|
CMD_GATE_DRIVING_VOLTAGE = 0x03
|
|
|
|
|
|
CMD_SOURCE_DRIVING_VOLT = 0x04
|
|
|
|
|
|
CMD_DEEP_SLEEP = 0x10
|
|
|
|
|
|
CMD_DATA_ENTRY_MODE = 0x11
|
|
|
|
|
|
CMD_SW_RESET = 0x12
|
|
|
|
|
|
CMD_TEMP_SENSOR = 0x18
|
|
|
|
|
|
CMD_MASTER_ACTIVATION = 0x20
|
|
|
|
|
|
CMD_DISP_UPDATE_CTRL_1 = 0x21
|
|
|
|
|
|
CMD_DISP_UPDATE_CTRL_2 = 0x22
|
|
|
|
|
|
CMD_WRITE_BLACK_VRAM = 0x24
|
|
|
|
|
|
CMD_WRITE_RED_VRAM = 0x26
|
|
|
|
|
|
CMD_WRITE_VCOM_REG = 0x2C
|
|
|
|
|
|
CMD_WRITE_LUT = 0x32
|
|
|
|
|
|
CMD_BORDER_WAVEFORM = 0x3C
|
|
|
|
|
|
CMD_END_OPTION = 0x3F
|
|
|
|
|
|
CMD_SET_RAMX_RANGE = 0x44
|
|
|
|
|
|
CMD_SET_RAMY_RANGE = 0x45
|
|
|
|
|
|
CMD_SET_RAMX_COUNTER = 0x4E
|
|
|
|
|
|
CMD_SET_RAMY_COUNTER = 0x4F
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
|
|
class Frame:
|
|
|
|
|
|
"""Composed B/W (and optionally red) frame ready to ship to the frontend."""
|
|
|
|
|
|
width: int
|
|
|
|
|
|
height: int
|
|
|
|
|
|
pixels: bytes # length == width * height; values 0=black, 1=white, 2=red
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
|
|
class Ssd168xEpaperSlave:
|
|
|
|
|
|
"""Stateful SSD168x SPI peripheral. Algorithm verbatim with the Python
|
|
|
|
|
|
reference in ``test/test_epaper/ssd168x_decoder.py``."""
|
|
|
|
|
|
|
|
|
|
|
|
component_id: str
|
|
|
|
|
|
width: int
|
|
|
|
|
|
height: int
|
|
|
|
|
|
on_flush: Optional[Callable[[Frame], None]] = None
|
2026-06-04 12:47:17 +07:00
|
|
|
|
# True for tri-colour B/W/Red panels (0x26 = additive red plane). False for
|
|
|
|
|
|
# plain B/W panels, where some controllers (e.g. GDEY029T94) put the image
|
|
|
|
|
|
# into 0x26 as a second mono plane.
|
|
|
|
|
|
is_bwr: bool = False
|
feat: Add support for SSD168x ePaper panels
- Introduced EPaperPanels.ts to define configurations for various ePaper panels including dimensions, refresh rates, and controller details.
- Implemented SSD168xDecoder.ts to handle the decoding of SPI commands for the SSD168x family of ePaper displays.
- Created EPaperPart.ts to manage the simulation of ePaper panels, integrating with the existing simulator architecture and handling events.
- Added example sketches for 2.13", 2.9", 4.2", and 7.5" ePaper displays, demonstrating basic functionality and text rendering.
- Ensured compatibility with AVR, RP2040, and ESP32 platforms, with appropriate pin configurations for each.
2026-04-30 08:23:00 +07:00
|
|
|
|
|
|
|
|
|
|
bw_ram: bytearray = field(init=False)
|
|
|
|
|
|
red_ram: bytearray = field(init=False)
|
2026-06-04 12:47:17 +07:00
|
|
|
|
# RAM geometry — sized to the LONGER side both ways so a rotated native
|
|
|
|
|
|
# layout (a 296x128 landscape panel whose controller RAM is 128x296) is
|
|
|
|
|
|
# captured without dropping rows. compose_frame() reads back the active
|
|
|
|
|
|
# window and rotates to the display orientation.
|
|
|
|
|
|
_ram_bpr: int = field(init=False, default=0)
|
|
|
|
|
|
_ram_rows: int = field(init=False, default=0)
|
feat: Add support for SSD168x ePaper panels
- Introduced EPaperPanels.ts to define configurations for various ePaper panels including dimensions, refresh rates, and controller details.
- Implemented SSD168xDecoder.ts to handle the decoding of SPI commands for the SSD168x family of ePaper displays.
- Created EPaperPart.ts to manage the simulation of ePaper panels, integrating with the existing simulator architecture and handling events.
- Added example sketches for 2.13", 2.9", 4.2", and 7.5" ePaper displays, demonstrating basic functionality and text rendering.
- Ensured compatibility with AVR, RP2040, and ESP32 platforms, with appropriate pin configurations for each.
2026-04-30 08:23:00 +07:00
|
|
|
|
_current_cmd: int = -1
|
|
|
|
|
|
_params: List[int] = field(default_factory=list)
|
|
|
|
|
|
_ram_target: str = "bw"
|
|
|
|
|
|
_x_byte: int = 0
|
|
|
|
|
|
_y: int = 0
|
|
|
|
|
|
_xrange: tuple = (0, 0)
|
|
|
|
|
|
_yrange: tuple = (0, 0)
|
fix(epaper): correct orientation across all boards + Pico VCC wire
ePaper panels rendered rotated/misaligned on AVR and RP2040 (e.g. the 2.13"
Pico clock came out sideways and clipped). The ESP32 worker decoder was just
taught to compose in the controller's native RAM geometry and rotate to the
display orientation, but the browser-side SSD168xDecoder (used by AVR/RP2040)
still composed at display dims with no rotation, so the two diverged.
- SSD168xDecoder.ts: port the worker's native-window compose + rotation.
* Size RAM to the longer side both ways so a rotated native layout
(128x296 behind a 296x128 panel) isn't truncated.
* Compose in the active RAM window, then rotate via the inverse of
Adafruit_GFX setRotation(1). Detect orientation by BYTE width so a
non-multiple-of-8 native width (the 2.13" panel is 122 px) is handled.
* Track the UNION of windows per frame: paged drivers (GxEPD2 page height
< panel) set one partial window per page, so compose must use the full
native area, not just the last page's strip. Fixes the all-white render
on paged panels (1.54" Uno, 4.2" Pico, 7.5" ESP32).
* Add an isBwr option: B/W panels treat 0x26 as a 2nd mono plane (white
only if both planes white), tri-colour panels keep red-wins.
* Default the active window to display geometry; the firmware overrides it.
- EPaperPart.ts: pass isBwr = cfg.palette === 'bwr' to the decoder.
- esp32_spi_slaves.py / esp32_worker.py: mirror the byte-aware rotation +
window-union in the worker, and derive is_bwr from panel_kind on the
runtime sensor_attach path too (fixes the tri-colour ESP32 alert badge).
- test_epaper/ssd168x_decoder.py: re-port the golden reference to match
(keeps the 3-way TS/Python/worker identity invariant). Tests updated to
construct tri-colour cases with is_bwr/palette='bwr'.
- examples-displays-epaper.ts: the Pico VCC wire referenced '3V3(OUT)',
which the velxio-pi-pico-w element doesn't expose (it has '3V3'), so the
wire snapped to the board corner. Use '3V3'.
2026-06-04 13:32:02 +07:00
|
|
|
|
# UNION of every RAM window set since the last flush — paged drivers set one
|
|
|
|
|
|
# partial window per page, so compose must use the union (full native area),
|
|
|
|
|
|
# not just the last page's strip.
|
|
|
|
|
|
_win_x0: int = 0
|
|
|
|
|
|
_win_x1: int = 0
|
|
|
|
|
|
_win_y0: int = 0
|
|
|
|
|
|
_win_y1: int = 0
|
|
|
|
|
|
_win_x_set: bool = False
|
|
|
|
|
|
_win_y_set: bool = False
|
feat: Add support for SSD168x ePaper panels
- Introduced EPaperPanels.ts to define configurations for various ePaper panels including dimensions, refresh rates, and controller details.
- Implemented SSD168xDecoder.ts to handle the decoding of SPI commands for the SSD168x family of ePaper displays.
- Created EPaperPart.ts to manage the simulation of ePaper panels, integrating with the existing simulator architecture and handling events.
- Added example sketches for 2.13", 2.9", 4.2", and 7.5" ePaper displays, demonstrating basic functionality and text rendering.
- Ensured compatibility with AVR, RP2040, and ESP32 platforms, with appropriate pin configurations for each.
2026-04-30 08:23:00 +07:00
|
|
|
|
_entry_mode: int = 0x03
|
|
|
|
|
|
refreshed_count: int = 0
|
|
|
|
|
|
unknown_cmds: List[int] = field(default_factory=list)
|
|
|
|
|
|
in_deep_sleep: bool = False
|
|
|
|
|
|
|
|
|
|
|
|
def __post_init__(self) -> None:
|
2026-06-04 12:47:17 +07:00
|
|
|
|
long_side = max(self.width, self.height)
|
|
|
|
|
|
self._ram_bpr = (long_side + 7) // 8
|
|
|
|
|
|
self._ram_rows = long_side
|
|
|
|
|
|
n = self._ram_bpr * self._ram_rows
|
|
|
|
|
|
self.bw_ram = bytearray([0xFF] * n)
|
|
|
|
|
|
# B/W panel: 0x26 is a second mono plane → init white. B/W/R panel:
|
|
|
|
|
|
# 0x26 is the additive red plane → init "no red" (0x00).
|
|
|
|
|
|
self.red_ram = bytearray([0x00 if self.is_bwr else 0xFF] * n)
|
fix(epaper): correct orientation across all boards + Pico VCC wire
ePaper panels rendered rotated/misaligned on AVR and RP2040 (e.g. the 2.13"
Pico clock came out sideways and clipped). The ESP32 worker decoder was just
taught to compose in the controller's native RAM geometry and rotate to the
display orientation, but the browser-side SSD168xDecoder (used by AVR/RP2040)
still composed at display dims with no rotation, so the two diverged.
- SSD168xDecoder.ts: port the worker's native-window compose + rotation.
* Size RAM to the longer side both ways so a rotated native layout
(128x296 behind a 296x128 panel) isn't truncated.
* Compose in the active RAM window, then rotate via the inverse of
Adafruit_GFX setRotation(1). Detect orientation by BYTE width so a
non-multiple-of-8 native width (the 2.13" panel is 122 px) is handled.
* Track the UNION of windows per frame: paged drivers (GxEPD2 page height
< panel) set one partial window per page, so compose must use the full
native area, not just the last page's strip. Fixes the all-white render
on paged panels (1.54" Uno, 4.2" Pico, 7.5" ESP32).
* Add an isBwr option: B/W panels treat 0x26 as a 2nd mono plane (white
only if both planes white), tri-colour panels keep red-wins.
* Default the active window to display geometry; the firmware overrides it.
- EPaperPart.ts: pass isBwr = cfg.palette === 'bwr' to the decoder.
- esp32_spi_slaves.py / esp32_worker.py: mirror the byte-aware rotation +
window-union in the worker, and derive is_bwr from panel_kind on the
runtime sensor_attach path too (fixes the tri-colour ESP32 alert badge).
- test_epaper/ssd168x_decoder.py: re-port the golden reference to match
(keeps the 3-way TS/Python/worker identity invariant). Tests updated to
construct tri-colour cases with is_bwr/palette='bwr'.
- examples-displays-epaper.ts: the Pico VCC wire referenced '3V3(OUT)',
which the velxio-pi-pico-w element doesn't expose (it has '3V3'), so the
wire snapped to the board corner. Use '3V3'.
2026-06-04 13:32:02 +07:00
|
|
|
|
# Default active window = DISPLAY geometry (the firmware overrides via
|
|
|
|
|
|
# 0x44/0x45 before writing). RAM is sized larger; until a window is set
|
|
|
|
|
|
# the panel is treated as un-rotated display-sized.
|
|
|
|
|
|
self._xrange = (0, (self.width + 7) // 8 - 1)
|
|
|
|
|
|
self._yrange = (0, self.height - 1)
|
feat: Add support for SSD168x ePaper panels
- Introduced EPaperPanels.ts to define configurations for various ePaper panels including dimensions, refresh rates, and controller details.
- Implemented SSD168xDecoder.ts to handle the decoding of SPI commands for the SSD168x family of ePaper displays.
- Created EPaperPart.ts to manage the simulation of ePaper panels, integrating with the existing simulator architecture and handling events.
- Added example sketches for 2.13", 2.9", 4.2", and 7.5" ePaper displays, demonstrating basic functionality and text rendering.
- Ensured compatibility with AVR, RP2040, and ESP32 platforms, with appropriate pin configurations for each.
2026-04-30 08:23:00 +07:00
|
|
|
|
|
|
|
|
|
|
# ── Public API ─────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
def feed(self, byte: int, dc_high: bool) -> None:
|
|
|
|
|
|
"""Process one SPI byte. ``dc_high`` mirrors the DC pin (False = command)."""
|
|
|
|
|
|
if not dc_high:
|
|
|
|
|
|
self._begin_command(byte & 0xFF)
|
|
|
|
|
|
else:
|
|
|
|
|
|
self._handle_data(byte & 0xFF)
|
|
|
|
|
|
|
|
|
|
|
|
def reset(self) -> None:
|
2026-06-04 12:47:17 +07:00
|
|
|
|
n = self._ram_bpr * self._ram_rows
|
|
|
|
|
|
self.bw_ram = bytearray([0xFF] * n)
|
|
|
|
|
|
self.red_ram = bytearray([0x00 if self.is_bwr else 0xFF] * n)
|
feat: Add support for SSD168x ePaper panels
- Introduced EPaperPanels.ts to define configurations for various ePaper panels including dimensions, refresh rates, and controller details.
- Implemented SSD168xDecoder.ts to handle the decoding of SPI commands for the SSD168x family of ePaper displays.
- Created EPaperPart.ts to manage the simulation of ePaper panels, integrating with the existing simulator architecture and handling events.
- Added example sketches for 2.13", 2.9", 4.2", and 7.5" ePaper displays, demonstrating basic functionality and text rendering.
- Ensured compatibility with AVR, RP2040, and ESP32 platforms, with appropriate pin configurations for each.
2026-04-30 08:23:00 +07:00
|
|
|
|
self._current_cmd = -1
|
|
|
|
|
|
self._params = []
|
|
|
|
|
|
self._ram_target = "bw"
|
|
|
|
|
|
self._x_byte = 0
|
|
|
|
|
|
self._y = 0
|
fix(epaper): correct orientation across all boards + Pico VCC wire
ePaper panels rendered rotated/misaligned on AVR and RP2040 (e.g. the 2.13"
Pico clock came out sideways and clipped). The ESP32 worker decoder was just
taught to compose in the controller's native RAM geometry and rotate to the
display orientation, but the browser-side SSD168xDecoder (used by AVR/RP2040)
still composed at display dims with no rotation, so the two diverged.
- SSD168xDecoder.ts: port the worker's native-window compose + rotation.
* Size RAM to the longer side both ways so a rotated native layout
(128x296 behind a 296x128 panel) isn't truncated.
* Compose in the active RAM window, then rotate via the inverse of
Adafruit_GFX setRotation(1). Detect orientation by BYTE width so a
non-multiple-of-8 native width (the 2.13" panel is 122 px) is handled.
* Track the UNION of windows per frame: paged drivers (GxEPD2 page height
< panel) set one partial window per page, so compose must use the full
native area, not just the last page's strip. Fixes the all-white render
on paged panels (1.54" Uno, 4.2" Pico, 7.5" ESP32).
* Add an isBwr option: B/W panels treat 0x26 as a 2nd mono plane (white
only if both planes white), tri-colour panels keep red-wins.
* Default the active window to display geometry; the firmware overrides it.
- EPaperPart.ts: pass isBwr = cfg.palette === 'bwr' to the decoder.
- esp32_spi_slaves.py / esp32_worker.py: mirror the byte-aware rotation +
window-union in the worker, and derive is_bwr from panel_kind on the
runtime sensor_attach path too (fixes the tri-colour ESP32 alert badge).
- test_epaper/ssd168x_decoder.py: re-port the golden reference to match
(keeps the 3-way TS/Python/worker identity invariant). Tests updated to
construct tri-colour cases with is_bwr/palette='bwr'.
- examples-displays-epaper.ts: the Pico VCC wire referenced '3V3(OUT)',
which the velxio-pi-pico-w element doesn't expose (it has '3V3'), so the
wire snapped to the board corner. Use '3V3'.
2026-06-04 13:32:02 +07:00
|
|
|
|
self._entry_mode = 0x03
|
|
|
|
|
|
self._xrange = (0, (self.width + 7) // 8 - 1)
|
|
|
|
|
|
self._yrange = (0, self.height - 1)
|
|
|
|
|
|
self._win_x_set = False
|
|
|
|
|
|
self._win_y_set = False
|
feat: Add support for SSD168x ePaper panels
- Introduced EPaperPanels.ts to define configurations for various ePaper panels including dimensions, refresh rates, and controller details.
- Implemented SSD168xDecoder.ts to handle the decoding of SPI commands for the SSD168x family of ePaper displays.
- Created EPaperPart.ts to manage the simulation of ePaper panels, integrating with the existing simulator architecture and handling events.
- Added example sketches for 2.13", 2.9", 4.2", and 7.5" ePaper displays, demonstrating basic functionality and text rendering.
- Ensured compatibility with AVR, RP2040, and ESP32 platforms, with appropriate pin configurations for each.
2026-04-30 08:23:00 +07:00
|
|
|
|
self.in_deep_sleep = False
|
|
|
|
|
|
|
|
|
|
|
|
def compose_frame(self) -> Frame:
|
2026-06-04 12:47:17 +07:00
|
|
|
|
# Compose in the controller's NATIVE geometry — the active RAM window
|
|
|
|
|
|
# the firmware actually wrote (0x44/0x45) — then rotate to the display
|
|
|
|
|
|
# orientation. Handles panels driven with setRotation() whose native
|
|
|
|
|
|
# RAM (e.g. 128x296) is the transpose of the display (296x128); the old
|
|
|
|
|
|
# code assumed display==native and dropped half the rows.
|
fix(epaper): correct orientation across all boards + Pico VCC wire
ePaper panels rendered rotated/misaligned on AVR and RP2040 (e.g. the 2.13"
Pico clock came out sideways and clipped). The ESP32 worker decoder was just
taught to compose in the controller's native RAM geometry and rotate to the
display orientation, but the browser-side SSD168xDecoder (used by AVR/RP2040)
still composed at display dims with no rotation, so the two diverged.
- SSD168xDecoder.ts: port the worker's native-window compose + rotation.
* Size RAM to the longer side both ways so a rotated native layout
(128x296 behind a 296x128 panel) isn't truncated.
* Compose in the active RAM window, then rotate via the inverse of
Adafruit_GFX setRotation(1). Detect orientation by BYTE width so a
non-multiple-of-8 native width (the 2.13" panel is 122 px) is handled.
* Track the UNION of windows per frame: paged drivers (GxEPD2 page height
< panel) set one partial window per page, so compose must use the full
native area, not just the last page's strip. Fixes the all-white render
on paged panels (1.54" Uno, 4.2" Pico, 7.5" ESP32).
* Add an isBwr option: B/W panels treat 0x26 as a 2nd mono plane (white
only if both planes white), tri-colour panels keep red-wins.
* Default the active window to display geometry; the firmware overrides it.
- EPaperPart.ts: pass isBwr = cfg.palette === 'bwr' to the decoder.
- esp32_spi_slaves.py / esp32_worker.py: mirror the byte-aware rotation +
window-union in the worker, and derive is_bwr from panel_kind on the
runtime sensor_attach path too (fixes the tri-colour ESP32 alert badge).
- test_epaper/ssd168x_decoder.py: re-port the golden reference to match
(keeps the 3-way TS/Python/worker identity invariant). Tests updated to
construct tri-colour cases with is_bwr/palette='bwr'.
- examples-displays-epaper.ts: the Pico VCC wire referenced '3V3(OUT)',
which the velxio-pi-pico-w element doesn't expose (it has '3V3'), so the
wire snapped to the board corner. Use '3V3'.
2026-06-04 13:32:02 +07:00
|
|
|
|
# Use the UNION of windows set this frame (paged drivers set one partial
|
|
|
|
|
|
# window per page); fall back to the display geometry if none was set.
|
|
|
|
|
|
if self._win_x_set:
|
|
|
|
|
|
x0, x1 = self._win_x0, self._win_x1
|
|
|
|
|
|
else:
|
|
|
|
|
|
x0, x1 = 0, (self.width + 7) // 8 - 1
|
|
|
|
|
|
if self._win_y_set:
|
|
|
|
|
|
y0, y1 = self._win_y0, self._win_y1
|
|
|
|
|
|
else:
|
|
|
|
|
|
y0, y1 = 0, self.height - 1
|
2026-06-04 12:47:17 +07:00
|
|
|
|
nw_bytes = max(0, x1 - x0 + 1)
|
|
|
|
|
|
nw = nw_bytes * 8 # native width (px)
|
|
|
|
|
|
nh = max(0, y1 - y0 + 1) # native height (rows)
|
|
|
|
|
|
native = bytearray(nw * nh)
|
|
|
|
|
|
for ny in range(nh):
|
|
|
|
|
|
row = (y0 + ny) * self._ram_bpr + x0
|
|
|
|
|
|
out_row = ny * nw
|
|
|
|
|
|
for xb in range(nw_bytes):
|
|
|
|
|
|
b_byte = self.bw_ram[row + xb]
|
|
|
|
|
|
r_byte = self.red_ram[row + xb]
|
|
|
|
|
|
base = xb << 3
|
feat: Add support for SSD168x ePaper panels
- Introduced EPaperPanels.ts to define configurations for various ePaper panels including dimensions, refresh rates, and controller details.
- Implemented SSD168xDecoder.ts to handle the decoding of SPI commands for the SSD168x family of ePaper displays.
- Created EPaperPart.ts to manage the simulation of ePaper panels, integrating with the existing simulator architecture and handling events.
- Added example sketches for 2.13", 2.9", 4.2", and 7.5" ePaper displays, demonstrating basic functionality and text rendering.
- Ensured compatibility with AVR, RP2040, and ESP32 platforms, with appropriate pin configurations for each.
2026-04-30 08:23:00 +07:00
|
|
|
|
for bit in range(8):
|
2026-06-04 12:47:17 +07:00
|
|
|
|
x = base + bit
|
|
|
|
|
|
if x >= nw:
|
feat: Add support for SSD168x ePaper panels
- Introduced EPaperPanels.ts to define configurations for various ePaper panels including dimensions, refresh rates, and controller details.
- Implemented SSD168xDecoder.ts to handle the decoding of SPI commands for the SSD168x family of ePaper displays.
- Created EPaperPart.ts to manage the simulation of ePaper panels, integrating with the existing simulator architecture and handling events.
- Added example sketches for 2.13", 2.9", 4.2", and 7.5" ePaper displays, demonstrating basic functionality and text rendering.
- Ensured compatibility with AVR, RP2040, and ESP32 platforms, with appropriate pin configurations for each.
2026-04-30 08:23:00 +07:00
|
|
|
|
break
|
|
|
|
|
|
mask = 0x80 >> bit
|
2026-06-04 12:47:17 +07:00
|
|
|
|
bw_white = bool(b_byte & mask)
|
|
|
|
|
|
if self.is_bwr:
|
|
|
|
|
|
# Tri-colour: red wins, else the B/W plane decides.
|
|
|
|
|
|
native[out_row + x] = 2 if (r_byte & mask) else (1 if bw_white else 0)
|
|
|
|
|
|
else:
|
|
|
|
|
|
# B/W: the image may live in either plane (0x24 or 0x26),
|
|
|
|
|
|
# so a pixel is white only if BOTH planes say white.
|
|
|
|
|
|
native[out_row + x] = 1 if (bw_white and (r_byte & mask)) else 0
|
|
|
|
|
|
|
fix(epaper): correct orientation across all boards + Pico VCC wire
ePaper panels rendered rotated/misaligned on AVR and RP2040 (e.g. the 2.13"
Pico clock came out sideways and clipped). The ESP32 worker decoder was just
taught to compose in the controller's native RAM geometry and rotate to the
display orientation, but the browser-side SSD168xDecoder (used by AVR/RP2040)
still composed at display dims with no rotation, so the two diverged.
- SSD168xDecoder.ts: port the worker's native-window compose + rotation.
* Size RAM to the longer side both ways so a rotated native layout
(128x296 behind a 296x128 panel) isn't truncated.
* Compose in the active RAM window, then rotate via the inverse of
Adafruit_GFX setRotation(1). Detect orientation by BYTE width so a
non-multiple-of-8 native width (the 2.13" panel is 122 px) is handled.
* Track the UNION of windows per frame: paged drivers (GxEPD2 page height
< panel) set one partial window per page, so compose must use the full
native area, not just the last page's strip. Fixes the all-white render
on paged panels (1.54" Uno, 4.2" Pico, 7.5" ESP32).
* Add an isBwr option: B/W panels treat 0x26 as a 2nd mono plane (white
only if both planes white), tri-colour panels keep red-wins.
* Default the active window to display geometry; the firmware overrides it.
- EPaperPart.ts: pass isBwr = cfg.palette === 'bwr' to the decoder.
- esp32_spi_slaves.py / esp32_worker.py: mirror the byte-aware rotation +
window-union in the worker, and derive is_bwr from panel_kind on the
runtime sensor_attach path too (fixes the tri-colour ESP32 alert badge).
- test_epaper/ssd168x_decoder.py: re-port the golden reference to match
(keeps the 3-way TS/Python/worker identity invariant). Tests updated to
construct tri-colour cases with is_bwr/palette='bwr'.
- examples-displays-epaper.ts: the Pico VCC wire referenced '3V3(OUT)',
which the velxio-pi-pico-w element doesn't expose (it has '3V3'), so the
wire snapped to the board corner. Use '3V3'.
2026-06-04 13:32:02 +07:00
|
|
|
|
# Map native -> display. `nw` is byte-padded (nw_bytes*8) so it can
|
|
|
|
|
|
# exceed the real native width when that isn't a multiple of 8 (e.g.
|
|
|
|
|
|
# the 2.13" panel is 122 px wide -> nw=128). Detect orientation by BYTE
|
|
|
|
|
|
# width and crop the padding using the true native width.
|
2026-06-04 12:47:17 +07:00
|
|
|
|
W, H = self.width, self.height
|
fix(epaper): correct orientation across all boards + Pico VCC wire
ePaper panels rendered rotated/misaligned on AVR and RP2040 (e.g. the 2.13"
Pico clock came out sideways and clipped). The ESP32 worker decoder was just
taught to compose in the controller's native RAM geometry and rotate to the
display orientation, but the browser-side SSD168xDecoder (used by AVR/RP2040)
still composed at display dims with no rotation, so the two diverged.
- SSD168xDecoder.ts: port the worker's native-window compose + rotation.
* Size RAM to the longer side both ways so a rotated native layout
(128x296 behind a 296x128 panel) isn't truncated.
* Compose in the active RAM window, then rotate via the inverse of
Adafruit_GFX setRotation(1). Detect orientation by BYTE width so a
non-multiple-of-8 native width (the 2.13" panel is 122 px) is handled.
* Track the UNION of windows per frame: paged drivers (GxEPD2 page height
< panel) set one partial window per page, so compose must use the full
native area, not just the last page's strip. Fixes the all-white render
on paged panels (1.54" Uno, 4.2" Pico, 7.5" ESP32).
* Add an isBwr option: B/W panels treat 0x26 as a 2nd mono plane (white
only if both planes white), tri-colour panels keep red-wins.
* Default the active window to display geometry; the firmware overrides it.
- EPaperPart.ts: pass isBwr = cfg.palette === 'bwr' to the decoder.
- esp32_spi_slaves.py / esp32_worker.py: mirror the byte-aware rotation +
window-union in the worker, and derive is_bwr from panel_kind on the
runtime sensor_attach path too (fixes the tri-colour ESP32 alert badge).
- test_epaper/ssd168x_decoder.py: re-port the golden reference to match
(keeps the 3-way TS/Python/worker identity invariant). Tests updated to
construct tri-colour cases with is_bwr/palette='bwr'.
- examples-displays-epaper.ts: the Pico VCC wire referenced '3V3(OUT)',
which the velxio-pi-pico-w element doesn't expose (it has '3V3'), so the
wire snapped to the board corner. Use '3V3'.
2026-06-04 13:32:02 +07:00
|
|
|
|
Wb = (W + 7) // 8
|
|
|
|
|
|
Hb = (H + 7) // 8
|
|
|
|
|
|
if nh == H and nw_bytes == Wb:
|
|
|
|
|
|
# Non-transposed (rotation 0): native actual width = W.
|
|
|
|
|
|
if nw == W:
|
|
|
|
|
|
pixels = native
|
|
|
|
|
|
else:
|
|
|
|
|
|
pixels = bytearray([1]) * (W * H)
|
|
|
|
|
|
for ny in range(H):
|
|
|
|
|
|
s = ny * nw
|
|
|
|
|
|
d = ny * W
|
|
|
|
|
|
for x in range(W):
|
|
|
|
|
|
pixels[d + x] = native[s + x]
|
|
|
|
|
|
elif nh == W and nw_bytes == Hb and nh:
|
|
|
|
|
|
# Transposed (rotation 1): native actual width = H. Inverse of
|
|
|
|
|
|
# Adafruit_GFX rotation 1: native(x_raw,y_raw) -> display(xd=y_raw,
|
|
|
|
|
|
# yd=Wn-1-x_raw), Wn = true native width = H.
|
2026-06-04 12:47:17 +07:00
|
|
|
|
pixels = bytearray([1]) * (W * H)
|
fix(epaper): correct orientation across all boards + Pico VCC wire
ePaper panels rendered rotated/misaligned on AVR and RP2040 (e.g. the 2.13"
Pico clock came out sideways and clipped). The ESP32 worker decoder was just
taught to compose in the controller's native RAM geometry and rotate to the
display orientation, but the browser-side SSD168xDecoder (used by AVR/RP2040)
still composed at display dims with no rotation, so the two diverged.
- SSD168xDecoder.ts: port the worker's native-window compose + rotation.
* Size RAM to the longer side both ways so a rotated native layout
(128x296 behind a 296x128 panel) isn't truncated.
* Compose in the active RAM window, then rotate via the inverse of
Adafruit_GFX setRotation(1). Detect orientation by BYTE width so a
non-multiple-of-8 native width (the 2.13" panel is 122 px) is handled.
* Track the UNION of windows per frame: paged drivers (GxEPD2 page height
< panel) set one partial window per page, so compose must use the full
native area, not just the last page's strip. Fixes the all-white render
on paged panels (1.54" Uno, 4.2" Pico, 7.5" ESP32).
* Add an isBwr option: B/W panels treat 0x26 as a 2nd mono plane (white
only if both planes white), tri-colour panels keep red-wins.
* Default the active window to display geometry; the firmware overrides it.
- EPaperPart.ts: pass isBwr = cfg.palette === 'bwr' to the decoder.
- esp32_spi_slaves.py / esp32_worker.py: mirror the byte-aware rotation +
window-union in the worker, and derive is_bwr from panel_kind on the
runtime sensor_attach path too (fixes the tri-colour ESP32 alert badge).
- test_epaper/ssd168x_decoder.py: re-port the golden reference to match
(keeps the 3-way TS/Python/worker identity invariant). Tests updated to
construct tri-colour cases with is_bwr/palette='bwr'.
- examples-displays-epaper.ts: the Pico VCC wire referenced '3V3(OUT)',
which the velxio-pi-pico-w element doesn't expose (it has '3V3'), so the
wire snapped to the board corner. Use '3V3'.
2026-06-04 13:32:02 +07:00
|
|
|
|
wn = H
|
|
|
|
|
|
for ny in range(nh): # ny = y_raw (0..W-1)
|
|
|
|
|
|
if ny >= W:
|
|
|
|
|
|
break
|
2026-06-04 12:47:17 +07:00
|
|
|
|
src = ny * nw
|
fix(epaper): correct orientation across all boards + Pico VCC wire
ePaper panels rendered rotated/misaligned on AVR and RP2040 (e.g. the 2.13"
Pico clock came out sideways and clipped). The ESP32 worker decoder was just
taught to compose in the controller's native RAM geometry and rotate to the
display orientation, but the browser-side SSD168xDecoder (used by AVR/RP2040)
still composed at display dims with no rotation, so the two diverged.
- SSD168xDecoder.ts: port the worker's native-window compose + rotation.
* Size RAM to the longer side both ways so a rotated native layout
(128x296 behind a 296x128 panel) isn't truncated.
* Compose in the active RAM window, then rotate via the inverse of
Adafruit_GFX setRotation(1). Detect orientation by BYTE width so a
non-multiple-of-8 native width (the 2.13" panel is 122 px) is handled.
* Track the UNION of windows per frame: paged drivers (GxEPD2 page height
< panel) set one partial window per page, so compose must use the full
native area, not just the last page's strip. Fixes the all-white render
on paged panels (1.54" Uno, 4.2" Pico, 7.5" ESP32).
* Add an isBwr option: B/W panels treat 0x26 as a 2nd mono plane (white
only if both planes white), tri-colour panels keep red-wins.
* Default the active window to display geometry; the firmware overrides it.
- EPaperPart.ts: pass isBwr = cfg.palette === 'bwr' to the decoder.
- esp32_spi_slaves.py / esp32_worker.py: mirror the byte-aware rotation +
window-union in the worker, and derive is_bwr from panel_kind on the
runtime sensor_attach path too (fixes the tri-colour ESP32 alert badge).
- test_epaper/ssd168x_decoder.py: re-port the golden reference to match
(keeps the 3-way TS/Python/worker identity invariant). Tests updated to
construct tri-colour cases with is_bwr/palette='bwr'.
- examples-displays-epaper.ts: the Pico VCC wire referenced '3V3(OUT)',
which the velxio-pi-pico-w element doesn't expose (it has '3V3'), so the
wire snapped to the board corner. Use '3V3'.
2026-06-04 13:32:02 +07:00
|
|
|
|
for x in range(wn): # x = x_raw (0..H-1, true native width)
|
|
|
|
|
|
pixels[(wn - 1 - x) * W + ny] = native[src + x]
|
2026-06-04 12:47:17 +07:00
|
|
|
|
else:
|
|
|
|
|
|
# Unexpected geometry — best-effort top-left copy onto white.
|
|
|
|
|
|
pixels = bytearray([1]) * (W * H)
|
|
|
|
|
|
for ny in range(min(nh, H)):
|
fix(epaper): correct orientation across all boards + Pico VCC wire
ePaper panels rendered rotated/misaligned on AVR and RP2040 (e.g. the 2.13"
Pico clock came out sideways and clipped). The ESP32 worker decoder was just
taught to compose in the controller's native RAM geometry and rotate to the
display orientation, but the browser-side SSD168xDecoder (used by AVR/RP2040)
still composed at display dims with no rotation, so the two diverged.
- SSD168xDecoder.ts: port the worker's native-window compose + rotation.
* Size RAM to the longer side both ways so a rotated native layout
(128x296 behind a 296x128 panel) isn't truncated.
* Compose in the active RAM window, then rotate via the inverse of
Adafruit_GFX setRotation(1). Detect orientation by BYTE width so a
non-multiple-of-8 native width (the 2.13" panel is 122 px) is handled.
* Track the UNION of windows per frame: paged drivers (GxEPD2 page height
< panel) set one partial window per page, so compose must use the full
native area, not just the last page's strip. Fixes the all-white render
on paged panels (1.54" Uno, 4.2" Pico, 7.5" ESP32).
* Add an isBwr option: B/W panels treat 0x26 as a 2nd mono plane (white
only if both planes white), tri-colour panels keep red-wins.
* Default the active window to display geometry; the firmware overrides it.
- EPaperPart.ts: pass isBwr = cfg.palette === 'bwr' to the decoder.
- esp32_spi_slaves.py / esp32_worker.py: mirror the byte-aware rotation +
window-union in the worker, and derive is_bwr from panel_kind on the
runtime sensor_attach path too (fixes the tri-colour ESP32 alert badge).
- test_epaper/ssd168x_decoder.py: re-port the golden reference to match
(keeps the 3-way TS/Python/worker identity invariant). Tests updated to
construct tri-colour cases with is_bwr/palette='bwr'.
- examples-displays-epaper.ts: the Pico VCC wire referenced '3V3(OUT)',
which the velxio-pi-pico-w element doesn't expose (it has '3V3'), so the
wire snapped to the board corner. Use '3V3'.
2026-06-04 13:32:02 +07:00
|
|
|
|
s = ny * nw
|
|
|
|
|
|
d = ny * W
|
2026-06-04 12:47:17 +07:00
|
|
|
|
for x in range(min(nw, W)):
|
fix(epaper): correct orientation across all boards + Pico VCC wire
ePaper panels rendered rotated/misaligned on AVR and RP2040 (e.g. the 2.13"
Pico clock came out sideways and clipped). The ESP32 worker decoder was just
taught to compose in the controller's native RAM geometry and rotate to the
display orientation, but the browser-side SSD168xDecoder (used by AVR/RP2040)
still composed at display dims with no rotation, so the two diverged.
- SSD168xDecoder.ts: port the worker's native-window compose + rotation.
* Size RAM to the longer side both ways so a rotated native layout
(128x296 behind a 296x128 panel) isn't truncated.
* Compose in the active RAM window, then rotate via the inverse of
Adafruit_GFX setRotation(1). Detect orientation by BYTE width so a
non-multiple-of-8 native width (the 2.13" panel is 122 px) is handled.
* Track the UNION of windows per frame: paged drivers (GxEPD2 page height
< panel) set one partial window per page, so compose must use the full
native area, not just the last page's strip. Fixes the all-white render
on paged panels (1.54" Uno, 4.2" Pico, 7.5" ESP32).
* Add an isBwr option: B/W panels treat 0x26 as a 2nd mono plane (white
only if both planes white), tri-colour panels keep red-wins.
* Default the active window to display geometry; the firmware overrides it.
- EPaperPart.ts: pass isBwr = cfg.palette === 'bwr' to the decoder.
- esp32_spi_slaves.py / esp32_worker.py: mirror the byte-aware rotation +
window-union in the worker, and derive is_bwr from panel_kind on the
runtime sensor_attach path too (fixes the tri-colour ESP32 alert badge).
- test_epaper/ssd168x_decoder.py: re-port the golden reference to match
(keeps the 3-way TS/Python/worker identity invariant). Tests updated to
construct tri-colour cases with is_bwr/palette='bwr'.
- examples-displays-epaper.ts: the Pico VCC wire referenced '3V3(OUT)',
which the velxio-pi-pico-w element doesn't expose (it has '3V3'), so the
wire snapped to the board corner. Use '3V3'.
2026-06-04 13:32:02 +07:00
|
|
|
|
pixels[d + x] = native[s + x]
|
2026-06-04 12:47:17 +07:00
|
|
|
|
return Frame(W, H, bytes(pixels))
|
feat: Add support for SSD168x ePaper panels
- Introduced EPaperPanels.ts to define configurations for various ePaper panels including dimensions, refresh rates, and controller details.
- Implemented SSD168xDecoder.ts to handle the decoding of SPI commands for the SSD168x family of ePaper displays.
- Created EPaperPart.ts to manage the simulation of ePaper panels, integrating with the existing simulator architecture and handling events.
- Added example sketches for 2.13", 2.9", 4.2", and 7.5" ePaper displays, demonstrating basic functionality and text rendering.
- Ensured compatibility with AVR, RP2040, and ESP32 platforms, with appropriate pin configurations for each.
2026-04-30 08:23:00 +07:00
|
|
|
|
|
|
|
|
|
|
def compose_frame_b64(self) -> str:
|
|
|
|
|
|
"""Convenience for the worker — same as compose_frame() but base64-encoded."""
|
|
|
|
|
|
return base64.b64encode(self.compose_frame().pixels).decode("ascii")
|
|
|
|
|
|
|
|
|
|
|
|
# ── Internal: command / data dispatch ──────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
def _begin_command(self, cmd: int) -> None:
|
|
|
|
|
|
self._current_cmd = cmd
|
|
|
|
|
|
self._params = []
|
|
|
|
|
|
|
|
|
|
|
|
if cmd == CMD_SW_RESET:
|
|
|
|
|
|
self.reset()
|
|
|
|
|
|
return
|
|
|
|
|
|
if cmd == CMD_MASTER_ACTIVATION:
|
|
|
|
|
|
self.refreshed_count += 1
|
|
|
|
|
|
frame = self.compose_frame()
|
fix(epaper): correct orientation across all boards + Pico VCC wire
ePaper panels rendered rotated/misaligned on AVR and RP2040 (e.g. the 2.13"
Pico clock came out sideways and clipped). The ESP32 worker decoder was just
taught to compose in the controller's native RAM geometry and rotate to the
display orientation, but the browser-side SSD168xDecoder (used by AVR/RP2040)
still composed at display dims with no rotation, so the two diverged.
- SSD168xDecoder.ts: port the worker's native-window compose + rotation.
* Size RAM to the longer side both ways so a rotated native layout
(128x296 behind a 296x128 panel) isn't truncated.
* Compose in the active RAM window, then rotate via the inverse of
Adafruit_GFX setRotation(1). Detect orientation by BYTE width so a
non-multiple-of-8 native width (the 2.13" panel is 122 px) is handled.
* Track the UNION of windows per frame: paged drivers (GxEPD2 page height
< panel) set one partial window per page, so compose must use the full
native area, not just the last page's strip. Fixes the all-white render
on paged panels (1.54" Uno, 4.2" Pico, 7.5" ESP32).
* Add an isBwr option: B/W panels treat 0x26 as a 2nd mono plane (white
only if both planes white), tri-colour panels keep red-wins.
* Default the active window to display geometry; the firmware overrides it.
- EPaperPart.ts: pass isBwr = cfg.palette === 'bwr' to the decoder.
- esp32_spi_slaves.py / esp32_worker.py: mirror the byte-aware rotation +
window-union in the worker, and derive is_bwr from panel_kind on the
runtime sensor_attach path too (fixes the tri-colour ESP32 alert badge).
- test_epaper/ssd168x_decoder.py: re-port the golden reference to match
(keeps the 3-way TS/Python/worker identity invariant). Tests updated to
construct tri-colour cases with is_bwr/palette='bwr'.
- examples-displays-epaper.ts: the Pico VCC wire referenced '3V3(OUT)',
which the velxio-pi-pico-w element doesn't expose (it has '3V3'), so the
wire snapped to the board corner. Use '3V3'.
2026-06-04 13:32:02 +07:00
|
|
|
|
# Start a fresh window union for the next frame's pages.
|
|
|
|
|
|
self._win_x_set = False
|
|
|
|
|
|
self._win_y_set = False
|
feat: Add support for SSD168x ePaper panels
- Introduced EPaperPanels.ts to define configurations for various ePaper panels including dimensions, refresh rates, and controller details.
- Implemented SSD168xDecoder.ts to handle the decoding of SPI commands for the SSD168x family of ePaper displays.
- Created EPaperPart.ts to manage the simulation of ePaper panels, integrating with the existing simulator architecture and handling events.
- Added example sketches for 2.13", 2.9", 4.2", and 7.5" ePaper displays, demonstrating basic functionality and text rendering.
- Ensured compatibility with AVR, RP2040, and ESP32 platforms, with appropriate pin configurations for each.
2026-04-30 08:23:00 +07:00
|
|
|
|
if self.on_flush:
|
|
|
|
|
|
try:
|
|
|
|
|
|
self.on_flush(frame)
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
# Never let the frontend hook raise back into QEMU thread.
|
|
|
|
|
|
pass
|
|
|
|
|
|
return
|
|
|
|
|
|
if cmd == CMD_WRITE_BLACK_VRAM:
|
|
|
|
|
|
self._ram_target = "bw"
|
|
|
|
|
|
return
|
|
|
|
|
|
if cmd == CMD_WRITE_RED_VRAM:
|
|
|
|
|
|
self._ram_target = "red"
|
|
|
|
|
|
return
|
|
|
|
|
|
if cmd in (
|
|
|
|
|
|
CMD_DRIVER_OUTPUT_CTRL, CMD_GATE_DRIVING_VOLTAGE,
|
|
|
|
|
|
CMD_SOURCE_DRIVING_VOLT, CMD_DEEP_SLEEP, CMD_DATA_ENTRY_MODE,
|
|
|
|
|
|
CMD_TEMP_SENSOR, CMD_DISP_UPDATE_CTRL_1, CMD_DISP_UPDATE_CTRL_2,
|
|
|
|
|
|
CMD_WRITE_VCOM_REG, CMD_WRITE_LUT, CMD_BORDER_WAVEFORM,
|
|
|
|
|
|
CMD_END_OPTION, CMD_SET_RAMX_RANGE, CMD_SET_RAMY_RANGE,
|
|
|
|
|
|
CMD_SET_RAMX_COUNTER, CMD_SET_RAMY_COUNTER,
|
|
|
|
|
|
):
|
|
|
|
|
|
return
|
|
|
|
|
|
self.unknown_cmds.append(cmd)
|
|
|
|
|
|
|
|
|
|
|
|
def _handle_data(self, byte: int) -> None:
|
|
|
|
|
|
cmd = self._current_cmd
|
|
|
|
|
|
params = self._params
|
|
|
|
|
|
params.append(byte)
|
|
|
|
|
|
|
|
|
|
|
|
if cmd == CMD_DEEP_SLEEP and len(params) == 1:
|
|
|
|
|
|
self.in_deep_sleep = byte != 0
|
|
|
|
|
|
elif cmd == CMD_DATA_ENTRY_MODE and len(params) == 1:
|
|
|
|
|
|
self._entry_mode = byte
|
|
|
|
|
|
elif cmd == CMD_SET_RAMX_RANGE and len(params) == 2:
|
|
|
|
|
|
self._xrange = (params[0], params[1])
|
|
|
|
|
|
self._x_byte = params[0]
|
fix(epaper): correct orientation across all boards + Pico VCC wire
ePaper panels rendered rotated/misaligned on AVR and RP2040 (e.g. the 2.13"
Pico clock came out sideways and clipped). The ESP32 worker decoder was just
taught to compose in the controller's native RAM geometry and rotate to the
display orientation, but the browser-side SSD168xDecoder (used by AVR/RP2040)
still composed at display dims with no rotation, so the two diverged.
- SSD168xDecoder.ts: port the worker's native-window compose + rotation.
* Size RAM to the longer side both ways so a rotated native layout
(128x296 behind a 296x128 panel) isn't truncated.
* Compose in the active RAM window, then rotate via the inverse of
Adafruit_GFX setRotation(1). Detect orientation by BYTE width so a
non-multiple-of-8 native width (the 2.13" panel is 122 px) is handled.
* Track the UNION of windows per frame: paged drivers (GxEPD2 page height
< panel) set one partial window per page, so compose must use the full
native area, not just the last page's strip. Fixes the all-white render
on paged panels (1.54" Uno, 4.2" Pico, 7.5" ESP32).
* Add an isBwr option: B/W panels treat 0x26 as a 2nd mono plane (white
only if both planes white), tri-colour panels keep red-wins.
* Default the active window to display geometry; the firmware overrides it.
- EPaperPart.ts: pass isBwr = cfg.palette === 'bwr' to the decoder.
- esp32_spi_slaves.py / esp32_worker.py: mirror the byte-aware rotation +
window-union in the worker, and derive is_bwr from panel_kind on the
runtime sensor_attach path too (fixes the tri-colour ESP32 alert badge).
- test_epaper/ssd168x_decoder.py: re-port the golden reference to match
(keeps the 3-way TS/Python/worker identity invariant). Tests updated to
construct tri-colour cases with is_bwr/palette='bwr'.
- examples-displays-epaper.ts: the Pico VCC wire referenced '3V3(OUT)',
which the velxio-pi-pico-w element doesn't expose (it has '3V3'), so the
wire snapped to the board corner. Use '3V3'.
2026-06-04 13:32:02 +07:00
|
|
|
|
if not self._win_x_set:
|
|
|
|
|
|
self._win_x0, self._win_x1 = params[0], params[1]
|
|
|
|
|
|
self._win_x_set = True
|
|
|
|
|
|
else:
|
|
|
|
|
|
self._win_x0 = min(self._win_x0, params[0])
|
|
|
|
|
|
self._win_x1 = max(self._win_x1, params[1])
|
feat: Add support for SSD168x ePaper panels
- Introduced EPaperPanels.ts to define configurations for various ePaper panels including dimensions, refresh rates, and controller details.
- Implemented SSD168xDecoder.ts to handle the decoding of SPI commands for the SSD168x family of ePaper displays.
- Created EPaperPart.ts to manage the simulation of ePaper panels, integrating with the existing simulator architecture and handling events.
- Added example sketches for 2.13", 2.9", 4.2", and 7.5" ePaper displays, demonstrating basic functionality and text rendering.
- Ensured compatibility with AVR, RP2040, and ESP32 platforms, with appropriate pin configurations for each.
2026-04-30 08:23:00 +07:00
|
|
|
|
elif cmd == CMD_SET_RAMY_RANGE and len(params) == 4:
|
|
|
|
|
|
self._yrange = (params[0] | (params[1] << 8),
|
|
|
|
|
|
params[2] | (params[3] << 8))
|
|
|
|
|
|
self._y = self._yrange[0]
|
fix(epaper): correct orientation across all boards + Pico VCC wire
ePaper panels rendered rotated/misaligned on AVR and RP2040 (e.g. the 2.13"
Pico clock came out sideways and clipped). The ESP32 worker decoder was just
taught to compose in the controller's native RAM geometry and rotate to the
display orientation, but the browser-side SSD168xDecoder (used by AVR/RP2040)
still composed at display dims with no rotation, so the two diverged.
- SSD168xDecoder.ts: port the worker's native-window compose + rotation.
* Size RAM to the longer side both ways so a rotated native layout
(128x296 behind a 296x128 panel) isn't truncated.
* Compose in the active RAM window, then rotate via the inverse of
Adafruit_GFX setRotation(1). Detect orientation by BYTE width so a
non-multiple-of-8 native width (the 2.13" panel is 122 px) is handled.
* Track the UNION of windows per frame: paged drivers (GxEPD2 page height
< panel) set one partial window per page, so compose must use the full
native area, not just the last page's strip. Fixes the all-white render
on paged panels (1.54" Uno, 4.2" Pico, 7.5" ESP32).
* Add an isBwr option: B/W panels treat 0x26 as a 2nd mono plane (white
only if both planes white), tri-colour panels keep red-wins.
* Default the active window to display geometry; the firmware overrides it.
- EPaperPart.ts: pass isBwr = cfg.palette === 'bwr' to the decoder.
- esp32_spi_slaves.py / esp32_worker.py: mirror the byte-aware rotation +
window-union in the worker, and derive is_bwr from panel_kind on the
runtime sensor_attach path too (fixes the tri-colour ESP32 alert badge).
- test_epaper/ssd168x_decoder.py: re-port the golden reference to match
(keeps the 3-way TS/Python/worker identity invariant). Tests updated to
construct tri-colour cases with is_bwr/palette='bwr'.
- examples-displays-epaper.ts: the Pico VCC wire referenced '3V3(OUT)',
which the velxio-pi-pico-w element doesn't expose (it has '3V3'), so the
wire snapped to the board corner. Use '3V3'.
2026-06-04 13:32:02 +07:00
|
|
|
|
if not self._win_y_set:
|
|
|
|
|
|
self._win_y0, self._win_y1 = self._yrange
|
|
|
|
|
|
self._win_y_set = True
|
|
|
|
|
|
else:
|
|
|
|
|
|
self._win_y0 = min(self._win_y0, self._yrange[0])
|
|
|
|
|
|
self._win_y1 = max(self._win_y1, self._yrange[1])
|
feat: Add support for SSD168x ePaper panels
- Introduced EPaperPanels.ts to define configurations for various ePaper panels including dimensions, refresh rates, and controller details.
- Implemented SSD168xDecoder.ts to handle the decoding of SPI commands for the SSD168x family of ePaper displays.
- Created EPaperPart.ts to manage the simulation of ePaper panels, integrating with the existing simulator architecture and handling events.
- Added example sketches for 2.13", 2.9", 4.2", and 7.5" ePaper displays, demonstrating basic functionality and text rendering.
- Ensured compatibility with AVR, RP2040, and ESP32 platforms, with appropriate pin configurations for each.
2026-04-30 08:23:00 +07:00
|
|
|
|
elif cmd == CMD_SET_RAMX_COUNTER and len(params) == 1:
|
|
|
|
|
|
self._x_byte = byte
|
|
|
|
|
|
elif cmd == CMD_SET_RAMY_COUNTER and len(params) == 2:
|
|
|
|
|
|
self._y = params[0] | (params[1] << 8)
|
|
|
|
|
|
elif cmd == CMD_WRITE_BLACK_VRAM:
|
|
|
|
|
|
self._write_ram_byte(self.bw_ram, byte)
|
|
|
|
|
|
elif cmd == CMD_WRITE_RED_VRAM:
|
|
|
|
|
|
self._write_ram_byte(self.red_ram, byte)
|
|
|
|
|
|
|
|
|
|
|
|
def _write_ram_byte(self, plane: bytearray, byte: int) -> None:
|
2026-06-04 12:47:17 +07:00
|
|
|
|
if 0 <= self._x_byte < self._ram_bpr and 0 <= self._y < self._ram_rows:
|
|
|
|
|
|
plane[self._y * self._ram_bpr + self._x_byte] = byte
|
feat: Add support for SSD168x ePaper panels
- Introduced EPaperPanels.ts to define configurations for various ePaper panels including dimensions, refresh rates, and controller details.
- Implemented SSD168xDecoder.ts to handle the decoding of SPI commands for the SSD168x family of ePaper displays.
- Created EPaperPart.ts to manage the simulation of ePaper panels, integrating with the existing simulator architecture and handling events.
- Added example sketches for 2.13", 2.9", 4.2", and 7.5" ePaper displays, demonstrating basic functionality and text rendering.
- Ensured compatibility with AVR, RP2040, and ESP32 platforms, with appropriate pin configurations for each.
2026-04-30 08:23:00 +07:00
|
|
|
|
x_inc = (self._entry_mode & 0x01) == 0x01
|
2026-06-05 08:50:21 +07:00
|
|
|
|
y_inc = (self._entry_mode & 0x02) == 0x02
|
|
|
|
|
|
end_of_row = False
|
feat: Add support for SSD168x ePaper panels
- Introduced EPaperPanels.ts to define configurations for various ePaper panels including dimensions, refresh rates, and controller details.
- Implemented SSD168xDecoder.ts to handle the decoding of SPI commands for the SSD168x family of ePaper displays.
- Created EPaperPart.ts to manage the simulation of ePaper panels, integrating with the existing simulator architecture and handling events.
- Added example sketches for 2.13", 2.9", 4.2", and 7.5" ePaper displays, demonstrating basic functionality and text rendering.
- Ensured compatibility with AVR, RP2040, and ESP32 platforms, with appropriate pin configurations for each.
2026-04-30 08:23:00 +07:00
|
|
|
|
if x_inc:
|
|
|
|
|
|
if self._x_byte < self._xrange[1]:
|
|
|
|
|
|
self._x_byte += 1
|
|
|
|
|
|
else:
|
|
|
|
|
|
self._x_byte = self._xrange[0]
|
2026-06-05 08:50:21 +07:00
|
|
|
|
end_of_row = True
|
feat: Add support for SSD168x ePaper panels
- Introduced EPaperPanels.ts to define configurations for various ePaper panels including dimensions, refresh rates, and controller details.
- Implemented SSD168xDecoder.ts to handle the decoding of SPI commands for the SSD168x family of ePaper displays.
- Created EPaperPart.ts to manage the simulation of ePaper panels, integrating with the existing simulator architecture and handling events.
- Added example sketches for 2.13", 2.9", 4.2", and 7.5" ePaper displays, demonstrating basic functionality and text rendering.
- Ensured compatibility with AVR, RP2040, and ESP32 platforms, with appropriate pin configurations for each.
2026-04-30 08:23:00 +07:00
|
|
|
|
else:
|
|
|
|
|
|
if self._x_byte > self._xrange[0]:
|
|
|
|
|
|
self._x_byte -= 1
|
|
|
|
|
|
else:
|
|
|
|
|
|
self._x_byte = self._xrange[1]
|
2026-06-05 08:50:21 +07:00
|
|
|
|
end_of_row = True
|
|
|
|
|
|
if end_of_row:
|
|
|
|
|
|
# Advance Y, WRAPPING at the window boundary like the SSD168x RAM
|
|
|
|
|
|
# address counter. Some drivers (e.g. GxEPD2_3C) write the 0x24 then
|
|
|
|
|
|
# the 0x26 plane without re-seeking the counter, relying on this
|
|
|
|
|
|
# wrap so the second plane lands in the window.
|
|
|
|
|
|
if y_inc:
|
|
|
|
|
|
self._y = self._yrange[0] if self._y >= self._yrange[1] else self._y + 1
|
|
|
|
|
|
else:
|
|
|
|
|
|
self._y = self._yrange[1] if self._y <= self._yrange[0] else self._y - 1
|
2026-04-30 10:24:20 +07:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ── UC8159c (ACeP 7-colour 5.65" GoodDisplay GDEP0565D90) ───────────────────
|
|
|
|
|
|
#
|
|
|
|
|
|
# Different command set from SSD168x. Pixel packing: 2 px per byte, upper
|
|
|
|
|
|
# nibble = first pixel, each nibble's lower 3 bits = palette index 0..6.
|
|
|
|
|
|
|
|
|
|
|
|
UC_CMD_PANEL_SETTING = 0x00
|
|
|
|
|
|
UC_CMD_POWER_SETTING = 0x01
|
|
|
|
|
|
UC_CMD_POWER_OFF = 0x02
|
|
|
|
|
|
UC_CMD_POWER_OFF_SEQ = 0x03
|
|
|
|
|
|
UC_CMD_POWER_ON = 0x04
|
|
|
|
|
|
UC_CMD_BOOSTER_SOFT_START = 0x06
|
|
|
|
|
|
UC_CMD_DEEP_SLEEP = 0x07
|
|
|
|
|
|
UC_CMD_DTM1 = 0x10
|
|
|
|
|
|
UC_CMD_DISPLAY_REFRESH = 0x12
|
|
|
|
|
|
UC_CMD_PLL_CONTROL = 0x30
|
|
|
|
|
|
UC_CMD_TSE = 0x41
|
|
|
|
|
|
UC_CMD_VCOM_DATA_INTERVAL = 0x50
|
|
|
|
|
|
UC_CMD_TCON_SETTING = 0x60
|
|
|
|
|
|
UC_CMD_RESOLUTION_SETTING = 0x61
|
|
|
|
|
|
UC_CMD_PWS = 0xE3
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
|
|
class Uc8159cEpaperSlave:
|
|
|
|
|
|
"""ACeP 7-colour decoder. Latches on 0x12 DRF and emits a Frame whose
|
|
|
|
|
|
`pixels` are 1 byte/pixel palette indices (0=black .. 6=orange). The
|
|
|
|
|
|
worker maps those indices to RGB on the frontend side."""
|
|
|
|
|
|
|
|
|
|
|
|
component_id: str
|
|
|
|
|
|
width: int
|
|
|
|
|
|
height: int
|
|
|
|
|
|
on_flush: Optional[Callable[[Frame], None]] = None
|
|
|
|
|
|
|
|
|
|
|
|
ram: bytearray = field(init=False)
|
|
|
|
|
|
_write_idx: int = 0
|
|
|
|
|
|
_current_cmd: int = -1
|
|
|
|
|
|
_params: List[int] = field(default_factory=list)
|
|
|
|
|
|
refreshed_count: int = 0
|
|
|
|
|
|
unknown_cmds: List[int] = field(default_factory=list)
|
|
|
|
|
|
in_deep_sleep: bool = False
|
|
|
|
|
|
powered_on: bool = False
|
|
|
|
|
|
|
|
|
|
|
|
def __post_init__(self) -> None:
|
|
|
|
|
|
# Default to all-white (index 1) so a freshly-mounted panel doesn't
|
|
|
|
|
|
# render as transparent.
|
|
|
|
|
|
self.ram = bytearray([1] * (self.width * self.height))
|
|
|
|
|
|
|
|
|
|
|
|
# ── Public API ─────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
def feed(self, byte: int, dc_high: bool) -> None:
|
|
|
|
|
|
if not dc_high:
|
|
|
|
|
|
self._begin_command(byte & 0xFF)
|
|
|
|
|
|
else:
|
|
|
|
|
|
self._handle_data(byte & 0xFF)
|
|
|
|
|
|
|
|
|
|
|
|
def reset(self) -> None:
|
|
|
|
|
|
self.ram = bytearray([1] * (self.width * self.height))
|
|
|
|
|
|
self._write_idx = 0
|
|
|
|
|
|
self._current_cmd = -1
|
|
|
|
|
|
self._params = []
|
|
|
|
|
|
self.refreshed_count = 0
|
|
|
|
|
|
self.powered_on = False
|
|
|
|
|
|
self.in_deep_sleep = False
|
|
|
|
|
|
|
|
|
|
|
|
def compose_frame(self) -> Frame:
|
|
|
|
|
|
return Frame(self.width, self.height, bytes(self.ram))
|
|
|
|
|
|
|
|
|
|
|
|
def compose_frame_b64(self) -> str:
|
|
|
|
|
|
return base64.b64encode(bytes(self.ram)).decode("ascii")
|
|
|
|
|
|
|
|
|
|
|
|
# ── Internal: command / data dispatch ──────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
def _begin_command(self, cmd: int) -> None:
|
|
|
|
|
|
self._current_cmd = cmd
|
|
|
|
|
|
self._params = []
|
|
|
|
|
|
|
|
|
|
|
|
if cmd == UC_CMD_POWER_ON:
|
|
|
|
|
|
self.powered_on = True
|
|
|
|
|
|
return
|
|
|
|
|
|
if cmd == UC_CMD_POWER_OFF:
|
|
|
|
|
|
self.powered_on = False
|
|
|
|
|
|
return
|
|
|
|
|
|
if cmd == UC_CMD_DTM1:
|
|
|
|
|
|
self._write_idx = 0
|
|
|
|
|
|
return
|
|
|
|
|
|
if cmd == UC_CMD_DISPLAY_REFRESH:
|
|
|
|
|
|
self.refreshed_count += 1
|
|
|
|
|
|
frame = self.compose_frame()
|
|
|
|
|
|
if self.on_flush:
|
|
|
|
|
|
try:
|
|
|
|
|
|
self.on_flush(frame)
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
pass
|
|
|
|
|
|
return
|
|
|
|
|
|
if cmd == UC_CMD_DEEP_SLEEP:
|
|
|
|
|
|
return
|
|
|
|
|
|
if cmd in (
|
|
|
|
|
|
UC_CMD_PANEL_SETTING, UC_CMD_POWER_SETTING, UC_CMD_POWER_OFF_SEQ,
|
|
|
|
|
|
UC_CMD_BOOSTER_SOFT_START, UC_CMD_PLL_CONTROL, UC_CMD_TSE,
|
|
|
|
|
|
UC_CMD_VCOM_DATA_INTERVAL, UC_CMD_TCON_SETTING,
|
|
|
|
|
|
UC_CMD_RESOLUTION_SETTING, UC_CMD_PWS,
|
|
|
|
|
|
):
|
|
|
|
|
|
return
|
|
|
|
|
|
self.unknown_cmds.append(cmd)
|
|
|
|
|
|
|
|
|
|
|
|
def _handle_data(self, byte: int) -> None:
|
|
|
|
|
|
cmd = self._current_cmd
|
|
|
|
|
|
self._params.append(byte)
|
|
|
|
|
|
|
|
|
|
|
|
if cmd == UC_CMD_DEEP_SLEEP:
|
|
|
|
|
|
if byte == 0xA5:
|
|
|
|
|
|
self.in_deep_sleep = True
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
if cmd == UC_CMD_DTM1:
|
|
|
|
|
|
total = self.width * self.height
|
|
|
|
|
|
if self._write_idx < total:
|
|
|
|
|
|
self.ram[self._write_idx] = (byte >> 4) & 0x07
|
|
|
|
|
|
self._write_idx += 1
|
|
|
|
|
|
if self._write_idx < total:
|
|
|
|
|
|
self.ram[self._write_idx] = byte & 0x07
|
|
|
|
|
|
self._write_idx += 1
|
feat(epaper): decode the UC8179/GD7965 7.5" panel + fix its BUSY polarity
The 7.5" 800x480 dashboard (GxEPD2_750_T7) rendered blank: it is a UC8179 /
GD7965 controller, but the panel config claimed controllerFamily 'ssd168x',
so the SSD168x decoder (which only reads 0x24/0x26/0x44/0x45) ignored its
0x10/0x13 DTM stream.
- Add a Uc8179 decoder (worker Uc8179EpaperSlave + browser Uc8179Decoder).
UC8179 is the same UltraChip command family as the UC8159c (0x10/0x13 DTM,
0x12 refresh) but mono (1 bit/px). GxEPD2 writes the visible image to 0x13
(DTM2 "current"; 0x10 is the ignored "previous"), framed by 0x91/0x90
(partial window, pixel coords MSB-first)/0x13 data/0x92. Data lands at
absolute pixel coords inside the window, so compose is just the RAM. The
Frame reuses the SSD168x palette (0=black, 1=white) so paintFrame renders it.
- EPaperPanels.ts: add the 'uc8179' family and point epaper-7in5-bw at it.
EPaperPart.ts + esp32_worker.py dispatch 'uc8179' to the new decoder.
- Fix the BUSY polarity: UC8179 (like the UC8159c) idles BUSY HIGH, not LOW.
The worker seeded BUSY LOW for every non-uc8159c panel, so GxEPD2_750_T7's
_PowerOn()/_InitDisplay() busy-wait timed out (~10 s, "Busy Timeout!") on
every refresh. Now _PowerOn returns in ~129 us.
- esp32_worker.py: the runtime sensor_attach epaper path still emitted the
epaper_update payload nested under 'data' (the old double-wrap bug); emit
it flat like the init path.
The 5.65" ACeP UC8159c example already rendered (it has its own decoder and
got the WS-plumbing fix); verified the 7 colour bars are correct.
2026-06-05 09:45:37 +07:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ── UC8179 / GD7965 (mono B/W 7.5" 800x480 Waveshare/GoodDisplay) ────────────
|
|
|
|
|
|
#
|
|
|
|
|
|
# UltraChip UC8179 — same command FAMILY as the UC8159c (0x10/0x13 DTM, 0x12
|
|
|
|
|
|
# refresh) but MONO (1 bit/px, 8 px/byte) with a partial window (0x90).
|
|
|
|
|
|
# GxEPD2_750_T7 writes the VISIBLE image to 0x13 (DTM2 "current"); 0x10 is the
|
|
|
|
|
|
# "previous" buffer (ignored). Each image write is framed 0x91 (partial in) /
|
|
|
|
|
|
# 0x90 (window x0/x1/y0/y1 in pixels, MSB-first, byte-aligned x) / 0x13 +
|
|
|
|
|
|
# row-major 1bpp data / 0x92 (partial out). Latches on 0x12. The composed Frame
|
|
|
|
|
|
# uses the SSD168x palette (0=black, 1=white) — `0xFF is white` per the driver —
|
|
|
|
|
|
# so the existing frontend paintFrame renders it. Data lands at ABSOLUTE pixel
|
|
|
|
|
|
# coords inside the window, so compose is just the RAM (no rotation/no union).
|
|
|
|
|
|
|
|
|
|
|
|
UC8179_CMD_POWER_OFF = 0x02
|
|
|
|
|
|
UC8179_CMD_POWER_ON = 0x04
|
|
|
|
|
|
UC8179_CMD_DEEP_SLEEP = 0x07
|
|
|
|
|
|
UC8179_CMD_DTM1 = 0x10 # previous/old buffer (ignored)
|
|
|
|
|
|
UC8179_CMD_DISPLAY_REFRESH = 0x12
|
|
|
|
|
|
UC8179_CMD_DTM2 = 0x13 # current/new image (the visible one)
|
|
|
|
|
|
UC8179_CMD_PARTIAL_WINDOW = 0x90
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
|
|
class Uc8179EpaperSlave:
|
|
|
|
|
|
"""Mono UC8179/GD7965 decoder (7.5" 800x480). Latches on 0x12 DRF and emits
|
|
|
|
|
|
a Frame with 1 byte/pixel (0=black, 1=white)."""
|
|
|
|
|
|
|
|
|
|
|
|
component_id: str
|
|
|
|
|
|
width: int
|
|
|
|
|
|
height: int
|
|
|
|
|
|
on_flush: Optional[Callable[[Frame], None]] = None
|
|
|
|
|
|
|
|
|
|
|
|
ram: bytearray = field(init=False)
|
|
|
|
|
|
_current_cmd: int = -1
|
|
|
|
|
|
_params: List[int] = field(default_factory=list)
|
|
|
|
|
|
_active_visible: bool = False # True while streaming 0x13 (DTM2)
|
|
|
|
|
|
_win_x0: int = 0
|
|
|
|
|
|
_win_x1: int = 0
|
|
|
|
|
|
_win_y0: int = 0
|
|
|
|
|
|
_win_y1: int = 0
|
|
|
|
|
|
_cx: int = 0
|
|
|
|
|
|
_cy: int = 0
|
|
|
|
|
|
refreshed_count: int = 0
|
|
|
|
|
|
unknown_cmds: List[int] = field(default_factory=list)
|
|
|
|
|
|
in_deep_sleep: bool = False
|
|
|
|
|
|
|
|
|
|
|
|
def __post_init__(self) -> None:
|
|
|
|
|
|
self.ram = bytearray([1] * (self.width * self.height)) # white
|
|
|
|
|
|
self._win_x1 = self.width - 1
|
|
|
|
|
|
self._win_y1 = self.height - 1
|
|
|
|
|
|
|
|
|
|
|
|
def feed(self, byte: int, dc_high: bool) -> None:
|
|
|
|
|
|
if not dc_high:
|
|
|
|
|
|
self._begin_command(byte & 0xFF)
|
|
|
|
|
|
else:
|
|
|
|
|
|
self._handle_data(byte & 0xFF)
|
|
|
|
|
|
|
|
|
|
|
|
def reset(self) -> None:
|
|
|
|
|
|
self.ram = bytearray([1] * (self.width * self.height))
|
|
|
|
|
|
self._current_cmd = -1
|
|
|
|
|
|
self._params = []
|
|
|
|
|
|
self._active_visible = False
|
|
|
|
|
|
self._win_x0 = 0
|
|
|
|
|
|
self._win_x1 = self.width - 1
|
|
|
|
|
|
self._win_y0 = 0
|
|
|
|
|
|
self._win_y1 = self.height - 1
|
|
|
|
|
|
self._cx = 0
|
|
|
|
|
|
self._cy = 0
|
|
|
|
|
|
self.in_deep_sleep = False
|
|
|
|
|
|
|
|
|
|
|
|
def compose_frame(self) -> Frame:
|
|
|
|
|
|
return Frame(self.width, self.height, bytes(self.ram))
|
|
|
|
|
|
|
|
|
|
|
|
def compose_frame_b64(self) -> str:
|
|
|
|
|
|
return base64.b64encode(bytes(self.ram)).decode("ascii")
|
|
|
|
|
|
|
|
|
|
|
|
def _begin_command(self, cmd: int) -> None:
|
|
|
|
|
|
self._current_cmd = cmd
|
|
|
|
|
|
self._params = []
|
|
|
|
|
|
if cmd == UC8179_CMD_DTM2:
|
|
|
|
|
|
self._active_visible = True
|
|
|
|
|
|
self._cx, self._cy = self._win_x0, self._win_y0
|
|
|
|
|
|
return
|
|
|
|
|
|
if cmd == UC8179_CMD_DTM1:
|
|
|
|
|
|
self._active_visible = False # old buffer — ignore its data
|
|
|
|
|
|
return
|
|
|
|
|
|
if cmd == UC8179_CMD_DISPLAY_REFRESH:
|
|
|
|
|
|
self.refreshed_count += 1
|
|
|
|
|
|
frame = self.compose_frame()
|
|
|
|
|
|
if self.on_flush:
|
|
|
|
|
|
try:
|
|
|
|
|
|
self.on_flush(frame)
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
pass
|
|
|
|
|
|
return
|
|
|
|
|
|
# 0x90 + init commands consume their data in _handle_data; others no-op.
|
|
|
|
|
|
|
|
|
|
|
|
def _handle_data(self, byte: int) -> None:
|
|
|
|
|
|
cmd = self._current_cmd
|
|
|
|
|
|
self._params.append(byte)
|
|
|
|
|
|
if cmd == UC8179_CMD_DEEP_SLEEP:
|
|
|
|
|
|
if byte == 0xA5:
|
|
|
|
|
|
self.in_deep_sleep = True
|
|
|
|
|
|
return
|
|
|
|
|
|
if cmd == UC8179_CMD_PARTIAL_WINDOW and len(self._params) == 9:
|
|
|
|
|
|
p = self._params
|
|
|
|
|
|
self._win_x0 = (p[0] << 8) | p[1]
|
|
|
|
|
|
self._win_x1 = (p[2] << 8) | p[3]
|
|
|
|
|
|
self._win_y0 = (p[4] << 8) | p[5]
|
|
|
|
|
|
self._win_y1 = (p[6] << 8) | p[7]
|
|
|
|
|
|
return
|
|
|
|
|
|
if cmd == UC8179_CMD_DTM2 and self._active_visible:
|
|
|
|
|
|
self._write_image_byte(byte)
|
|
|
|
|
|
|
|
|
|
|
|
def _write_image_byte(self, byte: int) -> None:
|
|
|
|
|
|
# 8 px, MSB = leftmost. bit=1 -> white(1), bit=0 -> black(0).
|
|
|
|
|
|
w, h = self.width, self.height
|
|
|
|
|
|
cy = self._cy
|
|
|
|
|
|
if 0 <= cy < h:
|
|
|
|
|
|
base = cy * w
|
|
|
|
|
|
for k in range(8):
|
|
|
|
|
|
x = self._cx + k
|
|
|
|
|
|
if self._win_x0 <= x <= self._win_x1 and 0 <= x < w:
|
|
|
|
|
|
self.ram[base + x] = 1 if (byte & (0x80 >> k)) else 0
|
|
|
|
|
|
self._cx += 8
|
|
|
|
|
|
if self._cx > self._win_x1:
|
|
|
|
|
|
self._cx = self._win_x0
|
|
|
|
|
|
self._cy += 1
|