Addresses the device-management feedback:
- The MQTT server address is no longer hardcoded (was 10.10.90.110). It's
auto-detected from the address you open HomeOS at (App\Support\HostAddress —
request host + port), so it follows a DHCP/changed IP. MQTT_DEVICE_HOST is now
an optional override only. Settings + device page use it.
- Discovery dedup: a finding whose prefix already belongs to a device (assigned
OR auto-onboarded) no longer shows under "Neue Geräte" — fixes the device
appearing both in discovery and in Geräte.
- Delete device: confirm-guarded button on the device page; deleting frees the
discovery finding so the device can be re-added.
- "Neu scannen" button on the network page → publishes homeos/sidecar/rescan;
the sidecar (now a subscriber) re-queries mDNS. ACL grants it read on that
topic. paho-mqtt pinned to the v2 callback API.
+4 tests (host override/fallback, dedup, delete frees finding). Suite 62 green,
12/12 clean. Live-verified: rescan reaches sidecar; duplicate Shelly gone.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
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>
- 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>
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>