Initial commit: Paperwall, a standalone e-ink dashboard
Navidrome now-playing, clock, and weather for a Waveshare epd4in0e panel, with a local web UI and install/uninstall scripts for the Pi.
This commit is contained in:
commit
66464291e1
33 files changed
+2215
No files matched your search
@@ -0,0 +1,108 @@
|
||||
# Paperwall
|
||||
|
||||
A standalone dashboard for one Waveshare `epd4in0e` (4", 6-color) e-ink panel:
|
||||
Navidrome now-playing, clock, and weather, 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
|
||||
|
||||
1. Copy the project folder onto the Pi however you like (SFTP, `scp -r`,
|
||||
whatever) -- anywhere, the installer figures out its own location.
|
||||
2. On the Pi:
|
||||
```bash
|
||||
sudo bash install.sh
|
||||
```
|
||||
|
||||
That's the whole thing. It installs the few system packages needed to build
|
||||
`lgpio` (`liblgpio-dev`, `swig`), 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 coordinates under **Weather**. Everything saves
|
||||
immediately and takes effect within a few seconds -- no restart needed.
|
||||
|
||||
## 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
|
||||
```
|
||||
Reference in new issue
Block a user