Go to file
Amir Alexander Abdelbaki 0caa21fc42 Add opt-in arrival push notifications; fix chores' unreachable container URLs
Rides the same arrival transition the visit log is built from, so "arrived" has
exactly one definition in this service rather than two that could disagree.

- notify_on_arrival (default off) is the "if enabled" half: push me when someone
  gets home. announce_arrivals (default ON) is a separate per-person opt-OUT of
  being announced, for anyone who doesn't want their comings and goings
  broadcast. The asymmetry is deliberate — if both defaulted off, ticking
  "notify me" would look broken until everyone else opted in too.
- Per-person notify_topic falling back to NTFY_DEFAULT_TOPIC. Topics are
  deduplicated, or a five-person household sharing one topic would get five
  identical pushes per arrival. The arriver is never told about themselves;
  subscribers who are away still are, since that's most of the point.
- The first sample after startup notifies nobody and establishes a baseline
  instead — otherwise a restart after a gap would fire "X just got home" for
  everyone already on the sofa. Costs one missed notification in that window.
- Face-recognition arrivals say "was just recognised at home", not "just got
  home" — the signals aren't equally reliable and the reader should know which.
- Pushes are sent outside _db_lock and isolated from each other, so a hanging
  ntfy can't stall request handling and one bad push can't swallow the rest.
- POST /people/<id>/test-notification, because the alternative way to find a
  typo'd topic is to wait for someone to walk in and notice nothing happened.

identity itself never touches the WAN — it POSTs to the ntfy already in this
stack. Whether the push reaches a phone that's AWAY is a network question, and
docs/network-integration.md's existing answer (WireGuard in, never a
port-forward) applies unchanged; iOS can't do this locally at all, since ntfy's
iOS app needs APNs and therefore an upstream relay. Documented as a table.

Also fixes two pre-existing bugs found while wiring this up: chores' template
pointed IDENTITY_URL and NTFY_URL at 127.0.0.1, which inside that container is
the container itself. chores would have reached neither identity nor ntfy, and
done nothing at all — silently, since both paths fail soft by design. Deployed
chores.env files still carry the old values and need editing by hand.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 09:37:10 +02:00
admin-canvas Add Phase 13 admin canvas, Phase 14 voice/display satellite, and thin-client capture-card input 2026-07-29 11:03:11 +02:00
chores Add opt-in arrival push notifications; fix chores' unreachable container URLs 2026-07-31 09:37:10 +02:00
digest-engine Add a counter-run verification pass as the digest's final hallucination filter 2026-07-28 16:05:29 +02:00
docs Add opt-in arrival push notifications; fix chores' unreachable container URLs 2026-07-31 09:37:10 +02:00
firmware Add trash/transit/chores services, RuView + BLE proxy firmware, network docs 2026-07-30 16:53:24 +02:00
hosts Add identity admin panel: nicknames, visit history, device rights, chore assignment 2026-07-31 09:05:14 +02:00
identity Add opt-in arrival push notifications; fix chores' unreachable container URLs 2026-07-31 09:37:10 +02:00
pantry-vision Add identity registration system and Phase 18 door panel 2026-07-30 13:40:04 +02:00
transit Add trash/transit/chores services, RuView + BLE proxy firmware, network docs 2026-07-30 16:53:24 +02:00
trash-calendar Add trash/transit/chores services, RuView + BLE proxy firmware, network docs 2026-07-30 16:53:24 +02:00
.gitignore Add idle-gallery weather/clock overlay and headless audio endpoint images 2026-07-29 11:37:29 +02:00
README.md Add opt-in arrival push notifications; fix chores' unreachable container URLs 2026-07-31 09:37:10 +02:00

README.md

AI-Managed Smart Home

Local-first, open-source smart home: Home Assistant + Zigbee + RuView (CSI presence)

  • Bermuda (BLE identity) + local LLM (Ollama) + Frigate (peephole face recognition)
  • Grocy (kitchen kiosk) + Nextcloud calendar sync + a Sway thin-client media station
  • a Sway touch panel + a camera-vision kitchen/fridge display + a voice/touch identity-registration door panel + a quarter-daily LLM-generated digest.

See docs/project-plan.md for the full hardware list, software stack, and phased implementation plan.

Repo layout

