Skip to content

Commit

Permalink
Clean up API and Errors (#49)
Browse files Browse the repository at this point in the history
* refactor can stuff into own module

* clean up api and error types
  • Loading branch information
pd0wm committed Mar 20, 2024
1 parent b5cd5e6 commit 7833a82
Show file tree
Hide file tree
Showing 17 changed files with 115 additions and 144 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Welcome to the `automotive` crate documentation. The purpose of this crate is to
The following adapter opens the first available adapter on the system, and then receives all frames.

```rust
let adapter = automotive::adapter::get_adapter().unwrap();
let adapter = automotive::can::get_adapter().unwrap();
let mut stream = adapter.recv();

while let Some(frame) = stream.next().await {
Expand All @@ -23,7 +23,7 @@ while let Some(frame) = stream.next().await {
The automotive crate also supplies interfaces for various diagnostic protocols such as UDS. The adapter is first wrapped to support the ISO Transport Layer, then a UDS Client is created. All methods are fully async, making it easy to communicate with multiple ECUs in parallel. See [https://github.com/I-CAN-hack/automotive/issues/21](https://github.com/I-CAN-hack/automotive/issues/21) for progress on the supported SIDs.

```rust
let adapter = automotive::adapter::get_adapter().unwrap();
let adapter = automotive::can::get_adapter().unwrap();
let isotp = automotive::isotp::IsoTPAdapter::from_id(&adapter, 0x7a1);
let uds = automotive::uds::UDSClient::new(&isotp);

Expand Down
2 changes: 1 addition & 1 deletion examples/can_printer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use tracing_subscriber;
async fn main() {
tracing_subscriber::fmt::init();

let adapter = automotive::adapter::get_adapter().unwrap();
let adapter = automotive::can::get_adapter().unwrap();
let mut stream = adapter.recv();

while let Some(frame) = stream.next().await {
Expand Down
2 changes: 1 addition & 1 deletion examples/isotp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use tracing_subscriber;
async fn main() {
tracing_subscriber::fmt::init();

let adapter = automotive::adapter::get_adapter().unwrap();
let adapter = automotive::can::get_adapter().unwrap();
let config = IsoTPConfig::new(0, Identifier::Standard(0x7a1));
let isotp = IsoTPAdapter::new(&adapter, config);

Expand Down
8 changes: 4 additions & 4 deletions examples/query_fw_versions.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use automotive::async_can::AsyncCanAdapter;
use automotive::can::AsyncCanAdapter;
use automotive::can::Identifier;
use automotive::error::Error;
use automotive::isotp::{IsoTPAdapter, IsoTPConfig};
use automotive::Error;

use automotive::uds::constants::DataIdentifier;
use automotive::uds::DataIdentifier;
use automotive::uds::UDSClient;

use bstr::ByteSlice;
Expand Down Expand Up @@ -43,7 +43,7 @@ async fn get_version(adapter: &AsyncCanAdapter, identifier: u32) -> Result<(), E
async fn main() {
tracing_subscriber::fmt::init();

let adapter = automotive::adapter::get_adapter().unwrap();
let adapter = automotive::can::get_adapter().unwrap();

let standard_ids = 0x700..=0x7ff;
let extended_ids = (0x00..=0xff).map(|i| 0x18da0000 + (i << 8) + 0xf1);
Expand Down
4 changes: 2 additions & 2 deletions examples/uds.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use automotive::uds::constants::{DataIdentifier, SessionType};
use automotive::uds::{DataIdentifier, SessionType};
use bstr::ByteSlice;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt::init();

let adapter = automotive::adapter::get_adapter()?;
let adapter = automotive::can::get_adapter()?;
let isotp = automotive::isotp::IsoTPAdapter::from_id(&adapter, 0x7a1);
let uds = automotive::uds::UDSClient::new(&isotp);

Expand Down
2 changes: 1 addition & 1 deletion src/adapter.rs → src/can/adapter.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Convenience functions to get a CAN adapter.

/// Convenience function to get the first available adapter on the system. Supports both comma.ai panda, and SocketCAN.
pub fn get_adapter() -> Result<crate::async_can::AsyncCanAdapter, crate::error::Error> {
pub fn get_adapter() -> Result<crate::can::AsyncCanAdapter, crate::error::Error> {
if let Ok(panda) = crate::panda::Panda::new_async() {
return Ok(panda);
}
Expand Down
File renamed without changes.
6 changes: 6 additions & 0 deletions src/can.rs → src/can/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
//! Generic CAN types and traits

pub mod adapter;
pub mod async_can;

use std::fmt;

pub use adapter::get_adapter;
pub use async_can::AsyncCanAdapter;

pub static DLC_TO_LEN: &[usize] = &[0, 1, 2, 3, 4, 5, 6, 7, 8, 12, 16, 20, 24, 32, 48, 64];

/// Identifier for a CAN frame
Expand Down
6 changes: 3 additions & 3 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ pub enum Error {
#[error("Timeout")]
Timeout,
#[error(transparent)]
IsoTPError(#[from] crate::isotp::error::Error),
IsoTPError(#[from] crate::isotp::Error),
#[error(transparent)]
LibUsbError(#[from] rusb::Error),
#[error(transparent)]
PandaError(#[from] crate::panda::error::Error),
PandaError(#[from] crate::panda::Error),
#[error(transparent)]
UDSError(#[from] crate::uds::error::Error),
UDSError(#[from] crate::uds::Error),
}

impl From<tokio_stream::Elapsed> for Error {
Expand Down
51 changes: 25 additions & 26 deletions src/isotp/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
//! ```rust
//! use futures_util::stream::StreamExt;
//! async fn isotp_example() {
//! let adapter = automotive::adapter::get_adapter().unwrap();
//! let adapter = automotive::can::get_adapter().unwrap();
//! let config = automotive::isotp::IsoTPConfig::new(0, automotive::can::Identifier::Standard(0x7a1));
//! let isotp = automotive::isotp::IsoTPAdapter::new(&adapter, config);
//!
Expand All @@ -13,17 +13,16 @@
//! }
//! ```

pub mod constants;
pub mod error;
pub mod types;
mod constants;
mod error;
mod types;

use crate::async_can::AsyncCanAdapter;
use crate::can::{Frame, Identifier, DLC_TO_LEN};
use crate::error::Error;
use crate::isotp::constants::FlowStatus;
use crate::isotp::constants::FLOW_SATUS_MASK;
use crate::isotp::constants::{FrameType, FRAME_TYPE_MASK};
pub use constants::{FlowStatus, FrameType, FLOW_SATUS_MASK, FRAME_TYPE_MASK};
pub use error::Error;

use crate::can::AsyncCanAdapter;
use crate::can::{Frame, Identifier, DLC_TO_LEN};
use crate::Result;
use async_stream::stream;
use futures_core::stream::Stream;
use tokio_stream::{StreamExt, Timeout};
Expand Down Expand Up @@ -175,7 +174,7 @@ impl<'a> IsoTPAdapter<'a> {
}

/// Build a CAN frame from the payload. Inserts extended address and padding if needed.
fn frame(&self, data: &[u8]) -> Result<Frame, Error> {
fn frame(&self, data: &[u8]) -> Result<Frame> {
let mut data = data.to_vec();

if let Some(ext_address) = self.config.ext_address {
Expand All @@ -185,7 +184,7 @@ impl<'a> IsoTPAdapter<'a> {
// Check if the data length is valid
if !DLC_TO_LEN.contains(&data.len()) {
println!("len {}", data.len());
return Err(crate::error::Error::MalformedFrame);
return Err(crate::Error::MalformedFrame);
}

let frame = Frame {
Expand All @@ -199,7 +198,7 @@ impl<'a> IsoTPAdapter<'a> {
Ok(frame)
}

pub async fn send_single_frame(&self, data: &[u8]) -> Result<(), Error> {
pub async fn send_single_frame(&self, data: &[u8]) -> Result<()> {
let mut buf;

if data.len() < 0xf {
Expand All @@ -220,7 +219,7 @@ impl<'a> IsoTPAdapter<'a> {
Ok(())
}

pub async fn send_first_frame(&self, data: &[u8]) -> Result<usize, Error> {
pub async fn send_first_frame(&self, data: &[u8]) -> Result<usize> {
let mut buf;
if data.len() <= ISO_TP_MAX_DLEN {
let b0: u8 = FrameType::First as u8 | ((data.len() >> 8) & 0xF) as u8;
Expand All @@ -242,7 +241,7 @@ impl<'a> IsoTPAdapter<'a> {
Ok(offset)
}

pub async fn send_consecutive_frame(&self, data: &[u8], idx: usize) -> Result<(), Error> {
pub async fn send_consecutive_frame(&self, data: &[u8], idx: usize) -> Result<()> {
let idx = ((idx + 1) & 0xF) as u8;

let mut buf = vec![FrameType::Consecutive as u8 | idx];
Expand All @@ -261,7 +260,7 @@ impl<'a> IsoTPAdapter<'a> {
async fn receive_flow_control(
&self,
stream: &mut std::pin::Pin<&mut Timeout<impl Stream<Item = Frame>>>,
) -> Result<FlowControlConfig, Error> {
) -> Result<FlowControlConfig> {
for _ in 0..MAX_WAIT_FC {
let mut frame = stream.next().await.unwrap()?;

Expand Down Expand Up @@ -296,7 +295,7 @@ impl<'a> IsoTPAdapter<'a> {
Err(crate::isotp::error::Error::TooManyFCWait.into())
}

async fn send_multiple(&self, data: &[u8]) -> Result<(), Error> {
async fn send_multiple(&self, data: &[u8]) -> Result<()> {
// Stream for receiving flow control
let stream = self
.adapter
Expand Down Expand Up @@ -346,7 +345,7 @@ impl<'a> IsoTPAdapter<'a> {
}

/// Asynchronously send an ISO-TP frame of up to 4095 bytes. Returns [`Error::Timeout`] if the ECU is not responding in time with flow control messages.
pub async fn send(&self, data: &[u8]) -> Result<(), Error> {
pub async fn send(&self, data: &[u8]) -> Result<()> {
debug!("TX {}", hex::encode(data));

// Single frame has 1 byte of overhead for CAN, and 2 bytes for CAN-FD with escape sequence
Expand All @@ -364,7 +363,7 @@ impl<'a> IsoTPAdapter<'a> {
Ok(())
}

async fn recv_single_frame(&self, data: &[u8]) -> Result<Vec<u8>, Error> {
async fn recv_single_frame(&self, data: &[u8]) -> Result<Vec<u8>> {
let mut len = (data[0] & 0xF) as usize;
let mut offset = 1;

Expand All @@ -384,7 +383,7 @@ impl<'a> IsoTPAdapter<'a> {
Ok(data[offset..len + offset].to_vec())
}

async fn recv_first_frame(&self, data: &[u8], buf: &mut Vec<u8>) -> Result<usize, Error> {
async fn recv_first_frame(&self, data: &[u8], buf: &mut Vec<u8>) -> Result<usize> {
let b0 = data[0] as u16;
let b1 = data[1] as u16;
let mut len = ((b0 << 8 | b1) & 0xFFF) as usize;
Expand Down Expand Up @@ -422,7 +421,7 @@ impl<'a> IsoTPAdapter<'a> {
buf: &mut Vec<u8>,
len: usize,
idx: u8,
) -> Result<u8, Error> {
) -> Result<u8> {
let msg_idx = data[0] & 0xF;
let remaining_len = len - buf.len();

Expand Down Expand Up @@ -462,7 +461,7 @@ impl<'a> IsoTPAdapter<'a> {
async fn recv_from_stream(
&self,
stream: &mut std::pin::Pin<&mut Timeout<impl Stream<Item = Frame>>>,
) -> Result<Vec<u8>, Error> {
) -> Result<Vec<u8>> {
let mut buf = Vec::new();
let mut len: Option<usize> = None;
let mut idx: u8 = 1;
Expand All @@ -478,7 +477,7 @@ impl<'a> IsoTPAdapter<'a> {
Some(FrameType::First) => {
// If we already received a first frame, something went wrong
if len.is_some() {
return Err(Error::IsoTPError(crate::isotp::error::Error::OutOfOrder));
return Err(Error::OutOfOrder.into());
}
len = Some(self.recv_first_frame(data, &mut buf).await?);
}
Expand All @@ -491,20 +490,20 @@ impl<'a> IsoTPAdapter<'a> {
return Ok(buf);
}
} else {
return Err(Error::IsoTPError(crate::isotp::error::Error::OutOfOrder));
return Err(Error::OutOfOrder.into());
}
}
Some(FrameType::FlowControl) => {} // Ignore flow control frames, these are from a simultaneous transmission
_ => {
return Err(crate::isotp::error::Error::UnknownFrameType.into());
return Err(Error::UnknownFrameType.into());
}
};
}
unreachable!();
}

/// Stream of ISO-TP packets. Can be used if multiple responses are expected from a single request. Returns [`Error::Timeout`] if the timeout is exceeded between individual ISO-TP frames. Note the total time to receive a packet may be longer than the timeout.
pub fn recv(&self) -> impl Stream<Item = Result<Vec<u8>, Error>> + '_ {
pub fn recv(&self) -> impl Stream<Item = Result<Vec<u8>>> + '_ {
let stream = self
.adapter
.recv_filter(|frame| {
Expand Down
13 changes: 7 additions & 6 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
//! ```rust
//! use futures_util::stream::StreamExt;
//! async fn can_example() {
//! let adapter = automotive::adapter::get_adapter().unwrap();
//! let adapter = automotive::can::get_adapter().unwrap();
//! let mut stream = adapter.recv();
//!
//! while let Some(frame) = stream.next().await {
Expand All @@ -24,12 +24,12 @@
//!
//! ```rust
//! async fn uds_example() {
//! let adapter = automotive::adapter::get_adapter().unwrap();
//! let adapter = automotive::can::get_adapter().unwrap();
//! let isotp = automotive::isotp::IsoTPAdapter::from_id(&adapter, 0x7a1);
//! let uds = automotive::uds::UDSClient::new(&isotp);
//!
//! uds.tester_present().await.unwrap();
//! let response = uds.read_data_by_identifier(automotive::uds::constants::DataIdentifier::ApplicationSoftwareIdentification as u16).await.unwrap();
//! let response = uds.read_data_by_identifier(automotive::uds::DataIdentifier::ApplicationSoftwareIdentification as u16).await.unwrap();
//!
//! println!("Application Software Identification: {}", hex::encode(response));
//! }
Expand All @@ -40,13 +40,14 @@
//! - comma.ai panda (all platforms)
//!

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

pub use error::Error;
pub type Result<T> = std::result::Result<T, Error>;

#[cfg(target_os = "linux")]
pub mod socketcan;
Loading

0 comments on commit 7833a82

Please sign in to comment.