uldaqrs 0.2.0

Safe Rust bindings for the uldaq library (Measurement Computing / Data Translation DAQ devices)
Documentation
//! Error handling for the safe uldaq bindings.

use std::ffi::CStr;
use std::fmt;

use uldaq_sys as sys;

/// A raw error code returned by the uldaq library.
///
/// This is a transparent wrapper around the `UlError` enumeration from
/// `uldaq.h`. The named constants below correspond one-to-one with the
/// library's own error codes; the numeric value of each constant is taken
/// directly from the bindings in [`uldaq_sys`], so the two can never drift
/// apart.
///
/// Use [`Error::Ul`] or [`check`] to convert a raw return code into a
/// [`Result`].
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct UlError(u32);

/// Define the error-code constants on [`UlError`]. Each entry maps a
/// convenient constant name onto the bindgen-generated constant from
/// `uldaq-sys`.
macro_rules! ul_error_consts {
    ($($name:ident => $sys_const:ident : $doc:literal),* $(,)?) => {
        $(
            #[doc = $doc]
            pub const $name: UlError = UlError(uldaq_sys::$sys_const);
        )*
    };
}

impl UlError {
    /// Returns the raw numeric error code.
    pub fn raw(&self) -> u32 {
        self.0
    }

    /// Wrap a raw error code returned by the library.
    pub fn from_raw(raw: u32) -> UlError {
        UlError(raw)
    }

    /// Obtain a human-readable description of the error from the library.
    pub fn message(&self) -> String {
        const ERR_MSG_LEN: usize = sys::ERR_MSG_LEN as usize;
        let mut errmsg = [0 as std::os::raw::c_char; ERR_MSG_LEN];
        // SAFETY: `errmsg` is ERR_MSG_LEN bytes long, which matches the size
        // the library expects. The library writes at most ERR_MSG_LEN - 1
        // characters plus a terminating NUL.
        let err = unsafe { sys::ulGetErrMsg(self.0, errmsg.as_mut_ptr()) };
        if err != sys::UlError_ERR_NO_ERROR {
            return format!("(unable to obtain error message, UL error code {err})");
        }
        // SAFETY: the library NUL-terminates the string on success.
        let cstr = unsafe { CStr::from_ptr(errmsg.as_ptr()) };
        cstr.to_string_lossy().into_owned()
    }

