Docs menu

Reference

slowd reference

slowd's configuration, its refresh rules and timings, its threads and files, the service's privileges, and its diagnostic commands.

What slowd does, and why, is on slowd. This page has the details. In file names and counters a slowapp is an app.

Configuration file (slowd.toml)

/etc/slowd/slowd.toml:

profile = "lyra-10in85g"
[hub]
url = "unix:/run/slowhub/device.sock"   # or "http://host:port" with token_file
# token_file = "/etc/slowd/hub.token"
[net]
wifi_setup = true          # may only disable what the profile allows
hub_web_port = 8080        # for the "connected" QR code
  • profile: the profile of the screen slowd drives.
  • [hub] url: where the hub is. A Unix socket for a hub on the same machine, or http://host:port with a token_file for a hub elsewhere (Install slowhub).
  • [net] wifi_setup: whether slowd may start slownet. It can only turn off what the profile allows.
  • [net] hub_web_port: the port written into the “connected” screen’s QR code.

Everything else (the layout, the schedule, the limits, the prefs) arrives from the hub in the config document; see Layout and preferences.

The Slowboard 1085’s screen

x 0 451 452 907 908 1359 y 0 strip 36 px strip 36 px strip 36 px y 480 c ntroller 0: x 0..679 controller 1: x 680..1359 surfaces: 452x444.k4 and 456x444.k4 slot width x 480 strip h slot 452 x card slot 456 x card slot 452 x card 444 444 444 y 444 left middle right

The default layout: three slots over 1360 × 480 pixels, each a 444-pixel card over a 36-pixel strip. The panel has two controller chips, each driving half the width. Other layouts divide the width differently.

Refresh rules

RuleSlowboard 1085
Cooldown per pixel area180 s: a refresh may only cover pixels no refresh touched in the last 180 s
Overlapping refreshesnever
Fast refresh (key presses)12.1 s
Normal refresh (scheduled content, heal)39 s
Daily ceiling120 refreshes in total, 48 per app
Longest gap before a heal24 h

The limits come from the profile; the hub can only lower them.

Each tick slowd starts at most one refresh, and only while the panel is idle, in this order: a press on a slot that offers keys; a due question on a cool slot; a heal; offering keys again (a strip-only refresh); scheduled content at grid time; commands and key presses from the hub, merged into the next tick.

Pending areas go out as one bounding box when that makes nothing wait, otherwise one after another. On the 1085 each controller takes one window, so any combination of slots fits one pass. A partial refresh takes as long as a full one with the same mode; what it gains is that the rest of the panel doesn’t flash.

After a press the slot’s card refreshes with the fast mode (with its strip, if the strip’s area is cool). Once the card’s area is within 30 s of cool and the strip’s area is cool, a strip-only refresh offers the keys again. A strip offers a key only when every outcome the key can lead to is stored locally: the next card, the home card, the home of every slot for a hold of the key no slot uses, and, for a question, all its answers’ reply cards. A press resolves through the outcomes stored when the strip was drawn, even if newer cards have arrived since.

Scheduled content follows a wall-clock grid: every minute of the day where minute_of_day % every_min == 0, inside active hours, outside quiet hours, in the screen’s time zone. At grid time a slot refreshes if its current card differs from the screen. Content that arrives late refreshes on arrival if it is within half of every_min of the grid time, otherwise at the next one. Changes are gathered for 20 s before the window planner runs.

Heal is a full normal-mode refresh at the first scheduled refresh after active hours start, or after 24 h without a refresh, and only when a refresh is due and nothing is waiting. With no clock sync for 6 h it counts from boot instead.

Stale content: past valid_until + stale_grace_min, the strip of a resting slot shows “as of HH:MM”. The card is never blanked.

Layout changes: slowd keeps the old layout until every slot of the new one has cards for its size. After 15 minutes it switches anyway and shows “waiting” on empty slots.

Config from the hub is clamped with the same function the hub uses: every_min must divide 1440 and be at least 15, keep_s is clamped to 60–3600, and limits can only go down. slowd stores the result, acknowledges it, and keeps running on it while offline.

