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
This commit is contained in:
Claude
2026-02-28 06:31:11 +00:00
parent a8ac309258
commit 6e0e539443
6 changed files with 118 additions and 179 deletions

View File

@@ -1104,25 +1104,12 @@ impl CsiParser {
return Err(AdapterError::DataFormat("PicoScenes packet too short".into()));
}
// Simplified parsing - real implementation would parse all segments
let rssi = data[20] as i8;
let channel = data[24];
// Placeholder - full implementation would parse the CSI segment
Ok(CsiPacket {
timestamp: Utc::now(),
source_id: "picoscenes".to_string(),
amplitudes: vec![],
phases: vec![],
rssi,
noise_floor: -92,
metadata: CsiPacketMetadata {
channel,
format: CsiPacketFormat::PicoScenes,
..Default::default()
},
raw_data: Some(data.to_vec()),
})
// PicoScenes CSI segment parsing is not yet implemented.
// The format requires parsing DeviceType, RxSBasic, CSI, and MVMExtra segments.
// See https://ps.zpj.io/packet-format.html for the full specification.
Err(AdapterError::DataFormat(
"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()
))
}
/// Parse JSON CSI format

View File

@@ -745,88 +745,28 @@ impl HardwareAdapter {
_ => return Err(AdapterError::Config("Invalid settings for ESP32".into())),
};
// In a real implementation, this would read from the serial port
// and parse ESP-CSI format data
tracing::trace!("Reading ESP32 CSI from {}", settings.port);
// Simulate read delay
tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
// Return placeholder - real implementation would parse serial data
Ok(CsiReadings {
timestamp: Utc::now(),
readings: vec![],
metadata: CsiMetadata {
device_type: DeviceType::Esp32,
channel: config.channel_config.channel,
bandwidth: config.channel_config.bandwidth,
num_subcarriers: config.channel_config.num_subcarriers,
rssi: None,
noise_floor: None,
fc_type: FrameControlType::Data,
},
})
Err(AdapterError::Hardware(format!(
"ESP32 CSI hardware adapter not yet implemented. Serial port {} configured but no parser available. See ADR-012 for ESP32 firmware specification.",
settings.port
)))
}
/// Read CSI from Intel 5300 NIC
async fn read_intel_5300_csi(config: &HardwareConfig) -> Result<CsiReadings, AdapterError> {
// Intel 5300 uses connector interface from Linux CSI Tool
tracing::trace!("Reading Intel 5300 CSI");
// In a real implementation, this would:
// 1. Open /proc/net/connector (netlink socket)
// 2. Listen for BFEE_NOTIF messages
// 3. Parse the bfee struct
tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
Ok(CsiReadings {
timestamp: Utc::now(),
readings: vec![],
metadata: CsiMetadata {
device_type: DeviceType::Intel5300,
channel: config.channel_config.channel,
bandwidth: config.channel_config.bandwidth,
num_subcarriers: 30, // Intel 5300 provides 30 subcarriers
rssi: None,
noise_floor: None,
fc_type: FrameControlType::Data,
},
})
async fn read_intel_5300_csi(_config: &HardwareConfig) -> Result<CsiReadings, AdapterError> {
Err(AdapterError::Hardware(
"Intel 5300 CSI adapter not yet implemented. Requires Linux CSI Tool kernel module and netlink connector parsing.".into()
))
}
/// Read CSI from Atheros NIC
async fn read_atheros_csi(
config: &HardwareConfig,
_config: &HardwareConfig,
driver: AtherosDriver,
) -> Result<CsiReadings, AdapterError> {
tracing::trace!("Reading Atheros ({:?}) CSI", driver);
// In a real implementation, this would:
// 1. Read from debugfs CSI buffer
// 2. Parse driver-specific CSI format
tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
let num_subcarriers = match driver {
AtherosDriver::Ath9k => 56,
AtherosDriver::Ath10k => 114,
AtherosDriver::Ath11k => 234,
};
Ok(CsiReadings {
timestamp: Utc::now(),
readings: vec![],
metadata: CsiMetadata {
device_type: DeviceType::Atheros(driver),
channel: config.channel_config.channel,
bandwidth: config.channel_config.bandwidth,
num_subcarriers,
rssi: None,
noise_floor: None,
fc_type: FrameControlType::Data,
},
})
Err(AdapterError::Hardware(format!(
"Atheros {:?} CSI adapter not yet implemented. Requires debugfs CSI buffer parsing.",
driver
)))
}
/// Read CSI from UDP socket
@@ -836,24 +776,10 @@ impl HardwareAdapter {
_ => return Err(AdapterError::Config("Invalid settings for UDP".into())),
};
tracing::trace!("Reading UDP CSI on {}:{}", settings.bind_address, settings.port);
// Placeholder - real implementation would receive and parse UDP packets
tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
Ok(CsiReadings {
timestamp: Utc::now(),
readings: vec![],
metadata: CsiMetadata {
device_type: DeviceType::UdpReceiver,
channel: config.channel_config.channel,
bandwidth: config.channel_config.bandwidth,
num_subcarriers: config.channel_config.num_subcarriers,
rssi: None,
noise_floor: None,
fc_type: FrameControlType::Data,
},
})
Err(AdapterError::Hardware(format!(
"UDP CSI receiver not yet implemented. Bind address {}:{} configured but no packet parser available.",
settings.bind_address, settings.port
)))
}
/// Read CSI from PCAP file
@@ -863,27 +789,10 @@ impl HardwareAdapter {
_ => return Err(AdapterError::Config("Invalid settings for PCAP".into())),
};
tracing::trace!("Reading PCAP CSI from {}", settings.file_path);
// Placeholder - real implementation would read and parse PCAP packets
tokio::time::sleep(tokio::time::Duration::from_millis(
(10.0 / settings.playback_speed) as u64,
))
.await;
Ok(CsiReadings {
timestamp: Utc::now(),
readings: vec![],
metadata: CsiMetadata {
device_type: DeviceType::PcapFile,
channel: config.channel_config.channel,
bandwidth: config.channel_config.bandwidth,
num_subcarriers: config.channel_config.num_subcarriers,
rssi: None,
noise_floor: None,
fc_type: FrameControlType::Data,
},
})
Err(AdapterError::Hardware(format!(
"PCAP CSI reader not yet implemented. File {} configured but no packet parser available.",
settings.file_path
)))
}
/// Generate simulated CSI data

