Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Peak CAN driver #14

Open
wants to merge 7 commits into
base: ag/tracing
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: CI

on:
push:
push:
pull_request:
types: [opened, synchronize, reopened]

Expand All @@ -28,7 +28,7 @@ jobs:
profile: minimal
override: true
components: rustfmt

- name: Setup cache
uses: Swatinem/rust-cache@v2

Expand All @@ -37,7 +37,7 @@ jobs:
with:
command: fmt
args: --check

lint:
name: Lint
runs-on: ubuntu-latest
Expand All @@ -53,7 +53,7 @@ jobs:
profile: minimal
override: true
components: clippy

- name: Setup cache
uses: Swatinem/rust-cache@v2

Expand All @@ -77,7 +77,7 @@ jobs:
toolchain: stable
profile: minimal
override: true

- name: Setup cache
uses: Swatinem/rust-cache@v2

Expand All @@ -90,8 +90,8 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: tarpaulin
args: --release --all-features --engine llvm --out xml
args: --release --features tracing,socketcan --engine llvm --out xml

- name: Upload to codecov.io
uses: codecov/codecov-action@v2
with:
Expand Down
10 changes: 8 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,25 @@ keywords = ["agriculture", "can", "canbus", "isobus", "j1939", "agritech", "smar

[dependencies]
rand = "0.8.5"
pcan-basic = { version = "1.0.2", optional = true }
socketcan = { version = "2.0.0", optional = true }
tracing = { version = "0.1.37", optional = true }

[features]
default = []
# Peak driver
peak = ["dep:pcan-basic"]
# Socketcan driver
socketcan = ["dep:socketcan"]
# Optional logging instrumentation
tracing = ["dep:tracing"]

[dev-dependencies]
clap = { version = "4.3.19", features = ["derive"] }
ctrlc = "3.4.0"
# TODO: Add optional tracing to the main library
tracing = "0.1.37"
tracing-subscriber = "0.3.17"

[[example]]
name = "forward"
required-features = ["socketcan"]
required-features = ["tracing"]
39 changes: 39 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,42 @@ Tests for this library are run with Cargo
```sh
cargo test
```

## Features

This crate provides multiple optional features that allow the user to pick and choose what they want
to compile.

| Feature name | Description | Enabled by default |
|--------------|----------------------------------------------------------------|--------------------|
| `peak` | Enables the `ag_iso_stack::driver::PeakDriver` | No |
| `socketcan` | Enables the `ag_iso_stack::driver::SocketcanDriver` | No |
| `tracing` | Enables developer diagnostic logging using the `tracing` crate | No |

Pass these features to Cargo when building like: `cargo build --features socketcan,tracing`.

### PCAN-Basic
Note that the `peak` CAN driver requires kernel support (should be enabled by default) _and_ the
`pcanbasic` library, for which there does not seem to be support for in the Ubuntu or Fedora package
repositories.

For Linux, you'll have to build and install from source after downloading the library here:
<https://www.peak-system.com/fileadmin/media/linux/index.htm#Section_Driver-Proproetary>

```sh
## Fedora:
# sudo dnf install kernel-devel popt-devel
sudo apt install linux-headers-generic libpopt-dev
tar -xzvf peak-linux-driver-8.16.0.tar.gz
cd peak-linux-driver-8.16.0/
make clean
make all
sudo make install
## Fedora:
# make KERNEL_LOCATION=/usr/src/kernels/6.5.9-200.fc38.x86_64/ clean
# EXTRA_CFLAGS=-Wno-error=incompatible-pointer-types make KERNEL_LOCATION=/usr/src/kernels/6.5.9-200.fc38.x86_64/ NET=NETDEV_SUPPORT all
# sudo make KERNEL_LOCATION=/usr/src/kernels/6.5.9-200.fc38.x86_64/ install
```

For Windows, it appears you can install the driver and PCAN-Basic library from
<https://www.peak-system.com/Drivers.523.0.html?&L=1>.
99 changes: 77 additions & 22 deletions examples/forward.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,46 @@

use std::sync::mpsc::channel;

use ag_iso_stack::driver::{Driver, DriverReadError, Frame, SocketcanDriver};
use clap::Parser;
#[cfg(feature = "peak")]
use ag_iso_stack::driver::PeakDriver;
#[cfg(feature = "socketcan")]
use ag_iso_stack::driver::SocketcanDriver;
use ag_iso_stack::driver::{Driver, Frame};
use ag_iso_stack::tracing;
use clap::{Parser, ValueEnum};
#[cfg(feature = "peak")]
use pcan_basic::bus::UsbBus;

fn parse_usb_bus(s: &str) -> Option<UsbBus> {
let s = s.to_uppercase();
match s.as_str() {
"USB1" => Some(UsbBus::USB1),
"USB2" => Some(UsbBus::USB2),
"USB3" => Some(UsbBus::USB3),
"USB4" => Some(UsbBus::USB4),
"USB5" => Some(UsbBus::USB5),
"USB6" => Some(UsbBus::USB6),
"USB7" => Some(UsbBus::USB7),
"USB8" => Some(UsbBus::USB8),
"USB9" => Some(UsbBus::USB9),
"USB10" => Some(UsbBus::USB10),
"USB11" => Some(UsbBus::USB11),
"USB12" => Some(UsbBus::USB12),
"USB13" => Some(UsbBus::USB13),
"USB14" => Some(UsbBus::USB14),
"USB15" => Some(UsbBus::USB15),
"USB16" => Some(UsbBus::USB16),
_ => None,
}
}

#[derive(Debug, Clone, ValueEnum)]
enum CanDriver {
#[cfg(feature = "socketcan")]
Socketcan,
#[cfg(feature = "peak")]
Pcan,
}

/// Forward CAN traffic from one interface to another
#[derive(Debug, Parser)]
Expand All @@ -16,42 +54,65 @@ struct Options {
/// The interface to read traffic from
///
/// Can be either a string interface name, or an integer interface index
#[clap(short, long, default_value_t = String::from("can0"))]
#[clap(short = 'i', long, default_value_t = String::from("can0"))]
pub input_interface: String,

/// The interface to write traffic to
///
/// Can be either a string interface name, or an integer interface index
#[clap(short, long, default_value_t = String::from("can1"))]
#[clap(short = 'o', long, default_value_t = String::from("can1"))]
pub output_interface: String,

/// The driver type to use for the input
#[clap(short = 'I', long)]
pub input_driver: CanDriver,

/// The driver type to use for the output
#[clap(short = 'O', long)]
pub output_driver: CanDriver,
}

fn create_driver(iface: &str) -> impl Driver {
if let Ok(index) = iface.parse::<u32>() {
SocketcanDriver::new_by_index(index)
} else {
SocketcanDriver::new_by_name(iface)
fn create_driver(iface: &str, driver: CanDriver) -> Box<dyn Driver> {
match driver {
#[cfg(feature = "socketcan")]
CanDriver::Socketcan => {
if let Ok(index) = iface.parse::<u32>() {
Box::new(SocketcanDriver::new_by_index(index))
} else {
Box::new(SocketcanDriver::new_by_name(iface))
}
}
#[cfg(feature = "peak")]
CanDriver::Pcan => {
let bus = parse_usb_bus(iface).unwrap();
let baud = ag_iso_stack::driver::Baudrate::Baud250K;
Notgnoshi marked this conversation as resolved.
Show resolved Hide resolved
Box::new(PeakDriver::new_usb(bus, baud))
}
#[allow(unreachable_patterns)]
_ => unreachable!(),
}
}

fn main() {
let opts = Options::parse();

let subscriber = tracing_subscriber::fmt()
// ... add configuration
.with_max_level(opts.log_level)
.finish();
tracing::subscriber::set_global_default(subscriber)
.map_err(|_err| eprintln!("Unable to set global default subscriber"))
.unwrap();

tracing::info!("AgIsoStack-rs example starts...");

tracing::info!(
"Forwarding CAN traffic from {} to {}",
opts.input_interface,
opts.output_interface
);

let mut input = create_driver(&opts.input_interface);
let mut output = create_driver(&opts.output_interface);
let mut input = create_driver(&opts.input_interface, opts.input_driver);
let mut output = create_driver(&opts.output_interface, opts.output_driver);

input.open().unwrap();
output.open().unwrap();
Expand All @@ -66,17 +127,11 @@ fn main() {

let mut frame = Frame::default();

match input.read_nonblocking(&mut frame) {
Ok(_) => {
tracing::info!("Read frame: {frame:?}");
tracing::info!("Attempting to write frame");
match output.write_nonblocking(&frame) {
Ok(_) => tracing::info!("Wrote frame: {frame:?}"),
Err(e) => tracing::info!("Failed to write frame: {e:?}"),
}
#[allow(clippy::collapsible_if)]
if input.read_nonblocking(&mut frame).is_ok() {
if output.write_nonblocking(&frame).is_err() {
break;
}
Err(DriverReadError::NoFrameReady) => {}
Err(e) => tracing::error!("Failed to read frame: {e:?}"),
}
}
}
18 changes: 18 additions & 0 deletions src/driver/driver.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,24 @@
// Copyright 2023 Raven Industries inc.
use crate::driver::Frame;

#[derive(Debug, PartialEq, Copy, Clone)]
pub enum Baudrate {
Baud1M,
Baud800K,
Baud500K,
Baud250K,
Baud125K,
Baud100K,
Baud95K,
Baud83K,
Baud50K,
Baud47K,
Baud33K,
Baud20K,
Baud10K,
Baud5K,
}

#[derive(Debug)]
#[non_exhaustive]
pub enum DriverOpenError {
Expand Down
9 changes: 8 additions & 1 deletion src/driver/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,18 @@ mod pgn;
#[cfg(feature = "socketcan")]
mod socketcan;

#[cfg(feature = "peak")]
mod peak;

pub use address::Address;
pub use can_id::{CanId, Priority, Type};
pub use driver::{Driver, DriverCloseError, DriverOpenError, DriverReadError, DriverWriteError};
pub use driver::{
Baudrate, Driver, DriverCloseError, DriverOpenError, DriverReadError, DriverWriteError,
};
pub use frame::{Channel, Frame};
pub use pgn::Pgn;

#[cfg(feature = "peak")]
pub use self::peak::PeakDriver;
#[cfg(feature = "socketcan")]
pub use self::socketcan::SocketcanDriver;
Loading
Loading