Offline behaviour

  • Stored cards. Every card picture of the current and next bundles is kept in /var/lib/slowd/pinned/ (about 1–3 MB, written by difference). After a reboot during an internet outage the decks come back and the keys are offered again.
  • Answers go to a durable outbox (outbox.jsonl: append, fsync, compact on acknowledgement, at most 512 events or 7 days) and are delivered when the hub is back.
  • Clock. “Synced” means adjtimex reports no STA_UNSYNC. Until then keys work, but scheduled content, quiet hours, expiry and questions pause; heal and the 24 h limit count from boot after 6 h; events are stamped at_synced: false.
  • Reboot. At boot every area is treated as just refreshed, and no slot offers keys until it may.
  • Power cut. Before each refresh slowd writes and fsyncs intent.json. If that file is present at boot, the screen’s contents are unknown and a full refresh runs at the first allowed moment.

Panel lifecycle and stopping

Each refresh powers the panel up, resets it, initialises both controllers, sends the frame (SPI chunks of 4096 bytes), sets the partial window per controller, refreshes, then powers off and puts the panel to deep sleep with the power line low. A guard waits up to 150 s for BUSY, then sleeps the panel; if SPI fails it drives power low.

On SIGTERM slowd finishes the current refresh, sleeps the panel and exits. The unit sets TimeoutStopSec=200 (systemd’s 90 s default would kill it mid-refresh) and ExecStopPost=/opt/slowboard/bin/slowd panel-safe. Run slowd idle-wait before replacing the binary.

Threads

slowd uses plain threads; there is no async runtime.

  • panel: the only owner of the panel driver. It takes one refresh at a time and replies with timings and a result.
  • keys: one request for the key lines, both edges, 20 ms debounce. A tap fires on release before 500 ms; a hold fires at 500 ms. Falls back to a 10 ms poll if edge events don’t work.
  • core: waits on keys, panel completion, hub and timers, and calls the pure function sb_core::step(state, input, now) -> (state, cmds). It journals its inputs to a 2 MB in-memory ring, pings the systemd watchdog, sleeps at most 30 s at a time, and wakes on clock steps.
  • hub-poll and hub-upload: two HTTP/1.1 clients on separate connections, so an upload never waits behind a held long-poll. Poll validates bundles and card pictures and verifies their ids. Upload drains the outbox and sends the frame report.
  • net-health (profiles with wifi_setup): every 20 s it checks wlan0 and that the hub is reachable. See slownet.
  • ctl: the diagnostics socket.

The core logic lives in the sb-core crate, split by Cargo feature so a port can take only what it needs: compose (the frame, strips, QR codes, built-in screens), policy (areas and cooldowns, the window planner, heal, ceilings, hours, clamp, the clock rules) and deck (decks, stored outcomes, offering keys, questions).

Files

PathContents
/var/lib/slowd/glass.binThe frame on the screen, with glass.prov.json (where each part came from)
/var/lib/slowd/intent.json, last.jsonThe refresh in progress, for power cuts; the last refresh and heal
/var/lib/slowd/deck.json, applied.jsonSlot states, answered questions, recent key presses from the hub, applied commands
/var/lib/slowd/config.jsonThe last acknowledged config, used offline
/var/lib/slowd/refreshes.jsonRefresh counts: lifetime, per day, per kind, per app
/var/lib/slowd/pinned/<id>Stored card pictures
/var/lib/slowd/outbox.jsonlAnswers not yet delivered
/run/sbblobs/A 16 MB in-memory store of card pictures in panel format
/run/slowd/ctl.sock, journal.jsonlDiagnostics

Service and privileges

slowd runs as the user slowd with the groups spi and gpio, no capabilities, DevicePolicy=closed with only /dev/spidev0.0 and /dev/gpiochip0 allowed, and RestrictAddressFamilies=AF_UNIX AF_NETLINK. It is a Type=notify unit with WatchdogSec=30, MemoryMax=40M and OOMScoreAdjust=-900, so under memory pressure the kernel stops app containers long before the panel driver.

Diagnostics

/run/slowd/ctl.sock (group slowd-admin) is for diagnostics; the hub never uses it. The slowd binary is also its client:

slowd status                       # state, areas, deck, counts, warnings
slowd selftest                     # exercise the panel
slowd idle-wait                    # wait until no refresh is running
slowd key red tap|hold             # the same path as a physical key press
slowd glass.png OUT.png            # the current frame as a PNG
slowd replay /run/slowd/journal.jsonl [--verbose]   # re-run the recorded inputs

The other subcommands are run (the program itself) and panel-safe (power the panel down). replay feeds a journal back through the same step() function, so a bug report can be reproduced on another machine.

Updates, now and then

A short note when there is a new board, a new slowapp, or kit news. Rarely more than one a month.