1use crate::{Controller, ControllerError};
4use async_trait::async_trait;
5use serde::{Deserialize, Serialize};
6
7#[async_trait]
14pub trait LidarController: Controller {
15 type Scan: Send + Sync;
17
18 async fn get_scan(&mut self) -> Result<Self::Scan, Self::Error>;
22
23 async fn set_motor_speed(&mut self, _rpm: u16) -> Result<(), Self::Error>
27 where
28 Self::Error: From<ControllerError>,
29 {
30 Err(Self::Error::from(ControllerError::NotSupported(
31 "Motor speed control not supported".to_string(),
32 )))
33 }
34
35 async fn get_motor_speed(&self) -> Result<u16, Self::Error>
37 where
38 Self::Error: From<ControllerError>,
39 {
40 Err(Self::Error::from(ControllerError::NotSupported(
41 "Motor speed reading not supported".to_string(),
42 )))
43 }
44
45 fn info(&self) -> LidarInfo;
47
48 fn field_of_view(&self) -> FieldOfView;
50
51 fn is_3d(&self) -> bool {
53 self.capabilities().features.get("3d").copied().unwrap_or(false)
54 }
55
56 fn has_intensity(&self) -> bool {
58 self.capabilities().features.get("intensity").copied().unwrap_or(false)
59 }
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize)]
68pub struct LidarInfo {
69 pub model: String,
71
72 pub scan_type: ScanType,
74
75 pub range_min: f32,
77
78 pub range_max: f32,
80
81 pub angular_resolution: f32,
83
84 pub scan_frequency: f32,
86
87 pub points_per_scan: Option<usize>,
89}
90
91impl Default for LidarInfo {
92 fn default() -> Self {
93 Self {
94 model: "Unknown".to_string(),
95 scan_type: ScanType::Planar2D,
96 range_min: 0.15,
97 range_max: 12.0,
98 angular_resolution: 0.36,
99 scan_frequency: 10.0,
100 points_per_scan: None,
101 }
102 }
103}
104
105#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
107pub enum ScanType {
108 Planar2D,
110
111 Volumetric3D,
113
114 MultiLayer,
116}
117
118#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
120pub struct FieldOfView {
121 pub horizontal: f32,
123
124 pub vertical: Option<f32>,
126
127 pub angle_min: f32,
129
130 pub angle_max: f32,
132}
133
134impl Default for FieldOfView {
135 fn default() -> Self {
136 Self {
137 horizontal: 2.0 * std::f32::consts::PI, vertical: None,
139 angle_min: 0.0,
140 angle_max: 2.0 * std::f32::consts::PI,
141 }
142 }
143}
144
145#[derive(Debug, Clone, Serialize, Deserialize)]
151pub struct LaserScan2D {
152 pub timestamp: u64,
154
155 pub scan_id: u64,
157
158 pub angle_min: f32,
160
161 pub angle_max: f32,
163
164 pub angle_increment: f32,
166
167 pub range_min: f32,
169
170 pub range_max: f32,
172
173 pub ranges: Vec<f32>,
175
176 pub intensities: Vec<f32>,
178
179 pub quality: Vec<u8>,
181}
182
183impl Default for LaserScan2D {
184 fn default() -> Self {
185 Self {
186 timestamp: 0,
187 scan_id: 0,
188 angle_min: 0.0,
189 angle_max: 2.0 * std::f32::consts::PI,
190 angle_increment: 0.01,
191 range_min: 0.15,
192 range_max: 12.0,
193 ranges: Vec::new(),
194 intensities: Vec::new(),
195 quality: Vec::new(),
196 }
197 }
198}
199
200impl LaserScan2D {
201 pub fn num_points(&self) -> usize {
203 self.ranges.len()
204 }
205
206 pub fn point_at(&self, index: usize) -> Option<(f32, f32)> {
208 if index >= self.ranges.len() {
209 return None;
210 }
211
212 let range = self.ranges[index];
213 if range < self.range_min || range > self.range_max {
214 return None;
215 }
216
217 let angle = self.angle_min + (index as f32) * self.angle_increment;
218 let x = range * angle.cos();
219 let y = range * angle.sin();
220
221 Some((x, y))
222 }
223
224 pub fn to_point_cloud(&self) -> Vec<(f32, f32)> {
226 (0..self.ranges.len()).filter_map(|i| self.point_at(i)).collect()
227 }
228}
229
230#[derive(Debug, Clone, Serialize, Deserialize)]
232pub struct PointCloud3D {
233 pub timestamp: u64,
235
236 pub scan_id: u64,
238
239 pub points: Vec<[f32; 3]>,
241
242 pub intensities: Vec<f32>,
244
245 pub colors: Vec<[u8; 3]>,
247
248 pub frame_id: String,
250}
251
252impl Default for PointCloud3D {
253 fn default() -> Self {
254 Self {
255 timestamp: 0,
256 scan_id: 0,
257 points: Vec::new(),
258 intensities: Vec::new(),
259 colors: Vec::new(),
260 frame_id: "lidar".to_string(),
261 }
262 }
263}
264
265impl PointCloud3D {
266 pub fn num_points(&self) -> usize {
268 self.points.len()
269 }
270
271 pub fn filter_range(&self, min: f32, max: f32) -> Self {
273 let mut filtered = Self {
274 timestamp: self.timestamp,
275 scan_id: self.scan_id,
276 frame_id: self.frame_id.clone(),
277 ..Default::default()
278 };
279
280 for (i, point) in self.points.iter().enumerate() {
281 let distance = (point[0] * point[0] + point[1] * point[1] + point[2] * point[2]).sqrt();
282 if distance >= min && distance <= max {
283 filtered.points.push(*point);
284 if i < self.intensities.len() {
285 filtered.intensities.push(self.intensities[i]);
286 }
287 if i < self.colors.len() {
288 filtered.colors.push(self.colors[i]);
289 }
290 }
291 }
292
293 filtered
294 }
295}
296
297#[derive(Debug, Clone, Serialize, Deserialize)]
303pub struct LidarConfig {
304 pub scan_frequency: f32,
306
307 pub angular_resolution: f32,
309
310 pub range_min: f32,
312
313 pub range_max: f32,
315
316 pub enable_intensity: bool,
318
319 pub motor_rpm: Option<u16>,
321
322 pub filter_invalid: bool,
324
325 pub median_filter_size: usize,
327}
328
329impl Default for LidarConfig {
330 fn default() -> Self {
331 Self {
332 scan_frequency: 10.0,
333 angular_resolution: 0.36,
334 range_min: 0.15,
335 range_max: 12.0,
336 enable_intensity: true,
337 motor_rpm: None,
338 filter_invalid: true,
339 median_filter_size: 0,
340 }
341 }
342}
343
344#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
350pub enum LidarHealthStatus {
351 Good,
353
354 Warning,
356
357 Error,
359
360 Unknown,
362}
363
364#[derive(Debug, Clone, Serialize, Deserialize)]
366pub struct LidarDiagnostics {
367 pub health: LidarHealthStatus,
369
370 pub motor_rpm: Option<u16>,
372
373 pub temperature: Option<f32>,
375
376 pub error_code: Option<u32>,
378
379 pub error_message: Option<String>,
381
382 pub valid_points: Option<usize>,
384
385 pub invalid_points: Option<usize>,
387
388 pub actual_scan_rate: Option<f32>,
390}
391
392impl Default for LidarDiagnostics {
393 fn default() -> Self {
394 Self {
395 health: LidarHealthStatus::Unknown,
396 motor_rpm: None,
397 temperature: None,
398 error_code: None,
399 error_message: None,
400 valid_points: None,
401 invalid_points: None,
402 actual_scan_rate: None,
403 }
404 }
405}