use std::sync::{Arc, Mutex, OnceLock};
use crate::error::Result;
use crate::transport::args::ArgSpec;
use crate::transport::async_transport::BoxFuture;
use crate::transport::device::AsyncTransportDevice;
pub use super::factory::TransportOptions as AsyncTransportOptions;
pub trait AsyncTransportFactory: Send + Sync {
fn priority(&self) -> u32;
fn name(&self) -> &'static str;
fn arg_specs(&self) -> Vec<ArgSpec> {
Vec::new()
}
fn create<'a>(
&'a self,
options: &'a AsyncTransportOptions,
) -> BoxFuture<'a, Result<Vec<Box<dyn AsyncTransportDevice>>>>;
}
#[cfg(target_os = "linux")]
#[derive(Debug, Default)]
pub struct AsyncFdcanusbFactory;
#[cfg(target_os = "linux")]
impl AsyncFdcanusbFactory {
pub fn new() -> Self {
Self
}
}
#[cfg(target_os = "linux")]
impl AsyncTransportFactory for AsyncFdcanusbFactory {
fn priority(&self) -> u32 {
10 }
fn name(&self) -> &'static str {
"fdcanusb"
}
fn arg_specs(&self) -> Vec<ArgSpec> {
use crate::transport::args::ArgType;
vec![ArgSpec {
name: "fdcanusb",
help: "Path to fdcanusb device (can be specified multiple times)",
arg_type: ArgType::MultiString,
default: None,
possible_values: None,
}]
}
fn create<'a>(
&'a self,
options: &'a AsyncTransportOptions,
) -> BoxFuture<'a, Result<Vec<Box<dyn AsyncTransportDevice>>>> {
Box::pin(async move {
use crate::transport::async_fdcanusb::AsyncFdcanusbDevice;
use crate::transport::device::TransportDeviceInfo;
use crate::transport::discovery::{detect_fdcanusbs, FdcanusbInfo};
let infos: Vec<FdcanusbInfo> = if options.fdcanusb_paths.is_empty() {
detect_fdcanusbs()
} else {
options
.fdcanusb_paths
.iter()
.map(|path| FdcanusbInfo {
path: path.clone(),
serial_number: None,
})
.collect()
};
let mut devices: Vec<Box<dyn AsyncTransportDevice>> = Vec::new();
for (idx, info) in infos.iter().enumerate() {
match AsyncFdcanusbDevice::open_with_options(
&info.path,
options.timeout,
options.disable_brs,
)
.await
{
Ok(mut device) => {
let mut dev_info = TransportDeviceInfo::new(idx, "AsyncFdcanusb");
if let Some(ref sn) = info.serial_number {
dev_info.detail = Some(format!("sn='{}'", sn));
dev_info.serial_number = Some(sn.clone());
}
device.info = dev_info;
devices.push(Box::new(device));
}
Err(_) => continue,
}
}
Ok(devices)
})
}
}
#[cfg(target_os = "linux")]
#[derive(Debug, Default)]
pub struct AsyncSocketCanFactory;
#[cfg(target_os = "linux")]
impl AsyncSocketCanFactory {
pub fn new() -> Self {
Self
}
}
#[cfg(target_os = "linux")]
impl AsyncTransportFactory for AsyncSocketCanFactory {
fn priority(&self) -> u32 {
11 }
fn name(&self) -> &'static str {
"socketcan"
}
fn arg_specs(&self) -> Vec<ArgSpec> {
use crate::transport::args::ArgType;
vec![ArgSpec {
name: "can-chan",
help: "SocketCAN interface (can be specified multiple times)",
arg_type: ArgType::MultiString,
default: None,
possible_values: None,
}]
}
fn create<'a>(
&'a self,
options: &'a AsyncTransportOptions,
) -> BoxFuture<'a, Result<Vec<Box<dyn AsyncTransportDevice>>>> {
Box::pin(async move {
use crate::transport::async_socketcan::AsyncSocketCanDevice;
use crate::transport::device::TransportDeviceInfo;
use crate::transport::discovery::detect_socketcan_interfaces;
let interfaces = if options.socketcan_interfaces.is_empty() {
detect_socketcan_interfaces()
.into_iter()
.map(|info| info.interface)
.collect()
} else {
options.socketcan_interfaces.clone()
};
let mut devices: Vec<Box<dyn AsyncTransportDevice>> = Vec::new();
for (idx, interface) in interfaces.iter().enumerate() {
match AsyncSocketCanDevice::with_options(
interface,
options.timeout,
options.disable_brs,
)
.await
{
Ok(mut device) => {
device.info = TransportDeviceInfo::new(idx, "AsyncSocketCan")
.with_serial(interface)
.with_detail(format!("'{}'", interface));
devices.push(Box::new(device));
}
Err(_) => continue,
}
}
Ok(devices)
})
}
}
static ASYNC_REGISTRY: OnceLock<Mutex<Vec<Arc<dyn AsyncTransportFactory>>>> = OnceLock::new();
fn get_async_registry() -> &'static Mutex<Vec<Arc<dyn AsyncTransportFactory>>> {
ASYNC_REGISTRY.get_or_init(|| {
#[cfg(target_os = "linux")]
let factories: Vec<Arc<dyn AsyncTransportFactory>> = vec![
Arc::new(AsyncFdcanusbFactory),
Arc::new(AsyncSocketCanFactory),
];
#[cfg(not(target_os = "linux"))]
let factories: Vec<Arc<dyn AsyncTransportFactory>> = Vec::new();
Mutex::new(factories)
})
}
pub fn register_async(factory: Arc<dyn AsyncTransportFactory>) {
get_async_registry().lock().unwrap().push(factory);
}
pub fn get_async_factories() -> Vec<Box<dyn AsyncTransportFactory>> {
#[cfg(target_os = "linux")]
{
vec![
Box::new(AsyncFdcanusbFactory::new()),
Box::new(AsyncSocketCanFactory::new()),
]
}
#[cfg(not(target_os = "linux"))]
{
Vec::new()
}
}
pub async fn create_async_transports(
options: &AsyncTransportOptions,
) -> Result<Vec<Box<dyn AsyncTransportDevice>>> {
use std::collections::HashSet;
let mut factories: Vec<Arc<dyn AsyncTransportFactory>> = {
let registry = get_async_registry().lock().unwrap();
registry.clone()
};
factories.sort_by_key(|f| f.priority());
if let Some(ref force) = options.force_transport {
factories.retain(|f| f.name() == force.as_str());
}
let mut all_devices = Vec::new();
let mut fdcanusb_serials: HashSet<String> = HashSet::new();
for factory in &factories {
match factory.create(options).await {
Ok(devices) => {
if factory.name() == "fdcanusb" {
for device in &devices {
if let Some(serial) = device.info().serial_number.as_ref() {
fdcanusb_serials.insert(serial.clone());
}
}
}
all_devices.extend(devices);
}
Err(_) => continue,
}
}
#[cfg(target_os = "linux")]
{
use crate::transport::discovery::detect_socketcan_interfaces;
let socketcan_infos = detect_socketcan_interfaces();
let mut filtered_devices = Vec::new();
for device in all_devices {
let should_skip = socketcan_infos.iter().any(|info| {
(device.info().serial_number.as_ref() == Some(&info.interface))
&& info
.fdcanusb_serial
.as_ref()
.is_some_and(|serial| fdcanusb_serials.contains(serial))
});
if !should_skip {
filtered_devices.push(device);
}
}
all_devices = filtered_devices;
}
Ok(all_devices)
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(target_os = "linux")]
#[test]
fn test_async_factory_priorities() {
let fdcanusb = AsyncFdcanusbFactory::new();
assert_eq!(fdcanusb.priority(), 10);
let socketcan = AsyncSocketCanFactory::new();
assert!(fdcanusb.priority() < socketcan.priority());
}
#[cfg(target_os = "linux")]
#[test]
fn test_async_factory_arg_specs() {
let fdcanusb = AsyncFdcanusbFactory::new();
let specs = fdcanusb.arg_specs();
assert_eq!(specs.len(), 1);
assert_eq!(specs[0].name, "fdcanusb");
let socketcan = AsyncSocketCanFactory::new();
let specs = socketcan.arg_specs();
assert_eq!(specs.len(), 1);
assert_eq!(specs[0].name, "can-chan");
}
#[cfg(target_os = "linux")]
#[test]
fn test_get_async_factories() {
let factories = get_async_factories();
assert!(!factories.is_empty());
let names: Vec<_> = factories.iter().map(|f| f.name()).collect();
assert!(names.contains(&"fdcanusb"));
}
}