Expand description
High-level Rust interface for Intrepid Control Systems vehicle network adapters.
This crate wraps the libicsneoc2 C library, providing safe, idiomatic
Rust access to CAN, CAN FD, LIN, Ethernet, and other automotive networks.
§Quick start
use icsneoc2::{Device, OpenOptions};
fn main() -> anyhow::Result<()> {
let device = Device::open_first(0, OpenOptions::DEFAULT)?;
println!("{}", device.description()?);
Ok(())
}§Examples
§Simple
use chrono::DateTime;
use icsneoc2::Device;
use icsneoc2::Message;
use icsneoc2::MessageCanFlags;
use icsneoc2::MessageType;
use icsneoc2::Netid::Dwcan01;
use icsneoc2::NetworkType;
use icsneoc2::OpenOptions;
fn main() -> anyhow::Result<()> {
print!("Enumerating devices... ");
let device_list = Device::enumerate(0)?;
let mut count = 0usize;
for _ in device_list.iter() {
count += 1;
}
println!("OK, {count} devices found...");
for info in &device_list {
let description = match info.description() {
Ok(d) => d,
Err(err) => {
println!("Failed to get description: {err}");
continue;
}
};
println!("{description}");
// Open with default options minus RTC sync and go-online so we can
// inspect settings before going online.
let options = OpenOptions::ENABLE_AUTO_UPDATE;
print!("\tOpening device... ");
let device = match Device::from_info(&info) {
Ok(d) => d,
Err(err) => {
println!("Failed to create: {err}");
continue;
}
};
match device.open(options) {
Ok(()) => {}
Err(err) => {
println!("Failed to open: {err}");
continue;
}
}
println!("OK");
if let Err(err) = run_device(&device) {
println!("Failed: {err}");
}
print_device_events(&device, &description)
.expect("Critical: Failed to print device events");
}
Ok(())
}
fn run_device(device: &Device) -> anyhow::Result<()> {
// Get timestamp resolution of the device
print!("\tGetting timestamp resolution... ");
let timestamp_resolution = device.timestamp_resolution()?;
println!("{timestamp_resolution}ns");
// Get baudrates for HSCAN
print!("\tGetting HSCAN baudrate... ");
let baudrate = device.settings().baudrate(Dwcan01)?;
println!("{baudrate}mbit/s");
// Get FD baudrates for HSCAN
print!("\tGetting FD HSCAN baudrate... ");
let fd_baudrate = device.settings().canfd_baudrate(Dwcan01)?;
println!("{fd_baudrate}mbit/s");
// Set baudrates for HSCAN
// saveToDevice: If this is set to true, the baudrate will be saved on the device
// and will persist through a power cycle
print!("\tSetting HSCAN baudrate... ");
device.settings().set_baudrate(Dwcan01, baudrate)?;
println!("OK");
// Set FD baudrates for HSCAN
print!("\tSetting FD HSCAN baudrate... ");
device.settings().set_canfd_baudrate(Dwcan01, fd_baudrate)?;
println!("OK");
// Get RTC
print!("\tGetting RTC... ");
let current_rtc = device.rtc()?;
println!(
"{current_rtc} ({})",
DateTime::from_timestamp(current_rtc, 0)
.map(|dt| dt.format("%Y-%m-%d %H:%M:%S UTC").to_string())
.unwrap_or_else(|| "invalid timestamp".to_string())
);
// Set RTC
print!("\tSetting RTC... ");
device.set_rtc(current_rtc)?;
println!("OK");
// Get RTC
print!("\tGetting RTC... ");
let rtc = device.rtc()?;
println!(
"{rtc} ({})",
DateTime::from_timestamp(rtc, 0)
.map(|dt| dt.format("%Y-%m-%d %H:%M:%S UTC").to_string())
.unwrap_or_else(|| "invalid timestamp".to_string())
);
// Go online, start acking traffic
print!("\tGoing online... ");
device.go_online(true)?;
// Redundant check to show how to check if the device is online, if the previous
// icsneoc2_go_online call was successful we can assume we are online already
let is_online = device.is_online()?;
println!("{}", if is_online { "Online" } else { "Offline" });
// Transmit CAN messages
transmit_can_messages(device)?;
println!("\tWaiting 1 second for messages...");
std::thread::sleep(std::time::Duration::from_secs(1));
// Get messages
let mut messages = Vec::new();
// Wait up to 3 seconds for first message then drain remaining non-blocking
if let Ok(msg) = device.message(3000) {
messages.push(msg);
while let Ok(msg) = device.message(0) {
messages.push(msg);
}
}
// Process the messages
process_messages(&messages)?;
Ok(())
}
fn transmit_can_messages(device: &Device) -> anyhow::Result<()> {
const MESSAGE_COUNT: usize = 100;
println!("\tTransmitting {MESSAGE_COUNT} messages...");
for i in 0..MESSAGE_COUNT {
let message = match Message::new(MessageType::Can) {
Ok(message) => message,
Err(err) => {
println!("Failed to create CAN message #{i}: {err}");
continue;
}
};
let flags = MessageCanFlags::BRS | MessageCanFlags::IDE | MessageCanFlags::FDF;
message.set_netid(Dwcan01)?;
message.set_can_props(Some(0x10), Some(flags))?;
message.set_data(&(i as u64).to_le_bytes())?;
device.transmit(&message)?;
}
Ok(())
}
fn process_messages(messages: &[Message]) -> anyhow::Result<()> {
let mut tx_count: u32 = 0;
for (i, message) in messages.iter().enumerate() {
// Check if the message is a frame, ignore otherwise
if !message.is_frame()? {
println!("\tIgnoring non-frame message at index {i}");
continue;
}
// Get the network type
let network_type = message.network_type()?;
// Check if message is a transmit message
if message.is_transmit()? {
tx_count += 1;
continue;
}
println!("\t{i}) network type: {network_type} ({network_type:?})");
// Check if the message is a CAN message
if network_type == NetworkType::Can {
let netid = message.netid()?;
let props = message.can_props()?;
let is_remote = props.flags.contains(MessageCanFlags::RTR);
let is_canfd = props.flags.contains(MessageCanFlags::FDF);
let is_extended = props.flags.contains(MessageCanFlags::IDE);
let data = message.data(None)?;
println!(
"\t NetID: {netid} ({netid:#02x})\tArbID: {:#02x}\t Remote: {is_remote}\t \
CANFD: {is_canfd}\t Extended: {is_extended}\t Data length: {}",
props.arb_id,
data.len()
);
println!("\t Data: {data:#02x?}");
} else {
println!("\tIgnoring bus message type: {network_type:?} ({network_type})");
}
}
println!(
"\tReceived {} messages total, {} were TX messages",
messages.len(),
tx_count
);
Ok(())
}
fn print_device_events(device: &Device, device_description: &String) -> anyhow::Result<()> {
let events = match device.events() {
Ok(events) => events,
Err(err) => {
println!("Failed to get device events: {err}");
return Err(err.into());
}
};
for (i, event) in events.iter().enumerate() {
println!("\t{device_description}: Event {i}: {event}");
}
// Get global events
let global_events = match icsneoc2::events(None) {
Ok(events) => events,
Err(err) => {
println!("Failed to get global events: {err}");
return Err(err.into());
}
};
for (i, event) in global_events.iter().enumerate() {
println!("\t{device_description}: Global Event {i}: {event}");
}
println!(
"\t{device_description}: Received {} events and {} global events\n",
events.len(),
global_events.len()
);
Ok(())
}§Transmit CAN
use icsneoc2::{Device, Message, MessageType, Netid, OpenOptions, Result};
fn main() -> Result<()> {
let device = Device::open_first(0, OpenOptions::DEFAULT)?;
let message = Message::new(MessageType::Can)?;
let netid = Netid::Dwcan01;
let arb_id: u64 = 0x123;
message.set_can_props(Some(arb_id), None)?;
message.set_netid(netid)?;
message.set_data(&[0xDE, 0xAD, 0xBE, 0xEF])?;
println!("Transmitting message with ARBID 0x123 and data DEADBEEF on NetID {netid:?}...");
device.transmit(&message)?;
println!("Message transmitted successfully");
Ok(())
}§Disk Format
use icsneoc2::{Device, DiskFormatFlags, DiskLayout, OpenOptions};
use std::io::{self, BufRead, Write};
unsafe extern "C" fn format_progress(
sectors_formatted: u64,
total_sectors: u64,
user_data: *mut std::ffi::c_void,
) -> u8 {
let pct = if total_sectors > 0 {
100 * sectors_formatted / total_sectors
} else {
0
};
if !user_data.is_null() {
let device = unsafe { &*(user_data as *const Device) };
let name = device
.description()
.unwrap_or_else(|_| "unknown".to_string());
print!(
"\r [{name}] Progress: {} / {} sectors ({}%)",
sectors_formatted, total_sectors, pct
);
} else {
print!(
"\r Progress: {} / {} sectors ({}%)",
sectors_formatted, total_sectors, pct
);
}
let _ = io::stdout().flush();
0 // Continue
}
fn main() -> anyhow::Result<()> {
// Open first device with minimal options (don't go online for formatting)
println!("Opening first available device...");
let options = OpenOptions::NONE;
let device = Device::open_first(0, options)?;
let desc = device.description()?;
println!("\tOpened device: {desc}");
// Check disk formatting support
let supported = device.supports_disk_formatting()?;
if !supported {
println!("\terror: {desc} does not support disk formatting");
return Ok(());
}
let disk_count = device.disk_count()?;
println!("\tDisk count: {disk_count}");
// Query disk details
print!("\tQuerying disk details... ");
let details = device.disk_details()?;
println!("OK");
// Display current state
let layout = details.layout()?;
let layout_name = match layout {
DiskLayout::Raid0 => "RAID0",
DiskLayout::Spanned => "Spanned",
_ => "Unknown",
};
println!("\t Layout : {layout_name}");
let detail_count = details.count()?;
let mut any_present = false;
for i in 0..detail_count {
let flags = details.flags(i)?;
println!("\t Disk [{i}]:");
println!(
"\t Present : {}",
if flags.contains(DiskFormatFlags::PRESENT) {
"yes"
} else {
"no"
}
);
println!(
"\t Initialized : {}",
if flags.contains(DiskFormatFlags::INITIALIZED) {
"yes"
} else {
"no"
}
);
println!(
"\t Formatted : {}",
if flags.contains(DiskFormatFlags::FORMATTED) {
"yes"
} else {
"no"
}
);
if flags.contains(DiskFormatFlags::PRESENT) {
let sz = details.size(i)?;
let mb = (sz.sectors * sz.bytes_per_sector) / (1024 * 1024);
println!(
"\t Size : {} MB ({} sectors x {} bytes)",
mb, sz.sectors, sz.bytes_per_sector
);
}
}
// Build format config: mark present disks for formatting
for i in 0..detail_count {
let flags = details.flags(i)?;
if flags.contains(DiskFormatFlags::PRESENT) {
details.set_flags(i, flags | DiskFormatFlags::FORMATTED)?;
any_present = true;
}
}
details.set_full_format(false)?; // Quick format
if !any_present {
println!("\n\terror: no disks are present in the device");
return Ok(());
}
// Confirm
println!("\n\tThis will format the disk(s) in {desc}.");
print!("\tAll existing data will be lost. Continue? [y/N]: ");
io::stdout().flush().unwrap();
let mut input = String::new();
io::stdin().lock().read_line(&mut input).unwrap();
let input = input.trim();
if !input.eq_ignore_ascii_case("y") {
println!("\tAborted.");
return Ok(());
}
// Format
println!("\n\tStarting format...");
unsafe {
let user_data = &device as *const Device as *mut std::ffi::c_void;
device.format_disk(&details, Some(format_progress), user_data)?;
}
println!("\n\tFormat complete!");
// Verify
print!("\n\tVerifying disk state after format... ");
let post_details = device.disk_details()?;
println!("OK");
let post_count = post_details.count()?;
for i in 0..post_count {
let flags = post_details.flags(i)?;
println!(
"\t Disk [{i}] formatted: {}",
if flags.contains(DiskFormatFlags::FORMATTED) {
"yes"
} else {
"no"
}
);
}
println!("\tClosing device: {desc}...");
Ok(())
}Re-exports§
pub use device::Device;pub use device::DeviceInfo;pub use device::DeviceInfoList;pub use disk::DiskDetails;pub use disk::DiskSize;pub use enums::DiskFormatFlags;pub use enums::MessageCanFlags;pub use enums::OpenOptions;pub use error::Error;pub use error::Result;pub use event::Event;pub use event::events;pub use message::CanProps;pub use message::Message;pub use message::MessageType;pub use script::ScriptStatus;pub use settings::Settings;pub use util::serial_num_to_string;pub use util::serial_string_to_num;pub use util::version;
Modules§
- device
- Device discovery, connection, and communication.
- disk
- Disk details and formatting for on-device storage.
- enums
- Bitflags types used across the crate.
- error
- Error types for the icsneoc2 crate.
- event
- Diagnostic events from the library and individual devices.
- message
- CAN and network message types.
- script
- CoreMini script management and status.
- settings
- Device settings management.
- util
- Standalone utility functions.