icsneoc2 0.1002002.0-rc.4

High-level Rust interface for Intrepid Control Systems vehicle network adapters
Documentation
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(())
}