Commit Graph

8 Commits (feat/phase-1-bootstrap)

Author SHA1 Message Date
HomeOS Bootstrap 2fa15decb4 feat(devices): instant toggle, icon picker, real update check, transport switch
- Issue 6 (2-3s toggle lag): the command service now writes the expected state
  immediately for ALL devices (not just demo). The card flips within the
  Livewire round-trip; the real MQTT echo / post-command HTTP poll reconciles it
  (monotonic — newer observed_at wins). Toggling now feels instant.
- Issue 7 (wrong icon): Device::displayIcon() with a per-device override
  (config.icon) + a picker on the device page (lamp/led/plug/window/door/
  doorbell/sensor/…); cards use it. New icons added.
- Issue 8 (dead "Update prüfen"): real Shelly.CheckForUpdate over HTTP now
  reports available version / up-to-date / unsupported instead of a demo string.
- Issue 9 (no MQTT option): device page can switch a Shelly between Local (HTTP,
  probed) and MQTT transport; driverFor already routes by protocol.

Suite 71 green, 12/12 tabs clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 10:04:19 +02:00
HomeOS Bootstrap 449533b60d fix: preserve raw input value on role/invert change; strict isOnline (R15)
Codex R15 on the previous fix:
- [P2] reclassifyInput derived the raw input level from the already-displayed
  (inverted) contact state, so toggling inversion twice didn't restore the
  original. The ingest + device page now carry the raw `on` inside the contact
  state, so flipping role/inversion is fully reversible. +1 round-trip test.
- Latent: Device::isOnline() could return null (null demo, no last_seen) and
  break the type hint / views. Now returns a strict bool.

Suite 58 green, 12/12 tabs clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 09:03:00 +02:00
HomeOS Bootstrap 71b3028a8e fix: harden MQTT sharing + input roles + dedup (adversarial review)
Multi-agent review (5 confirmed) + Codex found real issues in the rework:

- [P1 security] Shared `shelly` ACL had readwrite +/rpc, so a compromised
  device could inject Switch.Set into ANY other device's <prefix>/rpc. Now
  read-only on +/rpc (receive own commands) + write only homeos/rpc (reply).
  Broker-verified: a shelly publish to victim/rpc is denied; laravel's is not.
- [P2 security] `+` wildcard reaches reserved homeos/ring namespaces → bogus
  device. Ingest now rejects RESERVED_PREFIXES (homeos/ring/$SYS).
- [P2 security] Unbounded auto-onboarding = DB-exhaustion DoS. Added a device
  cap (homeos.mqtt.max_devices, default 250).
- [P2 correctness] Every Shelly `input` became a phantom window contact (wall
  switches shown as windows, possibly inverted). `input` is now a generic
  sensor; the user PROMOTES specific inputs to window/door contacts on the
  device page (invert-aware), stored in config->input_roles and applied in the
  ingest — this is the "assign contacts" flow the user asked for.
- [P3 ux] Motion pill read `active`; producer writes `on`. Now reads both.
- [P1 migrations] Dedup computed survivor keys once; 3+ duplicates could
  collide on unique(device_id,key). Re-query per duplicate (ring + mqtt_prefix).

+9 tests (reserved prefix, cap, input generic/promoted/inverted, demo echo).
Live-verified: input published → onboarded as input → assigned window contact
via UI → appears on Fenster page, persists across messages. Suite 57 green,
12/12 tabs clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 08:59:12 +02:00
HomeOS Bootstrap 2edffadbfd feat(panel): make tiles actually control + entity naming + reorder feedback
Addresses "I arrange tiles but nothing changes":
- Demo devices have no hardware to echo a new state, so toggling looked dead.
  DeviceCommandService now simulates that echo for demo devices (writes the new
  state + broadcasts DeviceStateChanged) — the mock home is fully interactive
  (handoff §13.2). Real devices are untouched; their own status message applies.
- Panel tiles show the entity name (device · room as sub) and drive the shared
  TogglesEntities toggle; wire:loading guards double-taps.
- Drag-reorder now confirms with a "Layout saved" cue so it doesn't feel inert.

+1 test (demo toggle simulates echo; real device does not). Browser-verified:
tapping an "Aus" tile flips it to "An". Suite 52 green, 12/12 clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 08:37:17 +02:00
HomeOS Bootstrap fef393954c feat(mqtt): Home-Assistant-style onboarding — shared account + auto-create
Addresses "I have to configure MQTT per device; HA just works":
- Shared device account `shelly` (one credential every Shelly uses) with a
  SCOPED ACL (status/events/online/rpc for any prefix; cannot touch homeos/,
  ring/ or $SYS). gen-passwd seeds it from MQTT_SHELLY_PASSWORD.
- Auto-onboarding: IngestShellyMessage creates the device on the first
  recognizable component (sys/wifi/cloud noise ignored), so pointing a Shelly
  at the broker is all it takes — no manual "Zuweisen". Partial unique index on
  config->>'mqtt_prefix' + race-safe create (merge-dedup migration).
- Settings → Geräte-MQTT card: server, username, reveal/copy password + steps
  (config/homeos.php, MQTT_DEVICE_HOST). Discovery "Zuweisen" now upserts by
  prefix (names/rooms an already-onboarded device, no duplicate) and no longer
  forces per-device creds.
- Per-device credentials kept as opt-in hardening: a "generate" button on the
  device page (pattern %u ACL retained).

Live-verified: publishing as `shelly` to a new prefix auto-creates the device
with switch+power state; a sys topic creates nothing. Suite 46 green, 12/12
tabs clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 08:20:57 +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 66f0d7b067 Fix R15: reject out-of-order MQTT state updates (concurrency)
With multiple Horizon workers, ingest jobs for one entity can finish out of
order and overwrite newer state with older. The listener now stamps each message
with a µs receive time (observed_at); IngestShellyMessage applies state only when
the incoming message is newer (race-safe via a conditional update + unique guard),
and broadcasts only when applied. Added a feature test for the ordering guard.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 23:01:32 +02:00
HomeOS Bootstrap ea9e35883b Phase 3: MQTT ingest — Mosquitto bus, Shelly driver, live device state
Real bidirectional MQTT so devices are live, not mock (handoff §13.3):
- Mosquitto 2 broker (auth + per-client ACLs for laravel/shelly/sidecar from day
  one); passwd generated by docker/mosquitto/gen-passwd.sh (gitignored).
- mqtt-listener daemon: subscribes `+/status/#`, parse + dispatch only (H2),
  exponential reconnect backoff, graceful SIGTERM. php-mqtt/laravel-client.
- Ingest path (H4): IngestShellyMessage resolves device by mqtt_prefix, upserts
  device_states, refreshes last_seen, broadcasts DeviceStateChanged
  (ShouldBroadcastNow) on the private `home` channel.
- Control path (H1): DeviceDriver contract + ShellyMqttDriver (command topic +
  Shelly.Reboot RPC) behind DeviceCommandService, which audits every command to
  the new `commands` table. Device detail toggles + restart route through it;
  flash reflects the real result.
- Live UI: dashboard + device pages listen via Echo (#[On('echo-private:home,
  .DeviceStateChanged')]) and re-render instantly.
- Vendor specifics isolated in Support/Mqtt + Support/Drivers (H3).

Verified end-to-end in a real browser: publishing an MQTT status turned a light
"An" on the dashboard in 3.0s with no reload, 0 console errors. R12 30/30;
15 feature tests green (incl. ingest + command audit). README/bootstrap document
the broker passwd step.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 22:51:14 +02:00