docs/                       Project plan and design notes
hosts/
  container-host/           Docker Compose stack: HA, Mosquitto, Zigbee2MQTT,
                             Frigate, Grocy, Node-RED, monitoring, etc.
    configs/                Per-service config files (mosquitto.conf, etc.)
    scripts/                Host setup / bootstrap scripts
  llm-host/                 Ollama + GPU host setup (separate physical machine)
  thin-client/              Sway kiosk/media-station ISO (live-build) + thinclient-agent
  audio-endpoint/           Headless Spotify Connect appliance for rooms with no thin
                             client — arm64 (rpi-image-gen) + amd64 (live-build) images
  touch-panel/              Touch-driven Sway panel: full Spotify GUI, a dedicated
                             Home Assistant Chromium kiosk window, and a general web
                             browser, switched via an always-on touch dock or by HA/
                             the local LLM over MQTT
  kitchen-display/           Single-purpose Sway kiosk for the fridge/pantry: one
                             Chromium window showing pantry-vision's Scan/Inventory/
                             Recipes frontend, camera capture via the browser itself
  door-panel/                 Single-purpose Sway kiosk by the door/wardrobe:
                             identity's weather+clothing/who's-home/groceries-low
                             dashboard by default, registration on demand — this
                             host's twin relationship to kitchen-display, but the
                             mic is the point here, not an opt-in edge case
firmware/
  ruview/                   RuView ESP32-S3 CSI presence node configs
  esphome-ble-proxy/        ESPHome configs for Bermuda BLE proxy nodes
  esp32-s3-touch-lcd-1.85c/ ESPHome voice satellite + status display (round LCD,
                            media/cover-art priority over an idle weather/time/
                            date cycle, voice-state visualizer)
identity/                   Person <-> BLE-identifier registry: multi-phone support,
                             anti-spoofing (allowlisted IRK-resolved/fixed-tag
                             entities only, never a raw MAC), voice/touch
                             registration, presence resolution, visit history
                             ("who was home when, with whom"), nicknames, per-device
                             rights, and an admin panel (write API + frontend/
                             static serving, consumed by kitchen-display and
                             door-panel)
digest-engine/              Quarter-daily LLM digest: mail/message/news/financial
                            ingestion, LLM synthesis, digest-canvas SDK rendering
admin-canvas/               On-demand sys-admin-llm display surface for the thin
                             clients: stats/graphics/media, pushed on demand
                             rather than on a schedule (write API + admin-web
                             static serving)
pantry-vision/               Kitchen-display backend: a photo held up to the camera
                             -> an Ollama vision-model proposal -> human-confirmed
                             write into Grocy stock; also proxies Grocy's inventory
                             (soonest-expiring first) and recipes to the kiosk
                             frontend (write API + frontend/ static serving)
trash-calendar/             Reads Kennelbach's personal trash-collection ICS feed,
                             writes matching events onto the shared CalDAV calendar
                             (daily systemd timer, read-only against the feed)
transit/                    Public transit: GET /departures (static GTFS lookup)
                             and GET /plan (OpenTripPlanner proxy, voice-usable via
                             HA Assist) — weekly GTFS refresh via systemd timer
chores/                      Presence/calendar-driven household chore nudging + a
                             passive fairness tally + camera-verified trash-bin/
                             dishes/litter checks (systemd-timed oneshot, no
                             long-lived service, no LLM-picked assignment)

