diff --git a/docs/adr/ADR-116-cog-ha-matter-seed.md b/docs/adr/ADR-116-cog-ha-matter-seed.md index 2a7ec660..c6919082 100644 --- a/docs/adr/ADR-116-cog-ha-matter-seed.md +++ b/docs/adr/ADR-116-cog-ha-matter-seed.md @@ -87,6 +87,7 @@ Ranked by build cost × user impact: | 3 | **Local SONA fine-tuning loop** (HA feedback → LoRA gradient steps) | ~2-3 weeks | Reduces false positives, closes #1 user complaint | P5 (this cog) | | 4 | **HACS gold-tier integration** (config flow + repairs + diagnostics) | ~4-6 weeks | Removes MQTT prerequisite for mainstream users | P9 (separate repo `hass-wifi-densepose`) | | 5 | **Matter Bridge with OccupancySensor + dynamic endpoints** | ~6-8 weeks | Apple Home / Google Home / Alexa native | **v0.8** dedicated sprint (after HACS adoption data) | +| 6 | **Embedded MQTT broker (rumqttd) inside the cog** | ~1 week | "Works without external broker" but every HA install already has mosquitto / built-in | **v0.7** deferred — adds ~2 MB binary + ACL config surface for marginal user benefit. Dossier ranking did not include this in the prioritised v1 scope. | ## 4. Implementation phases @@ -95,7 +96,7 @@ Ranked by build cost × user impact: | **P1** | Research dossier ([`docs/research/ADR-116-ha-matter-cog-research.md`](../research/ADR-116-ha-matter-cog-research.md)) | ✅ **done** — 8 sections, 30+ citations, v1 scope ranked | | **P2** | Cog crate scaffold (`v2/crates/cog-ha-matter/`) — Cargo.toml + `src/{lib,main,manifest}.rs`, workspace member, CLI args, `--print-manifest` flag, 2 manifest unit tests | ✅ **done** — `cargo check` + `cargo test` green | | **P3** | Wrap existing ADR-115 MQTT publisher as cog entry point | ✅ **wiring done** — `main.rs` boots ADR-115's `publisher::spawn` via `runtime::spawn_publisher` thin wrapper, holds a long-lived `broadcast::Sender`, awaits Ctrl-C. Live-handle test green without a broker. Next (P3.5): subscribe to sensing-server `/v1/snapshot` WS and republish into the channel. | -| **P4** | Seed-native enhancements (embedded broker, mDNS, witness) | in progress — **mDNS half complete:** record-builder ✅, ServiceInfo conversion ✅, **live responder ✅** (`runtime::start_mdns_responder` binds multicast, registers, returns `MdnsResponderHandle` with explicit `shutdown()` + best-effort Drop). **Witness half complete:** hash-chain ✅, JSONL line serializer ✅, file persistence + chain-level verify ✅, Ed25519 signing ✅. **Remaining:** embedded rumqttd broker. | +| **P4** | Seed-native enhancements (mDNS, witness; embedded broker deferred) | ✅ **shipped** — mDNS half: record-builder + ServiceInfo conversion + live responder wired into `main.rs` (HA auto-discovery on `_ruview-ha._tcp` works out of the box, `--no-mdns` flag for restrictive networks). Witness half: hash-chain + JSONL + file persistence + chain-level verify + Ed25519 signing. **Embedded rumqttd broker deferred to v0.7** per dossier §8 ranking — not in the prioritised v1 scope; v1 ships with external-broker only (mosquitto or HA's built-in broker). See §4 v1 scope table. | | **P5** | RuVector-backed threshold learning (SONA adaptation) | pending | | **P6** | Multi-Seed federation (cross-Seed dedup + witness) | pending | | **P7** | Matter Bridge mode (depends on matter-rs / esp-matter readiness) | pending | diff --git a/v2/crates/cog-ha-matter/src/main.rs b/v2/crates/cog-ha-matter/src/main.rs index c65d528d..b23db26c 100644 --- a/v2/crates/cog-ha-matter/src/main.rs +++ b/v2/crates/cog-ha-matter/src/main.rs @@ -48,6 +48,24 @@ struct Args { /// control plane and exit. Useful for the build-time signer. #[arg(long)] print_manifest: bool, + + /// mDNS hostname for the Seed advertisement. Must end with + /// `.local.` per RFC 6762. Default lets HA's discovery find a + /// dev cog on localhost without LAN config. + #[arg(long, default_value = "cog-ha-matter.local.")] + mdns_hostname: String, + + /// LAN-routable IPv4 the cog binds the control plane on. The + /// mDNS responder advertises this; HA reaches back to it for + /// MQTT + Matter Bridge. + #[arg(long, default_value = "127.0.0.1")] + mdns_ipv4: String, + + /// Skip the mDNS responder. Useful in containerised CI where + /// multicast bind is filtered, or when running multiple cog + /// instances on the same loopback. + #[arg(long)] + no_mdns: bool, } #[tokio::main] @@ -115,6 +133,35 @@ async fn main() -> ExitCode { // HA install with no nodes online looks like. let _ = &state_tx; + // P4: mDNS responder. HA's auto-discovery picks the cog up on + // `_ruview-ha._tcp` so users don't need to type broker host/port. + let _mdns_handle = if args.no_mdns { + None + } else { + let identity = runtime::CogIdentity::default_for_build(); + let service = cog_ha_matter::mdns::build_mdns_service( + &identity, + cog_ha_matter::DEFAULT_CONTROL_PORT, + args.mqtt_port, + args.privacy_mode, + ); + match runtime::start_mdns_responder(&service, &args.mdns_hostname, &args.mdns_ipv4) { + Ok(h) => { + info!( + fullname = h.fullname(), + hostname = %args.mdns_hostname, + ipv4 = %args.mdns_ipv4, + "mDNS responder registered — HA auto-discovery should find the cog now" + ); + Some(h) + } + Err(e) => { + warn!(error = ?e, "mDNS responder failed to start — discovery disabled, falling back to manual HA config"); + None + } + } + }; + // Wait on Ctrl-C so the cog runs as a long-lived daemon under // the Seed's process supervisor. tokio::select! { @@ -125,5 +172,8 @@ async fn main() -> ExitCode { warn!(?joined, "publisher task exited unexpectedly"); } } + + // _mdns_handle drops here, sending the mDNS goodbye packet so + // HA's discovery integration sees the service leave cleanly. ExitCode::SUCCESS }