phytrace_sdk/models/domains/
navigation.rs1use serde::{Deserialize, Serialize};
6use validator::Validate;
7
8use super::common::Position2D;
9use crate::models::enums::{LocalizationQuality, ObstacleType, PathState};
10
11#[derive(Debug, Clone, Default, Serialize, Deserialize, Validate)]
13pub struct NavigationDomain {
14 #[serde(skip_serializing_if = "Option::is_none")]
17 pub localization: Option<Localization>,
18
19 #[serde(skip_serializing_if = "Option::is_none")]
22 pub slam: Option<SlamStatus>,
23
24 #[serde(skip_serializing_if = "Option::is_none")]
27 pub path: Option<PathInfo>,
28
29 #[serde(skip_serializing_if = "Option::is_none")]
32 pub goal: Option<NavigationGoal>,
33
34 #[serde(skip_serializing_if = "Option::is_none")]
37 pub obstacles: Option<Vec<Obstacle>>,
38
39 #[serde(skip_serializing_if = "Option::is_none")]
41 pub obstacle_count: Option<u32>,
42
43 #[serde(skip_serializing_if = "Option::is_none")]
46 pub semantic_map: Option<SemanticMapInfo>,
47
48 #[serde(skip_serializing_if = "Option::is_none")]
51 pub costmap: Option<CostmapInfo>,
52}
53
54#[derive(Debug, Clone, Default, Serialize, Deserialize, Validate)]
56pub struct Localization {
57 #[serde(skip_serializing_if = "Option::is_none")]
59 pub quality: Option<LocalizationQuality>,
60
61 #[serde(skip_serializing_if = "Option::is_none")]
63 #[validate(range(min = 0.0, max = 1.0))]
64 pub confidence: Option<f64>,
65
66 #[serde(skip_serializing_if = "Option::is_none")]
68 pub method: Option<String>,
69
70 #[serde(skip_serializing_if = "Option::is_none")]
72 pub is_localized: Option<bool>,
73
74 #[serde(skip_serializing_if = "Option::is_none")]
76 pub position_covariance_m2: Option<f64>,
77
78 #[serde(skip_serializing_if = "Option::is_none")]
80 pub orientation_covariance_rad2: Option<f64>,
81
82 #[serde(skip_serializing_if = "Option::is_none")]
84 pub particle_count: Option<u32>,
85
86 #[serde(skip_serializing_if = "Option::is_none")]
88 pub features_matched: Option<u32>,
89
90 #[serde(skip_serializing_if = "Option::is_none")]
92 pub time_since_good_loc_sec: Option<f64>,
93}
94
95#[derive(Debug, Clone, Default, Serialize, Deserialize)]
97pub struct SlamStatus {
98 #[serde(skip_serializing_if = "Option::is_none")]
100 pub is_active: Option<bool>,
101
102 #[serde(skip_serializing_if = "Option::is_none")]
104 pub mode: Option<String>,
105
106 #[serde(skip_serializing_if = "Option::is_none")]
108 pub algorithm: Option<String>,
109
110 #[serde(skip_serializing_if = "Option::is_none")]
112 pub map_size: Option<u32>,
113
114 #[serde(skip_serializing_if = "Option::is_none")]
116 pub map_area_m2: Option<f64>,
117
118 #[serde(skip_serializing_if = "Option::is_none")]
120 pub loop_closures: Option<u32>,
121
122 #[serde(skip_serializing_if = "Option::is_none")]
124 pub last_loop_closure: Option<chrono::DateTime<chrono::Utc>>,
125}
126
127#[derive(Debug, Clone, Default, Serialize, Deserialize, Validate)]
129pub struct PathInfo {
130 #[serde(skip_serializing_if = "Option::is_none")]
132 pub state: Option<PathState>,
133
134 #[serde(skip_serializing_if = "Option::is_none")]
136 #[validate(range(min = 0.0))]
137 pub length_m: Option<f64>,
138
139 #[serde(skip_serializing_if = "Option::is_none")]
141 #[validate(range(min = 0.0))]
142 pub eta_sec: Option<f64>,
143
144 #[serde(skip_serializing_if = "Option::is_none")]
146 #[validate(range(min = 0.0))]
147 pub remaining_m: Option<f64>,
148
149 #[serde(skip_serializing_if = "Option::is_none")]
151 #[validate(range(min = 0.0, max = 100.0))]
152 pub progress_pct: Option<f64>,
153
154 #[serde(skip_serializing_if = "Option::is_none")]
156 pub waypoint_count: Option<u32>,
157
158 #[serde(skip_serializing_if = "Option::is_none")]
160 pub current_waypoint: Option<u32>,
161
162 #[serde(skip_serializing_if = "Option::is_none")]
164 pub waypoints: Option<Vec<Position2D>>,
165
166 #[serde(skip_serializing_if = "Option::is_none")]
168 pub planner: Option<String>,
169
170 #[serde(skip_serializing_if = "Option::is_none")]
172 pub is_valid: Option<bool>,
173
174 #[serde(skip_serializing_if = "Option::is_none")]
176 pub replan_count: Option<u32>,
177}
178
179#[derive(Debug, Clone, Default, Serialize, Deserialize, Validate)]
181pub struct NavigationGoal {
182 #[serde(skip_serializing_if = "Option::is_none")]
184 pub goal_id: Option<String>,
185
186 #[serde(skip_serializing_if = "Option::is_none")]
188 pub position: Option<Position2D>,
189
190 #[serde(skip_serializing_if = "Option::is_none")]
192 #[validate(range(min = -180.0, max = 180.0))]
193 pub orientation_deg: Option<f64>,
194
195 #[serde(skip_serializing_if = "Option::is_none")]
197 pub name: Option<String>,
198
199 #[serde(skip_serializing_if = "Option::is_none")]
201 pub goal_type: Option<String>,
202
203 #[serde(skip_serializing_if = "Option::is_none")]
205 pub frame_id: Option<String>,
206
207 #[serde(skip_serializing_if = "Option::is_none")]
209 #[validate(range(min = 0.0))]
210 pub distance_m: Option<f64>,
211
212 #[serde(skip_serializing_if = "Option::is_none")]
214 pub position_tolerance_m: Option<f64>,
215
216 #[serde(skip_serializing_if = "Option::is_none")]
218 pub orientation_tolerance_deg: Option<f64>,
219}
220
221#[derive(Debug, Clone, Default, Serialize, Deserialize, Validate)]
223pub struct Obstacle {
224 #[serde(skip_serializing_if = "Option::is_none")]
226 pub obstacle_id: Option<String>,
227
228 #[serde(skip_serializing_if = "Option::is_none")]
230 pub obstacle_type: Option<ObstacleType>,
231
232 #[serde(skip_serializing_if = "Option::is_none")]
234 pub position: Option<Position2D>,
235
236 #[serde(skip_serializing_if = "Option::is_none")]
238 #[validate(range(min = 0.0))]
239 pub distance_m: Option<f64>,
240
241 #[serde(skip_serializing_if = "Option::is_none")]
243 pub bearing_deg: Option<f64>,
244
245 #[serde(skip_serializing_if = "Option::is_none")]
247 pub velocity_mps: Option<f64>,
248
249 #[serde(skip_serializing_if = "Option::is_none")]
251 pub heading_deg: Option<f64>,
252
253 #[serde(skip_serializing_if = "Option::is_none")]
255 pub on_path: Option<bool>,
256
257 #[serde(skip_serializing_if = "Option::is_none")]
259 pub ttc_sec: Option<f64>,
260
261 #[serde(skip_serializing_if = "Option::is_none")]
263 #[validate(range(min = 0.0, max = 1.0))]
264 pub confidence: Option<f64>,
265}
266
267#[derive(Debug, Clone, Default, Serialize, Deserialize)]
269pub struct SemanticMapInfo {
270 #[serde(skip_serializing_if = "Option::is_none")]
272 pub map_id: Option<String>,
273
274 #[serde(skip_serializing_if = "Option::is_none")]
276 pub name: Option<String>,
277
278 #[serde(skip_serializing_if = "Option::is_none")]
280 pub version: Option<String>,
281
282 #[serde(skip_serializing_if = "Option::is_none")]
284 pub zone_count: Option<u32>,
285
286 #[serde(skip_serializing_if = "Option::is_none")]
288 pub waypoint_count: Option<u32>,
289
290 #[serde(skip_serializing_if = "Option::is_none")]
292 pub edge_count: Option<u32>,
293
294 #[serde(skip_serializing_if = "Option::is_none")]
296 pub current_zones: Option<Vec<String>>,
297}
298
299#[derive(Debug, Clone, Default, Serialize, Deserialize, Validate)]
301pub struct CostmapInfo {
302 #[serde(skip_serializing_if = "Option::is_none")]
304 pub width: Option<u32>,
305
306 #[serde(skip_serializing_if = "Option::is_none")]
308 pub height: Option<u32>,
309
310 #[serde(skip_serializing_if = "Option::is_none")]
312 pub resolution_m: Option<f64>,
313
314 #[serde(skip_serializing_if = "Option::is_none")]
316 #[validate(range(min = 0.0, max = 100.0))]
317 pub unknown_pct: Option<f64>,
318
319 #[serde(skip_serializing_if = "Option::is_none")]
321 #[validate(range(min = 0.0, max = 100.0))]
322 pub occupied_pct: Option<f64>,
323
324 #[serde(skip_serializing_if = "Option::is_none")]
326 pub last_update: Option<chrono::DateTime<chrono::Utc>>,
327}
328
329impl NavigationDomain {
330 pub fn from_localization(quality: LocalizationQuality, confidence: f64) -> Self {
332 Self {
333 localization: Some(Localization {
334 quality: Some(quality),
335 confidence: Some(confidence),
336 is_localized: Some(quality != LocalizationQuality::Lost),
337 ..Default::default()
338 }),
339 ..Default::default()
340 }
341 }
342
343 pub fn with_path(mut self, path: PathInfo) -> Self {
345 self.path = Some(path);
346 self
347 }
348
349 pub fn with_goal(mut self, goal: NavigationGoal) -> Self {
351 self.goal = Some(goal);
352 self
353 }
354
355 pub fn with_obstacle(mut self, obstacle: Obstacle) -> Self {
357 let obstacles = self.obstacles.get_or_insert_with(Vec::new);
358 obstacles.push(obstacle);
359 self.obstacle_count = Some(obstacles.len() as u32);
360 self
361 }
362}
363
364impl NavigationGoal {
365 pub fn new(x: f64, y: f64, yaw_deg: f64) -> Self {
367 Self {
368 position: Some(Position2D::new(x, y)),
369 orientation_deg: Some(yaw_deg),
370 ..Default::default()
371 }
372 }
373
374 pub fn with_name(mut self, name: impl Into<String>) -> Self {
376 self.name = Some(name.into());
377 self
378 }
379}
380
381impl PathInfo {
382 pub fn new(state: PathState, length_m: f64) -> Self {
384 Self {
385 state: Some(state),
386 length_m: Some(length_m),
387 is_valid: Some(state == PathState::Valid || state == PathState::Executing),
388 ..Default::default()
389 }
390 }
391
392 pub fn with_progress(mut self, remaining_m: f64, progress_pct: f64) -> Self {
394 self.remaining_m = Some(remaining_m);
395 self.progress_pct = Some(progress_pct);
396 self
397 }
398}
399
400#[cfg(test)]
401mod tests {
402 use super::*;
403
404 #[test]
405 fn test_navigation_domain() {
406 let nav = NavigationDomain::from_localization(LocalizationQuality::Good, 0.95)
407 .with_goal(NavigationGoal::new(10.0, 5.0, 90.0).with_name("pick_station"));
408
409 assert_eq!(nav.localization.as_ref().unwrap().confidence, Some(0.95));
410 assert_eq!(
411 nav.goal.as_ref().unwrap().name,
412 Some("pick_station".to_string())
413 );
414 }
415
416 #[test]
417 fn test_path_info() {
418 let path = PathInfo::new(PathState::Executing, 25.0).with_progress(10.0, 60.0);
419
420 assert_eq!(path.length_m, Some(25.0));
421 assert_eq!(path.remaining_m, Some(10.0));
422 assert_eq!(path.progress_pct, Some(60.0));
423 }
424}