Skip to content

Architecture Overview

System diagram

┌─────────────┐     callService      ┌──────────────────┐
│  Lovelace   │ ──────────────────▶  │  Home Assistant   │
│  Cards (JS) │                      │                   │
│             │ ◀── sensor state ──  │  helpers, scripts │
└─────────────┘                      │  automations      │
                                     └────────┬──────────┘
                                              │
                                     HASS plugin API
                                     (listen_state, call_service,
                                      fire_event, get_state)
                                              │
                                     ┌────────▼──────────┐
                                     │    AppDaemon       │
                                     │                    │
                                     │  apps/             │
                                     │  ├─ door_notify    │
                                     │  ├─ detection_*    │
                                     │  ├─ photo_frame_*  │
                                     │  ├─ dashboard_notify│
                                     │  ├─ calendar_from_*│
                                     │  ├─ immich_fetcher │
                                     │  ├─ health_checks  │
                                     │  ├─ media_dashboard│
                                     │  ├─ countdown_app  │
                                     │  ├─ school_lunch_* │
                                     │  └─ zigbee_ota     │
                                     │                    │
                                     │  providers/        │
                                     │  ├─ ai_providers   │
                                     │  ├─ ha_provisioner │
                                     │  ├─ photo_providers│
                                     │  ├─ media_providers│
                                     │  ├─ school_menu    │
                                     │  ├─ alertmanager   │
                                     │  └─ metrics        │
                                     └───────────────────-┘
                                              │
                                     External APIs
                                     (OpenAI, Gemini, Ollama,
                                      ComfyUI, Immich,
                                      School Nutrition and Fitness,
                                      Tautulli, TMDb, SerpApi,
                                      Alertmanager)

Data flow paths

Path Direction Mechanism
Commands (card → app) Card → HA → AppDaemon callService("script", "relay") → HA event → listen_event()
State (app → card) AppDaemon → HA → Card set_state() on sensor → card reads attributes
External APIs AppDaemon → Internet Provider adapters in providers/ make HTTP calls
Provisioning AppDaemon → HA REST API ha_provisioner creates helpers/scripts on startup
Static asset check AppDaemon → HA HTTP Unauthenticated HEAD /local/... to confirm HA is really serving a file an app asked it to stage
Checker self-heal AppDaemon → AppDaemon (admin namespace) The health-check controller reads the app.<name> entities to learn which checker apps should exist and what state they are in, and calls app/restart on one that never registered
Metrics exposition Prometheus → AppDaemon The cluster's Prometheus scrapes :9100/metrics, served by the metrics provider for the health-check controller. Apart from AppDaemon's own admin UI and API on port 5050, it is the only inbound HTTP path
Entity classification AppDaemon → HA render_template() over the device registry — zigbee_ota asks each tick which update.* entities are really Zigbee2MQTT devices, which is what makes a fleet-wide glob safe

Key concepts

Self-provisioning

Apps create their own HA entities (helpers, scripts) on startup via ha_provisioner. No manual entity setup is needed.

Relay script pattern

Lovelace cards communicate with AppDaemon through relay scripts:

  1. Card calls hass.callService("script", "<app>_relay", { command, payload })
  2. HA script fires an <app>_command event
  3. AppDaemon listens for the event and processes it

This pattern works for non-admin users (unlike fire_event which requires admin).

Health monitoring

The health check system uses the event bus as a decoupling layer between checker apps and a central controller. Checker apps register themselves and report status via HA events; the controller aggregates everything into a single sensor that custom Lovelace cards read. This pattern allows new checkers to be added — often config-only — without modifying the controller. Repair-capable checkers handle their own recovery logic (e.g., smart switch power cycling, config-entry reloads) while the controller only routes those commands. The one recovery the controller does itself is for checkers that never announce themselves. It reads AppDaemon's own list of apps to know which checkers should be present, restarts one that is missing (a few times, with growing waits), and reports it as a critical checker if it stays missing, so a dead checker can't silently hide the checks that depend on it (details). The controller exports every checker's status, repair outcomes and checker-supplied values as Prometheus metrics through the metrics provider, which the cluster scrapes for Grafana history (details). It also mirrors checker status into the cluster's Alertmanager via the alertmanager provider — critical findings page the phone, and recovery resolves the alert once it holds, so a flapping condition pages once per incident rather than once per swing.

Container separation

HA and AppDaemon run in separate Kubernetes pods. They share a /media NFS mount for file exchange. AppDaemon cannot access /config/www/ directly — it uses HA shell_command services for file operations in that directory. Because a shell command's result says little about whether the files actually landed, apps confirm the outcome by requesting the same /local/... URL the dashboard card will load, and only publish it once Home Assistant answers.