Commit Graph

9 Commits (8086a9647b4e99aba0ecda08c3f5ac1aab45321a)

Author SHA1 Message Date
HomeOS Bootstrap 52c1c5d3ac feat(addons): installable integrations framework + Ring connect UI
First half of the Ring integration (handoff §12: ring-mqtt, cloud token auth):
- Addons registry (static catalogue) + Addon model with encrypted config, so
  the Ring refresh token / credentials are never stored in plaintext.
- AddonService: install / uninstall (wipes secrets) / saveConnection (blank
  password keeps the stored one) / markStatus (from the bridge over MQTT).
- Addons page (new sidebar tab) with per-addon install, Cloud badge, live
  status pill, and a Ring connect modal capturing email/password/2FA — we
  never call Ring ourselves; creds are handed to the ring-mqtt sidecar.
- Uninstall behind a wire-elements confirm (R5). Fixed the confirm target:
  Livewire names App\Livewire\Addons\Index as "addons" (Index suffix dropped),
  and only $wire.$dispatch (not Alpine $dispatch) reaches the modal listener.
- DE/EN localization (R16); doorbell/package/cloud icons.

7 AddonTest cases (registry, install, encryption-at-rest, blank-password
keep, uninstall wipe, status guard). Suite 33 green; 12/12 tabs clean; full
install→connect→connecting→uninstall UI flow browser-verified, 0 console errors.

Backend bridge (ring-mqtt container) + Ring MQTT normalizer land next.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 01:47:02 +02:00
HomeOS Bootstrap d10172ac26 feat(automations): trigger→condition→action engine with cooldown + dry-run
- AutomationEngine: state_change + time triggers, cooldown debounce, dry-run
  logs instead of switching (H5), actions switch via DeviceCommandService
  (source=automation, audited H1) or notify via log.
- EvaluateAutomations: queued listener on DeviceStateChanged (keeps ingest
  fast, H2). automations:tick command scheduled every minute for time rules.
- CreateAutomation modal (wire-elements) wired to Automations index; DE/EN
  keys (R16). dry_run column migration + model cast.
- AutomationTest: 6 cases (trigger match, mismatch, cooldown, dry-run,
  time tick, disabled). Full suite 23 green; 11/11 tabs clean (R12).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 01:11:02 +02:00
HomeOS Bootstrap 124e667a0a Tablet control panel: touch tiles, drag-reorder, light colour modal
New "Steuerung" page for tablet control:
- Large touch tiles for every light/switch — tap toggles (through
  DeviceCommandService), highlighted when on, live via Echo.
- Drag-to-reorder via SortableJS (handle per tile); order persisted to
  entities.panel_sort.
- Lights get a colour/brightness modal → Light.Set (brightness + rgb) on the
  Shelly, with preset swatches + a custom colour picker. Driver contract gains
  setLight(); command service audits it (H1).

Nav check 11/11 tabs clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 00:58:24 +02:00
HomeOS Bootstrap a4edbbf801 Fix R15: provision a per-device MQTT credential when assigning a device
Assigning a Shelly now issues a real broker credential so the physical device can
connect (onboarding was incomplete before):
- MqttCredentialProvisioner writes a mosquitto-compatible PBKDF2-SHA512 ($7$)
  password line (in PHP) for username = the device's topic prefix, and touches a
  reload trigger. A small wrapper in the mosquitto container (docker/mosquitto/
  config/entrypoint.sh) SIGHUPs mosquitto so it re-reads the passwd live — no
  restart. Verified: a provisioned device authenticates and publishes to its own
  prefix (bound by the pattern %u ACL).
- The credential is shown once on the device page after assignment (enter it into
  the Shelly). passwd is app-owned + world-readable so the web request can write it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 00:50:53 +02:00
HomeOS Bootstrap 7d4c9c024e Fix R15: Shelly prefix from mDNS instance, discovery broadcast once, targeted confirm
- AssignDevice derives the Shelly MQTT prefix from the mDNS service instance name
  (the device id) instead of the slugified topic identifier (which carries the
  _shelly._tcp suffix); non-Shelly devices get no prefix.
- IngestDiscoveryMessage broadcasts DeviceDiscovered only on first creation, not on
  every retained/periodic re-publish.
- The generic Confirm modal can target its event at the opening component (->to),
  so a shared event name can't be caught by an unrelated component.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 00:34:56 +02:00
HomeOS Bootstrap b85d2f60fe Phase 5: UniFi presence — poll, person↔client mapping
- UnifiClient service (art-of-wifi/unifi-api-client) talks to the local UDM with a
  read-only account (self-signed cert, verify_ssl off). Verified live: logged in
  and read 32 active clients.
- presence:poll command (scheduled every minute, withoutOverlapping): "home"
  immediately on association, "away" only after an 8-min debounce so iPhone WLAN
  sleep can't cause false-aways. Broadcasts PresenceChanged on the presence channel.
- Persons page: "Person hinzufügen" modal picks the representing device straight
  from the live UniFi client list (falls back to manual MAC entry if UniFi is down);
  the sweep then tracks that person's presence. mac + last_seen_home_at on persons.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 00:31:32 +02:00
HomeOS Bootstrap f50a665735 Phase 4: network discovery — sidecar, ingest, "Neue Geräte" + assign
- Python discovery sidecar (zeroconf mDNS over host network) publishes findings
  to homeos/discovery/<source>/<id> as the sidecar user. Compose service with
  network_mode: host + NET_RAW. Verified live: it found real devices on the LAN
  (a printer and Shellys) via mDNS.
- Listener also subscribes homeos/discovery/#; IngestDiscoveryMessage upserts
  discovery_findings (preserving assigned/ignored) and broadcasts DeviceDiscovered
  on the private discovery channel.
- "Neue Geräte" page lists findings live with Assign (modal → creates a Device
  and links the finding) and Ignore/Restore. Per-device broker credentials are
  provisioned at onboarding (noted in the assign hint).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 00:25:17 +02:00
HomeOS Bootstrap 1520b95f8f Fix R15: include room-less devices in home status
HomeStatus aggregated from Room, so a device with room_id = null (a supported
state, e.g. after picking "no room") vanished from dashboard totals, KPIs and
warnings. Aggregate from all devices instead; the dashboard groups them by room
with a trailing "Ohne Raum" group so nothing disappears. Added a feature test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 21:58:35 +02:00
HomeOS Bootstrap f00a1ac069 Warnings → modal, device detail page, stable mock online state
- Warnings no longer clutter the dashboard: a compact "Warnungen (n)" button in
  the topbar opens a themed wire-elements/modal (R5) listing all messages. Dark
  modal wrapper overridden; HomeStatus service shared by dashboard + modal.
- Device detail page (/devices/{uuid}, UUID route key): edit name, room and
  active state; view info + live capabilities; Neustart (with confirm modal) and
  "Update prüfen" as mock commands (Phase 3 routes them through the real driver).
  Devices index (/devices) added; "Geräte" nav activated; dashboard device rows
  and index link to the detail. Generic Confirm modal + x-detail component.
- Mock devices no longer rot: online is now "active and (no last_seen or seen
  <10min)", so the demo stays healthy; the one offline device keeps a stale
  timestamp. Full DE/EN i18n for devices + modal copy.

Verified: R12 30/30 in headless Chromium (0 console errors, 0 failed requests);
10 feature tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 21:52:57 +02:00