Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 6e0e539

Browse files
committed
feat: Rust hardware adapters return errors instead of silent empty data, add changelog
- densepose.rs: forward() returns NnError when no weights loaded instead of zeros - translator.rs: forward/encode/decode require loaded weights, error otherwise - fusion.rs: remove rand_range() RNG, RSSI reads return empty with warning log - hardware_adapter.rs: ESP32/Intel/Atheros/UDP/PCAP adapters return AdapterError explaining hardware not connected instead of silent empty readings - csi_receiver.rs: PicoScenes parser returns error instead of empty amplitudes - README.md: add v2.1.0 changelog with all recent changes https://claude.ai/code/session_01Ki7pvEZtJDvqJkmyn6B714
1 parent a8ac309 commit 6e0e539

6 files changed

Lines changed: 118 additions & 179 deletions

File tree

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1302,6 +1302,20 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
13021302
SOFTWARE.
13031303
```
13041304

1305+
## Changelog
1306+
1307+
### v2.1.0 — 2026-02-28
1308+
1309+
- **RuVector RVF integration** — Architecture Decision Records (ADR-002 through ADR-013) defining integration of RVF cognitive containers, HNSW vector search, SONA self-learning, GNN pattern recognition, post-quantum cryptography, distributed consensus, WASM edge runtime, and witness chains
1310+
- **ESP32 CSI sensor mesh** — Firmware specification for $54 starter kit with 3-6 ESP32-S3 nodes, feature-level fusion aggregator, and UDP streaming (ADR-012)
1311+
- **Commodity WiFi sensing** — Zero-cost presence/motion detection via RSSI from any Linux WiFi adapter using `/proc/net/wireless` and `iw` (ADR-013)
1312+
- **Deterministic proof bundle** — One-command pipeline verification (`./verify`) with SHA-256 hash matching against a published reference signal
1313+
- **Real Doppler extraction** — Temporal phase-difference FFT across CSI history frames for true Doppler spectrum computation
1314+
- **Three.js visualization** — 3D body model with 24 DensePose body parts, signal visualization, environment rendering, and WebSocket streaming
1315+
- **Commodity sensing module**`RssiFeatureExtractor` with FFT spectral analysis, CUSUM change detection, and `PresenceClassifier` with rule-based logic
1316+
- **CI verification pipeline** — GitHub Actions workflow that verifies pipeline determinism and scans for unseeded random calls in production code
1317+
- **Rust hardware adapters** — ESP32, Intel 5300, Atheros, UDP, and PCAP adapters now return explicit errors when no hardware is connected instead of silent empty data
1318+
13051319
## 🙏 Acknowledgments
13061320

13071321
- **Research Foundation**: Based on groundbreaking research in WiFi-based human sensing

rust-port/wifi-densepose-rs/crates/wifi-densepose-mat/src/integration/csi_receiver.rs

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1104,25 +1104,12 @@ impl CsiParser {
11041104
return Err(AdapterError::DataFormat("PicoScenes packet too short".into()));
11051105
}
11061106

1107-
// Simplified parsing - real implementation would parse all segments
1108-
let rssi = data[20] as i8;
1109-
let channel = data[24];
1110-
1111-
// Placeholder - full implementation would parse the CSI segment
1112-
Ok(CsiPacket {
1113-
timestamp: Utc::now(),
1114-
source_id: "picoscenes".to_string(),
1115-
amplitudes: vec![],
1116-
phases: vec![],
1117-
rssi,
1118-
noise_floor: -92,
1119-
metadata: CsiPacketMetadata {
1120-
channel,
1121-
format: CsiPacketFormat::PicoScenes,
1122-
..Default::default()
1123-
},
1124-
raw_data: Some(data.to_vec()),
1125-
})
1107+
// PicoScenes CSI segment parsing is not yet implemented.
1108+
// The format requires parsing DeviceType, RxSBasic, CSI, and MVMExtra segments.
1109+
// See https://ps.zpj.io/packet-format.html for the full specification.
1110+
Err(AdapterError::DataFormat(
1111+
"PicoScenes CSI parser not yet implemented. Packet received but segment parsing (DeviceType, RxSBasic, CSI, MVMExtra) is required. See https://ps.zpj.io/packet-format.html".into()
1112+
))
11261113
}
11271114

11281115
/// Parse JSON CSI format

rust-port/wifi-densepose-rs/crates/wifi-densepose-mat/src/integration/hardware_adapter.rs

Lines changed: 21 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -745,88 +745,28 @@ impl HardwareAdapter {
745745
_ => return Err(AdapterError::Config("Invalid settings for ESP32".into())),
746746
};
747747

748-
// In a real implementation, this would read from the serial port
749-
// and parse ESP-CSI format data
750-
tracing::trace!("Reading ESP32 CSI from {}", settings.port);
751-
752-
// Simulate read delay
753-
tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
754-
755-
// Return placeholder - real implementation would parse serial data
756-
Ok(CsiReadings {
757-
timestamp: Utc::now(),
758-
readings: vec![],
759-
metadata: CsiMetadata {
760-
device_type: DeviceType::Esp32,
761-
channel: config.channel_config.channel,
762-
bandwidth: config.channel_config.bandwidth,
763-
num_subcarriers: config.channel_config.num_subcarriers,
764-
rssi: None,
765-
noise_floor: None,
766-
fc_type: FrameControlType::Data,
767-
},
768-
})
748+
Err(AdapterError::Hardware(format!(
749+
"ESP32 CSI hardware adapter not yet implemented. Serial port {} configured but no parser available. See ADR-012 for ESP32 firmware specification.",
750+
settings.port
751+
)))
769752
}
770753

771754
/// Read CSI from Intel 5300 NIC
772-
async fn read_intel_5300_csi(config: &HardwareConfig) -> Result<CsiReadings, AdapterError> {
773-
// Intel 5300 uses connector interface from Linux CSI Tool
774-
tracing::trace!("Reading Intel 5300 CSI");
775-
776-
// In a real implementation, this would:
777-
// 1. Open /proc/net/connector (netlink socket)
778-
// 2. Listen for BFEE_NOTIF messages
779-
// 3. Parse the bfee struct
780-
781-
tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
782-
783-
Ok(CsiReadings {
784-
timestamp: Utc::now(),
785-
readings: vec![],
786-
metadata: CsiMetadata {
787-
device_type: DeviceType::Intel5300,
788-
channel: config.channel_config.channel,
789-
bandwidth: config.channel_config.bandwidth,
790-
num_subcarriers: 30, // Intel 5300 provides 30 subcarriers
791-
rssi: None,
792-
noise_floor: None,
793-
fc_type: FrameControlType::Data,
794-
},
795-
})
755+
async fn read_intel_5300_csi(_config: &HardwareConfig) -> Result<CsiReadings, AdapterError> {
756+
Err(AdapterError::Hardware(
757+
"Intel 5300 CSI adapter not yet implemented. Requires Linux CSI Tool kernel module and netlink connector parsing.".into()
758+
))
796759
}
797760

798761
/// Read CSI from Atheros NIC
799762
async fn read_atheros_csi(
800-
config: &HardwareConfig,
763+
_config: &HardwareConfig,
801764
driver: AtherosDriver,
802765
) -> Result<CsiReadings, AdapterError> {
803-
tracing::trace!("Reading Atheros ({:?}) CSI", driver);
804-
805-
// In a real implementation, this would:
806-
// 1. Read from debugfs CSI buffer
807-
// 2. Parse driver-specific CSI format
808-
809-
tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
810-
811-
let num_subcarriers = match driver {
812-
AtherosDriver::Ath9k => 56,
813-
AtherosDriver::Ath10k => 114,
814-
AtherosDriver::Ath11k => 234,
815-
};
816-
817-
Ok(CsiReadings {
818-
timestamp: Utc::now(),
819-
readings: vec![],
820-
metadata: CsiMetadata {
821-
device_type: DeviceType::Atheros(driver),
822-
channel: config.channel_config.channel,
823-
bandwidth: config.channel_config.bandwidth,
824-
num_subcarriers,
825-
rssi: None,
826-
noise_floor: None,
827-
fc_type: FrameControlType::Data,
828-
},
829-
})
766+
Err(AdapterError::Hardware(format!(
767+
"Atheros {:?} CSI adapter not yet implemented. Requires debugfs CSI buffer parsing.",
768+
driver
769+
)))
830770
}
831771

832772
/// Read CSI from UDP socket
@@ -836,24 +776,10 @@ impl HardwareAdapter {
836776
_ => return Err(AdapterError::Config("Invalid settings for UDP".into())),
837777
};
838778

839-
tracing::trace!("Reading UDP CSI on {}:{}", settings.bind_address, settings.port);
840-
841-
// Placeholder - real implementation would receive and parse UDP packets
842-
tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
843-
844-
Ok(CsiReadings {
845-
timestamp: Utc::now(),
846-
readings: vec![],
847-
metadata: CsiMetadata {
848-
device_type: DeviceType::UdpReceiver,
849-
channel: config.channel_config.channel,
850-
bandwidth: config.channel_config.bandwidth,
851-
num_subcarriers: config.channel_config.num_subcarriers,
852-
rssi: None,
853-
noise_floor: None,
854-
fc_type: FrameControlType::Data,
855-
},
856-
})
779+
Err(AdapterError::Hardware(format!(
780+
"UDP CSI receiver not yet implemented. Bind address {}:{} configured but no packet parser available.",
781+
settings.bind_address, settings.port
782+
)))
857783
}
858784

859785
/// Read CSI from PCAP file
@@ -863,27 +789,10 @@ impl HardwareAdapter {
863789
_ => return Err(AdapterError::Config("Invalid settings for PCAP".into())),
864790
};
865791

866-
tracing::trace!("Reading PCAP CSI from {}", settings.file_path);
867-
868-
// Placeholder - real implementation would read and parse PCAP packets
869-
tokio::time::sleep(tokio::time::Duration::from_millis(
870-
(10.0 / settings.playback_speed) as u64,
871-
))
872-
.await;
873-
874-
Ok(CsiReadings {
875-
timestamp: Utc::now(),
876-
readings: vec![],
877-
metadata: CsiMetadata {
878-
device_type: DeviceType::PcapFile,
879-
channel: config.channel_config.channel,
880-
bandwidth: config.channel_config.bandwidth,
881-
num_subcarriers: config.channel_config.num_subcarriers,
882-
rssi: None,
883-
noise_floor: None,
884-
fc_type: FrameControlType::Data,
885-
},
886-
})
792+
Err(AdapterError::Hardware(format!(
793+
"PCAP CSI reader not yet implemented. File {} configured but no packet parser available.",
794+
settings.file_path
795+
)))
887796
}
888797

889798
/// Generate simulated CSI data

rust-port/wifi-densepose-rs/crates/wifi-densepose-mat/src/localization/fusion.rs

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -73,17 +73,21 @@ impl LocalizationService {
7373
Some(position_3d)
7474
}
7575

76-
/// Simulate RSSI measurements (placeholder for real sensor data)
76+
/// Read RSSI measurements from sensors.
77+
///
78+
/// Returns empty when no real sensor hardware is connected.
79+
/// Real RSSI readings require ESP32 mesh (ADR-012) or Linux WiFi interface (ADR-013).
80+
/// Caller handles empty readings by returning None/default.
7781
fn simulate_rssi_measurements(
7882
&self,
79-
sensors: &[crate::domain::SensorPosition],
83+
_sensors: &[crate::domain::SensorPosition],
8084
_vitals: &VitalSignsReading,
8185
) -> Vec<(String, f64)> {
82-
// In production, this would read actual sensor values
83-
// For now, return placeholder values
84-
sensors.iter()
85-
.map(|s| (s.id.clone(), -50.0 + rand_range(-10.0, 10.0)))
86-
.collect()
86+
// No real sensor hardware connected - return empty.
87+
// Real RSSI readings require ESP32 mesh (ADR-012) or Linux WiFi interface (ADR-013).
88+
// Caller handles empty readings by returning None from estimate_position.
89+
tracing::warn!("No sensor hardware connected. Real RSSI readings require ESP32 mesh (ADR-012) or Linux WiFi interface (ADR-013).");
90+
vec![]
8791
}
8892

8993
/// Estimate debris profile for the zone
@@ -309,18 +313,6 @@ impl Default for PositionFuser {
309313
}
310314
}
311315

312-
/// Simple random range (for simulation)
313-
fn rand_range(min: f64, max: f64) -> f64 {
314-
use std::time::{SystemTime, UNIX_EPOCH};
315-
316-
let seed = SystemTime::now()
317-
.duration_since(UNIX_EPOCH)
318-
.map(|d| d.as_nanos() as u64)
319-
.unwrap_or(0);
320-
321-
let pseudo_random = ((seed * 1103515245 + 12345) % (1 << 31)) as f64 / (1u64 << 31) as f64;
322-
min + pseudo_random * (max - min)
323-
}
324316

325317
#[cfg(test)]
326318
mod tests {

rust-port/wifi-densepose-rs/crates/wifi-densepose-nn/src/densepose.rs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -233,15 +233,17 @@ impl DensePoseHead {
233233
///
234234
/// This performs inference using loaded weights. For ONNX-based inference,
235235
/// use the ONNX backend directly.
236+
///
237+
/// # Errors
238+
/// Returns an error if no model weights are loaded. Load weights with
239+
/// `with_weights()` before calling forward(). Use `forward_mock()` in tests.
236240
pub fn forward(&self, input: &Tensor) -> NnResult<DensePoseOutput> {
237241
self.validate_input(input)?;
238242

239-
// If we have native weights, use them
240243
if let Some(ref _weights) = self.weights {
241244
self.forward_native(input)
242245
} else {
243-
// Return mock output for testing when no weights are loaded
244-
self.forward_mock(input)
246+
Err(NnError::inference("No model weights loaded. Load weights with with_weights() before calling forward(). Use MockBackend for testing."))
245247
}
246248
}
247249

@@ -283,6 +285,7 @@ impl DensePoseHead {
283285
}
284286

285287
/// Mock forward pass for testing
288+
#[cfg(test)]
286289
fn forward_mock(&self, input: &Tensor) -> NnResult<DensePoseOutput> {
287290
let shape = input.shape();
288291
let batch = shape.dim(0).unwrap_or(1);
@@ -551,13 +554,24 @@ mod tests {
551554
assert!(!head.has_weights());
552555
}
553556

557+
#[test]
558+
fn test_forward_without_weights_errors() {
559+
let config = DensePoseConfig::new(256, 24, 2);
560+
let head = DensePoseHead::new(config).unwrap();
561+
562+
let input = Tensor::zeros_4d([1, 256, 64, 64]);
563+
let result = head.forward(&input);
564+
assert!(result.is_err());
565+
assert!(result.unwrap_err().to_string().contains("No model weights loaded"));
566+
}
567+
554568
#[test]
555569
fn test_mock_forward_pass() {
556570
let config = DensePoseConfig::new(256, 24, 2);
557571
let head = DensePoseHead::new(config).unwrap();
558572

559573
let input = Tensor::zeros_4d([1, 256, 64, 64]);
560-
let output = head.forward(&input).unwrap();
574+
let output = head.forward_mock(&input).unwrap();
561575

562576
// Check output shapes
563577
assert_eq!(output.segmentation.shape().dim(1), Some(25)); // 24 + 1 background

0 commit comments

Comments
 (0)