Skip to main content

Crate icsneoc2

Crate icsneoc2 

Source
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.

Enums§

AeLinkMode
Devicetype
DiskFormatDirective
DiskLayout
EthPhyLinkMode
IoType
LinMode
MemoryType
MiscIoAnalogVoltage
Netid
NetworkType