fix(sensing-server): wire MQTT publisher into the binary — closes #872

#872 reported '--mqtt: unexpected argument' on the Docker image; prior
attempts chased a Docker *rebuild*, but the real cause was disconnected
*code*: the --mqtt* flags lived only in cli::Args (dead code — referenced
nowhere), while the binary parses a separate main::Args with no mqtt fields,
and main.rs never declared/started the mqtt:: publisher. So MQTT was fully
unwired: flags didn't parse, and the publisher never ran.

Fix:
- Extract the mqtt + privacy flags into a shared
  (#[derive(clap::Args)]); retarget mqtt::config::{from_args,build_tls} to it.
- #[command(flatten)] MqttArgs into the binary's main::Args (using the *lib*
  crate's type so it matches from_args), so --mqtt* now parse.
- Spawn the publisher on --mqtt: build MqttConfig, validate, and bridge the
  existing JSON sensing broadcast into the typed VitalsSnapshot stream the
  publisher consumes (defensive serde_json::Value mapping — absent fields
  default, never wrong values). #[cfg(feature=mqtt)]-gated; without the
  feature --mqtt WARNs and no-ops (documented contract). Fix the
  mqtt_publisher example for the new signature.

Verified end-to-end against local mosquitto: publisher connects and emits
20 HA auto-discovery entities + live state (presence ON, person_count, …).
Tests: 577 pass default / 580 pass --features mqtt / 0 fail; both configs
build.

Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
ruv
2026-05-31 09:39:21 -04:00
parent edbe57378a
commit a3f80b0cda
4 changed files with 188 additions and 6 deletions
@@ -3,6 +3,89 @@
use clap::Parser;
use std::path::PathBuf;
/// MQTT publisher (HA auto-discovery) + privacy-mode flags, shared via
/// `#[command(flatten)]` by both `cli::Args` and the binary's `main::Args`
/// so the `--mqtt*` flags reach the actual `Args::parse()` the server uses
/// (the publisher in `mqtt::` is keyed off this group). ADR-115 §3.8/§3.10.
#[derive(clap::Args, Debug, Clone)]
pub struct MqttArgs {
/// Enable MQTT publisher with HA auto-discovery
#[arg(long, env = "RUVIEW_MQTT")]
pub mqtt: bool,
/// MQTT broker host
#[arg(long, env = "RUVIEW_MQTT_HOST", default_value = "localhost")]
pub mqtt_host: String,
/// MQTT broker port (defaults: 1883 plain / 8883 with TLS)
#[arg(long, env = "RUVIEW_MQTT_PORT")]
pub mqtt_port: Option<u16>,
/// MQTT username
#[arg(long, env = "RUVIEW_MQTT_USERNAME")]
pub mqtt_username: Option<String>,
/// Environment variable holding the MQTT password
#[arg(long, default_value = "MQTT_PASSWORD")]
pub mqtt_password_env: String,
/// MQTT client ID (default: wifi-densepose-<pid>)
#[arg(long, env = "RUVIEW_MQTT_CLIENT_ID")]
pub mqtt_client_id: Option<String>,
/// Discovery topic prefix (ADR-115 §9.2 — accepted: `homeassistant`)
#[arg(long, env = "RUVIEW_MQTT_PREFIX", default_value = "homeassistant")]
pub mqtt_prefix: String,
/// Enable TLS to the broker
#[arg(long, env = "RUVIEW_MQTT_TLS")]
pub mqtt_tls: bool,
/// CA bundle for TLS
#[arg(long, value_name = "PATH")]
pub mqtt_ca_file: Option<PathBuf>,
/// Client certificate for mTLS
#[arg(long, value_name = "PATH")]
pub mqtt_client_cert: Option<PathBuf>,
/// Client key for mTLS
#[arg(long, value_name = "PATH")]
pub mqtt_client_key: Option<PathBuf>,
/// Discovery refresh interval (seconds)
#[arg(long, default_value = "600")]
pub mqtt_refresh_secs: u64,
/// Vitals publish rate (Hz) — HR/BR
#[arg(long, default_value = "0.2")]
pub mqtt_rate_vitals: f64,
/// Motion publish rate (Hz)
#[arg(long, default_value = "1.0")]
pub mqtt_rate_motion: f64,
/// Person count publish rate (Hz)
#[arg(long, default_value = "1.0")]
pub mqtt_rate_count: f64,
/// RSSI publish rate (Hz)
#[arg(long, default_value = "0.1")]
pub mqtt_rate_rssi: f64,
/// Publish pose keypoints over MQTT (off by default for bandwidth)
#[arg(long)]
pub mqtt_publish_pose: bool,
/// Pose publish rate (Hz) when --mqtt-publish-pose is set
#[arg(long, default_value = "1.0")]
pub mqtt_rate_pose: f64,
/// Strip biometrics (HR/BR/pose) before any MQTT/Matter publish (ADR-115 §3.10).
#[arg(long, env = "RUVIEW_PRIVACY_MODE")]
pub privacy_mode: bool,
}
/// CLI arguments for the sensing server.
#[derive(Parser, Debug)]
#[command(name = "sensing-server", about = "WiFi-DensePose sensing server")]