host-can 0.1.5

Host library for CAN adapters
Documentation
//! PCAN adapter module

use std::ffi::c_int;
use std::time::{Duration, Instant};
use std::u32;

use crate::adapter::Adapter;
use crate::frame::{CanFrame, Frame};
use crate::id;

mod bind;
use bind::{TPCANMsg, TPCANTimestamp, libPCBUSB};

use super::{AdapterBaud, AdapterError};

type PcanChannel = u16;
type PcanBaud = u16;

pub struct PcanAdapter {
    lib: libPCBUSB,
    channel: PcanChannel,
    fd: c_int,
}

fn try_as_channel(name: &str) -> Result<PcanChannel, AdapterError> {
    Ok(match name {
        "" | "1" => bind::PCAN_USBBUS1 as PcanChannel,
        "2" => bind::PCAN_USBBUS2 as PcanChannel,
        "3" => bind::PCAN_USBBUS3 as PcanChannel,
        "4" => bind::PCAN_USBBUS4 as PcanChannel,
        "5" => bind::PCAN_USBBUS5 as PcanChannel,
        "6" => bind::PCAN_USBBUS6 as PcanChannel,
        "7" => bind::PCAN_USBBUS7 as PcanChannel,
        "8" => bind::PCAN_USBBUS8 as PcanChannel,
        _ => return Err(AdapterError::UnknownDevice),
    })
}

fn try_as_baud(baud: u32) -> Result<PcanBaud, AdapterError> {
    Ok(match baud {
        10_000 => bind::PCAN_BAUD_10K as PcanBaud,
        20_000 => bind::PCAN_BAUD_20K as PcanBaud,
        50_000 => bind::PCAN_BAUD_50K as PcanBaud,
        100_000 => bind::PCAN_BAUD_100K as PcanBaud,
        125_000 => bind::PCAN_BAUD_125K as PcanBaud,
        250_000 => bind::PCAN_BAUD_250K as PcanBaud,
        500_000 => bind::PCAN_BAUD_500K as PcanBaud,
        800_000 => bind::PCAN_BAUD_800K as PcanBaud,
        1_000_000 => bind::PCAN_BAUD_1M as PcanBaud,
        _ => return Err(AdapterError::UnsupportedBaud),
    })
}

impl Default for TPCANMsg {
    fn default() -> Self {
        Self {
            ID: 0,
            MSGTYPE: 0,
            LEN: 0,
            DATA: [0; 8],
        }
    }
}

impl Default for TPCANTimestamp {
    fn default() -> Self {
        Self {
            millis: 0,
            millis_overflow: 0,
            micros: 0,
        }
    }
}

impl PcanAdapter {
    /// Create an instance of a PCAN adapter for the given channel and
    /// baud-rate.  Note that the "channel" is numerical and corresponds
    /// to `PCAN_USBBUS<#>` which is the only supported device-type.
    pub fn new(
        name: &str,
        baud: AdapterBaud,
    ) -> Result<Self, Box<dyn std::error::Error>> {
        #[cfg(target_os = "macos")]
        let lib = unsafe { libPCBUSB::new("libPCBUSB.dylib") }?;
        #[cfg(target_os = "windows")]
        let lib = unsafe { libPCBUSB::new("PCANBasic.dll") }?;

        let channel = try_as_channel(name)?;

        let status = unsafe {
            lib.CAN_Initialize(
                channel,
                try_as_baud(baud)?,
                bind::PCAN_USB as u8,
                0,
                0,
            )
        };
        match status {
            0 => {
                let mut fd: c_int = 0;
                let info_ptr: *mut c_int = &mut fd;
                match unsafe {
                    lib.CAN_GetValue(
                        channel,
                        bind::PCAN_RECEIVE_EVENT as u8,
                        info_ptr as *mut ::std::os::raw::c_void,
                        4,
                    )
                } {
                    bind::PCAN_ERROR_OK => Ok(Self { lib, fd, channel }),
                    _ => Err(Box::new(AdapterError::OpenFailed)),
                }
            }
            _ => Err(Box::new(AdapterError::OpenFailed)),
        }
    }
}

impl Drop for PcanAdapter {
    fn drop(&mut self) {
        unsafe { self.lib.CAN_Uninitialize(self.channel.into()) };
    }
}

impl Adapter for PcanAdapter {
    fn send(&self, frame: &CanFrame) -> Result<(), Box<AdapterError>> {
        let mut data: [u8; 8] = [0u8; 8];
        data[..frame.dlc()].copy_from_slice(frame.data());

        let mut msg = bind::TPCANMsg {
            ID: id::raw_u32(frame.id()),
            MSGTYPE: if frame.is_extended() {
                bind::PCAN_MODE_EXTENDED
            } else {
                bind::PCAN_MODE_STANDARD
            } as u8,
            LEN: frame.dlc() as u8,
            DATA: data,
        };

        let status = unsafe {
            self.lib
                .CAN_Write(self.channel.into(), &mut msg as *mut bind::TPCANMsg)
        };
        match status {
            0 => Ok(()),
            _ => Err(Box::new(AdapterError::WriteFailed)),
        }
    }