View File

@@ -73,17 +73,21 @@ impl LocalizationService {
Some(position_3d)
}
/// Simulate RSSI measurements (placeholder for real sensor data)
/// Read RSSI measurements from sensors.
///
/// Returns empty when no real sensor hardware is connected.
/// Real RSSI readings require ESP32 mesh (ADR-012) or Linux WiFi interface (ADR-013).
/// Caller handles empty readings by returning None/default.
fn simulate_rssi_measurements(
&self,
sensors: &[crate::domain::SensorPosition],
_sensors: &[crate::domain::SensorPosition],
_vitals: &VitalSignsReading,
) -> Vec<(String, f64)> {
// In production, this would read actual sensor values
// For now, return placeholder values
sensors.iter()
.map(|s| (s.id.clone(), -50.0 + rand_range(-10.0, 10.0)))
.collect()
// No real sensor hardware connected - return empty.
// Real RSSI readings require ESP32 mesh (ADR-012) or Linux WiFi interface (ADR-013).
// Caller handles empty readings by returning None from estimate_position.
tracing::warn!("No sensor hardware connected. Real RSSI readings require ESP32 mesh (ADR-012) or Linux WiFi interface (ADR-013).");
vec![]
}
/// Estimate debris profile for the zone
@@ -309,18 +313,6 @@ impl Default for PositionFuser {
}
}
/// Simple random range (for simulation)
fn rand_range(min: f64, max: f64) -> f64 {
use std::time::{SystemTime, UNIX_EPOCH};
let seed = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_nanos() as u64)
.unwrap_or(0);
let pseudo_random = ((seed * 1103515245 + 12345) % (1 << 31)) as f64 / (1u64 << 31) as f64;
min + pseudo_random * (max - min)
}
#[cfg(test)]
mod tests {