mirror of
https://github.com/ruvnet/RuView
synced 2026-07-28 18:21:42 +00:00
feat(adr-115): P6 + P10 — runnable wiring example + witness bundle (VERIFIED)
## P6 — Wiring example
`v2/crates/wifi-densepose-sensing-server/examples/mqtt_publisher.rs`
— a runnable end-to-end demo that constructs `MqttConfig` from CLI,
runs `mqtt::security::audit`, spawns the publisher, and feeds it
demo `VitalsSnapshot`s. Every line is the production-wiring blueprint
for `main.rs` when `args.mqtt` is true. Keeping it in `examples/`
lets us validate end-to-end without touching the 6,000-line main.rs
that the parallel ADR-110 agent is editing (see
[[feedback-multi-agent-worktree]]).
Run it:
cargo run --release -p wifi-densepose-sensing-server \
--features mqtt --example mqtt_publisher -- \
--mqtt --mqtt-host 127.0.0.1
Compile-checked clean under `--features mqtt`.
## P10 — Witness bundle (VERIFIED)
`scripts/witness-adr-115.sh` — generator that captures everything a
reviewer needs to verify ADR-115 from the receiving end:
- ADR-115 design doc snapshot
- `integration-docs/` — home-assistant.md + semantic-primitives-metrics.md
- `test-results/lib-tests.log` — cargo test --no-default-features --lib
(372 passed, 0 failed, 1 properly ignored)
- `test-results/lib-tests-mqtt-feature.log` — under --features mqtt
- `test-results/integration-tests.log` — opt-in via RUVIEW_RUN_INTEGRATION=1
- `bench-results/criterion-*.log` — opt-in via RUVIEW_RUN_BENCH=1
- `manifest/source-hashes.txt` — SHA-256 of every ADR-115 source file
- `manifest/git-head.txt` + `git-head-commit.txt` — exact source commit
- `VERIFY.sh` — self-verification script; recipient runs `bash VERIFY.sh`
and gets exit-0 if the bundle is internally consistent + lib tests
passed. Local self-test PASSED end-to-end on this commit.
- `WITNESS-LOG-115.md` — per-phase attestation matrix (P1–P10 status)
Bundle dropped at `dist/witness-bundle-ADR115-<sha>-<ts>.tar.gz`.
## Docs
- `docs/user-guide.md` — new "Home Assistant + Matter integration"
section between Data Sources and Web UI. 30-second Mosquitto-add-on
flow, --privacy-mode example for healthcare/AAL, Matter pairing
walk-through. Links back to docs/integrations/home-assistant.md
for the full reference.
- `CHANGELOG.md` Unreleased Added — single bullet announcing ADR-115
with the 21 entities, --privacy-mode architectural win, witness
bundle, deferred P7-P8 status.
## Phase status
| Phase | Status |
|---|---|
| P1 MQTT feature + CLI flags | ✅ |
| P2 HA discovery emitter | ✅ |
| P3 State + publisher | ✅ |
| P4 Mosquitto integration | ✅ (CI-gated) |
| P4.5 Semantic inference (HA-MIND) | ✅ |
| P5 Docs | ✅ |
| P6 Wiring example | ✅ |
| P7-P8 Matter Bridge | ⏸ deferred to v0.7.1+ per §9.10 |
| P9 Security + bench | ✅ |
| P10 Witness bundle | ✅ |
Total lines: ~6000. Total tests: 372 passed. Witness: VERIFIED.
Refs #776.
Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
//! ADR-115 P6 — minimal runnable example wiring the MQTT publisher
|
||||
//! against a broadcast channel of `VitalsSnapshot`s.
|
||||
//!
|
||||
//! Run with:
|
||||
//! cargo run --release -p wifi-densepose-sensing-server \
|
||||
//! --features mqtt --example mqtt_publisher -- \
|
||||
//! --mqtt --mqtt-host 127.0.0.1
|
||||
//!
|
||||
//! Then in another terminal:
|
||||
//! mosquitto_sub -h 127.0.0.1 -t 'homeassistant/#' -v
|
||||
//!
|
||||
//! You should see one HA discovery `config` topic per entity per node
|
||||
//! land within a second of startup, followed by `state` topics ticking
|
||||
//! at the configured rates.
|
||||
//!
|
||||
//! This example is the production-wiring blueprint for `main.rs`:
|
||||
//! every line below is what the binary's startup path should do when
|
||||
//! `args.mqtt` is true. Keeping it in `examples/` lets us validate the
|
||||
//! wiring end-to-end without touching the 6000-line main.rs (which is
|
||||
//! the active edit surface of the parallel ADR-110 agent — see
|
||||
//! [[feedback-multi-agent-worktree]]).
|
||||
|
||||
#![cfg(feature = "mqtt")]
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use clap::Parser;
|
||||
use tokio::sync::broadcast;
|
||||
use tracing::info;
|
||||
use wifi_densepose_sensing_server::cli::Args;
|
||||
use wifi_densepose_sensing_server::mqtt::{
|
||||
config::MqttConfig,
|
||||
publisher::{spawn, OwnedDiscoveryBuilder},
|
||||
security::audit,
|
||||
state::VitalsSnapshot,
|
||||
};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let args = Args::parse();
|
||||
|
||||
if !args.mqtt {
|
||||
eprintln!("This example requires --mqtt. Aborting.");
|
||||
std::process::exit(2);
|
||||
}
|
||||
|
||||
// 1. Build MqttConfig from CLI + run the security audit before any
|
||||
// network I/O. A failed audit short-circuits with a clear error.
|
||||
let cfg = Arc::new(MqttConfig::from_args(&args));
|
||||
match audit(&cfg) {
|
||||
Ok(()) => {}
|
||||
Err(e) if !e.is_fatal() => {
|
||||
tracing::warn!(error = %e, "non-fatal MQTT audit advisory");
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("MQTT audit failed: {e}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. The DiscoveryBuilder owns the per-node identity. In a real
|
||||
// deployment each ESP32 node would get its own builder; here we
|
||||
// fake one for demonstration.
|
||||
let builder = OwnedDiscoveryBuilder {
|
||||
discovery_prefix: cfg.discovery_prefix.clone(),
|
||||
node_id: "example_node".into(),
|
||||
node_friendly_name: Some("Example RuView Node".into()),
|
||||
sw_version: env!("CARGO_PKG_VERSION").into(),
|
||||
model: "ESP32-S3 CSI node (example)".into(),
|
||||
via_device: None,
|
||||
};
|
||||
|
||||
// 3. Broadcast channel — `sensing-server` already creates one of
|
||||
// these in main.rs (the one the WebSocket handler subscribes to).
|
||||
// We mirror it here.
|
||||
let (tx, rx) = broadcast::channel::<VitalsSnapshot>(256);
|
||||
|
||||
// 4. Spawn the publisher. It returns a JoinHandle the caller can
|
||||
// await on shutdown.
|
||||
let publisher = spawn(cfg.clone(), builder, rx);
|
||||
info!("publisher spawned, sending demo snapshots every 500ms");
|
||||
|
||||
// 5. Demo loop — produce a fresh VitalsSnapshot every 500ms with
|
||||
// alternating presence so HA sees ON/OFF transitions.
|
||||
let mut tick: u64 = 0;
|
||||
let mut interval = tokio::time::interval(Duration::from_millis(500));
|
||||
let stop = tokio::signal::ctrl_c();
|
||||
tokio::pin!(stop);
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = interval.tick() => {
|
||||
tick += 1;
|
||||
let snap = VitalsSnapshot {
|
||||
node_id: "example_node".into(),
|
||||
timestamp_ms: chrono::Utc::now().timestamp_millis(),
|
||||
presence: tick % 20 < 10,
|
||||
fall_detected: tick % 60 == 30,
|
||||
motion: 0.10 + ((tick as f64).sin().abs() * 0.30),
|
||||
motion_energy: 1000.0 + (tick as f64).cos() * 200.0,
|
||||
presence_score: 0.85,
|
||||
breathing_rate_bpm: Some(13.0 + ((tick as f64) * 0.05).sin()),
|
||||
heartrate_bpm: Some(68.0 + ((tick as f64) * 0.03).sin() * 5.0),
|
||||
n_persons: if tick % 20 < 10 { 1 } else { 0 },
|
||||
rssi_dbm: Some(-50.0 + ((tick as f64) * 0.1).sin() * 5.0),
|
||||
vital_confidence: 0.85,
|
||||
};
|
||||
let _ = tx.send(snap);
|
||||
}
|
||||
_ = &mut stop => {
|
||||
info!("ctrl-c received, shutting down");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drop(tx); // close broadcast → publisher publishes `offline` + disconnects.
|
||||
let _ = tokio::time::timeout(Duration::from_secs(2), publisher).await;
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user