#[derive(Debug, Clone)]
pub struct FdcanusbInfo {
pub path: String,
pub serial_number: Option<String>,
}
#[derive(Debug, Clone)]
pub struct SocketCanInfo {
pub interface: String,
pub fdcanusb_serial: Option<String>,
}
#[derive(Debug, Clone, Default)]
pub struct DetectedDevices {
pub fdcanusb: Vec<FdcanusbInfo>,
pub socketcan: Vec<SocketCanInfo>,
}
const FDCANUSB_IDS: &[(u16, u16)] = &[
(0x0483, 0x5740), (0x1209, 0x2323), ];
#[cfg(target_os = "linux")]
pub fn detect_fdcanusbs() -> Vec<FdcanusbInfo> {
use std::fs;
let mut result = Vec::new();
if let Ok(entries) = fs::read_dir("/dev/serial/by-id") {
for entry in entries.filter_map(|e| e.ok()) {
let file_name = entry.file_name();
let name = file_name.to_string_lossy();
if name.to_lowercase().contains("fdcanusb") {
let path = entry.path();
let resolved = fs::canonicalize(&path).unwrap_or_else(|_| path.clone());
let serial_number = name
.rsplit('_')
.next()
.and_then(|s| s.strip_suffix("-if00").or_else(|| s.strip_suffix("-if01")))
.map(String::from);
result.push(FdcanusbInfo {
path: resolved.to_string_lossy().to_string(),
serial_number,
});
}
}
}
if !result.is_empty() {
result.sort_by(|a, b| a.path.cmp(&b.path));
return result;
}
detect_fdcanusbs_by_vid_pid()
}
#[cfg(not(target_os = "linux"))]
pub fn detect_fdcanusbs() -> Vec<FdcanusbInfo> {
detect_fdcanusbs_by_vid_pid()
}
fn detect_fdcanusbs_by_vid_pid() -> Vec<FdcanusbInfo> {
#[cfg(feature = "serialport")]
{
use serialport::available_ports;
let mut result = Vec::new();
if let Ok(ports) = available_ports() {
for port in ports {
if let serialport::SerialPortType::UsbPort(usb_info) = port.port_type {
let is_fdcanusb = FDCANUSB_IDS
.iter()
.any(|(vid, pid)| usb_info.vid == *vid && usb_info.pid == *pid);
if is_fdcanusb {
result.push(FdcanusbInfo {
path: port.port_name,
serial_number: usb_info.serial_number,
});
}
}
}
}
result.sort_by(|a, b| a.path.cmp(&b.path));
result
}
#[cfg(not(feature = "serialport"))]
{
Vec::new()
}
}
#[cfg(target_os = "linux")]
pub fn detect_socketcan_interfaces() -> Vec<SocketCanInfo> {
use std::fs;
let mut result = Vec::new();
if let Ok(entries) = fs::read_dir("/sys/class/net") {
for entry in entries.filter_map(|e| e.ok()) {
let ifname = entry.file_name().to_string_lossy().to_string();
let type_path = entry.path().join("type");
if let Ok(type_str) = fs::read_to_string(&type_path) {
if type_str.trim() == "280" {
let fdcanusb_serial = detect_fdcanusb_serial_linux(&ifname);
result.push(SocketCanInfo {
interface: ifname,
fdcanusb_serial,
});
}
}
}
}
result.sort_by(|a, b| a.interface.cmp(&b.interface));
result
}
#[cfg(target_os = "linux")]
fn detect_fdcanusb_serial_linux(ifname: &str) -> Option<String> {
use std::fs;
use std::path::PathBuf;
let device_path = PathBuf::from(format!("/sys/class/net/{}/device", ifname));
let device_path = fs::canonicalize(&device_path).ok()?;
let mut current = device_path.as_path();
for _ in 0..10 {
current = current.parent()?;
let vid_path = current.join("idVendor");
let pid_path = current.join("idProduct");
let serial_path = current.join("serial");
if vid_path.exists() && pid_path.exists() {
let vid_str = fs::read_to_string(&vid_path).ok()?;
let pid_str = fs::read_to_string(&pid_path).ok()?;
let vid = u16::from_str_radix(vid_str.trim(), 16).ok()?;
let pid = u16::from_str_radix(pid_str.trim(), 16).ok()?;
if FDCANUSB_IDS.iter().any(|(v, p)| *v == vid && *p == pid) {
return fs::read_to_string(&serial_path)
.ok()
.map(|s| s.trim().to_string());
}
return None;
}
}
None
}
#[cfg(not(target_os = "linux"))]
pub fn detect_socketcan_interfaces() -> Vec<SocketCanInfo> {
Vec::new()
}
pub fn detect_all_devices() -> DetectedDevices {
DetectedDevices {
fdcanusb: detect_fdcanusbs(),
socketcan: detect_socketcan_interfaces(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_detect_all_devices_runs() {
let devices = detect_all_devices();
let _ = devices;
}
#[test]
fn test_fdcanusb_ids() {
assert!(FDCANUSB_IDS.contains(&(0x0483, 0x5740)));
assert!(FDCANUSB_IDS.contains(&(0x1209, 0x2323)));
}
}