use std::os::raw::{c_char, c_int, c_longlong};
use uldaq_sys as sys;
use crate::error::*;
use crate::types::*;
const MAX_DEV_COUNT: u32 = 100;
fn c_char_array_to_string(arr: &[c_char]) -> String {
let len = arr.iter().position(|&c| c == 0).unwrap_or(arr.len());
let bytes = unsafe { std::slice::from_raw_parts(arr.as_ptr() as *const u8, len) };
String::from_utf8_lossy(bytes).into_owned()
}
fn string_to_c_char_array<const N: usize>(s: &str) -> [c_char; N] {
let mut arr = [0 as c_char; N];
let bytes = s.as_bytes();
let n = bytes.len().min(N - 1);
arr[..n]
.copy_from_slice(unsafe { std::slice::from_raw_parts(bytes.as_ptr() as *const c_char, n) });
arr
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DaqDeviceDescriptor {
pub product_name: String,
pub product_id: u32,
pub interface: DaqDeviceInterface,
pub dev_string: String,
pub unique_id: String,
}
impl DaqDeviceDescriptor {
fn from_sys(d: &sys::DaqDeviceDescriptor) -> DaqDeviceDescriptor {
DaqDeviceDescriptor {
product_name: c_char_array_to_string(&d.productName),
product_id: d.productId,
interface: DaqDeviceInterface::from_bits_truncate(d.devInterface),
dev_string: c_char_array_to_string(&d.devString),
unique_id: c_char_array_to_string(&d.uniqueId),
}
}
fn to_sys(&self) -> sys::DaqDeviceDescriptor {
let mut d: sys::DaqDeviceDescriptor = unsafe { std::mem::zeroed() };
d.productName = string_to_c_char_array(&self.product_name);
d.productId = self.product_id;
d.devInterface = self.interface.bits();
d.devString = string_to_c_char_array(&self.dev_string);
d.uniqueId = string_to_c_char_array(&self.unique_id);
d
}
}
pub fn get_device_inventory(interface: DaqDeviceInterface) -> Result<Vec<DaqDeviceDescriptor>> {
let mut descriptors: Vec<sys::DaqDeviceDescriptor> = (0..MAX_DEV_COUNT)
.map(|_| unsafe { std::mem::zeroed() })
.collect();
let mut num_devices = MAX_DEV_COUNT;
let err = unsafe {
sys::ulGetDaqDeviceInventory(interface.bits(), descriptors.as_mut_ptr(), &mut num_devices)
};
check(err)?;
let num_devices = num_devices as usize;
descriptors.truncate(num_devices);
Ok(descriptors
.iter()
.map(DaqDeviceDescriptor::from_sys)
.collect())
}
#[derive(Debug)]
pub struct DaqDevice {
handle: sys::DaqDeviceHandle,
}
impl DaqDevice {
pub fn create(desc: &DaqDeviceDescriptor) -> Result<DaqDevice> {
let sysdesc = desc.to_sys();
let handle = unsafe { sys::ulCreateDaqDevice(sysdesc) };
if handle == 0 {
return Err(Error::DeviceCreateFailed);
}
Ok(DaqDevice { handle })
}
pub fn connect(&self) -> Result<()> {
check(unsafe { sys::ulConnectDaqDevice(self.handle) })
}
pub fn disconnect(&self) -> Result<()> {
check(unsafe { sys::ulDisconnectDaqDevice(self.handle) })
}
pub fn release(&self) -> Result<()> {
check(unsafe { sys::ulReleaseDaqDevice(self.handle) })
}
pub fn set_ai_config(&self, item: AiConfigItem, channel: u32, value: i64) -> Result<()> {
check(unsafe { sys::ulAISetConfig(self.handle, item.into(), channel, value as c_longlong) })
}
pub fn set_ai_config_dbl(&self, item: AiConfigItemDbl, channel: u32, value: f64) -> Result<()> {
check(unsafe { sys::ulAISetConfigDbl(self.handle, item.into(), channel, value) })
}
pub fn set_ai_sensor_sensitivity(&self, channel: u32, sensitivity: f64) -> Result<()> {
self.set_ai_config_dbl(AiConfigItemDbl::ChanSensorSensitivity, channel, sensitivity)
}
pub fn set_ai_coupling_mode(&self, channel: u32, mode: CouplingMode) -> Result<()> {
self.set_ai_config(AiConfigItem::ChanCouplingMode, channel, mode as i64)
}
pub fn set_ai_iepe_mode(&self, channel: u32, mode: IepeMode) -> Result<()> {
self.set_ai_config(AiConfigItem::ChanIepeMode, channel, mode as i64)
}
#[allow(clippy::too_many_arguments)]
pub fn daq_in_scan(
&self,
channels: &[DaqInChanDescriptor],
samples_per_channel: usize,
rate: f64,
options: ScanOption,
flags: DaqInScanFlag,
data: &mut [f64],
) -> Result<f64> {
if channels.is_empty() {
return Err(Error::InvalidArgument("at least one channel is required"));
}
if samples_per_channel == 0 {
return Err(Error::InvalidArgument(
"samples per channel must be greater than zero",
));
}
let required = channels.len() * samples_per_channel;
if data.len() < required {
return Err(Error::InvalidArgument(
"data buffer too small: expected at least channels.len() * samples_per_channel samples",
));
}
let mut sysdescs: Vec<sys::DaqInChanDescriptor> = channels.iter().map(Into::into).collect();
let mut rate = rate;
let err = unsafe {
sys::ulDaqInScan(
self.handle,
sysdescs.as_mut_ptr(),
sysdescs.len() as c_int,
samples_per_channel as c_int,
&mut rate,
options.bits(),
flags.into(),
data.as_mut_ptr(),
)
};
check(err)?;
Ok(rate)
}
pub fn daq_in_scan_status(&self) -> Result<(ScanStatus, TransferStatus)> {
let mut status: sys::ScanStatus = 0;
let mut xfer: sys::TransferStatus = unsafe { std::mem::zeroed() };
let err = unsafe { sys::ulDaqInScanStatus(self.handle, &mut status, &mut xfer) };
check(err)?;
let status = ScanStatus::from_raw(status)
.ok_or(Error::Internal("driver returned an unknown scan status"))?;
Ok((status, xfer.into()))
}
pub fn daq_in_scan_stop(&self) -> Result<()> {
check(unsafe { sys::ulDaqInScanStop(self.handle) })
}
#[allow(clippy::too_many_arguments)]
pub fn a_out_scan(
&self,
low_chan: i32,
high_chan: i32,
range: Range,
samples_per_channel: usize,
rate: f64,
options: ScanOption,
flags: AOutScanFlag,
data: &mut [f64],
) -> Result<f64> {
if low_chan < 0 || high_chan < low_chan {
return Err(Error::InvalidArgument(
"invalid channel range: require 0 <= low_chan <= high_chan",
));
}
if samples_per_channel == 0 {
return Err(Error::InvalidArgument(
"samples per channel must be greater than zero",
));
}
let nchannels = (high_chan - low_chan + 1) as usize;
if data.len() < nchannels * samples_per_channel {
return Err(Error::InvalidArgument(
"data buffer too small: expected at least (high_chan - low_chan + 1) * samples_per_channel samples",
));
}
let mut rate = rate;
let err = unsafe {
sys::ulAOutScan(
self.handle,
low_chan,
high_chan,
range.into(),
samples_per_channel as c_int,
&mut rate,
options.bits(),
flags.into(),
data.as_mut_ptr(),
)
};
check(err)?;
Ok(rate)
}
pub fn a_out_scan_status(&self) -> Result<(ScanStatus, TransferStatus)> {
let mut status: sys::ScanStatus = 0;
let mut xfer: sys::TransferStatus = unsafe { std::mem::zeroed() };
let err = unsafe { sys::ulAOutScanStatus(self.handle, &mut status, &mut xfer) };
check(err)?;
let status = ScanStatus::from_raw(status)
.ok_or(Error::Internal("driver returned an unknown scan status"))?;
Ok((status, xfer.into()))
}
pub fn a_out_scan_stop(&self) -> Result<()> {
check(unsafe { sys::ulAOutScanStop(self.handle) })
}
}
impl Drop for DaqDevice {
fn drop(&mut self) {
unsafe {
let _ = sys::ulDisconnectDaqDevice(self.handle);
let _ = sys::ulReleaseDaqDevice(self.handle);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn c_string_conversion_roundtrip() {
let arr = string_to_c_char_array::<64>("DT9837A");
assert_eq!(c_char_array_to_string(&arr), "DT9837A");
}
#[test]
fn descriptor_roundtrip() {
let desc = DaqDeviceDescriptor {
product_name: "DT9837A".into(),
product_id: 0x3998A,
interface: DaqDeviceInterface::USB,
dev_string: "DT9837A".into(),
unique_id: "ABCDEF".into(),
};
let sysdesc = desc.to_sys();
let back = DaqDeviceDescriptor::from_sys(&sysdesc);
assert_eq!(back, desc);
}
#[test]
fn long_string_is_truncated() {
let arr = string_to_c_char_array::<16>("this string is way too long for the array");
assert_eq!(arr[15], 0);
let s = c_char_array_to_string(&arr);
assert!(s.len() < 16);
}
}