New screens/journey.py, ported and simplified from github.com/EiS94/db_info (a Home Assistant integration). Deliberately uses only the regional EFA (Elektronische Fahrplanauskunft) backends the reference project queries (VRR prioritized for NRW routes, falling back to Bavaria/BW instances) and drops its bahn.de-internal-API source entirely -- that's DB's own infrastructure, exactly the kind of thing DB has been actively blocking (see the Departures screen). EFA instances are run by regional transit authorities, not DB, so they aren't part of that clampdown. Also dropped the reference project's parallel async fetch-and-score-all-sources machinery for simple sequential fallback, matching this project's fully synchronous style. Verified against live VRR data for a real NRW route (Münster -> Dortmund): real trains, platforms, transfer counts, even a night bus connection. Caught a real bug during that live test: EFA returns UTC timestamps (Z suffix), and datetime.fromisoformat() parses that correctly into an aware datetime, but strftime() on an aware datetime prints its own stored hour/minute rather than converting to local time first -- everything displayed 2 hours behind (CEST is UTC+2), which looked like "next" departures had already left. Fixed by converting with astimezone() before formatting; re-verified live afterward that departures now correctly show after the query time. Settings: origin/destination resolved via Open-Meteo's free geocoding (same pattern as Weather's city field), how many connections to show (1/2/3/5), and its own configurable update interval.
125 lines
5.4 KiB
Markdown
125 lines
5.4 KiB
Markdown
# Paperwall
|
|
|
|
A standalone dashboard for one Waveshare `epd4in0e` (4", 6-color) e-ink panel:
|
|
Navidrome now-playing, clock, weather, departure boards, a fixed-route
|
|
journey planner, and pictures (upload or URL), switchable/controllable from
|
|
a local web UI. Started as an InkyPi replacement, but doesn't depend on
|
|
InkyPi at all -- own venv, own vendored copy of the Waveshare driver files.
|
|
|
|
Confirmed by reading the vendor driver directly: this panel has no
|
|
partial-refresh mode at all -- every update is a full flash. So the design
|
|
here isn't about avoiding that flash, it's about only triggering it when
|
|
something real actually changed (song change, minute tick on the clock,
|
|
weather update), via hash-comparing each rendered frame against what's
|
|
currently on screen.
|
|
|
|
## Local development (no hardware needed)
|
|
|
|
```bash
|
|
python3 -m venv .venv && source .venv/bin/activate
|
|
pip install -r requirements.txt
|
|
python3 main.py --mock --port 8080
|
|
```
|
|
|
|
`--mock` saves each frame to `mock_output/` as a PNG instead of writing to
|
|
real hardware. The web UI (http://localhost:8080) works identically either
|
|
way -- same status page, same settings forms, same "switch screen now"
|
|
buttons. `requirements-pi.txt` (gpiozero/lgpio/RPi.GPIO) is only needed on
|
|
the real Pi, so local dev doesn't need it -- and those generally won't even
|
|
build off a Pi anyway.
|
|
|
|
## Installing on the Pi
|
|
|
|
```bash
|
|
git clone https://gitea.honeyy.duckdns.org/Honey/Paperwall.git /root/paperwall
|
|
cd /root/paperwall
|
|
sudo bash install.sh
|
|
```
|
|
|
|
That's the whole thing. It installs the few system packages needed to build
|
|
`lgpio`/`spidev` (`liblgpio-dev`, `swig`, `python3-dev`), enables SPI/I2C in
|
|
`config.txt` if not already on, creates its own venv right next to the
|
|
project and installs everything into it (`requirements.txt` +
|
|
`requirements-pi.txt`), and installs+starts `paperwall.service`. If it finds
|
|
InkyPi (or anything else) already running, it stops it first -- two things
|
|
can't drive the same panel at once -- but nothing is copied from or reused
|
|
out of it. Safe to re-run.
|
|
|
|
Then open `http://<pi-ip>:8080`, fill in Navidrome credentials under
|
|
**Navidrome** and a city under **Weather**. Everything saves immediately and
|
|
takes effect within a few seconds -- no restart needed.
|
|
|
|
## Updating
|
|
|
|
```bash
|
|
cd /root/paperwall
|
|
git pull
|
|
sudo systemctl restart paperwall.service
|
|
```
|
|
|
|
`venv/` and `config.json` are gitignored, so `git pull` never touches your
|
|
installed dependencies or your Navidrome/weather settings -- just the code.
|
|
A plain restart is enough for most changes. Only re-run `sudo bash
|
|
install.sh` when `requirements.txt` or `requirements-pi.txt` changed (new
|
|
dependency) or you need SPI/I2C re-checked -- it's idempotent, safe to run
|
|
any time.
|
|
|
|
## Uninstalling
|
|
|
|
```bash
|
|
sudo bash uninstall.sh
|
|
```
|
|
|
|
Stops the service, removes the systemd unit and the venv `install.sh`
|
|
created. Leaves the project folder and `config.json` alone -- delete those
|
|
yourself if you want it fully gone. Doesn't touch SPI/I2C or anything else
|
|
on the system.
|
|
|
|
(If you're migrating away from InkyPi specifically and want to remove *that*
|
|
too, see `uninstall_inkypi.sh` -- separate script, since it's InkyPi's
|
|
install that needs reversing, not Paperwall's.)
|
|
|
|
## Deploying to a different e-ink screen
|
|
|
|
Everything panel-specific lives behind the `Display` protocol in
|
|
`display/__init__.py` (`init()`, `show(image)`, `sleep()`) -- screens never
|
|
talk to hardware directly, they just return a `PIL.Image` sized to
|
|
`display.resolution`. To support a different panel:
|
|
|
|
1. **Waveshare panel, different model** (e.g. a monochrome one for fast
|
|
partial refresh): vendor that model's driver files into
|
|
`display/waveshare_epd/` the same way (check the Waveshare `e-Paper`
|
|
GitHub repo for whether it's in the standard
|
|
`RaspberryPi_JetsonNano/python/lib/waveshare_epd/` path or, like
|
|
`epd4in0e`, needs the "Separate Program" path instead). Write a new
|
|
driver class mirroring `display/epd4in0e_driver.py`, with the panel's
|
|
real pixel resolution. If the model exposes `displayPartial()` (common
|
|
on B/W and B/W/red panels, unlike this 6-color one), that's a real
|
|
opportunity: add a `supports_partial` flag and an optional
|
|
`show_partial(image, region)` to the `Display` protocol, and have the
|
|
scheduler use it for screens that only change a small region (e.g. just
|
|
the clock's time text) instead of always doing a full flash.
|
|
2. **Pimoroni Inky panel**: add `inky` to `requirements-pi.txt` and write a
|
|
driver using `inky.auto.auto()` instead of the Waveshare EPD classes --
|
|
simpler, since Pimoroni's library handles the hardware differences for
|
|
you.
|
|
3. Either way, update `resolution` in the new driver, and swap which driver
|
|
`main.py`'s `build_display()` constructs.
|
|
|
|
Nothing in `scheduler.py`, `config.py`, `web/`, or any screen needs to
|
|
change -- that's the point of the `Display` boundary.
|
|
|
|
## Layout
|
|
|
|
```
|
|
main.py entrypoint: init display, load config, start scheduler + web app
|
|
config.py config.json load/save
|
|
scheduler.py rotation / Navidrome-preemption / hash-dedup logic
|
|
display/ Display protocol + epd4in0e driver (vendored) + mock driver for dev
|
|
screens/ one module per screen (navidrome.py, clock.py, weather.py)
|
|
web/ Flask status page + settings forms
|
|
install.sh sets up venv + service on the Pi, standalone
|
|
uninstall.sh removes what install.sh set up
|
|
uninstall_inkypi.sh separate: removes InkyPi itself, if you were migrating from it
|
|
```
|