Skip to content

Commit

Permalink
Add features for adapter support (#74)
Browse files Browse the repository at this point in the history
* Add features for adapter supprot

* fix all adapters
  • Loading branch information
pd0wm authored Nov 21, 2024
1 parent 7ea3060 commit cf4516a
Show file tree
Hide file tree
Showing 9 changed files with 62 additions and 55 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,4 @@ jobs:
- name: Build
run: cargo build --verbose
- name: Run tests
run: cargo test --features=test_vcan --verbose
run: cargo test --features=test-vcan --verbose
15 changes: 9 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,22 @@ repository = "https://github.com/I-CAN-hack/automotive"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[features]
default = []
default = ["default-adapters"]
all = ["all-adapters", "serde"]
all-adapters = ["vector-xl"]
default-adapters = ["panda", "socketcan"]
all-adapters = ["default-adapters", "vector-xl"]
serde = ["dep:serde"]

# adapters
vector-xl = []
panda = []
socketcan = []

# adapter tests
test_panda = []
test_socketcan = []
test_vector = ["vector-xl"]
test_vcan = []
test-panda = ["panda"]
test-socketcan = ["socketcan"]
test-vector = ["vector-xl"]
test-vcan = ["socketcan"]

[dependencies]
async-stream = "0.3.5"
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ This library supports awaiting a sent frame and waiting for the ACK on the CAN b
- SocketCAN Devices
- SocketCAN drivers without `IFF_ECHO`: This class of SocketCAN drivers has no hardware support for notifying the driver when a frame was ACKed. This is instead emulated by the [Linux kernel](https://github.com/torvalds/linux/blob/master/net/can/af_can.c#L256). Due to transmitted frames immediately being received again this can cause the receive queue to fill up if more than 476 (default RX queue size on most systems) are transmitted in one go. To solve this we implement emulated ACKs ourself, instead of relying on the ACKs from the kernel.
- PCAN-USB: The Peak CAN adapters have two drivers:
- Kenel built in driver (`peak_usb`). The kernel driver properly implements `IFF_ECHO`, but has a rather small TX queue. This should not cause any issues, but it can be inreased with `ifconfig can0 txqueuelen <size>`.
- Kenel built in driver (`peak_usb`). The kernel driver properly implements `IFF_ECHO`, but has a rather small TX queue. This should not cause any issues, but it can be increased with `ifconfig can0 txqueuelen <size>`.
- Out-of-tree driver (`pcan`) that can be [downloaded](https://www.peak-system.com/fileadmin/media/linux/index.htm) from Peak System's website. The out-of-tree driver is not recommended as it does not implement `IFF_ECHO`.
- neoVI/ValueCAN: Use of Intrepid Control System's devices is not recommended due to issues in their SocketCAN driver. If many frames are transmitted simultaneously it will cause the whole system/kernel to hang. [intrepid-socketcan-kernel-module#20](https://github.com/intrepidcs/intrepid-socketcan-kernel-module/issues/20) tracks this issue.
- comma.ai panda
Expand Down
9 changes: 6 additions & 3 deletions src/can/adapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@
/// Convenience function to get the first available adapter on the system. Supports both comma.ai panda, and SocketCAN.
pub fn get_adapter() -> Result<crate::can::AsyncCanAdapter, crate::error::Error> {
if let Ok(panda) = crate::panda::Panda::new_async() {
return Ok(panda);
#[cfg(feature = "panda")]
{
if let Ok(panda) = crate::panda::Panda::new_async() {
return Ok(panda);
}
}

#[cfg(target_os = "linux")]
#[cfg(all(target_os = "linux", feature = "socketcan"))]
{
// TODO: iterate over all available SocketCAN adapters to also find things like vcan0
for iface in ["can0", "vcan0"] {
Expand Down
7 changes: 5 additions & 2 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,21 @@ pub enum Error {
Timeout,
#[error("Disconnected")]
Disconnected,

#[error(transparent)]
IsoTPError(#[from] crate::isotp::Error),
#[error(transparent)]
LibUsbError(#[from] rusb::Error),
#[error(transparent)]
PandaError(#[from] crate::panda::Error),
#[error(transparent)]
UDSError(#[from] crate::uds::Error),

#[cfg(all(target_os = "windows", feature = "vector-xl"))]
#[error(transparent)]
VectorError(#[from] crate::vector::Error),

#[cfg(feature = "panda")]
#[error(transparent)]
PandaError(#[from] crate::panda::Error),
}

impl From<tokio_stream::Elapsed> for Error {
Expand Down
32 changes: 19 additions & 13 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,35 +45,38 @@
//! The following CAN adapters are supported.
//!
//! ### Supported CAN adapters
//! - SocketCAN (Linux only, supported using [socketcan-rs](https://github.com/socketcan-rs/socketcan-rs))
//! - SocketCAN (Linux only)
//! - comma.ai panda (all platforms using [rusb](https://crates.io/crates/rusb))
//! - Vector Devices (Windows x64 only)
//!
//! ### Known limitations / Notes
//! This library has some unique features that might expose (performance) issues in drivers you wouldn't otherwise notice, so check the list of known limitations below.
//! This library has some unique features that might expose (performance) issues in drivers you wouldn't otherwise notice, so check the list of known limitations below.
//!
//! This library supports awaiting a sent frame and waiting for the ACK on the CAN bus. This requires receiving these ACKs from the adapter, and matching them to the appropriate sent frame. This requires some level of hardware support that is not offered by all adapters/drivers. If this is not supported by the driver, an ACK will be simulated as soon as the frame is transmitted, but this can cause issues if precise timing is needed.
//!
//! - SocketCAN drivers without `IFF_ECHO`: This class of SocketCAN drivers has no hardware support for notifying the driver when a frame was ACKed. This is instead emulated by the [Linux kernel](https://github.com/torvalds/linux/blob/master/net/can/af_can.c#L256). Due to transmitted frames immediately being received again this can cause the receive queue to fill up if more than 476 (default RX queue size on most systems) are transmitted in one go. To solve this we implement emulated ACKs ourself, instead of relying on the ACKs from the kernel.
//! - comma.ai panda
//! - The panda does not retry frames that are not ACKed, and drops them instead. This can cause panics in some internal parts of the library when frames are dropped. [panda#1922](https://github.com/commaai/panda/issues/1922) tracks this issue.
//! - The CAN-FD flag on a frame is ignored, if the hardware is configured for CAN-FD all frames will be interpreted as FD regardless of the FD frame bit (r0 bit).
//! - PCAN-USB: The Peak CAN adapters have two drivers:
//! - Kenel built in driver(`peak_usb`). The kernel driver properly implements `IFF_ECHO`, but has a rather small TX queue. This should not cause any issues, but it can be inreased with `ifconfig can0 txqueuelen <size>`.
//! - Out-of-tree driver (`pcan`) that can be [downloaded](https://www.peak-system.com/fileadmin/media/linux/index.htm) from Peak System's website. The out-of-tree driver is not recommended as it does not implement `IFF_ECHO`.
//! - neoVI/ValueCAN: Use of Intrepid Control System's devices is not recommended due to issues in their SocketCAN driver. If many frames are transmitted simultaneously it will cause the whole system/kernel to hang. [intrepid-socketcan-kernel-module#20](https://github.com/intrepidcs/intrepid-socketcan-kernel-module/issues/20) tracks this issue.
//! - SocketCAN Devices
//! - SocketCAN drivers without `IFF_ECHO`: This class of SocketCAN drivers has no hardware support for notifying the driver when a frame was ACKed. This is instead emulated by the [Linux kernel](https://github.com/torvalds/linux/blob/master/net/can/af_can.c#L256). Due to transmitted frames immediately being received again this can cause the receive queue to fill up if more than 476 (default RX queue size on most systems) are transmitted in one go. To solve this we implement emulated ACKs ourself, instead of relying on the ACKs from the kernel.
//! - PCAN-USB: The Peak CAN adapters have two drivers:
//! - Kenel built in driver (`peak_usb`). The kernel driver properly implements `IFF_ECHO`, but has a rather small TX queue. This should not cause any issues, but it can be increased with `ifconfig can0 txqueuelen <size>`.
//! - Out-of-tree driver (`pcan`) that can be [downloaded](https://www.peak-system.com/fileadmin/media/linux/index.htm) from Peak System's website. The out-of-tree driver is not recommended as it does not implement `IFF_ECHO`.
//! - neoVI/ValueCAN: Use of Intrepid Control System's devices is not recommended due to issues in their SocketCAN driver. If many frames are transmitted simultaneously it will cause the whole system/kernel to hang. [intrepid-socketcan-kernel-module#20](https://github.com/intrepidcs/intrepid-socketcan-kernel-module/issues/20) tracks this issue.
//! - comma.ai panda
//! - The panda does not retry frames that are not ACKed, and drops them instead. This can cause panics in some internal parts of the library when frames are dropped. [panda#1922](https://github.com/commaai/panda/issues/1922) tracks this issue.
//! - The CAN-FD flag on a frame is ignored, if the hardware is configured for CAN-FD all frames will be interpreted as FD regardless of the FD frame bit (r0 bit).
//! - Vector Devices are supported through the Vector XL Driver Library, and support can be enabled using the `vector-xl` feature. Make sure to distribute `vxlapi64.dll` alongside your application.
//!
//!
//! ### Implementing a New Adapter
//! Implementing a new adapter is done by implementing the `CanAdapter` Trait. Hardware implementations can be blocking, as the [AsyncCanAdapter](https://docs.rs/automotive/latest/automotive/async_can/struct.AsyncCanAdapter.html) takes care of presenting an async interface to the user. The library makes some assumptions around sending/receiving frames. These assumption are also verified by the tests in `tests/adapter_tests.rs`.
//!
//! - The `send` function takes a `&mut VecDequeue` of frames. Frames to be sent are taken from the *front* of this queue. If there is no space in the hardware or driver buffer to send out all messages it's OK to return before the queue is fully empty. If an error occurs make sure to put the message back at the beginning of the queue and return.
//! - The hardware or driver is free to prioritize sending frames with a lower Arbitration ID to prevent priority inversion. However frames with the same Arbitration ID need to be send out on the CAN bus in the same order as they were queued. This assumption is needed to match a received ACK to the correct frame.
//! - Once a frame is ACKed it should be put in the receive queue with the `loopback` flag set. The `AsyncCanAdapter` wrapper will take care of matching it against the right transmit frame and resolving the Future. If this is not supported by the underlying hardware, this can be faked by looping back all transmitted frames immediately.
//! - Once a frame is ACKed it should be put in the receive queue with the `loopback` flag set. The `AsyncCanAdapter` wrapper will take care of matching it against the right transmit frame and resolving the Future. If this is not supported by the underlying hardware, this can be faked by looping back all transmitted frames immediately.

pub mod can;
mod error;
pub mod isotp;
pub mod panda;
pub mod uds;

/// Re-export of relevant stream traits from `tokio_stream`.
Expand All @@ -82,8 +85,11 @@ pub use tokio_stream::{Stream, StreamExt, Timeout};
pub use error::Error;
pub type Result<T> = std::result::Result<T, Error>;

#[cfg(target_os = "linux")]
#[cfg(all(target_os = "linux", feature = "socketcan"))]
pub mod socketcan;

#[cfg(all(target_os = "windows", feature = "vector-xl"))]
pub mod vector;

#[cfg(feature = "panda")]
pub mod panda;
28 changes: 10 additions & 18 deletions tests/adapter_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,39 +90,39 @@ async fn bulk_send(adapter: &AsyncCanAdapter) {
.unwrap();
}

#[cfg(feature = "test_panda")]
#[cfg(feature = "test-panda")]
#[test]
#[serial_test::serial]
fn panda_bulk_send_sync() {
let mut panda = Panda::new().unwrap();
bulk_send_sync(&mut panda);
}

#[cfg(feature = "test_panda")]
#[cfg(feature = "test-panda")]
#[tokio::test]
#[serial_test::serial]
async fn panda_bulk_send_async() {
let panda = automotive::panda::Panda::new_async().unwrap();
bulk_send(&panda).await;
}

#[cfg(feature = "test_vector")]
#[cfg(feature = "test-vector")]
#[test]
#[serial_test::serial]
fn vector_bulk_send_sync() {
let mut vector = automotive::vector::VectorCan::new(0).unwrap();
bulk_send_sync(&mut vector);
}

#[cfg(feature = "test_vector")]
#[cfg(feature = "test-vector")]
#[tokio::test]
#[serial_test::serial]
async fn vector_bulk_send_async() {
let vector = automotive::vector::VectorCan::new_async(0).unwrap();
bulk_send(&vector).await;
}

#[cfg(feature = "test_socketcan")]
#[cfg(feature = "test-socketcan")]
#[test]
#[serial_test::serial]
fn socketcan_bulk_send_sync() {
Expand All @@ -131,39 +131,31 @@ fn socketcan_bulk_send_sync() {
bulk_send_sync(&mut adapter);
}

#[cfg(feature = "test_socketcan")]
#[cfg(feature = "test-socketcan")]
#[tokio::test]
#[serial_test::serial]
async fn socketcan_bulk_send_async() {
let adapter = automotive::socketcan::SocketCan::new_async("can0").unwrap();
bulk_send(&adapter).await;
}

// #[cfg(feature = "test_socketcan")]
// #[tokio::test]
// #[serial_test::serial]
// async fn socketcan_send_fd() {
// let adapter = automotive::socketcan::SocketCan::new_async("can0").unwrap();
// adapter.send(&Frame::new(0, 0x123.into(), &[0u8; 64])).await;
// }

#[cfg(feature = "test_vcan")]
#[cfg(feature = "test-vcan")]
#[test]
#[serial_test::serial]
fn vcan_bulk_send_sync() {
let mut adapter = automotive::socketcan::SocketCan::new("vcan0").unwrap();
bulk_send_sync(&mut adapter);
}

#[cfg(feature = "test_vcan")]
#[cfg(feature = "test-vcan")]
#[tokio::test]
#[serial_test::serial]
async fn vcan_bulk_send_async() {
let adapter = automotive::socketcan::SocketCan::new_async("vcan0").unwrap();
bulk_send(&adapter).await;
}

#[cfg(feature = "test_vcan")]
#[cfg(feature = "test-vcan")]
#[tokio::test]
#[serial_test::serial]
async fn vcan_send_fd() {
Expand All @@ -173,7 +165,7 @@ async fn vcan_send_fd() {
.await;
}

#[cfg(target_os = "linux")]
#[cfg(all(target_os = "linux", feature = "socketcan"))]
#[tokio::test]
#[serial_test::serial]
async fn socketcan_open_nonexistent() {
Expand Down
20 changes: 10 additions & 10 deletions tests/isotp_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ async fn vecu_spawn(adapter: &AsyncCanAdapter, config: VECUConfig) -> ChildGuard
vecu
}

#[cfg(feature = "test_vcan")]
#[cfg(feature = "test-vcan")]
async fn isotp_test_echo(msg_len: usize, config: VECUConfig) {
let adapter = automotive::socketcan::SocketCan::new_async("vcan0").unwrap();
let _vecu = vecu_spawn(&adapter, config).await;
Expand All @@ -92,7 +92,7 @@ async fn isotp_test_echo(msg_len: usize, config: VECUConfig) {
assert_eq!(response, request);
}

#[cfg(feature = "test_vcan")]
#[cfg(feature = "test-vcan")]
#[tokio::test]
#[serial_test::serial]
async fn isotp_test_flow_control() {
Expand All @@ -107,7 +107,7 @@ async fn isotp_test_flow_control() {
isotp_test_echo(256, config).await;
}

#[cfg(feature = "test_vcan")]
#[cfg(feature = "test-vcan")]
#[tokio::test]
#[serial_test::serial]
async fn isotp_test_padding() {
Expand All @@ -122,7 +122,7 @@ async fn isotp_test_padding() {
isotp_test_echo(64, config).await;
}

#[cfg(feature = "test_vcan")]
#[cfg(feature = "test-vcan")]
#[tokio::test]
#[serial_test::serial]
async fn isotp_test_stmin() {
Expand All @@ -137,7 +137,7 @@ async fn isotp_test_stmin() {
assert!(start.elapsed() > stmin * 8);
}

#[cfg(feature = "test_vcan")]
#[cfg(feature = "test-vcan")]
#[tokio::test]
#[serial_test::serial]
async fn isotp_test_bs() {
Expand All @@ -154,7 +154,7 @@ async fn isotp_test_bs() {
}
}

#[cfg(feature = "test_vcan")]
#[cfg(feature = "test-vcan")]
#[tokio::test]
#[serial_test::serial]
async fn isotp_test_fd() {
Expand Down Expand Up @@ -182,7 +182,7 @@ async fn isotp_test_fd() {
isotp_test_echo(5000, config).await;
}

#[cfg(feature = "test_vcan")]
#[cfg(feature = "test-vcan")]
#[tokio::test]
#[serial_test::serial]
async fn isotp_test_fd_max_dlen() {
Expand All @@ -205,7 +205,7 @@ async fn isotp_test_fd_max_dlen() {
isotp_test_echo(5000, config).await;
}

#[cfg(feature = "test_vcan")]
#[cfg(feature = "test-vcan")]
#[tokio::test]
#[serial_test::serial]
async fn isotp_test_extended() {
Expand All @@ -223,7 +223,7 @@ async fn isotp_test_extended() {
isotp_test_echo(256, config).await;
}

#[cfg(feature = "test_vcan")]
#[cfg(feature = "test-vcan")]
#[tokio::test]
#[serial_test::serial]
async fn isotp_test_fd_extended() {
Expand Down Expand Up @@ -251,7 +251,7 @@ async fn isotp_test_fd_extended() {
isotp_test_echo(5000, config).await;
}

#[cfg(feature = "test_vcan")]
#[cfg(feature = "test-vcan")]
#[tokio::test]
#[serial_test::serial]
async fn isotp_test_fd_extended_max_dlen() {
Expand Down
2 changes: 1 addition & 1 deletion tests/uds_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ async fn vecu_spawn(adapter: &AsyncCanAdapter) -> ChildGuard {
vecu
}

#[cfg(feature = "test_vcan")]
#[cfg(feature = "test-vcan")]
#[tokio::test]
#[serial_test::serial]
async fn uds_test_sids() {
Expand Down

0 comments on commit cf4516a

Please sign in to comment.