1use crate::error::DriverError;
6use crate::pipeline::PipelineConfig;
7use crate::piper::Piper;
8
9#[cfg(all(not(feature = "mock"), target_os = "linux"))]
10use piper_can::SocketCanAdapter;
11
12#[cfg(not(feature = "mock"))]
13use piper_can::gs_usb::GsUsbCanAdapter;
14
15#[cfg(not(feature = "mock"))]
16use piper_can::gs_usb_udp::GsUsbUdpAdapter;
17
18#[cfg(not(feature = "mock"))]
19use piper_can::{CanDeviceError, CanDeviceErrorKind, CanError};
20
21#[derive(Debug, Clone, Copy)]
23pub enum DriverType {
24 Auto,
28 SocketCan,
30 GsUsb,
32}
33
34pub struct PiperBuilder {
61 interface: Option<String>,
66 baud_rate: Option<u32>,
68 pipeline_config: Option<PipelineConfig>,
70 daemon_addr: Option<String>,
74 driver_type: DriverType,
76}
77
78impl PiperBuilder {
79 pub fn new() -> Self {
89 Self {
90 interface: None,
91 baud_rate: None,
92 pipeline_config: None,
93 daemon_addr: None,
94 driver_type: DriverType::Auto,
95 }
96 }
97
98 pub fn with_driver_type(mut self, driver_type: DriverType) -> Self {
112 self.driver_type = driver_type;
113 self
114 }
115
116 pub fn interface(mut self, interface: impl Into<String>) -> Self {
138 self.interface = Some(interface.into());
139 self
140 }
141
142 pub fn baud_rate(mut self, baud_rate: u32) -> Self {
144 self.baud_rate = Some(baud_rate);
145 self
146 }
147
148 pub fn pipeline_config(mut self, config: PipelineConfig) -> Self {
150 self.pipeline_config = Some(config);
151 self
152 }
153
154 pub fn with_daemon(mut self, daemon_addr: impl Into<String>) -> Self {
182 self.daemon_addr = Some(daemon_addr.into());
183 self
184 }
185
186 pub fn build(self) -> Result<Piper, DriverError> {
209 #[cfg(not(feature = "mock"))]
211 if let Some(ref daemon_addr) = self.daemon_addr {
212 return self.build_gs_usb_daemon(daemon_addr.clone());
213 }
214
215 #[cfg(not(feature = "mock"))]
217 {
218 match self.driver_type {
219 DriverType::Auto => {
220 #[cfg(target_os = "linux")]
222 {
223 if let Some(ref interface) = self.interface {
224 if interface.starts_with("can") && interface.len() <= 5 {
226 if let Ok(piper) = self.build_socketcan(interface.as_str()) {
228 return Ok(piper);
229 }
230 tracing::info!(
232 "SocketCAN interface '{}' not available, falling back to GS-USB",
233 interface
234 );
235 }
236 }
237 self.build_gs_usb_direct()
239 }
240
241 #[cfg(not(target_os = "linux"))]
243 {
244 self.build_gs_usb_direct()
245 }
246 },
247 DriverType::SocketCan => {
248 #[cfg(target_os = "linux")]
249 {
250 let interface = self.interface.as_deref().unwrap_or("can0");
251 self.build_socketcan(interface)
252 }
253 #[cfg(not(target_os = "linux"))]
254 {
255 Err(DriverError::Can(CanError::Device(CanDeviceError::new(
256 CanDeviceErrorKind::UnsupportedConfig,
257 "SocketCAN is only available on Linux",
258 ))))
259 }
260 },
261 DriverType::GsUsb => self.build_gs_usb_direct(),
262 }
263 }
264
265 #[cfg(feature = "mock")]
267 {
268 use piper_can::MockCanAdapter;
269 let can = MockCanAdapter::new();
270
271 let interface = self.interface.unwrap_or_else(|| "mock".to_string());
272 let bus_speed = self.baud_rate.unwrap_or(1_000_000);
273 Piper::new(can, self.pipeline_config)
274 .map(|p| p.with_metadata(interface, bus_speed))
275 .map_err(DriverError::Can)
276 }
277 }
278
279 #[cfg(all(not(feature = "mock"), target_os = "linux"))]
281 fn build_socketcan(&self, interface: &str) -> Result<Piper, DriverError> {
282 let mut can = SocketCanAdapter::new(interface).map_err(DriverError::Can)?;
283
284 if let Some(bitrate) = self.baud_rate {
286 can.configure(bitrate).map_err(DriverError::Can)?;
287 }
288
289 let config = self.pipeline_config.clone().unwrap_or_default();
290 can.set_read_timeout(std::time::Duration::from_millis(config.receive_timeout_ms))
291 .map_err(DriverError::Can)?;
292
293 let interface = interface.to_string();
295 let bus_speed = self.baud_rate.unwrap_or(1_000_000);
296 Piper::new_dual_thread(can, self.pipeline_config.clone())
297 .map(|p| p.with_metadata(interface, bus_speed))
298 .map_err(DriverError::Can)
299 }
300
301 #[cfg(not(feature = "mock"))]
303 fn build_gs_usb_direct(&self) -> Result<Piper, DriverError> {
304 let mut can = match &self.interface {
310 Some(serial) if serial.contains(':') => {
311 let parts: Vec<&str> = serial.split(':').collect();
313 if parts.len() == 2 {
314 if let (Ok(bus), Ok(addr)) = (parts[0].parse::<u8>(), parts[1].parse::<u8>()) {
315 use piper_can::gs_usb::device::{GsUsbDevice, GsUsbDeviceSelector};
316 let selector = GsUsbDeviceSelector::by_bus_address(bus, addr);
317 let _device = GsUsbDevice::open(&selector).map_err(|e| {
318 DriverError::Can(CanError::Device(CanDeviceError::new(
319 CanDeviceErrorKind::Backend,
320 format!("Failed to open GS-USB device at {}:{}: {}", bus, addr, e),
321 )))
322 })?;
323 GsUsbCanAdapter::new_with_serial(Some(serial.as_str()))
326 .map_err(DriverError::Can)?
327 } else {
328 GsUsbCanAdapter::new_with_serial(Some(serial.as_str()))
329 .map_err(DriverError::Can)?
330 }
331 } else {
332 GsUsbCanAdapter::new_with_serial(Some(serial.as_str()))
333 .map_err(DriverError::Can)?
334 }
335 },
336 Some(serial) => {
337 GsUsbCanAdapter::new_with_serial(Some(serial.as_str())).map_err(DriverError::Can)?
338 },
339 None => GsUsbCanAdapter::new().map_err(DriverError::Can)?,
340 };
341
342 let bitrate = self.baud_rate.unwrap_or(1_000_000);
343 can.configure(bitrate).map_err(DriverError::Can)?;
344
345 let config = self.pipeline_config.clone().unwrap_or_default();
346 can.set_receive_timeout(std::time::Duration::from_millis(config.receive_timeout_ms));
347
348 let interface = self.interface.clone().unwrap_or_else(|| {
350 "unknown".to_string()
352 });
353 let bus_speed = bitrate;
354 Piper::new_dual_thread(can, self.pipeline_config.clone())
355 .map(|p| p.with_metadata(interface, bus_speed))
356 .map_err(DriverError::Can)
357 }
358
359 #[cfg(not(feature = "mock"))]
363 fn build_gs_usb_daemon(&self, daemon_addr: String) -> Result<Piper, DriverError> {
364 let mut can = if daemon_addr.starts_with('/') || daemon_addr.starts_with("unix:") {
365 #[cfg(unix)]
367 {
368 let path = daemon_addr.strip_prefix("unix:").unwrap_or(&daemon_addr);
369 GsUsbUdpAdapter::new_uds(path).map_err(DriverError::Can)?
370 }
371 #[cfg(not(unix))]
372 {
373 return Err(DriverError::Can(CanError::Device(
374 "Unix Domain Sockets are not supported on this platform. Please use UDP address format (e.g., 127.0.0.1:8888)".into(),
375 )));
376 }
377 } else {
378 GsUsbUdpAdapter::new_udp(&daemon_addr).map_err(DriverError::Can)?
380 };
381
382 can.connect(vec![]).map_err(DriverError::Can)?;
384
385 let interface = daemon_addr.clone();
388 let bus_speed = self.baud_rate.unwrap_or(1_000_000);
389 Piper::new(can, self.pipeline_config.clone())
390 .map(|p| p.with_metadata(interface, bus_speed))
391 .map_err(DriverError::Can)
392 }
393}
394
395impl Default for PiperBuilder {
396 fn default() -> Self {
397 Self::new()
398 }
399}
400
401#[cfg(test)]
402mod tests {
403 use super::*;
404
405 #[test]
406 fn test_piper_builder_new() {
407 let builder = PiperBuilder::new();
408 assert_eq!(builder.interface, None);
409 assert_eq!(builder.baud_rate, None);
410 assert!(builder.pipeline_config.is_none());
413 assert_eq!(builder.daemon_addr, None);
414 }
415
416 #[test]
417 fn test_piper_builder_chain() {
418 let builder = PiperBuilder::new().interface("can0").baud_rate(500_000);
419
420 assert_eq!(builder.interface, Some("can0".to_string()));
421 assert_eq!(builder.baud_rate, Some(500_000));
422 }
423
424 #[test]
425 fn test_piper_builder_default() {
426 let builder = PiperBuilder::default();
427 assert_eq!(builder.interface, None);
428 assert_eq!(builder.baud_rate, None);
429 }
430
431 #[test]
432 fn test_piper_builder_pipeline_config() {
433 let config = PipelineConfig {
434 receive_timeout_ms: 5,
435 frame_group_timeout_ms: 20,
436 velocity_buffer_timeout_us: 10_000,
437 };
438 let builder = PiperBuilder::new().pipeline_config(config.clone());
439
440 assert!(builder.pipeline_config.is_some());
442 let stored_config = builder.pipeline_config.as_ref().unwrap();
443 assert_eq!(stored_config.receive_timeout_ms, 5);
444 assert_eq!(stored_config.frame_group_timeout_ms, 20);
445 }
446
447 #[test]
448 fn test_piper_builder_all_options() {
449 let config = PipelineConfig {
450 receive_timeout_ms: 3,
451 frame_group_timeout_ms: 15,
452 velocity_buffer_timeout_us: 10_000,
453 };
454 let builder =
455 PiperBuilder::new().interface("can1").baud_rate(250_000).pipeline_config(config);
456
457 assert_eq!(builder.interface, Some("can1".to_string()));
458 assert_eq!(builder.baud_rate, Some(250_000));
459 assert!(builder.pipeline_config.is_some());
460 }
461
462 #[test]
463 fn test_piper_builder_interface_chaining() {
464 let builder1 = PiperBuilder::new().interface("can0");
465 let builder2 = builder1.interface("can1");
466
467 assert_eq!(builder2.interface, Some("can1".to_string()));
469 }
470
471 #[test]
472 fn test_piper_builder_receive_timeout_config() {
473 let config = PipelineConfig {
476 receive_timeout_ms: 5,
477 frame_group_timeout_ms: 20,
478 velocity_buffer_timeout_us: 10_000,
479 };
480 let builder = PiperBuilder::new().pipeline_config(config.clone());
481
482 assert!(builder.pipeline_config.is_some());
484 let stored_config = builder.pipeline_config.as_ref().unwrap();
485 assert_eq!(stored_config.receive_timeout_ms, 5);
486 assert_eq!(stored_config.frame_group_timeout_ms, 20);
487
488 let default_config = PipelineConfig::default();
490 assert_eq!(default_config.receive_timeout_ms, 2);
491 assert_eq!(default_config.frame_group_timeout_ms, 10);
492 }
493
494 #[test]
495 fn test_piper_builder_baud_rate_chaining() {
496 let builder1 = PiperBuilder::new().baud_rate(1_000_000);
497 let builder2 = builder1.baud_rate(500_000);
498
499 assert_eq!(builder2.baud_rate, Some(500_000));
501 }
502
503 #[test]
504 fn test_piper_builder_with_daemon_uds() {
505 let builder = PiperBuilder::new().with_daemon("/tmp/gs_usb_daemon.sock");
506 assert_eq!(
507 builder.daemon_addr,
508 Some("/tmp/gs_usb_daemon.sock".to_string())
509 );
510 }
511
512 #[test]
513 fn test_piper_builder_with_daemon_udp() {
514 let builder = PiperBuilder::new().with_daemon("127.0.0.1:8888");
515 assert_eq!(builder.daemon_addr, Some("127.0.0.1:8888".to_string()));
516 }
517
518 #[test]
519 fn test_piper_builder_with_daemon_chaining() {
520 let builder1 = PiperBuilder::new().with_daemon("/tmp/test1.sock");
521 let builder2 = builder1.with_daemon("127.0.0.1:8888");
522
523 assert_eq!(builder2.daemon_addr, Some("127.0.0.1:8888".to_string()));
525 }
526
527 #[test]
528 fn test_piper_builder_daemon_and_interface() {
529 let builder =
531 PiperBuilder::new().with_daemon("/tmp/gs_usb_daemon.sock").interface("ABC123");
532
533 assert_eq!(
534 builder.daemon_addr,
535 Some("/tmp/gs_usb_daemon.sock".to_string())
536 );
537 assert_eq!(builder.interface, Some("ABC123".to_string()));
538 }
539
540 #[test]
541 fn test_piper_builder_driver_type() {
542 let builder1 = PiperBuilder::new();
543 assert!(matches!(builder1.driver_type, DriverType::Auto));
544
545 let builder2 = PiperBuilder::new().with_driver_type(DriverType::GsUsb);
546 assert!(matches!(builder2.driver_type, DriverType::GsUsb));
547
548 let builder3 = PiperBuilder::new().with_driver_type(DriverType::SocketCan);
549 assert!(matches!(builder3.driver_type, DriverType::SocketCan));
550 }
551}