Status

  • Project plan drafted
  • Container host setup script v1 (HA, Mosquitto, Zigbee2MQTT USB, Frigate, Grocy)
  • Node-RED + monitoring (Netdata) + dashboard (Homepage) + ntfy + Portainer added to compose stack
  • Backup (restic) setup — scripted, off by default until a backup target is picked (ENABLE_BACKUPS)
  • Bermuda / ESPHome BLE proxy configs — firmware/esphome-ble-proxy/ built (stock ESPHome bluetooth_proxy component), not yet flashed to real hardware, see that directory's README
  • RuView node configs — firmware/ruview/ documents the real upstream project (github.com/ruvnet/ruview, integrated not forked) + a per-room provisioning wrapper + automations.yaml.example (sleep → dim lights, possible-distress → whole-household alert, concurrent elevated heart rate → colored lighting, bathroom occupancy → an external door indicator). Every automation's entity_id is an unconfirmed placeholder, and the concurrent-two-person-heart-rate rule rests on an unconfirmed assumption about RuView's multi-target vital-sign capability — see firmware/ruview/README.md §56 and docs/project-plan.md open decisions #3233 before relying on any of it
  • Frigate peephole camera config (real RTSP details)
  • Grocy kiosk (Pi + touchscreen) setup
  • LLM host (Ollama) setup script
  • CalDAV / Nextcloud calendar integration notes
  • Sway thin-client ISO (live-build) + thinclient-agent — built, not yet boot-tested on real hardware; RDP replaced by wayvnc (resolved), remaining open items (mic-enabled rooms, exact hardware target, wayvnc password provisioning) in docs/project-plan.md §4
  • Thin-client follow-ups in progress: fullscreen-aware now-playing widget (cover art + controls), minimal Firefox chrome + uBlock Origin/SponsorBlock, persistent audio-output selection, outbound RDP/VNC client (rdp-vnc.json), HA mobile-app browser remote control (text input + mouse buttons), capture-card ("receiver box") video source selection on a new 5:capture workspace, idle-gallery weather/clock overlay (clock always, weather via a new smarthome/weather/current MQTT topic an HA automation has to publish) — built, not yet tried against real capture-card hardware or a real weather automation, see hosts/thin-client/README.md
  • Quarter-daily digest engine (mail/Signal/Telegram/Discord/WhatsApp, news, financial ingestion; LLM synthesis; digest-canvas SDK) — built and wired into setup-container-host.sh (ENABLE_DIGEST_ENGINE, off by default), not yet run against real credentials; household/calendar ingest (CalDAV/Grocy) still needs a real data source wired in, see docs/project-plan.md §4
  • admin-canvas + admin-web (sys-admin-llm on-demand display surface for the thin clients) — built and wired into setup-container-host.sh (ENABLE_ADMIN_CANVAS, off by default); the HA-side tool/rest_command wiring and the specific entities it surfaces (e.g. power-monitoring) are still undecided, see docs/project-plan.md §4
  • ESP32-S3-Touch-LCD-1.85C-V2 voice satellite + status display (firmware/esp32-s3-touch-lcd-1.85c/) — ESPHome config written and passes esphome config, not yet flashed to real hardware; media_player/weather entity IDs still need to be chosen, see docs/project-plan.md §4
  • Headless audio endpoint (hosts/audio-endpoint/) — per-room independent Spotify Connect appliance for rooms without a thin client, arm64 (Raspberry Pi + HiFiBerry Amp2, rpi-image-gen) and amd64 (mini PC + USB DAC/amp, live-build) build pipelines written, neither built/flashed/booted on real hardware — rpi-image-gen's exact config schema in particular is unverified, see hosts/audio-endpoint/README.md
  • Sway touch panel (hosts/touch-panel/) — touch-driven Sway image: full Spotify GUI (Flathub), a dedicated Home Assistant Chromium kiosk window, a general web browser, an always-on touch dock for app switching, an on-screen keyboard (toggled manually, no auto-show), and touchpanel-agent (HA MQTT control, same LLM-mediated-through-HA security model as the thin client) — built, no touch-panel hardware chosen and nothing booted on real metal, see hosts/touch-panel/README.md
  • Kitchen/fridge display + pantry-vision (hosts/kitchen-display/, pantry-vision/) — hold a grocery item up to the camera, an Ollama vision model proposes what it is and roughly how long it keeps, a human confirms (never auto-committed) before it's written into Grocy stock; the display then shows inventory sorted by soonest-to-expire, groceries running low, and Grocy's recipes — built and wired into setup-container-host.sh (ENABLE_PANTRY_VISION, off by default), nothing run against a real camera, vision model, or Grocy instance — the Grocy API call shapes in particular are written from documentation only, see pantry-vision/README.md and hosts/kitchen-display/README.md
  • identity + door panel (identity/, hosts/door-panel/) — the person <-> BLE-identifier registry: "register me as <name>" by voice or touchscreen, multi-phone support (multiple identifiers per person), anti-spoofing (only allowlisted IRK-resolved/fixed-tag entities are ever accepted as candidates, never a raw MAC), device-less people (a "no device" flag plus a hand-operated Home/Away toggle — the concrete case: a grandmother without a smartphone), and an anonymous "Guest" path. Backs hosts/door-panel/'s weather+clothing/who's-home/groceries-running-low dashboard and hosts/kitchen-display/'s "Show registration" screen — built and wired into setup-container-host.sh (ENABLE_IDENTITY, off by default), nothing run against a real HA instance, real Private BLE Device entities, or a real voice pipelineTRUSTED_ENTITY_PREFIXES above all needs checking against Developer Tools -> States, see identity/README.md and hosts/door-panel/README.md
  • identity also corroborates presence from Frigate face recognition (Phase 20, Tapo pan/tilt cameras) — an OR-ed-in second signal only, never a registration signal; and owns the per-person chore-system settings (chore_exempt, chore_reminder_style, plus chore assignment) consumed by chores/, see identity/README.md
  • identity's admin panel (identity/frontend/admin.html, Phase 6b) — managing people/guests: edit every field, nicknames (an input alias only — /resolve accepts them, but the assistant always speaks the real speak_name), visit history sampled from /presence plus a derived "who was home with whom" view, "select all that last visited before <date>" pruning (the filter selects, a human confirms the exact list, the filter is never re-run at delete time), per-device rights for self-entry via a smart lock (identity only ever answers GET /device-access — HA asks and HA acts, deny is the default), chore assignment, and opt-in arrival push notifications ("tell me when someone gets home", via the self-hosted ntfy this stack already runs — identity itself never touches the WAN; getting the push while you're away is docs/network-integration.md's WireGuard answer, and iOS is a documented exception). Deliberately not a kiosk page and not linked from any wall panel. Covered by API-level tests; never opened in a real browser, and DEPARTURE_GRACE_SECONDS is an untuned guess — see identity/README.md
  • trash-calendar + transit (Phase 19, Kennelbach AT trash pickup + Vorarlberg public transit) — built and wired into setup-container-host.sh (ENABLE_TRASH_CALENDAR/ENABLE_TRANSIT/ENABLE_TRIP_PLANNING, all off by default), nothing run against a live ICS feed, a live GTFS feed, or a real OpenTripPlanner instance — trip planning also needs a manually-built OTP graph this repo does not build for you, see trash-calendar/README.md and transit/README.md's "Route planning scope"
  • chores (Phase 20) — presence/calendar-driven household chore nudging: "I don't care who does it, as long as it gets done" — prefers whoever's been assigned a chore in identity's admin panel but falls through to whoever's actually home rather than waiting (CHORE_ASSIGNMENT_STRICT flips that), redirects to someone else if a chore goes neglected, keeps a passive fairness tally that never feeds back into who gets nudged, and camera-checks trash bins/dishes/litter via Frigate + an Ollama vision model. Litter remains the exception to everything — it ignores both chore-exemption and assignment, because cleaning up what you left out was never a task anyone could be assigned. Built and wired into setup-container-host.sh (ENABLE_CHORES, off by default, every-2-hours systemd timer), no Tapo camera hardware chosen and nothing run against real hardware, see chores/README.md
  • Music Assistant (optional, additive multi-room audio) — wired into setup-container-host.sh (ENABLE_MUSIC_ASSISTANT, off by default), its default port is an unverified guess that collides with PANTRY_VISION_PORT if both are enabled together, see docs/project-plan.md open decision #31
  • docs/network-integration.md (OPNsense VLAN segmentation + why nothing here should be port-forwarded to the WAN) — written, not run against a real OPNsense instance

