wifi_densepose_mat/localization/
fusion.rs1use crate::domain::{
4 Coordinates3D, LocationUncertainty, ScanZone, VitalSignsReading,
5 DepthEstimate, DebrisProfile,
6};
7use super::{Triangulator, TriangulationConfig, DepthEstimator, DepthEstimatorConfig};
8
9pub struct LocalizationService {
11 triangulator: Triangulator,
12 depth_estimator: DepthEstimator,
13 position_fuser: PositionFuser,
14}
15
16impl LocalizationService {
17 pub fn new() -> Self {
19 Self {
20 triangulator: Triangulator::with_defaults(),
21 depth_estimator: DepthEstimator::with_defaults(),
22 position_fuser: PositionFuser::new(),
23 }
24 }
25
26 pub fn with_config(
28 triangulation_config: TriangulationConfig,
29 depth_config: DepthEstimatorConfig,
30 ) -> Self {
31 Self {
32 triangulator: Triangulator::new(triangulation_config),
33 depth_estimator: DepthEstimator::new(depth_config),
34 position_fuser: PositionFuser::new(),
35 }
36 }
37
38 pub fn estimate_position(
40 &self,
41 vitals: &VitalSignsReading,
42 zone: &ScanZone,
43 ) -> Option<Coordinates3D> {
44 let sensors = zone.sensor_positions();
46
47 if sensors.len() < 3 {
48 return None;
49 }
50
51 let rssi_values = self.simulate_rssi_measurements(sensors, vitals);
54 let position_2d = self.triangulator.estimate_position(sensors, &rssi_values)?;
55
56 let debris_profile = self.estimate_debris_profile(zone);
58 let signal_attenuation = self.calculate_signal_attenuation(&rssi_values);
59 let depth_estimate = self.depth_estimator.estimate_depth(
60 signal_attenuation,
61 0.0,
62 &debris_profile,
63 )?;
64
65 let position_3d = Coordinates3D::new(
67 position_2d.x,
68 position_2d.y,
69 -depth_estimate.depth, self.combine_uncertainties(&position_2d.uncertainty, &depth_estimate),
71 );
72
73 Some(position_3d)
74 }
75
76 fn simulate_rssi_measurements(
82 &self,
83 _sensors: &[crate::domain::SensorPosition],
84 _vitals: &VitalSignsReading,
85 ) -> Vec<(String, f64)> {
86 tracing::warn!("No sensor hardware connected. Real RSSI readings require ESP32 mesh (ADR-012) or Linux WiFi interface (ADR-013).");
90 vec![]
91 }
92
93 fn estimate_debris_profile(&self, _zone: &ScanZone) -> DebrisProfile {
95 DebrisProfile::default()
97 }
98
99 fn calculate_signal_attenuation(&self, rssi_values: &[(String, f64)]) -> f64 {
101 if rssi_values.is_empty() {
102 return 0.0;
103 }
104
105 const REFERENCE_RSSI: f64 = -30.0;
107
108 let avg_rssi: f64 = rssi_values.iter().map(|(_, r)| r).sum::<f64>()
109 / rssi_values.len() as f64;
110
111 (REFERENCE_RSSI - avg_rssi).max(0.0)
112 }
113
114 fn combine_uncertainties(
116 &self,
117 horizontal: &LocationUncertainty,
118 depth: &DepthEstimate,
119 ) -> LocationUncertainty {
120 LocationUncertainty {
121 horizontal_error: horizontal.horizontal_error,
122 vertical_error: depth.uncertainty,
123 confidence: (horizontal.confidence * depth.confidence).sqrt(),
124 }
125 }
126}
127
128impl Default for LocalizationService {
129 fn default() -> Self {
130 Self::new()
131 }
132}
133
134pub struct PositionFuser {
136 history: parking_lot::RwLock<Vec<PositionEstimate>>,
138 max_history: usize,
140}
141
142#[derive(Debug, Clone)]
144pub struct PositionEstimate {
145 pub position: Coordinates3D,
147 pub timestamp: chrono::DateTime<chrono::Utc>,
149 pub source: EstimateSource,
151 pub weight: f64,
153}
154
155#[derive(Debug, Clone, PartialEq, Eq)]
157pub enum EstimateSource {
158 RssiTriangulation,
160 TimeOfArrival,
162 CsiFingerprint,
164 AngleOfArrival,
166 DepthEstimation,
168 Fused,
170}
171
172impl PositionFuser {
173 pub fn new() -> Self {
175 Self {
176 history: parking_lot::RwLock::new(Vec::new()),
177 max_history: 20,
178 }
179 }
180
181 pub fn add_estimate(&self, estimate: PositionEstimate) {
183 let mut history = self.history.write();
184 history.push(estimate);
185
186 if history.len() > self.max_history {
188 history.remove(0);
189 }
190 }
191
192 pub fn fuse(&self, estimates: &[PositionEstimate]) -> Option<Coordinates3D> {
194 if estimates.is_empty() {
195 return None;
196 }
197
198 if estimates.len() == 1 {
199 return Some(estimates[0].position.clone());
200 }
201
202 let mut total_weight = 0.0;
204 let mut sum_x = 0.0;
205 let mut sum_y = 0.0;
206 let mut sum_z = 0.0;
207
208 for estimate in estimates {
209 let weight = self.calculate_weight(estimate);
210 total_weight += weight;
211 sum_x += estimate.position.x * weight;
212 sum_y += estimate.position.y * weight;
213 sum_z += estimate.position.z * weight;
214 }
215
216 if total_weight == 0.0 {
217 return None;
218 }
219
220 let fused_x = sum_x / total_weight;
221 let fused_y = sum_y / total_weight;
222 let fused_z = sum_z / total_weight;
223
224 let fused_uncertainty = self.calculate_fused_uncertainty(estimates);
226
227 Some(Coordinates3D::new(
228 fused_x,
229 fused_y,
230 fused_z,
231 fused_uncertainty,
232 ))
233 }
234
235 pub fn fuse_with_history(&self, current: &PositionEstimate) -> Option<Coordinates3D> {
237 self.add_estimate(current.clone());
239
240 let history = self.history.read();
241
242 let alpha: f64 = 0.3; let mut smoothed = current.position.clone();
245
246 for (i, estimate) in history.iter().rev().enumerate().skip(1) {
247 let weight = alpha * (1.0_f64 - alpha).powi(i as i32);
248 smoothed.x = smoothed.x * (1.0 - weight) + estimate.position.x * weight;
249 smoothed.y = smoothed.y * (1.0 - weight) + estimate.position.y * weight;
250 smoothed.z = smoothed.z * (1.0 - weight) + estimate.position.z * weight;
251 }
252
253 Some(smoothed)
254 }
255
256 fn calculate_weight(&self, estimate: &PositionEstimate) -> f64 {
258 let source_weight = match estimate.source {
260 EstimateSource::TimeOfArrival => 1.0,
261 EstimateSource::AngleOfArrival => 0.9,
262 EstimateSource::CsiFingerprint => 0.8,
263 EstimateSource::RssiTriangulation => 0.7,
264 EstimateSource::DepthEstimation => 0.6,
265 EstimateSource::Fused => 1.0,
266 };
267
268 let uncertainty_factor = 1.0 / (1.0 + estimate.position.uncertainty.horizontal_error);
270
271 let user_weight = estimate.weight;
273
274 source_weight * uncertainty_factor * user_weight
275 }
276
277 fn calculate_fused_uncertainty(&self, estimates: &[PositionEstimate]) -> LocationUncertainty {
279 if estimates.is_empty() {
280 return LocationUncertainty::default();
281 }
282
283 let n = estimates.len() as f64;
285
286 let avg_h_error: f64 = estimates.iter()
287 .map(|e| e.position.uncertainty.horizontal_error)
288 .sum::<f64>() / n;
289
290 let avg_v_error: f64 = estimates.iter()
291 .map(|e| e.position.uncertainty.vertical_error)
292 .sum::<f64>() / n;
293
294 let reduction = (1.0 / n.sqrt()).max(0.5);
296
297 LocationUncertainty {
298 horizontal_error: avg_h_error * reduction,
299 vertical_error: avg_v_error * reduction,
300 confidence: (0.95 * (1.0 + (n - 1.0) * 0.02)).min(0.99),
301 }
302 }
303
304 pub fn clear_history(&self) {
306 self.history.write().clear();
307 }
308}
309
310impl Default for PositionFuser {
311 fn default() -> Self {
312 Self::new()
313 }
314}
315
316
317#[cfg(test)]
318mod tests {
319 use super::*;
320 use chrono::Utc;
321
322 fn create_test_estimate(x: f64, y: f64, z: f64) -> PositionEstimate {
323 PositionEstimate {
324 position: Coordinates3D::with_default_uncertainty(x, y, z),
325 timestamp: Utc::now(),
326 source: EstimateSource::RssiTriangulation,
327 weight: 1.0,
328 }
329 }
330
331 #[test]
332 fn test_single_estimate_fusion() {
333 let fuser = PositionFuser::new();
334 let estimate = create_test_estimate(5.0, 10.0, -2.0);
335
336 let result = fuser.fuse(&[estimate]);
337 assert!(result.is_some());
338
339 let pos = result.unwrap();
340 assert!((pos.x - 5.0).abs() < 0.001);
341 }
342
343 #[test]
344 fn test_multiple_estimate_fusion() {
345 let fuser = PositionFuser::new();
346
347 let estimates = vec![
348 create_test_estimate(4.0, 9.0, -1.5),
349 create_test_estimate(6.0, 11.0, -2.5),
350 ];
351
352 let result = fuser.fuse(&estimates);
353 assert!(result.is_some());
354
355 let pos = result.unwrap();
356 assert!(pos.x > 4.0 && pos.x < 6.0);
358 assert!(pos.y > 9.0 && pos.y < 11.0);
359 }
360
361 #[test]
362 fn test_fused_uncertainty_reduction() {
363 let fuser = PositionFuser::new();
364
365 let estimates = vec![
366 create_test_estimate(5.0, 10.0, -2.0),
367 create_test_estimate(5.1, 10.1, -2.1),
368 create_test_estimate(4.9, 9.9, -1.9),
369 ];
370
371 let single_uncertainty = estimates[0].position.uncertainty.horizontal_error;
372 let fused_uncertainty = fuser.calculate_fused_uncertainty(&estimates);
373
374 assert!(fused_uncertainty.horizontal_error < single_uncertainty);
376 }
377
378 #[test]
379 fn test_localization_service_creation() {
380 let service = LocalizationService::new();
381 assert!(true);
383 drop(service);
384 }
385}