{ config, lib, pkgs, self, ... }: let cfg = config.eink.renderer; pythonEnv = pkgs.python3.withPackages (ps: with ps; [ pillow numpy playwright requests # The Waveshare 12.48" lib is not packaged in nixpkgs; we vendor it # under renderer/waveshare/ and import it as a local module. spidev rpi-gpio ]); in { options.eink.renderer = { enable = lib.mkEnableOption "E-paper renderer (Playwright + Pillow + Waveshare driver)"; dashboardUrl = lib.mkOption { type = lib.types.str; default = "http://127.0.0.1:3000"; }; refreshSeconds = lib.mkOption { type = lib.types.int; default = 300; description = "Seconds between full panel refreshes. Below 180 will wear the panel."; }; panel = lib.mkOption { type = lib.types.enum [ "bw" "bwr" ]; default = "bw"; description = "Panel variant: bw = 12.48\" B/W, bwr = 12.48\" Module (B) B/W/Red."; }; }; config = lib.mkIf cfg.enable { # Playwright needs a Chromium and its system deps. Use the nixpkgs one # so the binary path is stable across rebuilds (not the npm-downloaded # browser, which won't find its sandbox libs on NixOS). systemd.services.eink-renderer = { description = "E-paper renderer loop"; after = [ "eink-dashboard.service" "network-online.target" ]; wants = [ "eink-dashboard.service" ]; wantedBy = [ "multi-user.target" ]; environment = { DASHBOARD_URL = cfg.dashboardUrl; REFRESH_SECONDS = toString cfg.refreshSeconds; PANEL_VARIANT = cfg.panel; PLAYWRIGHT_BROWSERS_PATH = "${pkgs.playwright-driver.browsers}"; PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD = "1"; PYTHONPATH = "${self}/renderer"; }; serviceConfig = { Type = "simple"; ExecStart = "${pythonEnv}/bin/python ${self}/renderer/render.py"; Restart = "always"; RestartSec = "10s"; # The Waveshare lib needs raw SPI + GPIO. Run as a dedicated user # in the spi/gpio groups rather than root. User = "eink"; Group = "eink"; SupplementaryGroups = [ "spi" "gpio" ]; }; }; users.users.eink = { isSystemUser = true; group = "eink"; extraGroups = [ "spi" "gpio" ]; }; users.groups.eink = {}; }; }