Daily newspaper front pages on a 42" Avalue EPD-42S monochrome e-ink display, driven by an always-on Mac mini running an OpenDisplay WiFi server.
Time-based newspaper rotation (Hong Kong time):
- 06:00 — South China Morning Post (SCMP)
- 13:00 — The Guardian
- 15:00 — Financial Times
- 17:00 — The New York Times
- 21:00 — Los Angeles Times
Each newspaper is fetched using dedicated high-resolution scrapers (NYT via direct PDF at 200 DPI, others via PressReader/direct sources), processed to grayscale, and served via the OpenDisplay WiFi protocol. The display polls the server every 5 minutes and renders in real 16-level grayscale on the e-ink panel.
Background: the EPD-42S is a 42" Android 5.1.1 e-ink panel (2880×2160, 4:3 ratio) originally sold by Avalue as a digital signage product. Uses E Ink VB3300-RBA Salt driving board. Several people on a Reddit thread and the associated Discord picked them up cheap second-hand without instructions. This is one of the projects to come out of that community reverse-engineering effort.
Mac mini (always-on macOS)
├── launchd (hourly) → scraper.py → NYT PDF → pdftoppm 200 DPI
├── processor.py → fit/grayscale/rotate → images/current.png
├── server.py → OpenDisplay WiFi server :2446 + mDNS
├── watchdog.py → heartbeat monitor (every 5 min)
├── tplink_admin.py → bridge status/reboot via admin UI
└── tools/remote_shell.py → reverse shell listener for OTA management
TP-Link WR802N (Client mode bridge, WiFi-to-Ethernet)
EPD-42S Display (Android 5.1.1, Ethernet via bridge)
├── install-recovery.sh → DHCP retry + daemon startup at boot
├── tp_watchdog.sh → auto-reboots bridge on connectivity loss
├── display_remote.sh → reverse shell to Mac mini for OTA access
└── OpenDisplay WiFi app → polls server, renders on e-ink panel
config.json selection strategy + display orientation
src/
scraper.py fetch newspaper images (dispatches to per-paper scrapers)
nyt_scraper.py NYT high-res PDF scraper (200 DPI via pdftoppm)
processor.py resize/grayscale/letterbox for the display
server.py OpenDisplay WiFi server + heartbeat file
watchdog.py pipeline health check (heartbeat freshness)
tplink_admin.py TP-Link WR802N admin UI CLI (status / reboot)
tests/
test_e2e.py smoke test that pretends to be the display
tools/
remote_shell.py reverse shell listener for OTA display management
deploy/
install.sh one-shot installer (creates venv, loads launchd jobs)
com.e-newspaper.server.plist always-on server launchd job
com.e-newspaper.daily-update.plist hourly scraper+processor launchd job
com.e-newspaper.watchdog.plist pipeline watchdog launchd job
DISPLAY_SETUP.md how to set up the EPD-42S to talk to the server
images/ gitignored output directory
You need Python 3.11+ (py-opendisplay requires it) and poppler
(brew install poppler for pdftoppm).
git clone https://github.com/andygmassey/HR018-e-newspaper.git
cd HR018-e-newspaper
python3.11 -m venv .venv
.venv/bin/pip install requests beautifulsoup4 Pillow \
"py-opendisplay @ git+https://github.com/balloob/py-opendisplay@wifi-server"
# Pull today's papers
.venv/bin/python src/scraper.py
# Process today's chosen paper (per config.json) → images/current.png
.venv/bin/python src/processor.py
# Run the server in the foreground
.venv/bin/python src/server.py
# In another terminal: smoke test (pretends to be the display)
.venv/bin/python tests/test_e2e.pygit clone https://github.com/andygmassey/HR018-e-newspaper.git ~/projects/HR018-e-newspaper
cd ~/projects/HR018-e-newspaper
./deploy/install.shThe installer:
- Creates a Python 3.11 venv at
.venv/ - Installs dependencies
- Runs a smoke scrape to confirm the pipeline works
- Rewrites the launchd plist paths to match the install location
- Loads launchd jobs (server always-on, scraper hourly, watchdog every 5 min)
After install:
tail -f server.log # OpenDisplay server log
tail -f scraper.log # scrape/process log
tail -f watchdog.log # watchdog log
launchctl start com.e-newspaper.daily-update # force a refresh nowEdit config.json:
selection |
Behaviour |
|---|---|
weekday |
Picks a different paper for each day of the week (weekday_map) |
rotate |
Cycles through rotation list, one per day |
fixed |
Always shows fixed |
orientation is portrait by default (newspapers are tall, the e-ink
panel is rendered as 2160x2880). Set to landscape to render 2880x2160
instead.
To list all the papers available on frontpages.com today:
.venv/bin/python src/scraper.py --listSee deploy/DISPLAY_SETUP.md for the full
flow: disabling the AdSign boot app via ADB, sideloading the OpenDisplay
WiFi APK, and setting it as the home activity so it survives reboots.
Once the display is on the same LAN as the backend, it will discover the server via mDNS and start polling automatically.
High-resolution scrapers are used for all the daily newspapers:
- New York Times — Public print-edition PDF from
static01.nyt.com, rasterised at 200 DPI (~2442x4685 pixels) - South China Morning Post & The Guardian — PressReader API via library card access
- Financial Times — Direct high-resolution scraper
- Los Angeles Times — Direct high-resolution scraper
- Washington Post — Direct high-resolution scraper (used in rotation/weekday modes)
All scrapers are ET-date idempotent with sidecar files to avoid re-fetching the same edition.
frontpages.com serves as fallback for other papers — aggregates ~130 newspapers worldwide at 600×800 webp thumbnails. Used for papers without dedicated high-res scrapers in the rotation modes.
- The OpenDisplay Android app and py-opendisplay library by Paulus Schoutsen
- frontpages.com for the daily aggregated images
- The r/eink community and the EPD-42 Discord for collectively reverse engineering this hardware
- Inspired in part by Max Braun's "Paper" project
MIT — see LICENSE.

