SmartestHome/hosts/kitchen-display
Amir Alexander Abdelbaki a948f4b375 Workshop assistant, fleet monitoring, infra health, and shared endpoint surfaces
Adds the workshop/office assistant and the plumbing several other features
were waiting on. The through-line: every new capability that could act on
its own proposes instead, and says out loud when it does not know something.

New service — workshop/
  Project notebook (workshop.db) plus a never-pruned knowledge store
  (workshop-knowledge.db): standing workflow instructions by activity,
  keyword facts, durable project learnings, and the household's ONE hardware
  inventory. GET /context returns everything applying right now in one call,
  so the assistant is told the standing considerations rather than reminded
  of them. Two databases because they have different lifetimes: rebuilding
  the project store must not take the note about how you solder with it.

  Hardware statuses distinguish reserved (still on the shelf) from in_use
  (installed and working) — "can I use this right now" has different answers
  for the two, and naming a project on an in_use item never silently demotes
  it.

  Gitea repos with append-only history: commit/push/branch yes, unattended;
  force-push/rebase/amend/reset/filter-repo never, enforced server-side by
  branch protection rather than only by this code refusing. When history
  genuinely must be scrubbed, /scrub-request prints the commands for a human
  to run — the manual step is the safety mechanism.

  Fleet scripts: one monitoring-agent script per kind of machine, fetched by
  each endpoint's fleet-bootstrap timer. Remote code execution by design, so
  the constraints are the design — upload is a draft, publishing is separate,
  scripts live in SQLite rather than on the writable share, every version is
  kept, and the endpoint verifies the checksum and reports pass or fail.
  Slots exist for the ESP32s and network appliances that cannot run a script
  at all, holding the CheckMK-server-side config instead.

Infrastructure health
  opnsense becomes a LIST of firewalls, each named, keyed by name rather than
  index. CheckMK joins it. Both are polled by workshop (always-on) and read
  by digest-engine, so the digest can say "critical since Tuesday" instead of
  quoting a six-hour-old snapshot. Three states, because "I could not ask" is
  not "nothing is wrong".

pantry-vision
  All four stock movements are camera-driven; stock counts individual units
  and folds brand-free via Grocy product groups. Door-sensor-triggered
  appliance cameras record sightings as hints with timestamps, never as
  stock — a camera at a door cannot tell in from out.

identity
  Per-person colour and settable profile picture, assigned to avoid collisions
  between people sharing an initial, on the 2-bit-per-channel lattice a colour
  Pebble renders natively.

render/ — shared, vendored, dependency-free
  media-visualiser: two-tier by necessity, since most endpoints have no local
  audio; the synthetic tier says on screen that it is not an analysis.
  floorplan-3d: canvas 2D rather than three.js — the scene is prisms on a
  plane, which an isometric projection draws in ~200 lines, predictably on
  weak panels, with the frontend still at zero dependencies.

Config and fleet plumbing
  Rooms are one vocabulary (an HA area_id) from CoreSystemConfig through the
  builders to suggested_area. Keycloak and FreeIPA are coupled as one
  decision with USR_HA_ group naming, declaration-only for now and validated
  as such. Immich alongside the photo share, read-only. Thin clients get the
  full media-key set for a wireless remote.

Docs: fridge-item-location, workshop-assistant, rooms-and-endpoints,
endpoint-surfaces, pebble-presence-watchface.

Testing is stubbed suites and headless unit checks only — no real Grocy,
camera, vision model, CheckMK, Gitea, Samba or browser has been involved.
The CheckMK API shape and Gitea's branch-protection payload are written from
documentation and have version-sensitive field names.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FanS1vyE2gLhGkqKq6HtYj
2026-08-10 14:54:37 +02:00
..
agent Workshop assistant, fleet monitoring, infra health, and shared endpoint surfaces 2026-08-10 14:54:37 +02:00
configs Add identity registration system and Phase 18 door panel 2026-07-30 13:40:04 +02:00
live-build/config Add identity registration system and Phase 18 door panel 2026-07-30 13:40:04 +02:00
README.md Workshop assistant, fleet monitoring, infra health, and shared endpoint surfaces 2026-08-10 14:54:37 +02:00

README.md

Sway kitchen display