    ul_error_consts! {
        NO_ERROR => UlError_ERR_NO_ERROR: "No error has occurred.",
        UNHANDLED_EXCEPTION => UlError_ERR_UNHANDLED_EXCEPTION: "Unhandled internal exception.",
        BAD_DEV_HANDLE => UlError_ERR_BAD_DEV_HANDLE: "Invalid device handle.",
        BAD_DEV_TYPE => UlError_ERR_BAD_DEV_TYPE: "This function cannot be used with this device.",
        USB_DEV_NO_PERMISSION => UlError_ERR_USB_DEV_NO_PERMISSION: "Insufficient permission to access this device.",
        USB_INTERFACE_CLAIMED => UlError_ERR_USB_INTERFACE_CLAIMED: "USB interface is already claimed.",
        DEV_NOT_FOUND => UlError_ERR_DEV_NOT_FOUND: "Device not found.",
        DEV_NOT_CONNECTED => UlError_ERR_DEV_NOT_CONNECTED: "Device not connected or connection lost.",
        DEAD_DEV => UlError_ERR_DEAD_DEV: "Device no longer responding.",
        BAD_BUFFER_SIZE => UlError_ERR_BAD_BUFFER_SIZE: "Buffer too small for operation.",
        BAD_BUFFER => UlError_ERR_BAD_BUFFER: "Invalid buffer.",
        BAD_MEM_TYPE => UlError_ERR_BAD_MEM_TYPE: "Invalid memory type.",
        BAD_MEM_REGION => UlError_ERR_BAD_MEM_REGION: "Invalid memory region.",
        BAD_RANGE => UlError_ERR_BAD_RANGE: "Invalid range.",
        BAD_AI_CHAN => UlError_ERR_BAD_AI_CHAN: "Invalid analog input channel specified.",
        BAD_INPUT_MODE => UlError_ERR_BAD_INPUT_MODE: "Invalid input mode specified.",
        ALREADY_ACTIVE => UlError_ERR_ALREADY_ACTIVE: "A background process is already in progress.",
        BAD_TRIG_TYPE => UlError_ERR_BAD_TRIG_TYPE: "Invalid trigger type specified.",
        OVERRUN => UlError_ERR_OVERRUN: "FIFO overrun, data was not transferred from device fast enough.",
        UNDERRUN => UlError_ERR_UNDERRUN: "FIFO underrun, data was not transferred to device fast enough.",
        TIMEDOUT => UlError_ERR_TIMEDOUT: "Operation timed out.",
        BAD_OPTION => UlError_ERR_BAD_OPTION: "Invalid option specified.",
        BAD_RATE => UlError_ERR_BAD_RATE: "Invalid sampling rate specified.",
        BAD_BURSTIO_COUNT => UlError_ERR_BAD_BURSTIO_COUNT: "Sample count cannot be greater than FIFO size for BURSTIO scans.",
        CONFIG_NOT_SUPPORTED => UlError_ERR_CONFIG_NOT_SUPPORTED: "Configuration not supported.",
        BAD_CONFIG_VAL => UlError_ERR_BAD_CONFIG_VAL: "Invalid configuration value.",
        BAD_AI_CHAN_TYPE => UlError_ERR_BAD_AI_CHAN_TYPE: "Invalid analog input channel type specified.",
        ADC_OVERRUN => UlError_ERR_ADC_OVERRUN: "ADC overrun occurred.",
        BAD_TC_TYPE => UlError_ERR_BAD_TC_TYPE: "Invalid thermocouple type specified.",
        BAD_UNIT => UlError_ERR_BAD_UNIT: "Invalid unit specified.",
        BAD_QUEUE_SIZE => UlError_ERR_BAD_QUEUE_SIZE: "Invalid queue size.",
        BAD_CONFIG_ITEM => UlError_ERR_BAD_CONFIG_ITEM: "Invalid config item specified.",
        BAD_INFO_ITEM => UlError_ERR_BAD_INFO_ITEM: "Invalid info item specified.",
        BAD_FLAG => UlError_ERR_BAD_FLAG: "Invalid flag specified.",
        BAD_SAMPLE_COUNT => UlError_ERR_BAD_SAMPLE_COUNT: "Invalid sample count specified.",
        INTERNAL => UlError_ERR_INTERNAL: "Internal error.",
        BAD_COUPLING_MODE => UlError_ERR_BAD_COUPLING_MODE: "Invalid coupling mode.",
        BAD_SENSOR_SENSITIVITY => UlError_ERR_BAD_SENSOR_SENSITIVITY: "Invalid sensor sensitivity.",
        BAD_IEPE_MODE => UlError_ERR_BAD_IEPE_MODE: "Invalid IEPE mode.",
        BAD_AI_CHAN_QUEUE => UlError_ERR_BAD_AI_CHAN_QUEUE: "Invalid channel queue specified.",
        BAD_AI_GAIN_QUEUE => UlError_ERR_BAD_AI_GAIN_QUEUE: "Invalid gain queue specified.",
        BAD_AI_MODE_QUEUE => UlError_ERR_BAD_AI_MODE_QUEUE: "Invalid mode queue specified.",
        FPGA_FILE_NOT_FOUND => UlError_ERR_FPGA_FILE_NOT_FOUND: "FPGA file not found.",
        UNABLE_TO_READ_FPGA_FILE => UlError_ERR_UNABLE_TO_READ_FPGA_FILE: "Unable to read FPGA file.",
        NO_FPGA => UlError_ERR_NO_FPGA: "No FPGA installed.",
        BAD_ARG => UlError_ERR_BAD_ARG: "Invalid argument.",
        MIN_SLOPE_VAL_REACHED => UlError_ERR_MIN_SLOPE_VAL_REACHED: "Minimum slope value reached.",
        MAX_SLOPE_VAL_REACHED => UlError_ERR_MAX_SLOPE_VAL_REACHED: "Maximum slope value reached.",
        MIN_OFFSET_VAL_REACHED => UlError_ERR_MIN_OFFSET_VAL_REACHED: "Minimum offset value reached.",
        MAX_OFFSET_VAL_REACHED => UlError_ERR_MAX_OFFSET_VAL_REACHED: "Maximum offset value reached.",
        BAD_PORT_TYPE => UlError_ERR_BAD_PORT_TYPE: "Invalid port type.",
        WRONG_DIG_CONFIG => UlError_ERR_WRONG_DIG_CONFIG: "Digital direction configuration is wrong.",
        BAD_BIT_NUM => UlError_ERR_BAD_BIT_NUM: "Invalid bit number.",
        BAD_PORT_VAL => UlError_ERR_BAD_PORT_VAL: "Invalid port value.",
        BAD_RETRIG_COUNT => UlError_ERR_BAD_RETRIG_COUNT: "Invalid retrigger count.",
        BAD_AO_CHAN => UlError_ERR_BAD_AO_CHAN: "Invalid analog output channel specified.",
        BAD_DA_VAL => UlError_ERR_BAD_DA_VAL: "Invalid D/A value.",
        BAD_TMR => UlError_ERR_BAD_TMR: "Invalid timer specified.",
        BAD_FREQUENCY => UlError_ERR_BAD_FREQUENCY: "Invalid frequency.",
        BAD_DUTY_CYCLE => UlError_ERR_BAD_DUTY_CYCLE: "Invalid duty cycle.",
        BAD_INITIAL_DELAY => UlError_ERR_BAD_INITIAL_DELAY: "Invalid initial delay.",
        BAD_CTR => UlError_ERR_BAD_CTR: "Invalid counter specified.",
        BAD_CTR_VAL => UlError_ERR_BAD_CTR_VAL: "Invalid counter value.",
        BAD_DAQI_CHAN_TYPE => UlError_ERR_BAD_DAQI_CHAN_TYPE: "Invalid DAQ input channel type specified.",
        BAD_NUM_CHANS => UlError_ERR_BAD_NUM_CHANS: "Invalid number of channels specified.",
        BAD_CTR_REG => UlError_ERR_BAD_CTR_REG: "Invalid counter register specified.",
        BAD_CTR_MEASURE_TYPE => UlError_ERR_BAD_CTR_MEASURE_TYPE: "Invalid counter measurement type.",
        BAD_CTR_MEASURE_MODE => UlError_ERR_BAD_CTR_MEASURE_MODE: "Invalid counter measurement mode.",
        BAD_DEBOUNCE_TIME => UlError_ERR_BAD_DEBOUNCE_TIME: "Invalid debounce time.",
        BAD_DEBOUNCE_MODE => UlError_ERR_BAD_DEBOUNCE_MODE: "Invalid debounce mode.",
        BAD_EDGE_DETECTION => UlError_ERR_BAD_EDGE_DETECTION: "Invalid edge detection.",
        BAD_TICK_SIZE => UlError_ERR_BAD_TICK_SIZE: "Invalid tick size.",
        BAD_DAQO_CHAN_TYPE => UlError_ERR_BAD_DAQO_CHAN_TYPE: "Invalid DAQ output channel type specified.",
        NO_CONNECTION_ESTABLISHED => UlError_ERR_NO_CONNECTION_ESTABLISHED: "No connection established.",
        BAD_EVENT_TYPE => UlError_ERR_BAD_EVENT_TYPE: "Invalid event type.",
        EVENT_ALREADY_ENABLED => UlError_ERR_EVENT_ALREADY_ENABLED: "Event is already enabled.",
        BAD_EVENT_PARAMETER => UlError_ERR_BAD_EVENT_PARAMETER: "Invalid event parameter.",
        BAD_CALLBACK_FUCNTION => UlError_ERR_BAD_CALLBACK_FUCNTION: "Invalid callback function.",
        BAD_MEM_ADDRESS => UlError_ERR_BAD_MEM_ADDRESS: "Invalid memory address.",
        MEM_ACCESS_DENIED => UlError_ERR_MEM_ACCESS_DENIED: "Memory access denied.",
        DEV_UNAVAILABLE => UlError_ERR_DEV_UNAVAILABLE: "Device unavailable.",
        BAD_RETRIG_TRIG_TYPE => UlError_ERR_BAD_RETRIG_TRIG_TYPE: "Invalid retrigger trigger type.",
        BAD_DEV_VER => UlError_ERR_BAD_DEV_VER: "Invalid device version.",
        BAD_DIG_OPERATION => UlError_ERR_BAD_DIG_OPERATION: "Invalid digital operation.",
        BAD_PORT_INDEX => UlError_ERR_BAD_PORT_INDEX: "Invalid port index.",
        OPEN_CONNECTION => UlError_ERR_OPEN_CONNECTION: "Unable to open connection.",
        DEV_NOT_READY => UlError_ERR_DEV_NOT_READY: "Device not ready.",
        PACER_OVERRUN => UlError_ERR_PACER_OVERRUN: "Pacer overrun.",
        BAD_TRIG_CHANNEL => UlError_ERR_BAD_TRIG_CHANNEL: "Invalid trigger channel.",
        BAD_TRIG_LEVEL => UlError_ERR_BAD_TRIG_LEVEL: "Invalid trigger level.",
        BAD_CHAN_ORDER => UlError_ERR_BAD_CHAN_ORDER: "Invalid channel order.",
        TEMP_OUT_OF_RANGE => UlError_ERR_TEMP_OUT_OF_RANGE: "Temperature is out of range.",
        TRIG_THRESHOLD_OUT_OF_RANGE => UlError_ERR_TRIG_THRESHOLD_OUT_OF_RANGE: "Trigger threshold is out of range.",
        INCOMPATIBLE_FIRMWARE => UlError_ERR_INCOMPATIBLE_FIRMWARE: "Incompatible firmware.",
        BAD_NET_IFC => UlError_ERR_BAD_NET_IFC: "Invalid network interface.",
        BAD_NET_HOST => UlError_ERR_BAD_NET_HOST: "Invalid network host.",
        BAD_NET_PORT => UlError_ERR_BAD_NET_PORT: "Invalid network port.",
        NET_IFC_UNAVAILABLE => UlError_ERR_NET_IFC_UNAVAILABLE: "Network interface unavailable.",
        NET_CONNECTION_FAILED => UlError_ERR_NET_CONNECTION_FAILED: "Network connection failed.",
        BAD_CONNECTION_CODE => UlError_ERR_BAD_CONNECTION_CODE: "Invalid connection code.",
        CONNECTION_CODE_IGNORED => UlError_ERR_CONNECTION_CODE_IGNORED: "Connection code ignored.",
        NET_DEV_IN_USE => UlError_ERR_NET_DEV_IN_USE: "Network device in use.",
        BAD_NET_FRAME => UlError_ERR_BAD_NET_FRAME: "Invalid network frame.",
        NET_TIMEOUT => UlError_ERR_NET_TIMEOUT: "Network timeout.",
        DATA_SOCKET_CONNECTION_FAILED => UlError_ERR_DATA_SOCKET_CONNECTION_FAILED: "Data socket connection failed.",
        PORT_USED_FOR_ALARM => UlError_ERR_PORT_USED_FOR_ALARM: "Port used for alarm.",
        BIT_USED_FOR_ALARM => UlError_ERR_BIT_USED_FOR_ALARM: "Bit used for alarm.",
        CMR_EXCEEDED => UlError_ERR_CMR_EXCEEDED: "Common mode rejection exceeded.",
        NET_BUFFER_OVERRUN => UlError_ERR_NET_BUFFER_OVERRUN: "Network buffer overrun.",
        BAD_NET_BUFFER => UlError_ERR_BAD_NET_BUFFER: "Invalid network buffer.",
    }
}

