# eink NixOS-managed Raspberry Pi 4 driving a Waveshare 12.48" e-paper panel as a calendar / day-planner. CalDAV data comes from the household Stalwart at `mail.rubenhensen.nl`. The Pi pulls every commit on `main` via [comin](https://github.com/nlewo/comin) — never touch the SD card after the first flash. ## Architecture ``` ┌─────────────────────────────────────────────────────────────────────────┐ │ Raspberry Pi 4 (NixOS aarch64, hostname: dayplanner) │ │ │ │ systemd: eink-dashboard.service systemd: eink-renderer.service │ │ ┌──────────────────────────┐ ┌──────────────────────────────┐ │ │ │ SvelteKit (Node adapter) │◄──────│ Playwright → Pillow dither │ │ │ │ :3000 │ HTTP │ → Waveshare 12.48" driver │ │ │ │ fetches CalDAV every load│ │ (every REFRESH_SECONDS) │ │ │ └────────────┬─────────────┘ └───────────────┬──────────────┘ │ │ │ │ SPI │ │ ▼ ▼ │ │ mail.rubenhensen.nl Waveshare 12.48" HAT │ │ (Stalwart CalDAV) (1304×984, 4 sub-panels) │ └─────────────────────────────────────────────────────────────────────────┘ Deploy path: laptop → git push → Gitea → comin (60s poll) → Pi ``` ## Layout ``` eink/ ├── flake.nix # nixpkgs + comin → nixosConfigurations.dayplanner ├── hosts/dayplanner/ # this Pi's NixOS config (users, comin, hardware) ├── modules/ # eink-dashboard.nix + eink-renderer.nix ├── dashboard/ # SvelteKit app rendered headlessly at 1304×984 └── renderer/ # Python loop: screenshot → dither → push ``` ## One-time bootstrap 1. **Flash NixOS aarch64 SD image** onto a card and boot the Pi. - Image: - On first boot, set a `nixos` user password via the serial console or monitor + keyboard. Enable SSH manually (`sudo systemctl start sshd`) and note the Pi's IP. 2. **Add your SSH public key** to `hosts/dayplanner/default.nix` under `users.users.ruben.openssh.authorizedKeys.keys`. 3. **Push this repo to a Git remote** the Pi can reach (e.g. your Gitea). Update `services.comin.remotes[0].url` in `hosts/dayplanner/default.nix` if the URL differs. 4. **First synchronous deploy from your laptop:** ```sh nix run nixpkgs#nixos-rebuild -- switch \ --flake .#dayplanner \ --target-host nixos@ \ --build-host nixos@ \ --use-remote-sudo ``` (Building on the Pi itself avoids needing a cross-compile setup on your Mac. Slow but one-time.) 5. **Drop the CalDAV password** on the Pi: ```sh ssh ruben@ 'sudo install -d -m 700 /var/lib/eink/secrets' ssh ruben@ 'sudo tee /var/lib/eink/secrets/caldav-password >/dev/null' \ <<< 'your-caldav-app-password' ssh ruben@ 'sudo chmod 600 /var/lib/eink/secrets/caldav-password' ``` (Long-term replace this with [sops-nix](https://github.com/Mic92/sops-nix) so the secret lives encrypted in this repo.) 6. **Vendor the Waveshare driver** (see `renderer/waveshare/README.md`). After this, the steady-state workflow is just: ```sh git push origin main # comin picks it up within ~60s and switches the Pi ``` ## Iterating on the dashboard locally You don't need the Pi to develop the UI — just open the SvelteKit dev server in a browser sized to 1304×984: ```sh cd dashboard npm install CALDAV_PASSWORD=... npm run dev -- --host # open http://localhost:5173 in a browser with viewport 1304×984 ``` Chrome devtools → "Toggle device toolbar" → set to 1304×984 to preview exactly what the panel will see. ## Refresh cadence & panel wear `eink.renderer.refreshSeconds = 300` (5 min) is the default. The 12.48" panel has **no partial refresh** — every update is a full flash that takes ~8s and consumes some panel life. Below 180 s is abusive; 300-600 s is comfortable. Consider scheduling a nightly invert to spread wear. ## Panel variant Set `eink.renderer.panel = "bwr"` in `hosts/dayplanner/default.nix` if you have the **12.48inch e-Paper Module (B)** (B/W/Red) instead of the plain B/W version. The dashboard's red channel is whatever CSS color matches `rgb(>180, <100, <100)` — see `renderer/render.py:to_bwr`. ## Troubleshooting - **Comin not pulling:** `journalctl -u comin.service -f` on the Pi. - **Renderer dry-running:** check `/tmp/eink-last.png` — if it exists, the screenshot+dither pipeline works and the Waveshare driver is the remaining piece (see `renderer/waveshare/README.md`). - **Dashboard 500:** `journalctl -u eink-dashboard.service -f`. Most common cause: empty `/var/lib/eink/secrets/caldav-password`.