feat: Add wifi-densepose-mat disaster detection module
Implements WiFi-Mat (Mass Casualty Assessment Tool) for detecting and localizing survivors trapped in rubble, earthquakes, and natural disasters. Architecture: - Domain-Driven Design with bounded contexts (Detection, Localization, Alerting) - Modular Rust crate integrating with existing wifi-densepose-* crates - Event-driven architecture for audit trails and distributed deployments Features: - Breathing pattern detection from CSI amplitude variations - Heartbeat detection using micro-Doppler analysis - Movement classification (gross, fine, tremor, periodic) - START protocol-compatible triage classification - 3D position estimation via triangulation and depth estimation - Real-time alert generation with priority escalation Documentation: - ADR-001: Architecture Decision Record for wifi-Mat - DDD domain model specification
This commit is contained in:
@@ -0,0 +1,324 @@
|
||||
//! Adapter for wifi-densepose-hardware crate.
|
||||
|
||||
use super::AdapterError;
|
||||
use crate::domain::{SensorPosition, SensorType};
|
||||
|
||||
/// Hardware adapter for sensor communication
|
||||
pub struct HardwareAdapter {
|
||||
/// Connected sensors
|
||||
sensors: Vec<SensorInfo>,
|
||||
/// Whether hardware is initialized
|
||||
initialized: bool,
|
||||
}
|
||||
|
||||
/// Information about a connected sensor
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SensorInfo {
|
||||
/// Unique sensor ID
|
||||
pub id: String,
|
||||
/// Sensor position
|
||||
pub position: SensorPosition,
|
||||
/// Current status
|
||||
pub status: SensorStatus,
|
||||
/// Last RSSI reading (if available)
|
||||
pub last_rssi: Option<f64>,
|
||||
/// Battery level (0-100, if applicable)
|
||||
pub battery_level: Option<u8>,
|
||||
}
|
||||
|
||||
/// Status of a sensor
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum SensorStatus {
|
||||
/// Sensor is connected and operational
|
||||
Connected,
|
||||
/// Sensor is disconnected
|
||||
Disconnected,
|
||||
/// Sensor is in error state
|
||||
Error,
|
||||
/// Sensor is initializing
|
||||
Initializing,
|
||||
/// Sensor battery is low
|
||||
LowBattery,
|
||||
}
|
||||
|
||||
impl HardwareAdapter {
|
||||
/// Create a new hardware adapter
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
sensors: Vec::new(),
|
||||
initialized: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize hardware communication
|
||||
pub fn initialize(&mut self) -> Result<(), AdapterError> {
|
||||
// In production, this would initialize actual hardware
|
||||
// using wifi-densepose-hardware crate
|
||||
self.initialized = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Discover available sensors
|
||||
pub fn discover_sensors(&mut self) -> Result<Vec<SensorInfo>, AdapterError> {
|
||||
if !self.initialized {
|
||||
return Err(AdapterError::Hardware("Hardware not initialized".into()));
|
||||
}
|
||||
|
||||
// In production, this would scan for WiFi devices
|
||||
// For now, return empty list (would be populated by real hardware)
|
||||
Ok(Vec::new())
|
||||
}
|
||||
|
||||
/// Add a sensor
|
||||
pub fn add_sensor(&mut self, sensor: SensorInfo) -> Result<(), AdapterError> {
|
||||
if self.sensors.iter().any(|s| s.id == sensor.id) {
|
||||
return Err(AdapterError::Hardware(format!(
|
||||
"Sensor {} already registered",
|
||||
sensor.id
|
||||
)));
|
||||
}
|
||||
|
||||
self.sensors.push(sensor);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Remove a sensor
|
||||
pub fn remove_sensor(&mut self, sensor_id: &str) -> Result<(), AdapterError> {
|
||||
let initial_len = self.sensors.len();
|
||||
self.sensors.retain(|s| s.id != sensor_id);
|
||||
|
||||
if self.sensors.len() == initial_len {
|
||||
return Err(AdapterError::Hardware(format!(
|
||||
"Sensor {} not found",
|
||||
sensor_id
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get all sensors
|
||||
pub fn sensors(&self) -> &[SensorInfo] {
|
||||
&self.sensors
|
||||
}
|
||||
|
||||
/// Get operational sensors
|
||||
pub fn operational_sensors(&self) -> Vec<&SensorInfo> {
|
||||
self.sensors
|
||||
.iter()
|
||||
.filter(|s| s.status == SensorStatus::Connected)
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Get sensor positions for localization
|
||||
pub fn sensor_positions(&self) -> Vec<SensorPosition> {
|
||||
self.sensors
|
||||
.iter()
|
||||
.filter(|s| s.status == SensorStatus::Connected)
|
||||
.map(|s| s.position.clone())
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Read CSI data from sensors
|
||||
pub fn read_csi(&self) -> Result<CsiReadings, AdapterError> {
|
||||
if !self.initialized {
|
||||
return Err(AdapterError::Hardware("Hardware not initialized".into()));
|
||||
}
|
||||
|
||||
// In production, this would read actual CSI data
|
||||
// For now, return empty readings
|
||||
Ok(CsiReadings {
|
||||
timestamp: chrono::Utc::now(),
|
||||
readings: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Read RSSI from all sensors
|
||||
pub fn read_rssi(&self) -> Result<Vec<(String, f64)>, AdapterError> {
|
||||
if !self.initialized {
|
||||
return Err(AdapterError::Hardware("Hardware not initialized".into()));
|
||||
}
|
||||
|
||||
// Return last known RSSI values
|
||||
Ok(self
|
||||
.sensors
|
||||
.iter()
|
||||
.filter_map(|s| s.last_rssi.map(|rssi| (s.id.clone(), rssi)))
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// Update sensor position
|
||||
pub fn update_sensor_position(
|
||||
&mut self,
|
||||
sensor_id: &str,
|
||||
position: SensorPosition,
|
||||
) -> Result<(), AdapterError> {
|
||||
let sensor = self
|
||||
.sensors
|
||||
.iter_mut()
|
||||
.find(|s| s.id == sensor_id)
|
||||
.ok_or_else(|| AdapterError::Hardware(format!("Sensor {} not found", sensor_id)))?;
|
||||
|
||||
sensor.position = position;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check hardware health
|
||||
pub fn health_check(&self) -> HardwareHealth {
|
||||
let total = self.sensors.len();
|
||||
let connected = self
|
||||
.sensors
|
||||
.iter()
|
||||
.filter(|s| s.status == SensorStatus::Connected)
|
||||
.count();
|
||||
let low_battery = self
|
||||
.sensors
|
||||
.iter()
|
||||
.filter(|s| matches!(s.battery_level, Some(b) if b < 20))
|
||||
.count();
|
||||
|
||||
let status = if connected == 0 && total > 0 {
|
||||
HealthStatus::Critical
|
||||
} else if connected < total / 2 {
|
||||
HealthStatus::Degraded
|
||||
} else if low_battery > 0 {
|
||||
HealthStatus::Warning
|
||||
} else {
|
||||
HealthStatus::Healthy
|
||||
};
|
||||
|
||||
HardwareHealth {
|
||||
status,
|
||||
total_sensors: total,
|
||||
connected_sensors: connected,
|
||||
low_battery_sensors: low_battery,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for HardwareAdapter {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// CSI readings from sensors
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CsiReadings {
|
||||
/// Timestamp of readings
|
||||
pub timestamp: chrono::DateTime<chrono::Utc>,
|
||||
/// Individual sensor readings
|
||||
pub readings: Vec<SensorCsiReading>,
|
||||
}
|
||||
|
||||
/// CSI reading from a single sensor
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SensorCsiReading {
|
||||
/// Sensor ID
|
||||
pub sensor_id: String,
|
||||
/// CSI amplitudes (per subcarrier)
|
||||
pub amplitudes: Vec<f64>,
|
||||
/// CSI phases (per subcarrier)
|
||||
pub phases: Vec<f64>,
|
||||
/// RSSI value
|
||||
pub rssi: f64,
|
||||
/// Noise floor
|
||||
pub noise_floor: f64,
|
||||
}
|
||||
|
||||
/// Hardware health status
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct HardwareHealth {
|
||||
/// Overall status
|
||||
pub status: HealthStatus,
|
||||
/// Total number of sensors
|
||||
pub total_sensors: usize,
|
||||
/// Number of connected sensors
|
||||
pub connected_sensors: usize,
|
||||
/// Number of sensors with low battery
|
||||
pub low_battery_sensors: usize,
|
||||
}
|
||||
|
||||
/// Health status levels
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum HealthStatus {
|
||||
/// All systems operational
|
||||
Healthy,
|
||||
/// Minor issues, still functional
|
||||
Warning,
|
||||
/// Significant issues, reduced capability
|
||||
Degraded,
|
||||
/// System not functional
|
||||
Critical,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn create_test_sensor(id: &str) -> SensorInfo {
|
||||
SensorInfo {
|
||||
id: id.to_string(),
|
||||
position: SensorPosition {
|
||||
id: id.to_string(),
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 1.5,
|
||||
sensor_type: SensorType::Transceiver,
|
||||
is_operational: true,
|
||||
},
|
||||
status: SensorStatus::Connected,
|
||||
last_rssi: Some(-45.0),
|
||||
battery_level: Some(80),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_sensor() {
|
||||
let mut adapter = HardwareAdapter::new();
|
||||
adapter.initialize().unwrap();
|
||||
|
||||
let sensor = create_test_sensor("s1");
|
||||
assert!(adapter.add_sensor(sensor).is_ok());
|
||||
assert_eq!(adapter.sensors().len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_duplicate_sensor_error() {
|
||||
let mut adapter = HardwareAdapter::new();
|
||||
adapter.initialize().unwrap();
|
||||
|
||||
let sensor1 = create_test_sensor("s1");
|
||||
let sensor2 = create_test_sensor("s1");
|
||||
|
||||
adapter.add_sensor(sensor1).unwrap();
|
||||
assert!(adapter.add_sensor(sensor2).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_health_check() {
|
||||
let mut adapter = HardwareAdapter::new();
|
||||
adapter.initialize().unwrap();
|
||||
|
||||
// No sensors - should be healthy (nothing to fail)
|
||||
let health = adapter.health_check();
|
||||
assert!(matches!(health.status, HealthStatus::Healthy));
|
||||
|
||||
// Add connected sensor
|
||||
adapter.add_sensor(create_test_sensor("s1")).unwrap();
|
||||
let health = adapter.health_check();
|
||||
assert!(matches!(health.status, HealthStatus::Healthy));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sensor_positions() {
|
||||
let mut adapter = HardwareAdapter::new();
|
||||
adapter.initialize().unwrap();
|
||||
|
||||
adapter.add_sensor(create_test_sensor("s1")).unwrap();
|
||||
adapter.add_sensor(create_test_sensor("s2")).unwrap();
|
||||
|
||||
let positions = adapter.sensor_positions();
|
||||
assert_eq!(positions.len(), 2);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user