Disclaimer: This project was mostly generated with AI assistance (Claude / Cursor). It has been tested on real hardware but may contain bugs or suboptimal patterns. Contributions and corrections are welcome.
E-Paper display driver and distraction-free typewriter for the WeAct Studio 4.2" E-Paper Module (SSD1683, 400x300 B/W) on the Orange Pi Zero 2W (Allwinner H618, Armbian).
- Display: WeAct Studio 4.2" E-Paper (SSD1683 controller, 400x300px)
- Compatible with Waveshare 4.2" V2 displays
- SBC: Orange Pi Zero 2W (Allwinner H618, 64-bit ARM)
- OS: Armbian (Debian-based)
Follows the WeAct Studio Raspberry Pi pinout:
| Display Pin | Header Pin | GPIO | Function |
|---|---|---|---|
| DIN / MOSI | Pin 19 | PH7 (231) | SPI1 MOSI (hardware) |
| CLK / SCK | Pin 23 | PH6 (230) | SPI1 CLK (hardware) |
| CS | Pin 24 | PH5 (229) | Chip Select (GPIO) |
| DC | Pin 22 | PI6 (262) | Data/Command (GPIO) |
| RST | Pin 11 | PH2 (226) | Reset (GPIO) |
| BUSY | Pin 18 | PH4 (228) | Busy signal (GPIO input) |
| VCC | Pin 17 | 3.3V | Power |
| GND | Pin 20 | GND | Ground |
Important: CS is controlled via GPIO (not hardware SPI CS). The hardware SPI1 CS1 on Pin 26 (PH9) is not used. This is because the WeAct pinout places CS on Pin 24, which is not the Orange Pi's SPI1 CS pin.
etyper includes a distraction-free typewriter application inspired by ZeroWriter.
Features:
- Portrait display (rotated 90 CCW, 300x400 effective resolution)
- Auto-opens last document on startup
- Autosave every 10 seconds
- USB keyboard input (any standard USB keyboard, US QWERTY layout)
- Full text editing with arrow key cursor movement
- Word wrap, auto-scrolling to follow cursor
- Partial refresh for fast typing response (~0.5s per update)
- Time-based full refresh every 5 minutes to clean e-paper ghosting
- Auto-start on boot via systemd service
- Survives power outages (autosave + auto-start + e-paper retains image)
Typing:
| Key | Action |
|---|---|
| A-Z, 0-9, symbols | Insert character at cursor position |
| Space | Insert space |
| Enter | Insert new line |
| Tab | Insert 4 spaces |
| Backspace | Delete character before cursor |
| Delete | Delete character after cursor |
Cursor movement:
| Key | Action |
|---|---|
| Left arrow | Move cursor one character left |
| Right arrow | Move cursor one character right |
| Up arrow | Move cursor up one visual line |
| Down arrow | Move cursor down one visual line |
| Home | Jump to start of current line |
| End | Jump to end of current line |
Shortcuts:
| Shortcut | Action |
|---|---|
| Ctrl+S | Save document |
| Ctrl+N | Save current and create new document |
| Ctrl+R | Force full display refresh (cleans ghosting) |
| Ctrl+Q | Save and restart (quit + auto-restart via systemd) |
The bottom of the screen shows: *doc_20260115_143022.txt L12:5 482c
*= unsaved changes (disappears after save/autosave)L12:5= cursor at line 12, column 5482c= total character count
Run manually:
sudo python3 typewriter.pyRun as boot service (auto-starts on power on):
sudo bash install.sh
# Or manually:
sudo cp etyper.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now etyperService management:
sudo systemctl status etyper # Check status
sudo systemctl stop etyper # Stop
sudo systemctl start etyper # Start
sudo systemctl restart etyper # Restart after code changes
journalctl -u etyper -f # View live logs- Saved to
~/etyper_docs/as plain.txtfiles - Filenames are timestamped:
doc_20260115_143022.txt - Last opened document is tracked in
~/etyper_docs/.last_doc - On startup, the last document is automatically reopened with cursor at the end
Add the SPI1 device tree overlay in /boot/armbianEnv.txt:
overlays=spidev1_1
Reboot. Verify /dev/spidev1.1 exists:
ls /dev/spidev*apt-get update
apt-get install python3-spidev python3-libgpiod python3-pil python3-evdevOr via pip (except libgpiod and evdev):
pip3 install spidev Pillow
python3-libgpiodandpython3-evdevmust be installed via apt.
cd etyper
python3 examples/hello_world.pyfrom epd42_driver import EPD42
from PIL import Image, ImageDraw, ImageFont
with EPD42() as epd:
# Initialize and clear
epd.init()
epd.clear()
epd.sleep()
# Create an image
img = Image.new("1", (epd.width, epd.height), 255) # white background
draw = ImageDraw.Draw(img)
draw.text((50, 100), "Hello!", fill=0)
# Display it
epd.init()
epd.display_image(img)
epd.sleep()| Method | Description |
|---|---|
EPD42(pins, spi_bus, spi_dev, spi_speed, spi_mode, gpiochip) |
Constructor with optional config |
epd.init() |
Initialize display for full refresh |
epd.init_partial() |
Switch to partial refresh mode (call after init + display) |
epd.display(buffer) |
Write raw buffer and full refresh (~4s) |
epd.display_partial(buffer) |
Write raw buffer and partial refresh (~0.5s) |
epd.display_image(image) |
Display a PIL Image with full refresh |
epd.display_image_partial(image) |
Display a PIL Image with partial refresh |
epd.full_refresh(buffer) |
Force a full refresh to clean ghosting |
epd.clear(color=0xFF) |
Clear to white (0xFF) or black (0x00) |
epd.sleep() |
Enter deep sleep (requires init() to wake) |
epd.reset() |
Hardware reset |
epd.close() |
Release GPIO and SPI resources |
EPD42.getbuffer(image) |
Static: convert PIL Image to raw buffer |
epd = EPD42(pins={
"dc": 262,
"cs": 229,
"rst": 226,
"busy": 228,
})- SPI: Hardware SPI1 at 4MHz, Mode 0. CS managed via GPIO.
- Controller: SSD1683 (Solomon Systech)
- Full refresh: ~4 seconds, no ghosting. Used on startup and every 5 minutes.
- Partial refresh: ~0.5 seconds, slight ghosting. Used for typing updates.
- Display buffer: 15,000 bytes (400/8 * 300). 1 bit per pixel, MSB first. 1=white, 0=black.
- Deep sleep: ~1uA current draw. Requires hardware reset to wake.
- Typewriter font: Atkinson Hyperlegible Mono Medium 16px, 28 chars x 15 lines in portrait mode. Line height follows WCAG 1.5x recommendation. Designed by the Braille Institute for maximum legibility on low-resolution displays. Falls back to DejaVu Sans Mono if not found. Licensed under SIL Open Font License 1.1.
etyper/
epd42_driver.py # E-paper display driver (full + partial refresh)
typewriter.py # Typewriter application
install.sh # Installer (dependencies + systemd service)
etyper.service # systemd unit file
requirements.txt # Python dependencies
README.md # This file
fonts/
AtkinsonHyperlegibleMono-Medium.ttf # Primary display font
AtkinsonHyperlegibleMono-SemiBold.ttf # Heavier variant
AtkinsonHyperlegibleMono-Regular.ttf # Lighter variant
AtkinsonHyperlegibleMono-Bold.ttf # Bold variant
OFL.txt # SIL Open Font License
examples/
hello_world.py # Basic "Hello World" demo
test_patterns.py # Diagnostic test patterns
- WeAct Studio - Display manufacturer & reference C driver
- Waveshare - Compatible Python driver reference
- Atkinson Hyperlegible Mono - Display font by the Braille Institute (SIL OFL 1.1)