Phase 17 of docs/project-plan.md. Builds a Debian 12 live ISO for a single-purpose fridge/pantry display: hold a grocery item up to its camera, confirm what the vision model thinks it is and when it'll go bad, put it away — then browse the resulting inventory (soonest-to-expire first) and Grocy's recipes, all touch-driven.

The camera-vision and Grocy read/write logic does not live in this directory — that's ../../pantry-vision/, a container-host service this device's kiosk browser calls directly. This host is deliberately thin: a Sway session, one Chromium kiosk window, and kitchen-display-agent, which only ever controls which screen is showing (the unload-groceries scanner, Inventory or Recipes, plus Registration — see below) over MQTT — the same "the agent controls the surface, a separate write API owns the content" split hosts/thin-client's admin canvas already established.

This is also one of the camera-equipped endpoints ../../identity/'s person registration flow can run on — "register me as <name>" by voice (needs ENABLE_VOICE_SATELLITE=true and a real mic on this specific unit) or the touchscreen form either way. See identity's own README for the actual design (multi-phone support, anti-spoofing); this host only adds the mic/kiosk-page plumbing.

A third, simpler sibling of ../thin-client/ and ../touch-panel/, not a variant of either — one workspace, one app, no dock, no music, no general browsing. It reuses the thin client's live-build toolchain and configs/+agent/ split.

Compositor Sway, one workspace, one Chromium kiosk window
Autologin greetd, straight into /usr/local/bin/kiosk-session
Touch input Native Wayland wl_touch, same assumption as ../touch-panel/
Camera Browser-native getUserMedia() inside the Chromium kiosk page — no separate native capture app on this device at all
Content pantry-vision's frontend (../../pantry-vision/frontend/) by default; identity's registration page on demand
Voice wyoming-satellite + openWakeWord — opt-in, off by default, needed for "register me as <name>"
Remote control (HA/LLM) kitchen-display-agentShow scan / Show inventory / Show recipes / Show registration buttons only
Remote control (human) SSH only — no wayvnc, same scope decision as ../touch-panel/

Hardware

No specific unit has been chosen. What's assumed: a touchscreen (native wl_touch, same caveat as ../touch-panel/README.md) mounted somewhere near the fridge/pantry, with a USB webcam pointed at wherever items get held up — either built into the panel or a separate camera on a short cable/gooseneck mount so the angle can be adjusted independently of the screen. See §1.15 of docs/project-plan.md for the (unverified, non-specific) hardware line item.

Before you build

Deploy pantry-vision/pantry-web first (ENABLE_PANTRY_VISION in tools/setup-container-host.sh) — this image builds and boots fine without it, but every tab will show a connection error until it exists. Then edit the # CONFIGURATION block at the top of tools/build-kitchen-display-iso.sh:

Variable What to put in it
MQTT_BROKER_HOST LAN IP of the container host running Mosquitto
PANTRY_WEB_URL The pantry-web service's URL, e.g. http://192.168.1.10:8096
PANTRY_VISION_URL The pantry-vision API's URL, e.g. http://192.168.1.10:8095 — read by app.js from the kiosk window's own URL query params (see pantry-kiosk's comment), never baked into the served frontend files
PANTRY_VISION_TOKEN Must exactly match PANTRY_VISION_TOKEN in pantry-vision/pantry-vision.env on the container host — there's no way for this repo to push that value between the two hosts for you
IDENTITY_WEB_URL / IDENTITY_URL / IDENTITY_TOKEN Same shape as the PANTRY_* ones above, for ../../identity/ (ENABLE_IDENTITY in setup-container-host.sh) — IDENTITY_TOKEN must match identity/identity.env's own token
ENABLE_VOICE_SATELLITE false unless this specific unit has a real microphone attached — see ../thin-client/README.md's identical flag for the same per-image reasoning
KIOSK_USERNAME / IMAGE_HOSTNAME / KITCHEN_DISPLAY_NAME Per-device identity
SSH_AUTHORIZED_KEY Optional — password auth is disabled and there is no wayvnc

Build

sudo -E tools/build-kitchen-display-iso.sh

Same directory-split convention as every other host in this project: configs/ and agent/ are human-edited and git-tracked; live-build/config/includes.chroot/ is generated, gitignored, never hand-edited.

Why the camera has no native capture app

../touch-panel/ and ../thin-client/ both shell out to native processes for their media (mpv, the Spotify Flatpak). This device doesn't need that: Chromium's own getUserMedia() API, called from pantry-vision/frontend/app.js, talks to the webcam directly and hands a captured frame straight to POST /identify as a blob — no intermediate script, no temp file, no separate process to supervise. The one thing this needs from the kiosk launch itself is --use-fake-ui-for-media-stream (configs/sway/pantry-kiosk), which auto-accepts the camera permission prompt that would otherwise sit unanswered on a screen nobody is there to click "Allow" on.

Home Assistant entities

kitchen-display-agent publishes four buttons on connect: Show scan, Show inventory, Show recipes, Show registration — the first three kill and relaunch the kiosk Chromium window at PANTRY_WEB_URL/index.html?...#<tab>. ("Show scan" still publishes the fragment scan, which the frontend treats as an alias for its unload-groceries screen; the four stock-movement screens are reached by tapping, not over MQTT, since they are all things a person is standing at the display doing.) The fourth relaunches it at IDENTITY_WEB_URL/register.html?... instead (a different backend, identity, behind the same "Show X" shape — see kitchen_display_agent/main.py's on_show()). That's the entire MQTT surface; there is no media_player, no workspace select (one workspace), no capture/audio/ remote-desktop entities the way the thin client has. Reading or writing the actual inventory, or registering a person, is a separate path — Home Assistant/the LLM calling pantry-vision's or identity's own published API directly, not through this agent, exactly mirroring the split hosts/thin-client's admin canvas already established between "which screen is showing" (MQTT, this agent) and "what's actually on it" (a dedicated write API). Voice registration in particular never touches this agent at all — see ../../identity/README.md's worked HA intent-script example.

Security boundary

Same shape and reasoning as every other host's agent in this project: kitchen_display_agent/mqtt_discovery.py is the entire inbound MQTT control surface of this machine — LLM tool call → HA service call → MQTT → this agent, no HTTP listener, no websocket, no exposed Sway IPC socket, no VNC. pantry-vision is a separate, deliberately published service with its own bearer-token boundary — see ../../pantry-vision/README.md's "A real network listener, unlike admin-canvas" section for why that one's trust model is different on purpose.

Manual verification still outstanding

None of this has been run on hardware. In rough order — on top of everything already flagged as unverified in ../../pantry-vision/README.md (the Grocy API assumptions, the vision model's accuracy/latency, which this device's whole usefulness rides on):

  1. The ISO builds at all.
  2. greetd lands in Sway with no login prompt.
  3. Touch input as native wl_touch — same open item as ../touch-panel/.
  4. Camera enumeration and --use-fake-ui-for-media-stream — whether Chromium picks the right /dev/videoN when more than one video device is present (a built-in laptop-style webcam plus a dedicated one, say), and whether that flag actually suppresses the permission prompt on this Chromium/Wayland combination specifically (it's a well-known flag for automated testing, not confirmed here against a real kiosk session).
  5. kitchen-display-agent connects to Mosquitto and the device appears in HA.
  6. End-to-end scan latency: camera capture → /identify → a usable proposal on screen, with a real vision model on real LLM-host hardware. This is the number that decides whether the whole "hold it up, wait, confirm" workflow feels usable or like standing at the fridge for 30 seconds waiting on an LLM.
  7. Whether ?api=...&token=... survives being passed through --app= URL escaping correctly for every character that can appear in PANTRY_VISION_URL — the sed-based percent-encoding in configs/sway/pantry-kiosk handles / and : only, which is enough for a bare http://host:port but not for a URL with a path or query string of its own.
  8. Idle-blank timeout (20 minutes, configs/sway/config) and touch-resume — untested.
  9. Switching between pantry-kiosk and identity-kiosk (both configs/sway/) actually replaces the window rather than leaving two Chromium instances up — both scripts kill any chromium process, not just their own profile, specifically because this device has only one workspace; never exercised against a real in-progress scan or registration getting interrupted mid-flow by the other.
  10. Everything in ../../identity/README.md's own verification list, especially TRUSTED_ENTITY_PREFIXES — registration will not find a candidate at all until that's set to match your real HA entity IDs.