impl fmt::Display for UlError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "UL error {}: {}", self.0, self.message())
    }
}

impl std::error::Error for UlError {}

/// Convert a raw return code from a UL function into a [`Result`].
///
/// Returns `Ok(())` when the code is `ERR_NO_ERROR`, and `Err(Error::Ul(..))`
/// otherwise.
pub fn check(raw: u32) -> Result<()> {
    if raw == sys::UlError_ERR_NO_ERROR {
        Ok(())
    } else {
        Err(Error::Ul(UlError(raw)))
    }
}

/// Errors that can occur when interacting with the uldaq library.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Error {
    /// An error code returned by the UL library itself.
    Ul(UlError),
    /// [`DaqDevice::create`](crate::DaqDevice::create) returned a null handle
    /// without a specific error code. This typically means the device is
    /// already in use, or the descriptor is invalid.
    DeviceCreateFailed,
    /// An invalid argument was passed to a function.
    InvalidArgument(&'static str),
    /// An internal invariant was violated; this is probably a bug.
    Internal(&'static str),
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Error::Ul(e) => write!(f, "{e}"),
            Error::DeviceCreateFailed => write!(
                f,
                "Unable to create a handle to the DAQ device. Is the device currently in use?"
            ),
            Error::InvalidArgument(msg) => write!(f, "Invalid argument: {msg}"),
            Error::Internal(msg) => write!(f, "Internal error: {msg}"),
        }
    }
}

impl std::error::Error for Error {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match self {
            Error::Ul(e) => Some(e),
            _ => None,
        }
    }
}

impl From<UlError> for Error {
    fn from(e: UlError) -> Error {
        Error::Ul(e)
    }
}

/// Convenience `Result` alias for the safe bindings.
pub type Result<T> = std::result::Result<T, Error>;

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn error_code_matches_raw_value() {
        // Sanity check that a handful of the most relevant codes line up with
        // the values documented in uldaq.h.
        assert_eq!(UlError::NO_ERROR.raw(), 0);
        assert_eq!(UlError::OVERRUN.raw(), 18);
        assert_eq!(UlError::UNDERRUN.raw(), 19);
        assert_eq!(UlError::BAD_DEV_HANDLE.raw(), 2);
        assert_eq!(UlError::INTERNAL.raw(), 35);
    }

    #[test]
    fn check_maps_no_error_to_ok() {
        assert!(check(0).is_ok());
        let err = check(18).unwrap_err();
        assert!(matches!(err, Error::Ul(e) if e == UlError::OVERRUN));
    }

    #[test]
    fn display_contains_code() {
        let err = UlError::from_raw(0);
        assert!(!err.message().is_empty());
        assert!(format!("{err}").contains("0"));
    }
}