    #[cfg(not(target_os = "windows"))]
    fn recv(
        &self,
        timeout: Option<Duration>,
    ) -> Result<CanFrame, Box<AdapterError>> {
        use nix::poll::{PollFd, PollFlags, PollTimeout, poll};
        use std::os::unix::io::BorrowedFd;

        let start_time = Instant::now();

        let mut msg = bind::TPCANMsg::default();
        let mut timestamp = TPCANTimestamp::default();

        loop {
            let status = unsafe {
                self.lib.CAN_Read(
                    self.channel.into(),
                    &mut msg as *mut bind::TPCANMsg,
                    &mut timestamp as *mut bind::TPCANTimestamp,
                )
            };

            match status {
                bind::PCAN_ERROR_OK => {
                    let id = match msg.MSGTYPE as u32 {
                        bind::PCAN_MODE_STANDARD => {
                            id::new_standard(msg.ID as u16)
                        }
                        bind::PCAN_MODE_EXTENDED => id::new_extended(msg.ID),
                        _ => return Err(Box::new(AdapterError::ReadFailed)),
                    }
                    .ok_or(Box::new(AdapterError::ReadFailed))?;

                    let frame =
                        CanFrame::new(id, &msg.DATA[..msg.LEN as usize])
                            .ok_or(Box::new(AdapterError::ReadFailed))?;
                    return Ok(frame);
                }

                // No packet available: poll the event FD periodically,
                // checking every 1s until the specified timeout (since
                // the event FD does not appear to signal when there is
                // a hardware error, e.g. unplugging the adapter, we need
                // to call CAN_Read to get this)
                bind::PCAN_ERROR_QRCVEMPTY => {
                    let poll_interval = match timeout {
                        Some(t) => {
                            let now = Instant::now();
                            let end = start_time + t;
                            if now >= end {
                                return Err(Box::new(
                                    AdapterError::ReadTimeout,
                                ));
                            }

                            let remaining = (end - now).as_millis();

                            if remaining < 1000 {
                                PollTimeout::from(remaining as u16)
                            } else {
                                PollTimeout::from(1000u16)
                            }
                        }
                        None => PollTimeout::from(1000u16),
                    };

                    let pollfd = PollFd::new(
                        unsafe { BorrowedFd::borrow_raw(self.fd) },
                        PollFlags::POLLIN,
                    );
                    if poll(&mut [pollfd], poll_interval).is_err() {
                        return Err(Box::new(AdapterError::ReadFailed));
                    }
                }

                _ => return Err(Box::new(AdapterError::ReadFailed)),
            }
        }
    }

    #[cfg(target_os = "windows")]
    fn recv(
        &self,
        timeout: Option<Duration>,
    ) -> Result<CanFrame, Box<AdapterError>> {
        let start_time = Instant::now();

        let mut msg = bind::TPCANMsg::default();
        let mut timestamp = TPCANTimestamp::default();

        loop {
            let status = unsafe {
                self.lib.CAN_Read(
                    self.channel.into(),
                    &mut msg as *mut bind::TPCANMsg,
                    &mut timestamp as *mut bind::TPCANTimestamp,
                )
            };

            match status {
                bind::PCAN_ERROR_OK => {
                    let id = match msg.MSGTYPE as u32 {
                        bind::PCAN_MODE_STANDARD => {
                            id::new_standard(msg.ID as u16)
                        }
                        bind::PCAN_MODE_EXTENDED => id::new_extended(msg.ID),
                        _ => return Err(Box::new(AdapterError::ReadFailed)),
                    }
                    .ok_or(Box::new(AdapterError::ReadFailed))?;

                    let frame =
                        CanFrame::new(id, &msg.DATA[..msg.LEN as usize])
                            .ok_or(Box::new(AdapterError::ReadFailed))?;
                    return Ok(frame);
                }

                // No packet available: sleep a short amount and retry.  This
                // should be done via PCAN_RECEIVE_EVENT to reduce CPU load.
                bind::PCAN_ERROR_QRCVEMPTY => {
                    if let Some(timeout) = timeout {
                        std::thread::sleep(Duration::from_micros(10));
                        if Instant::now() - start_time >= timeout {
                            return Err(Box::new(AdapterError::ReadTimeout));
                        }
                    }
                }

                _ => return Err(Box::new(AdapterError::ReadFailed)),
            }
        }
    }
}