- Add --train CLI flag with dataset loading, graph transformer training, cosine-scheduled SGD, PCK/OKS validation, and checkpoint saving - Refactor main.rs to import training modules from lib.rs instead of duplicating mod declarations - Add ADR-021 (vital sign detection), ADR-022 (Windows WiFi enhanced fidelity), ADR-023 (trained DensePose pipeline) documentation - Add wifi-densepose-vitals crate: breathing, heartrate, anomaly detection, preprocessor, and temporal store - Add wifi-densepose-wifiscan crate: 8-stage signal intelligence pipeline with netsh/wlanapi adapters, multi-BSSID registry, attention weighting, spatial correlation, and breathing extraction Co-Authored-By: claude-flow <ruv@ruv.net>
55 KiB
ADR-022: Enhanced Windows WiFi DensePose Fidelity via RuVector Multi-BSSID Pipeline
| Field | Value |
|---|---|
| Status | Partially Implemented |
| Date | 2026-02-28 |
| Deciders | ruv |
| Relates to | ADR-013 (Feature-Level Sensing Commodity Gear), ADR-014 (SOTA Signal Processing), ADR-016 (RuVector Integration), ADR-018 (ESP32 Dev Implementation), ADR-021 (Vital Sign Detection) |
1. Context
1.1 The Problem: Single-RSSI Bottleneck
The current Windows WiFi mode in wifi-densepose-sensing-server (:main.rs:382-464) spawns a netsh wlan show interfaces subprocess every 500ms, extracting a single RSSI% value from the connected AP. This creates a pseudo-single-subcarrier Esp32Frame with:
- 1 amplitude value (signal%)
- 0 phase information
- ~2 Hz effective sampling rate (process spawn overhead)
- No spatial diversity (single observation point)
This is insufficient for any meaningful DensePose estimation. The ESP32 path provides 56 subcarriers with I/Q data at 100+ Hz, while the Windows path provides 1 scalar at 2 Hz -- a 2,800x data deficit.
1.2 The Opportunity: Multi-BSSID Spatial Diversity
A standard Windows WiFi environment exposes 10-30+ BSSIDs via netsh wlan show networks mode=bssid. Testing on the target machine (Intel Wi-Fi 7 BE201 320MHz) reveals:
| Property | Value |
|---|---|
| Adapter | Intel Wi-Fi 7 BE201 320MHz (NDIS 6.89) |
| Visible BSSIDs | 23 |
| Bands | 2.4 GHz (channels 3,5,8,11), 5 GHz (channels 36,48) |
| Radio types | 802.11n, 802.11ac, 802.11ax |
| Signal range | 18% to 99% |
Each BSSID travels a different physical path through the environment. A person's body reflects/absorbs/diffracts each path differently depending on the AP's relative position, frequency, and channel. This creates spatial diversity equivalent to pseudo-subcarriers.
1.3 The Enhancement: Three-Tier Fidelity Improvement
| Tier | Method | Subcarriers | Sample Rate | Implementation |
|---|---|---|---|---|
| Current | netsh show interfaces |
1 | ~2 Hz | Subprocess spawn |
| Tier 1 | netsh show networks mode=bssid |
23 | ~2 Hz | Parse multi-BSSID output |
| Tier 2 | Windows WLAN API (wlanapi.dll FFI) |
23 | 10-20 Hz | Native FFI, no subprocess |
| Tier 3 | Intel Wi-Fi Sensing SDK (802.11bf) | 56+ | 100 Hz | Vendor SDK integration |
This ADR covers Tier 1 and Tier 2. Tier 3 is deferred to a future ADR pending Intel SDK access.
1.4 What RuVector Enables
The vendor/ruvector crate ecosystem provides signal processing primitives that transform multi-BSSID RSSI vectors into meaningful sensing data:
| RuVector Primitive | Role in Windows WiFi Enhancement |
|---|---|
PredictiveLayer (nervous-system) |
Suppresses static BSSIDs (no body interaction), transmits only residual changes. At 23 BSSIDs, 80-95% are typically static. |
ScaledDotProductAttention (attention) |
Learns which BSSIDs are most body-sensitive per environment. Attention query = body-motion spectral profile, keys = per-BSSID variance profiles. |
RuvectorLayer (gnn) |
Builds cross-correlation graph over BSSIDs. Nodes = BSSIDs, edges = temporal cross-correlation. Message passing identifies BSSID clusters affected by the same person. |
OscillatoryRouter (nervous-system) |
Isolates breathing-band (0.1-0.5 Hz) oscillations in multi-BSSID variance for coarse respiratory sensing. |
ModernHopfield (nervous-system) |
Template matching for BSSID fingerprint patterns (standing, sitting, walking, empty). |
SpectralCoherenceScore (coherence) |
Measures spectral gap in BSSID correlation graph; strong gap = good signal separation. |
TieredStore (temporal-tensor) |
Stores multi-BSSID time series with adaptive quantization (8/5/3-bit tiers). |
AdaptiveThresholds (ruQu) |
Self-tuning presence/motion thresholds with Welford stats, EMA, outcome-based learning. |
DriftDetector (ruQu) |
Detects environmental changes (AP power cycling, furniture movement, new interference sources). 5 drift profiles: Stable, Linear, StepChange, Oscillating, VarianceExpansion. |
FilterPipeline (ruQu) |
Three-filter gate (Structural/Shift/Evidence) for signal quality assessment. Only PERMITs readings with statistically rigorous confidence. |
SonaEngine (sona) |
Per-environment micro-LoRA adaptation of BSSID weights and filter parameters. |
2. Decision
Implement an Enhanced Windows WiFi sensing pipeline as a new module within the wifi-densepose-sensing-server crate (and partially in a new wifi-densepose-wifiscan crate), using Domain-Driven Design with bounded contexts. The pipeline scans all visible BSSIDs, constructs multi-dimensional pseudo-CSI frames, and processes them through the RuVector signal pipeline to achieve ESP32-comparable presence/motion detection and coarse vital sign estimation.
2.1 Core Design Principles
- Multi-BSSID as pseudo-subcarriers: Each visible BSSID maps to a subcarrier slot in the existing
Esp32Framestructure, enabling reuse of all downstream signal processing. - Progressive enhancement: Tier 1 (netsh parsing) ships first with zero new dependencies. Tier 2 (wlanapi FFI) adds
windows-sysbehind a feature flag. - Graceful degradation: When fewer BSSIDs are visible (<5), the system falls back to single-AP RSSI mode with reduced confidence scores.
- Environment learning: SONA adapts BSSID weights and thresholds per deployment via micro-LoRA, stored in
TieredStore. - Same API surface: The output is a standard
SensingUpdatemessage, indistinguishable from ESP32 mode to the UI.
3. Architecture (Domain-Driven Design)
3.1 Strategic Design: Bounded Contexts
┌─────────────────────────────────────────────────────────────────────────────┐
│ WiFi DensePose Windows Enhancement │
│ │
│ ┌──────────────────────┐ ┌──────────────────────┐ ┌──────────────────┐ │
│ │ BSSID Acquisition │ │ Signal Intelligence │ │ Sensing Output │ │
│ │ (Supporting Domain) │ │ (Core Domain) │ │ (Generic Domain) │ │
│ │ │ │ │ │ │ │
│ │ • WlanScanner │ │ • BssidAttention │ │ • FrameBuilder │ │
│ │ • BssidRegistry │ │ • SpatialCorrelator │ │ • UpdateEmitter │ │
│ │ • ScanScheduler │ │ • MotionEstimator │ │ • QualityGate │ │
│ │ • RssiNormalizer │ │ • BreathingExtractor │ │ • HistoryStore │ │
│ │ │ │ • DriftMonitor │ │ │ │
│ │ Port: WlanScanPort │ │ • EnvironmentAdapter │ │ Port: SinkPort │ │
│ │ Adapter: NetshScan │ │ │ │ Adapter: WsSink │ │
│ │ Adapter: WlanApiScan│ │ Port: SignalPort │ │ Adapter: RestSink│ │
│ └──────────────────────┘ └──────────────────────┘ └──────────────────┘ │
│ │ │ │ │
│ │ Anti-Corruption │ Anti-Corruption │ │
│ │ Layer (ACL) │ Layer (ACL) │ │
│ └────────────────────────┘────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ Shared Kernel │ │
│ │ • BssidId, RssiDbm, SignalPercent, ChannelInfo, BandType │ │
│ │ • Esp32Frame (reused as universal frame type) │ │
│ │ • SensingUpdate, FeatureInfo, ClassificationInfo │ │
│ └──────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
3.2 Tactical Design: Aggregates and Entities
Bounded Context 1: BSSID Acquisition (Supporting Domain)
Aggregate Root: BssidRegistry
Tracks all visible BSSIDs across scans, maintaining identity stability (BSSIDs appear/disappear as APs beacon).
/// Value Object: unique BSSID identifier
#[derive(Clone, Hash, Eq, PartialEq)]
pub struct BssidId(pub [u8; 6]); // MAC address
/// Value Object: single BSSID observation
#[derive(Clone, Debug)]
pub struct BssidObservation {
pub bssid: BssidId,
pub rssi_dbm: f64,
pub signal_pct: f64,
pub channel: u8,
pub band: BandType,
pub radio_type: RadioType,
pub ssid: String,
pub timestamp: std::time::Instant,
}
#[derive(Clone, Debug, PartialEq)]
pub enum BandType { Band2_4GHz, Band5GHz, Band6GHz }
#[derive(Clone, Debug, PartialEq)]
pub enum RadioType { N, Ac, Ax, Be }
/// Aggregate Root: tracks all visible BSSIDs
pub struct BssidRegistry {
/// Known BSSIDs with sliding window of observations
entries: HashMap<BssidId, BssidEntry>,
/// Ordered list of BSSID IDs for consistent subcarrier mapping
/// (sorted by first-seen time for stability)
subcarrier_map: Vec<BssidId>,
/// Maximum tracked BSSIDs (maps to max subcarriers)
max_bssids: usize,
}
/// Entity: tracked BSSID with history
pub struct BssidEntry {
pub id: BssidId,
pub meta: BssidMeta,
/// Ring buffer of recent RSSI observations
pub history: RingBuffer<f64>,
/// Welford online stats (mean, variance)
pub stats: RunningStats,
/// Last seen timestamp (for expiry)
pub last_seen: std::time::Instant,
/// Subcarrier index in the pseudo-frame (-1 if unmapped)
pub subcarrier_idx: Option<usize>,
}
Port: WlanScanPort (Hexagonal architecture)
/// Port: abstracts WiFi scanning backend
#[async_trait::async_trait]
pub trait WlanScanPort: Send + Sync {
/// Perform a scan and return all visible BSSIDs
async fn scan(&self) -> Result<Vec<BssidObservation>>;
/// Get the connected BSSID (if any)
async fn connected(&self) -> Option<BssidObservation>;
/// Trigger an active scan (may not be supported)
async fn trigger_active_scan(&self) -> Result<()>;
}
Adapter 1: NetshBssidScanner (Tier 1)
/// Tier 1 adapter: parses `netsh wlan show networks mode=bssid`
pub struct NetshBssidScanner;
#[async_trait::async_trait]
impl WlanScanPort for NetshBssidScanner {
async fn scan(&self) -> Result<Vec<BssidObservation>> {
let output = tokio::process::Command::new("netsh")
.args(["wlan", "show", "networks", "mode=bssid"])
.output()
.await?;
let text = String::from_utf8_lossy(&output.stdout);
parse_bssid_scan_output(&text)
}
// ...
}
/// Parse multi-BSSID netsh output into structured observations
fn parse_bssid_scan_output(output: &str) -> Result<Vec<BssidObservation>> {
// Parses blocks like:
// SSID 1 : MyNetwork
// BSSID 1 : aa:bb:cc:dd:ee:ff
// Signal : 84%
// Radio type : 802.11ax
// Band : 2.4 GHz
// Channel : 5
// Returns Vec<BssidObservation> with all fields populated
todo!()
}
Adapter 2: WlanApiBssidScanner (Tier 2, feature-gated)
/// Tier 2 adapter: uses wlanapi.dll via FFI for 10-20 Hz polling
#[cfg(all(target_os = "windows", feature = "wlanapi"))]
pub struct WlanApiBssidScanner {
handle: WlanHandle,
interface_guid: GUID,
}
#[cfg(all(target_os = "windows", feature = "wlanapi"))]
#[async_trait::async_trait]
impl WlanScanPort for WlanApiBssidScanner {
async fn scan(&self) -> Result<Vec<BssidObservation>> {
// WlanGetNetworkBssList returns WLAN_BSS_LIST with per-BSSID:
// - RSSI (i32, dBm)
// - Link quality (u32, 0-100)
// - Channel (from PHY)
// - BSS type, beacon period, IEs
// Much faster than netsh (~5ms vs ~200ms per call)
let bss_list = unsafe {
wlanapi::WlanGetNetworkBssList(
self.handle.0,
&self.interface_guid,
std::ptr::null(),
wlanapi::dot11_BSS_type_any,
0, // security disabled
std::ptr::null_mut(),
std::ptr::null_mut(),
)
};
// ... parse WLAN_BSS_ENTRY structs into BssidObservation
todo!()
}
async fn trigger_active_scan(&self) -> Result<()> {
// WlanScan triggers a fresh scan; results arrive async
unsafe { wlanapi::WlanScan(self.handle.0, &self.interface_guid, ...) };
Ok(())
}
}
Domain Service: ScanScheduler
/// Coordinates scan timing and BSSID registry updates
pub struct ScanScheduler {
scanner: Box<dyn WlanScanPort>,
registry: BssidRegistry,
/// Scan interval (Tier 1: 500ms, Tier 2: 50-100ms)
interval: Duration,
/// Adaptive scan rate based on motion detection
adaptive_rate: bool,
}
impl ScanScheduler {
/// Run continuous scanning loop, updating registry
pub async fn run(&mut self, frame_tx: mpsc::Sender<MultiApFrame>) {
let mut ticker = tokio::time::interval(self.interval);
loop {
ticker.tick().await;
match self.scanner.scan().await {
Ok(observations) => {
self.registry.update(&observations);
let frame = self.registry.to_pseudo_frame();
let _ = frame_tx.send(frame).await;
}
Err(e) => tracing::warn!("Scan failed: {e}"),
}
}
}
}
Bounded Context 2: Signal Intelligence (Core Domain)
This is where RuVector primitives compose into a sensing pipeline.
Domain Service: WindowsWifiPipeline
/// Core pipeline that transforms multi-BSSID scans into sensing data
pub struct WindowsWifiPipeline {
// ── Stage 1: Predictive Gating ──
/// Suppresses static BSSIDs (no body interaction)
/// ruvector-nervous-system::routing::PredictiveLayer
predictive: PredictiveLayer,
// ── Stage 2: Attention Weighting ──
/// Learns BSSID body-sensitivity per environment
/// ruvector-attention::ScaledDotProductAttention
attention: ScaledDotProductAttention,
// ── Stage 3: Spatial Correlation ──
/// Cross-correlation graph over BSSIDs
/// ruvector-gnn::RuvectorLayer (nodes=BSSIDs, edges=correlation)
correlator: BssidCorrelator,
// ── Stage 4: Motion/Presence Estimation ──
/// Multi-BSSID motion score with per-AP weighting
motion_estimator: MultiApMotionEstimator,
// ── Stage 5: Coarse Vital Signs ──
/// Breathing extraction from body-sensitive BSSID oscillations
/// ruvector-nervous-system::routing::OscillatoryRouter
breathing: CoarseBreathingExtractor,
// ── Stage 6: Quality Gate ──
/// ruQu three-filter pipeline + adaptive thresholds
quality_gate: VitalCoherenceGate,
// ── Stage 7: Fingerprint Matching ──
/// Hopfield template matching for posture classification
/// ruvector-nervous-system::hopfield::ModernHopfield
fingerprint: BssidFingerprintMatcher,
// ── Stage 8: Environment Adaptation ──
/// SONA micro-LoRA per deployment
/// sona::SonaEngine
adapter: SonaEnvironmentAdapter,
// ── Stage 9: Drift Monitoring ──
/// ruQu drift detection per BSSID baseline
drift: Vec<DriftDetector>,
// ── Storage ──
/// Tiered storage for BSSID time series
/// ruvector-temporal-tensor::TieredStore
store: TieredStore,
config: WindowsWifiConfig,
}
Value Object: WindowsWifiConfig
pub struct WindowsWifiConfig {
/// Maximum BSSIDs to track (default: 32)
pub max_bssids: usize,
/// Scan interval for Tier 1 (default: 500ms)
pub tier1_interval_ms: u64,
/// Scan interval for Tier 2 (default: 50ms)
pub tier2_interval_ms: u64,
/// PredictiveLayer residual threshold (default: 0.05)
pub predictive_threshold: f32,
/// Minimum BSSIDs for multi-AP mode (default: 3)
pub min_bssids: usize,
/// BSSID expiry after no observation (default: 30s)
pub bssid_expiry_secs: u64,
/// Enable coarse breathing extraction (default: true)
pub enable_breathing: bool,
/// Enable fingerprint matching (default: true)
pub enable_fingerprint: bool,
/// Enable SONA adaptation (default: true)
pub enable_adaptation: bool,
/// Breathing band (Hz) — relaxed for low sample rate
pub breathing_band: (f64, f64),
/// Motion variance threshold for presence detection
pub motion_threshold: f64,
}
impl Default for WindowsWifiConfig {
fn default() -> Self {
Self {
max_bssids: 32,
tier1_interval_ms: 500,
tier2_interval_ms: 50,
predictive_threshold: 0.05,
min_bssids: 3,
bssid_expiry_secs: 30,
enable_breathing: true,
enable_fingerprint: true,
enable_adaptation: true,
breathing_band: (0.1, 0.5),
motion_threshold: 0.15,
}
}
}
Domain Service: Stage-by-Stage Processing
impl WindowsWifiPipeline {
pub fn process(&mut self, frame: &MultiApFrame) -> Option<EnhancedSensingResult> {
let n = frame.bssid_count;
if n < self.config.min_bssids {
return None; // Too few BSSIDs, degrade to legacy
}
// ── Stage 1: Predictive Gating ──
// Convert RSSI dBm to linear amplitude for PredictiveLayer
let amplitudes: Vec<f32> = frame.rssi_dbm.iter()
.map(|&r| 10.0f32.powf((r as f32 + 100.0) / 20.0))
.collect();
let has_change = self.predictive.should_transmit(&litudes);
self.predictive.update(&litudes);
if !has_change {
return None; // Environment static, no body present
}
// ── Stage 2: Attention Weighting ──
// Query: variance profile of breathing band per BSSID
// Key: current RSSI variance per BSSID
// Value: amplitude vector
let query = self.compute_breathing_variance_query(frame);
let keys = self.compute_bssid_variance_keys(frame);
let key_refs: Vec<&[f32]> = keys.iter().map(|k| k.as_slice()).collect();
let val_refs: Vec<&[f32]> = amplitudes.chunks(1).collect(); // per-BSSID
let weights = self.attention.compute(&query, &key_refs, &val_refs);
// ── Stage 3: Spatial Correlation ──
// Build correlation graph: edge(i,j) = pearson_r(bssid_i, bssid_j)
let correlation_features = self.correlator.forward(&frame.histories);
// ── Stage 4: Motion Estimation ──
let motion = self.motion_estimator.estimate(
&weights,
&correlation_features,
&frame.per_bssid_variance,
);
// ── Stage 5: Coarse Breathing ──
let breathing = if self.config.enable_breathing && motion.level == MotionLevel::Minimal {
self.breathing.extract_from_weighted_bssids(
&weights,
&frame.histories,
frame.sample_rate_hz,
)
} else {
None
};
// ── Stage 6: Quality Gate (ruQu) ──
let reading = PreliminaryReading {
motion,
breathing,
signal_quality: self.compute_signal_quality(n, &weights),
};
let verdict = self.quality_gate.gate(&reading);
if matches!(verdict, Verdict::Deny) {
return None;
}
// ── Stage 7: Fingerprint Matching ──
let posture = if self.config.enable_fingerprint {
self.fingerprint.classify(&litudes)
} else {
None
};
// ── Stage 8: Environment Adaptation ──
if self.config.enable_adaptation {
self.adapter.end_trajectory(reading.signal_quality);
}
// ── Stage 9: Drift Monitoring ──
for (i, drift) in self.drift.iter_mut().enumerate() {
if i < n {
drift.push(frame.rssi_dbm[i]);
}
}
// ── Stage 10: Store ──
let tick = frame.sequence as u64;
self.store.put(
ruvector_temporal_tensor::BlockKey::new(0, tick),
&litudes,
ruvector_temporal_tensor::Tier::Hot,
tick,
);
Some(EnhancedSensingResult {
motion,
breathing,
posture,
signal_quality: reading.signal_quality,
bssid_count: n,
verdict,
})
}
}
Bounded Context 3: Sensing Output (Generic Domain)
Domain Service: FrameBuilder
Converts EnhancedSensingResult to the existing SensingUpdate and Esp32Frame types for compatibility.
/// Converts multi-BSSID scan into Esp32Frame for downstream compatibility
pub struct FrameBuilder;
impl FrameBuilder {
pub fn to_esp32_frame(
registry: &BssidRegistry,
observations: &[BssidObservation],
) -> Esp32Frame {
let subcarrier_map = registry.subcarrier_map();
let n_sub = subcarrier_map.len();
let mut amplitudes = vec![0.0f64; n_sub];
let mut phases = vec![0.0f64; n_sub];
for obs in observations {
if let Some(idx) = registry.subcarrier_index(&obs.bssid) {
// Convert RSSI dBm to linear amplitude
amplitudes[idx] = 10.0f64.powf((obs.rssi_dbm + 100.0) / 20.0);
// Phase: encode channel as pseudo-phase (for downstream
// tools that expect phase data)
phases[idx] = (obs.channel as f64 / 48.0) * std::f64::consts::PI;
}
}
Esp32Frame {
magic: 0xC511_0002, // New magic for multi-BSSID frames
node_id: 0,
n_antennas: 1,
n_subcarriers: n_sub as u8,
freq_mhz: 2437, // Mixed; could use median
sequence: 0, // Set by caller
rssi: observations.iter()
.map(|o| o.rssi_dbm as i8)
.max()
.unwrap_or(-90),
noise_floor: -95,
amplitudes,
phases,
}
}
pub fn to_sensing_update(
result: &EnhancedSensingResult,
frame: &Esp32Frame,
registry: &BssidRegistry,
tick: u64,
) -> SensingUpdate {
let nodes: Vec<NodeInfo> = registry.subcarrier_map().iter()
.filter_map(|bssid| registry.get(bssid))
.enumerate()
.map(|(i, entry)| NodeInfo {
node_id: i as u8,
rssi_dbm: entry.stats.mean,
position: estimate_ap_position(entry),
amplitude: vec![frame.amplitudes.get(i).copied().unwrap_or(0.0)],
subcarrier_count: 1,
})
.collect();
SensingUpdate {
msg_type: "sensing_update".to_string(),
timestamp: chrono::Utc::now().timestamp_millis() as f64 / 1000.0,
source: format!("wifi:multi-bssid:{}", result.bssid_count),
tick,
nodes,
features: result.to_feature_info(),
classification: result.to_classification_info(),
signal_field: generate_enhanced_signal_field(result, tick),
}
}
}
3.3 Module Structure
rust-port/wifi-densepose-rs/crates/wifi-densepose-wifiscan/
├── Cargo.toml
└── src/
├── lib.rs # Public API, re-exports
├── domain/
│ ├── mod.rs
│ ├── bssid.rs # BssidId, BssidObservation, BandType, RadioType
│ ├── registry.rs # BssidRegistry aggregate, BssidEntry entity
│ ├── frame.rs # MultiApFrame value object
│ └── result.rs # EnhancedSensingResult, PreliminaryReading
├── port/
│ ├── mod.rs
│ ├── scan_port.rs # WlanScanPort trait
│ └── sink_port.rs # SensingOutputPort trait
├── adapter/
│ ├── mod.rs
│ ├── netsh_scanner.rs # NetshBssidScanner (Tier 1)
│ ├── wlanapi_scanner.rs # WlanApiBssidScanner (Tier 2, feature-gated)
│ └── frame_builder.rs # FrameBuilder (to Esp32Frame / SensingUpdate)
├── pipeline/
│ ├── mod.rs
│ ├── config.rs # WindowsWifiConfig
│ ├── predictive_gate.rs # PredictiveLayer wrapper for multi-BSSID
│ ├── attention_weight.rs # AttentionSubcarrierWeighter for BSSIDs
│ ├── spatial_correlator.rs # GNN-based BSSID correlation
│ ├── motion_estimator.rs # Multi-AP motion/presence estimation
│ ├── breathing.rs # CoarseBreathingExtractor
│ ├── quality_gate.rs # ruQu VitalCoherenceGate
│ ├── fingerprint.rs # ModernHopfield posture fingerprinting
│ ├── drift_monitor.rs # Per-BSSID DriftDetector
│ ├── embedding.rs # BssidEmbedding (SONA micro-LoRA per-BSSID)
│ └── pipeline.rs # WindowsWifiPipeline orchestrator
├── application/
│ ├── mod.rs
│ └── scan_scheduler.rs # ScanScheduler service
└── error.rs # WifiScanError type
3.4 Cargo.toml Dependencies
[package]
name = "wifi-densepose-wifiscan"
version = "0.1.0"
edition = "2021"
[features]
default = []
wlanapi = ["windows-sys"] # Tier 2: native WLAN API
full = ["wlanapi"]
[dependencies]
# Internal
wifi-densepose-signal = { path = "../wifi-densepose-signal" }
# RuVector (vendored)
ruvector-nervous-system = { path = "../../../../vendor/ruvector/crates/ruvector-nervous-system" }
ruvector-attention = { path = "../../../../vendor/ruvector/crates/ruvector-attention" }
ruvector-gnn = { path = "../../../../vendor/ruvector/crates/ruvector-gnn" }
ruvector-coherence = { path = "../../../../vendor/ruvector/crates/ruvector-coherence" }
ruvector-temporal-tensor = { path = "../../../../vendor/ruvector/crates/ruvector-temporal-tensor" }
ruvector-core = { path = "../../../../vendor/ruvector/crates/ruvector-core" }
ruqu = { path = "../../../../vendor/ruvector/crates/ruQu" }
sona = { path = "../../../../vendor/ruvector/crates/sona" }
# Async runtime
tokio = { workspace = true }
async-trait = "0.1"
# Serialization
serde = { workspace = true }
serde_json = { workspace = true }
# Logging
tracing = { workspace = true }
# Time
chrono = "0.4"
# Windows native API (Tier 2, optional)
[target.'cfg(target_os = "windows")'.dependencies]
windows-sys = { version = "0.52", features = [
"Win32_NetworkManagement_WiFi",
"Win32_Foundation",
], optional = true }
4. Signal Processing Pipeline Detail
4.1 BSSID-to-Subcarrier Mapping
Visible BSSIDs (23):
┌──────────────────┬─────┬──────┬──────┬─────────┐
│ BSSID (MAC) │ Ch │ Band │ RSSI │ SubIdx │
├──────────────────┼─────┼──────┼──────┼─────────┤
│ a6:aa:c3:52:1b:28│ 11 │ 2.4G │ -2dBm│ 0 │
│ 82:cd:d6:d6:c3:f5│ 8 │ 2.4G │ -1dBm│ 1 │
│ 16:0a:c5:39:e3:5d│ 5 │ 2.4G │-16dBm│ 2 │
│ 16:27:f5:b2:6b:ae│ 8 │ 2.4G │-17dBm│ 3 │
│ 10:27:f5:b2:6b:ae│ 8 │ 2.4G │-22dBm│ 4 │
│ c8:9e:43:47:a1:3f│ 3 │ 2.4G │-40dBm│ 5 │
│ 90:aa:c3:52:1b:28│ 11 │ 2.4G │ -2dBm│ 6 │
│ ... │ ... │ ... │ ... │ ... │
│ 92:aa:c3:52:1b:20│ 36 │ 5G │ -6dBm│ 20 │
│ c8:9e:43:47:a1:40│ 48 │ 5G │-78dBm│ 21 │
│ ce:9e:43:47:a1:40│ 48 │ 5G │-82dBm│ 22 │
└──────────────────┴─────┴──────┴──────┴─────────┘
Mapping rule: sorted by first-seen time (stable ordering).
New BSSIDs get the next available subcarrier index.
BSSIDs not seen for >30s are expired and their index recycled.
4.2 Spatial Diversity: Why Multi-BSSID Works
┌────[AP1: ch3]
│ │
body │ │ path A (partially blocked)
┌───┐ │ │
│ │──┤ ▼
│ P │ │ ┌──────────┐
│ │──┤ │ WiFi │
└───┘ │ │ Adapter │
│ │ (BE201) │
┌──────┤ └──────────┘
│ │ ▲
[AP2: ch11] │ │ path B (unobstructed)
│ │
└────[AP3: ch36]
│ path C (reflected off wall)
Person P attenuates path A by 3-8 dB, while paths B and C
are unaffected. This differential is the multi-BSSID body signal.
At different body positions/orientations, different AP combinations
show attenuation → spatial diversity ≈ pseudo-subcarrier diversity.
4.3 RSSI-to-Amplitude Conversion
/// Convert RSSI dBm to linear amplitude (normalized)
/// RSSI range: -100 dBm (noise) to -20 dBm (very strong)
fn rssi_to_linear(rssi_dbm: f64) -> f64 {
// Map -100..0 dBm to 0..1 linear scale
// Using 10^((rssi+100)/20) gives log-scale amplitude
10.0f64.powf((rssi_dbm + 100.0) / 20.0)
}
/// Convert linear amplitude back to dBm
fn linear_to_rssi(amplitude: f64) -> f64 {
20.0 * amplitude.max(1e-10).log10() - 100.0
}
4.4 Pseudo-Phase Encoding
Since RSSI provides no phase information, we encode channel and band as a pseudo-phase for downstream tools:
/// Encode BSSID channel/band as pseudo-phase
/// This preserves frequency-group identity for the GNN correlator
fn encode_pseudo_phase(channel: u8, band: BandType) -> f64 {
let band_offset = match band {
BandType::Band2_4GHz => 0.0,
BandType::Band5GHz => std::f64::consts::PI,
BandType::Band6GHz => std::f64::consts::FRAC_PI_2,
};
// Spread channels across [0, PI) within each band
let ch_phase = (channel as f64 / 48.0) * std::f64::consts::FRAC_PI_2;
band_offset + ch_phase
}
5. RuVector Integration Map
5.1 Crate-to-Stage Mapping
| Pipeline Stage | RuVector Crate | Specific Type | Purpose |
|---|---|---|---|
| Predictive Gate | ruvector-nervous-system |
PredictiveLayer |
RMS residual gating (threshold 0.05); suppresses scans with no body-caused changes |
| Attention Weight | ruvector-attention |
ScaledDotProductAttention |
Query=breathing variance profile, Key=per-BSSID variance, Value=amplitude; outputs per-BSSID importance weights |
| Spatial Correlator | ruvector-gnn |
RuvectorLayer + LayerNorm |
Correlation graph over BSSIDs; single message-passing layer identifies co-varying BSSID clusters |
| Breathing Extraction | ruvector-nervous-system |
OscillatoryRouter |
0.15 Hz oscillator phase-locks to strongest breathing component in weighted BSSID variance |
| Fingerprint Matching | ruvector-nervous-system |
ModernHopfield |
Stores 4 templates: empty-room, standing, sitting, walking; exponential capacity retrieval |
| Signal Quality | ruvector-coherence |
SpectralCoherenceScore |
Spectral gap of BSSID correlation graph; higher gap = cleaner body signal |
| Quality Gate | ruQu |
FilterPipeline + AdaptiveThresholds |
Three-filter PERMIT/DENY/DEFER; self-tunes thresholds with Welford/EMA |
| Drift Monitor | ruQu |
DriftDetector |
Per-BSSID baseline tracking; 5 profiles (Stable/Linear/StepChange/Oscillating/VarianceExpansion) |
| Environment Adapt | sona |
SonaEngine |
Per-deployment micro-LoRA adaptation of attention weights and filter parameters |
| Tiered Storage | ruvector-temporal-tensor |
TieredStore |
8-bit hot / 5-bit warm / 3-bit cold; 23 BSSIDs × 1024 samples ≈ 24 KB hot |
| Pattern Search | ruvector-core |
VectorDB (HNSW) |
BSSID fingerprint nearest-neighbor lookup (<1ms for 1000 templates) |
5.2 Data Volume Estimates
| Metric | Tier 1 (netsh) | Tier 2 (wlanapi) |
|---|---|---|
| BSSIDs per scan | 23 | 23 |
| Scan rate | 2 Hz | 20 Hz |
| Samples/sec | 46 | 460 |
| Bytes/sec (raw) | 184 B | 1,840 B |
| Ring buffer memory (1024 samples × 23 BSSIDs × 8 bytes) | 188 KB | 188 KB |
| PredictiveLayer savings | 80-95% suppressed | 90-99% suppressed |
| Net processing rate | 2-9 frames/sec | 2-46 frames/sec |
6. Expected Fidelity Improvements
6.1 Quantitative Targets
| Metric | Current (1 RSSI) | Tier 1 (Multi-BSSID) | Tier 2 (+ Native API) |
|---|---|---|---|
| Presence detection accuracy | ~70% (threshold) | ~88% (multi-AP attention) | ~93% (temporal + spatial) |
| Presence detection latency | 500ms | 500ms | 50ms |
| Motion level classification | 2 levels | 4 levels (static/minimal/moderate/active) | 4 levels + direction |
| Room-level localization | None | Coarse (nearest AP cluster) | Moderate (3-AP trilateration) |
| Breathing rate detection | None | Marginal (0.3 confidence) | Fair (0.5-0.6 confidence) |
| Heart rate detection | None | None | None (need CSI for HR) |
| Posture classification | None | 4 classes (empty/standing/sitting/walking) | 4 classes + confidence |
| Environmental drift resilience | None | Good (ruQu adaptive) | Good (+ SONA adaptation) |
6.2 Confidence Score Calibration
/// Signal quality as a function of BSSID count and variance spread
fn compute_signal_quality(
bssid_count: usize,
attention_weights: &[f32],
spectral_gap: f64,
) -> f64 {
// Factor 1: BSSID diversity (more APs = more spatial info)
let diversity = (bssid_count as f64 / 20.0).min(1.0);
// Factor 2: Attention concentration (body-sensitive BSSIDs dominate)
let max_weight = attention_weights.iter().copied().fold(0.0f32, f32::max);
let mean_weight = attention_weights.iter().sum::<f32>() / attention_weights.len() as f32;
let concentration = (max_weight / mean_weight.max(1e-6) - 1.0).min(5.0) as f64 / 5.0;
// Factor 3: Spectral gap (clean body signal separation)
let separation = spectral_gap.min(1.0);
// Combined quality
(diversity * 0.3 + concentration * 0.4 + separation * 0.3).clamp(0.0, 1.0)
}
7. Integration with Sensing Server
7.1 Modified Data Source Selection
// In main(), extend auto-detection:
let source = match args.source.as_str() {
"auto" => {
if probe_esp32(args.udp_port).await {
"esp32"
} else if probe_multi_bssid().await {
"wifi-enhanced" // NEW: multi-BSSID mode
} else if probe_windows_wifi().await {
"wifi" // Legacy single-RSSI
} else {
"simulate"
}
}
other => other,
};
// Start appropriate background task
match source {
"esp32" => {
tokio::spawn(udp_receiver_task(state.clone(), args.udp_port));
tokio::spawn(broadcast_tick_task(state.clone(), args.tick_ms));
}
"wifi-enhanced" => {
// NEW: multi-BSSID enhanced pipeline
tokio::spawn(enhanced_wifi_task(state.clone(), args.tick_ms));
}
"wifi" => {
tokio::spawn(windows_wifi_task(state.clone(), args.tick_ms));
}
_ => {
tokio::spawn(simulated_data_task(state.clone(), args.tick_ms));
}
}
7.2 Enhanced WiFi Task
async fn enhanced_wifi_task(state: SharedState, tick_ms: u64) {
let scanner: Box<dyn WlanScanPort> = {
#[cfg(feature = "wlanapi")]
{ Box::new(WlanApiBssidScanner::new().unwrap_or_else(|_| {
tracing::warn!("WLAN API unavailable, falling back to netsh");
Box::new(NetshBssidScanner)
})) }
#[cfg(not(feature = "wlanapi"))]
{ Box::new(NetshBssidScanner) }
};
let mut registry = BssidRegistry::new(32);
let mut pipeline = WindowsWifiPipeline::new(WindowsWifiConfig::default());
let mut interval = tokio::time::interval(Duration::from_millis(tick_ms));
let mut seq: u32 = 0;
info!("Enhanced WiFi multi-BSSID pipeline active (tick={}ms)", tick_ms);
loop {
interval.tick().await;
seq += 1;
let observations = match scanner.scan().await {
Ok(obs) => obs,
Err(e) => { warn!("Scan failed: {e}"); continue; }
};
registry.update(&observations);
let frame = FrameBuilder::to_esp32_frame(®istry, &observations);
// Run through RuVector-powered pipeline
let multi_frame = registry.to_multi_ap_frame();
let result = pipeline.process(&multi_frame);
let mut s = state.write().await;
s.source = format!("wifi-enhanced:{}", observations.len());
s.tick += 1;
let tick = s.tick;
let update = match result {
Some(r) => FrameBuilder::to_sensing_update(&r, &frame, ®istry, tick),
None => {
// Fallback: basic update from frame
let (features, classification) = extract_features_from_frame(&frame);
SensingUpdate {
msg_type: "sensing_update".into(),
timestamp: chrono::Utc::now().timestamp_millis() as f64 / 1000.0,
source: format!("wifi-enhanced:{}", observations.len()),
tick,
nodes: vec![],
features,
classification,
signal_field: generate_signal_field(
frame.rssi as f64, 1.0, 0.05, tick,
),
}
}
};
if let Ok(json) = serde_json::to_string(&update) {
let _ = s.tx.send(json);
}
s.latest_update = Some(update);
}
}
8. Performance Considerations
8.1 Latency Budget
| Stage | Tier 1 Latency | Tier 2 Latency | Notes |
|---|---|---|---|
| BSSID scan | ~200ms (netsh) | ~5ms (wlanapi) | Process spawn vs FFI |
| Registry update | <1ms | <1ms | HashMap lookup |
| PredictiveLayer gate | <10us | <10us | 23-element RMS |
| Attention weighting | <50us | <50us | 23×64 matmul |
| GNN correlation | <100us | <100us | 23-node single layer |
| Motion estimation | <20us | <20us | Weighted variance |
| Breathing extraction | <30us | <30us | Bandpass + peak detect |
| ruQu quality gate | <10us | <10us | Three comparisons |
| Fingerprint match | <50us | <50us | Hopfield retrieval |
| Total per tick | ~200ms | ~5ms | Scan dominates Tier 1 |
8.2 Memory Budget
| Component | Memory |
|---|---|
| BssidRegistry (32 entries × history) | ~264 KB |
| PredictiveLayer (32-element) | <1 KB |
| Attention weights | ~8 KB |
| GNN layer | ~12 KB |
| Hopfield (32-dim, 10 templates) | ~3 KB |
| TieredStore (256 KB budget) | 256 KB |
| DriftDetector (32 instances) | ~32 KB |
| Total | ~576 KB |
9. Security Considerations
- No raw BSSID data to UI: Only aggregated sensing updates are broadcast. Individual BSSID MACs, SSIDs, and locations are kept server-side to prevent WiFi infrastructure fingerprinting.
- BSSID anonymization: The
NodeInfo.node_iduses sequential indices, not MAC addresses. - Local-only processing: All signal processing occurs on-device. No scan data is transmitted externally.
- Scan permission:
netsh wlan show networksrequires no admin privileges.WlanGetNetworkBssListrequires the WLAN service to be running (default on Windows).
10. Alternatives Considered
Alt 1: Single-AP RSSI Enhancement Only
Improve the current single-RSSI path with better filtering and drift detection, without multi-BSSID.
Rejected: A single RSSI value lacks spatial diversity. No amount of temporal filtering can recover spatial information from a 1D signal. Multi-BSSID is the minimum viable path to meaningful presence sensing.
Alt 2: Monitor Mode / Packet Capture
Put the WiFi adapter into monitor mode to capture raw 802.11 frames with per-subcarrier CSI.
Rejected for Windows: Monitor mode requires specialized drivers (nexmon, picoscenes) that are Linux-only for Intel adapters. Windows NDIS does not expose raw CSI. Tier 3 (Intel SDK) is the legitimate Windows path to CSI.
Alt 3: External USB WiFi Adapter
Use a separate USB adapter in monitor mode on Linux via WSL.
Rejected: Adds hardware dependency, WSL USB passthrough complexity, and defeats the "commodity gear, zero setup" value proposition.
Alt 4: Bluetooth RSSI Augmentation
Scan BLE beacons for additional spatial observations.
Deferred: Could complement multi-BSSID but adds BLE scanning complexity. Future enhancement, not core path.
11. Consequences
Positive
- 10-20x data improvement: From 1 RSSI at 2 Hz to 23 BSSIDs at 2-20 Hz
- Spatial awareness: Different APs provide different body-interaction paths
- Reuses existing pipeline:
Esp32FrameandSensingUpdateare unchanged; UI works without modification - Zero hardware required: Uses commodity WiFi infrastructure already present
- RuVector composition: Leverages 8 existing crates; ~80% of the intelligence is pre-built
- Progressive enhancement: Tier 1 ships immediately, Tier 2 adds behind feature flag
- Environment-adaptive: SONA + ruQu self-tune per deployment
Negative
- Still no CSI phase: RSSI-only means no heart rate and limited breathing detection
- AP density dependent: Fewer visible APs = degraded fidelity (min 3 required)
- Scan latency: Tier 1 netsh is slow (~200ms); Tier 2 wlanapi required for real-time
- AP mobility: Moving APs (phones as hotspots) create false motion signals
- Cross-platform:
wlanapi.dllis Windows-only; Linux/macOS need separate adapters - New crate: Adds
wifi-densepose-wifiscanto workspace, increasing compile scope
12. Implementation Roadmap
Phase 1: Tier 1 Foundation (Week 1)
- Create
wifi-densepose-wifiscancrate with DDD module structure - Implement
BssidId,BssidObservation,BandType,RadioTypevalue objects - Implement
BssidRegistryaggregate with ring buffer history and Welford stats - Implement
NetshBssidScanneradapter (parsenetsh wlan show networks mode=bssid) - Implement
MultiApFrame,EnhancedSensingResult,WlanScanPort, error types - All 42 unit tests passing (parser, domain types, registry, result types)
- Implement
FrameBuilder::to_esp32_frame()(multi-BSSID → pseudo-Esp32Frame) - Implement
ScanSchedulerwith configurable interval - Integration test: scan → registry → pseudo-frame → existing sensing pipeline
- Wire
enhanced_wifi_taskinto sensing servermain()
Phase 2: RuVector Signal Pipeline (Weeks 2-3)
- Implement
PredictiveGatewrapper overPredictiveLayerfor multi-BSSID - Implement
AttentionSubcarrierWeighterwith breathing-variance query - Implement
BssidCorrelatorusingRuvectorLayercorrelation graph - Implement
MultiApMotionEstimatorwith weighted variance - Implement
CoarseBreathingExtractorwithOscillatoryRouter - Implement
VitalCoherenceGate(ruQu three-filter pipeline) - Implement
BssidFingerprintMatcherwithModernHopfieldtemplates - Implement
WindowsWifiPipelineorchestrator - Unit tests with synthetic multi-BSSID data
Phase 3: Tier 2 + Adaptation (Week 4)
- Implement
WlanApiBssidScannerusingwindows-sysFFI - Benchmark: netsh vs wlanapi latency
- Implement
SonaEnvironmentAdapterfor per-deployment learning - Implement per-BSSID
DriftDetectorarray - Implement
TieredStorewrapper for BSSID time series - Performance benchmarking (latency budget validation)
- End-to-end integration test on real Windows WiFi
Phase 4: Hardening (Week 5)
- Signal quality calibration against known ground truth
- Confidence score validation (presence/motion/breathing)
- BSSID anonymization in output messages
- Adaptive scan rate (faster when motion detected)
- Documentation and API reference
- Feature flag verification (
wlanapion/off)
Review Errata (Applied)
The following issues were identified during code review against the vendored RuVector source and corrected in this ADR:
| # | Issue | Fix Applied |
|---|---|---|
| 1 | GnnLayer does not exist in ruvector-gnn; actual export is RuvectorLayer |
Renamed all references to RuvectorLayer |
| 2 | ScaledDotProductAttention has no .forward() method; actual API is .compute(query, keys, values) with &[&[f32]] slice-of-slices |
Updated Stage 2 code to use .compute() with correct parameter types |
| 3 | SonaEngine::new(SonaConfig{...}) incorrect; actual constructor is SonaEngine::with_config(config) and SonaConfig uses micro_lora_lr not learning_rate |
Fixed constructor and field names in Section 14 |
| 4 | apply_micro_lora returns nothing; actual signature writes into &mut [f32] output buffer |
Fixed to use mutable output buffer pattern |
| 5 | TieredStore.put(&data) missing required params; actual signature: put(key, data, tier, tick) |
Added BlockKey, Tier, and tick parameters |
| 6 | WindowsWifiPipeline mislabeled as "Aggregate Root"; it is a domain service/orchestrator |
Relabeled to "Domain Service" |
Open items from review (not yet addressed):
OscillatoryRouteris designed for gamma-band (30-90 Hz) neural synchronization; using it at 0.15 Hz for breathing extraction is a semantic stretch. Consider replacing with a dedicated IIR bandpass filter.- BSSID flapping/index recycling could invalidate GNN correlation graphs; needs explicit invalidation logic.
netshoutput is locale-dependent; parser may fail on non-English Windows. Consider positional parsing as fallback.- Tier 1 breathing detection at 2 Hz is marginal due to subprocess spawn timing jitter; should require Tier 2 for breathing feature.
13. Testing Strategy
13.1 Unit Tests (TDD London School)
#[cfg(test)]
mod tests {
// Domain: BssidRegistry
#[test]
fn registry_assigns_stable_subcarrier_indices();
#[test]
fn registry_expires_stale_bssids();
#[test]
fn registry_maintains_welford_stats();
// Adapter: NetshBssidScanner
#[test]
fn parse_bssid_scan_output_extracts_all_bssids();
#[test]
fn parse_bssid_scan_output_handles_multi_band();
#[test]
fn parse_bssid_scan_output_handles_empty_output();
// Pipeline: PredictiveGate
#[test]
fn predictive_gate_suppresses_static_environment();
#[test]
fn predictive_gate_transmits_body_caused_changes();
// Pipeline: MotionEstimator
#[test]
fn motion_estimator_detects_presence_from_multi_ap();
#[test]
fn motion_estimator_classifies_four_levels();
// Pipeline: BreathingExtractor
#[test]
fn breathing_extracts_rate_from_oscillating_bssid();
// Integration
#[test]
fn full_pipeline_produces_sensing_update();
#[test]
fn graceful_degradation_with_few_bssids();
}
13.2 Integration Tests
- Real
netshscan on CI Windows runner - Mock BSSID data for deterministic pipeline testing
- Benchmark: processing latency per tick
14. Custom BSSID Embeddings with Micro-LoRA (SONA)
14.1 The Problem with Raw RSSI Vectors
Raw RSSI values are noisy, device-dependent, and non-stationary. A -50 dBm reading from AP1 on channel 3 is not directly comparable to -50 dBm from AP2 on channel 36 (different propagation, antenna gain, PHY). Feeding raw RSSI into the RuVector pipeline produces suboptimal attention weights and fingerprint matches.
14.2 Solution: Learned BSSID Embeddings
Instead of using raw RSSI, we learn a per-BSSID embedding that captures each AP's environmental signature using SONA's micro-LoRA adaptation:
use sona::{SonaEngine, SonaConfig, TrajectoryBuilder};
/// Per-BSSID learned embedding that captures environmental signature
pub struct BssidEmbedding {
/// SONA engine for micro-LoRA parameter adaptation
sona: SonaEngine,
/// Per-BSSID embedding vectors (d_embed dimensions per BSSID)
embeddings: Vec<Vec<f32>>,
/// Embedding dimension
d_embed: usize,
}
impl BssidEmbedding {
pub fn new(max_bssids: usize, d_embed: usize) -> Self {
Self {
sona: SonaEngine::with_config(SonaConfig {
hidden_dim: d_embed,
embedding_dim: d_embed,
micro_lora_lr: 0.001,
ewc_lambda: 100.0, // Prevent forgetting previous environments
..Default::default()
}),
embeddings: vec![vec![0.0; d_embed]; max_bssids],
d_embed,
}
}
/// Encode a BSSID observation into a learned embedding
/// Combines: RSSI, channel, band, radio type, variance, history
pub fn encode(&self, entry: &BssidEntry) -> Vec<f32> {
let mut raw = vec![0.0f32; self.d_embed];
// Static features (learned via micro-LoRA)
raw[0] = rssi_to_linear(entry.stats.mean) as f32;
raw[1] = entry.stats.variance().sqrt() as f32;
raw[2] = channel_to_norm(entry.meta.channel);
raw[3] = band_to_feature(entry.meta.band);
raw[4] = radio_to_feature(entry.meta.radio_type);
// Temporal features (from ring buffer)
if entry.history.len() >= 4 {
raw[5] = entry.history.delta(1) as f32; // 1-step velocity
raw[6] = entry.history.delta(2) as f32; // 2-step velocity
raw[7] = entry.history.trend_slope() as f32;
}
// Apply micro-LoRA adaptation: raw → adapted
let mut adapted = vec![0.0f32; self.d_embed];
self.sona.apply_micro_lora(&raw, &mut adapted);
adapted
}
/// Train embeddings from outcome feedback
/// Called when presence/motion ground truth is available
pub fn train(&mut self, bssid_idx: usize, embedding: &[f32], quality: f32) {
let trajectory = self.sona.begin_trajectory(embedding.to_vec());
self.sona.end_trajectory(trajectory, quality);
// EWC++ prevents catastrophic forgetting of previous environments
}
}
14.3 Micro-LoRA Adaptation Cycle
Scan 1: Raw RSSI [AP1:-42, AP2:-58, AP3:-71, ...]
│
▼
BssidEmbedding.encode() → [e1, e2, e3, ...] (d_embed=16 per BSSID)
│
▼
AttentionSubcarrierWeighter (query=breathing_profile, key=embeddings)
│
▼
Pipeline produces: motion=0.7, breathing=16.2, quality=0.85
│
▼
User/system feedback: correct=true (person was present)
│
▼
BssidEmbedding.train(quality=0.85)
│
▼
SONA micro-LoRA updates embedding weights
EWC++ preserves prior environment learnings
│
▼
Scan 2: Same raw RSSI → BETTER embeddings → BETTER attention → BETTER output
14.4 Benefits of Custom Embeddings
| Aspect | Raw RSSI | Learned Embedding |
|---|---|---|
| Device normalization | No | Yes (micro-LoRA adapts per adapter) |
| AP gain compensation | No | Yes (learned per BSSID) |
| Channel/band encoding | Lost | Preserved as features |
| Temporal dynamics | Not captured | Velocity + trend features |
| Cross-environment transfer | No | EWC++ preserves learnings |
| Attention quality | Noisy | Clean (adapted features) |
| Fingerprint matching | Raw distance | Semantically meaningful distance |
14.5 Integration with Pipeline Stages
The custom embeddings replace raw RSSI at the attention and fingerprint stages:
// In WindowsWifiPipeline::process():
// Stage 2 (MODIFIED): Attention on embeddings, not raw RSSI
let bssid_embeddings: Vec<Vec<f32>> = frame.entries.iter()
.map(|entry| self.embedding.encode(entry))
.collect();
let weights = self.attention.forward(
&self.compute_breathing_query(),
&bssid_embeddings, // Learned embeddings, not raw RSSI
&litudes,
);
// Stage 7 (MODIFIED): Fingerprint on embedding space
let posture = self.fingerprint.classify_embedding(&bssid_embeddings);
Implementation Status (2026-02-28)
Phase 1: Domain Model -- COMPLETE
wifi-densepose-wifiscancrate created with DDD bounded contextsMultiApFramevalue object with amplitudes, phases, variances, historiesBssidRegistryaggregate root with Welford running statistics (capacity 32, 30s expiry)NetshBssidScanneradapter parsingnetsh wlan show networks mode=bssid(56 unit tests)EnhancedSensingResultoutput type with motion, breathing, posture, quality- Hexagonal architecture:
WlanScanPorttrait for adapter abstraction
Phase 2: Signal Intelligence Pipeline -- COMPLETE
8-stage pure-Rust pipeline with 125 passing tests:
| Stage | Module | Implementation |
|---|---|---|
| 1 | predictive_gate |
EMA-based residual filter (replaces PredictiveLayer) |
| 2 | attention_weighter |
Softmax dot-product attention (replaces ScaledDotProductAttention) |
| 3 | correlator |
Pearson correlation + BFS clustering (replaces RuvectorLayer GNN) |
| 4 | motion_estimator |
Weighted variance + EMA smoothing |
| 5 | breathing_extractor |
IIR bandpass (0.1-0.5 Hz) + zero-crossing |
| 6 | quality_gate |
Three-filter gate (structural/shift/evidence), inspired by ruQu |
| 7 | fingerprint_matcher |
Cosine similarity templates (replaces ModernHopfield) |
| 8 | orchestrator |
WindowsWifiPipeline domain service |
Performance: ~2.1M frames/sec (debug), ~12M frames/sec (release).
Phase 3: Server Integration -- IN PROGRESS
- Wiring
WindowsWifiPipelineintowifi-densepose-sensing-server - Tier 2
WlanApiScannerasync adapter stub (upgrade path to native WLAN API) - Extended
SensingUpdatewith enhanced motion, breathing, posture, quality fields
Phase 4: Tier 2 Native WLAN API -- PLANNED
- Native
wlanapi.dllFFI for 10-20 Hz scan rates - SONA adaptation layer for per-environment tuning
- Multi-environment benchmarking
15. References
- IEEE 802.11bf WiFi Sensing Standard (2024)
- Adib, F. et al. "See Through Walls with WiFi!" SIGCOMM 2013
- Ali, K. et al. "Keystroke Recognition Using WiFi Signals" MobiCom 2015
- Halperin, D. et al. "Tool Release: Gathering 802.11n Traces with Channel State Information" ACM SIGCOMM CCR 2011
- Intel Wi-Fi 7 BE200/BE201 Specifications (2024)
- Microsoft WLAN API Documentation:
WlanGetNetworkBssList,WlanScan - RuVector v2.0.4 crate documentation