Quick start

cd hosts/container-host/scripts
sudo ./setup-container-host.sh

Edit the variables at the top of the script first (timezone, Zigbee USB device path, Mealie/hardware-accel toggles, and whether to enable Node-RED/Netdata/ Homepage/ntfy/Portainer and restic backups — all but backups are on by default). See docs/project-plan.md for the full phased rollout order — don't skip straight to Phase 4+ automations before the Phase 2 reactive baseline (presence -> light, no LLM in the loop) is working.

The script brings up everything that runs on this one Debian host: Home Assistant, Mosquitto, Zigbee2MQTT, Node-RED, Frigate, Grocy, Netdata, a Homepage dashboard, ntfy, and Portainer, plus an optional scheduled restic backup timer and an optional quarter-daily digest engine (ENABLE_DIGEST_ENGINE, off by default — needs digest-engine/ checked out on the host and its .env filled in first, see digest-engine/README.md). What it can't do for you, because they need separate hardware, credentials, or physical setup: pairing Zigbee sensors, flashing RuView/ESPHome/Bermuda BLE proxy boards, pointing Frigate at a real camera RTSP URL, the Grocy kiosk touchscreen, the separate LLM/GPU host, wiring up the Nextcloud CalDAV integration, building/flashing the thin-client ISO (hosts/thin-client/), and provisioning real credentials for the digest engine's mail/message/news/financial sources — see the Status checklist above and docs/project-plan.md for those.