Lightweight framebuffer dashboards for a 320×240 SPI TFT on Raspberry Pi.
- No X11/Wayland
- No SDL
- Direct rendering to
/dev/fb1(RGB565)
Use the following names as the source of truth for systemd-managed runtime modes.
| Service name | Script target | Mode |
|---|---|---|
display.service |
display_rotator.py |
Day rotator (multi-page cycle, touch navigation) |
pihole-display-dark.service |
scripts/piholestats_v1.2.py |
Night dark mode (single Pi-hole dashboard) |
currency-update.service |
scripts/currency-rate.py |
06:00 currency image refresh |
| Service | Script path | Mode / status |
|---|---|---|
display.service |
/opt/zero2dash/display_rotator.py |
Canonical day mode |
pihole-display-dark.service |
/opt/zero2dash/scripts/piholestats_v1.2.py |
Canonical night mode |
currency-update.service |
/opt/zero2dash/scripts/currency-rate.py |
Daily GBP/PLN image refresh |
day-mode.service |
(legacy alias; not shipped in this repo) | Legacy naming; replace with display.service |
dark-mode.service |
(legacy alias; not shipped in this repo) | Legacy naming; replace with pihole-display-dark.service |
zero2dash/
├── display_rotator.py
├── scripts/
│ ├── _config.py
│ ├── pihole_api.py
│ ├── pihole-display-pre.sh
│ ├── piholestats_v1.1.py # legacy daytime variant
│ ├── piholestats_v1.2.py # canonical dark-mode service target
│ ├── piholestats_v1.3.py
│ ├── calendash-api.py
│ ├── calendash-img.py
│ ├── currency-rate.py
│ ├── currency.py
│ ├── photos-shuffle.py
│ ├── drive-sync.py
│ └── photo-resize.py
├── systemd/
│ ├── display.service
│ ├── pihole-display-dark.service
│ ├── currency-update.service
│ ├── currency-update.timer
│ ├── day.timer
│ └── night.timer
└── README.md
scripts/piholestats_v1.0.pyandscripts/test.pyare not part of this repository and should not be used in deployment docs.day-mode.serviceanddark-mode.serviceare treated as legacy names only.
- Raspberry Pi OS (SPI enabled)
- Python 3
- Pillow (
python3-pil) - systemd
- Pi-hole API connectivity
sudo rm -rf LCD-show
git clone https://github.com/goodtft/LCD-show.git
cd LCD-show
sudo ./LCD24-show
# reboot to activate /dev/fb1sudo apt update
sudo apt install -y python3-pip python3-pilsudo mkdir -p /opt/zero2dash
sudo cp -r . /opt/zero2dash/
sudo chmod +x /opt/zero2dash/scripts/pihole-display-pre.shCreate and secure an env file:
cp /opt/zero2dash/.env.example /opt/zero2dash/.env
chmod 600 /opt/zero2dash/.envSet at minimum for Pi-hole:
PIHOLE_HOSTPIHOLE_SCHEMEifPIHOLE_HOSTis remote and does not already includehttp://orhttps://PIHOLE_PASSWORDfor v6 session auth, orPIHOLE_API_TOKENfor legacy token authPIHOLE_VERIFY_TLS=falsefor self-signed HTTPS, orPIHOLE_CA_BUNDLE=/path/to/ca.pemto verify a private CAPIHOLE_TIMEOUTREFRESH_SECSACTIVE_HOURS(inclusivestart,endhour window in 24h format; cross-midnight values like22,7are supported)FB_DEVICE(optional override; defaults to/dev/fb1)FB_WIDTH/FB_HEIGHT(optional override for static renderer geometry; defaults320x240)
Google OAuth notes:
- Use Desktop OAuth clients for Calendar and Photos.
- Loopback OAuth only: complete sign-in on the same machine as the script, or tunnel the callback port from a headless Pi with
ssh -L 8080:localhost:8080 pihole@pihole. - If the Google consent screen is in testing, add your account as a test user.
calendash-api.pydefaultsGOOGLE_TOKEN_PATHtotoken.jsonrelative to/opt/zero2dashunder systemd;photos-shuffle.pymust keep using a separateGOOGLE_TOKEN_PATH_PHOTOS.
Drive-backed photos notes:
scripts/photos-shuffle.pynow treatsLOCAL_PHOTOS_DIRas the primary source.- Use
scripts/drive-sync.pyto populate that directory from a shared Google Drive folder. scripts/photo-resize.pyproportionally reduces changed images to 50% before they are reused locally.- Normal personal/shared Google Photos albums are no longer a reliable headless source; if you still configure
GOOGLE_PHOTOS_ALBUM_ID, treat it as an app-created-album fallback only.
Install and enable canonical units:
sudo cp /opt/zero2dash/systemd/*.service /etc/systemd/system/
sudo cp /opt/zero2dash/systemd/*.timer /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now display.service
sudo systemctl enable --now day.timer night.timer currency-update.timerUseful checks:
journalctl -u display.service -n 50 --no-pager
journalctl -u pihole-display-dark.service -n 50 --no-pager
journalctl -u currency-update.service -n 50 --no-pagerscripts/photos-shuffle.py resolves OAuth credentials in this order:
GOOGLE_PHOTOS_CLIENT_SECRETS_PATHfile path from env/.env(default:~/zero2dash/client_secret.json)GOOGLE_PHOTOS_CLIENT_ID+GOOGLE_PHOTOS_CLIENT_SECRETfrom env/.envGOOGLE_CLIENT_ID+GOOGLE_CLIENT_SECRETfrom env/.env(legacy fallback)
Use python3 scripts/photos-shuffle.py --check-config to validate the configuration and print the credential source that will be used.
Use a shared Google Drive folder when you want remote photo management without depending on the now-hobbled Google Photos album API.
Required configuration:
LOCAL_PHOTOS_DIRGOOGLE_DRIVE_FOLDER_IDGOOGLE_DRIVE_SERVICE_ACCOUNT_JSON(~/.config/zero2dash/drive-service-account.jsonis the recommended Pi path)
Recommended workflow:
python3 scripts/drive-sync.py
python3 scripts/photos-shuffle.py --testdrive-sync.py downloads images from the shared Drive folder into LOCAL_PHOTOS_DIR and then runs photo-resize.py, which shrinks new or changed images to 50% of their original width and height before reuse.
display_rotator.pyexcludespiholestats_v1.2.py,calendash-api.py,currency-rate.py,_config.py,drive-sync.py, andphoto-resize.pyby default so helper scripts do not end up in the day rotator.calendash-img.pyis a rotator-friendly page script, not a systemd service unit by itself.currency-rate.pyis the scheduled generator;currency.pyis the rotator-friendly page script that only displays the generated image.
Both canonical service units now set FB_DEVICE=/dev/fb1 by default and load /opt/zero2dash/.env afterward, so setting FB_DEVICE in .env overrides the unit default without editing unit files.