#![allow(clippy::upper_case_acronyms)]
use parking_lot::RwLock;
use pico_common::{
ChannelConfig, Driver, FromPicoStr, PicoChannel, PicoError, PicoInfo, PicoRange, PicoResult,
SampleConfig,
};
pub use resolution::Resolution;
use std::{fmt, pin::Pin, sync::Arc};
use thiserror::Error;
use version_compare::Version;
mod dependencies;
pub mod kernel_driver;
pub mod ps2000;
pub mod ps2000a;
pub mod ps3000a;
pub mod ps4000;
pub mod ps4000a;
pub mod ps5000a;
pub mod ps6000;
pub mod ps6000a;
mod resolution;
mod trampoline;
#[derive(Error, Debug)]
pub enum DriverLoadError {
#[error("Pico driver error: {0}")]
DriverError(#[from] PicoError),
#[error("Library load error: {0}")]
LibloadingError(#[from] libloading::Error),
#[error("Invalid Driver Version: Requires >= {required}, Found: {found}")]
VersionError { found: String, required: String },
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug)]
pub struct EnumerationResult {
pub variant: String,
pub serial: String,
}
pub trait PicoDriver: fmt::Debug + Send + Sync {
fn get_driver(&self) -> Driver;
fn get_version(&self) -> PicoResult<String>;
fn get_path(&self) -> PicoResult<Option<String>>;
fn enumerate_units(&self) -> PicoResult<Vec<EnumerationResult>>;
fn open_unit(&self, serial: Option<&str>) -> PicoResult<i16>;
fn ping_unit(&self, handle: i16) -> PicoResult<()>;
fn maximum_value(&self, handle: i16) -> PicoResult<i16>;
fn close(&self, handle: i16) -> PicoResult<()>;
fn get_unit_info(&self, handle: i16, info_type: PicoInfo) -> PicoResult<String>;
fn get_channel_ranges(&self, handle: i16, channel: PicoChannel) -> PicoResult<Vec<PicoRange>>;
fn enable_channel(
&self,
handle: i16,
channel: PicoChannel,
config: &ChannelConfig,
) -> PicoResult<()>;
fn disable_channel(&self, handle: i16, channel: PicoChannel) -> PicoResult<()>;
fn set_data_buffer(
&self,
handle: i16,
channel: PicoChannel,
buffer: Arc<RwLock<Pin<Vec<i16>>>>,
buffer_len: usize,
) -> PicoResult<()>;
fn start_streaming(
&self,
handle: i16,
sample_config: &SampleConfig,
) -> PicoResult<SampleConfig>;
fn get_latest_streaming_values<'a>(
&self,
handle: i16,
channels: &[PicoChannel],
callback: Box<dyn FnMut(usize, usize) + 'a>,
) -> PicoResult<()>;
fn stop(&self, handle: i16) -> PicoResult<()>;
fn check_version(&self) -> Result<(), DriverLoadError> {
let loaded_str = &self.get_version()?;
let loaded_version = Version::from(loaded_str);
#[allow(clippy::expect_fun_call)]
let required_str = get_min_required_version(self.get_driver());
let required_version = Version::from(required_str);
if loaded_version < required_version {
Err(DriverLoadError::VersionError {
found: loaded_str.to_string(),
required: required_str.to_string(),
})
} else {
Ok(())
}
}
}
pub type ArcDriver = Arc<dyn PicoDriver>;
pub(crate) fn get_version_string(input: &str) -> String {
input
.split(|s| s == ' ' || s == ',')
.last()
.expect("Invalid version string")
.to_string()
}
pub(crate) fn parse_enum_result(buffer: &[i8], len: usize) -> Vec<EnumerationResult> {
let serials_list = buffer.from_pico_i8_string(len);
serials_list
.split(',')
.map(String::from)
.map(|device| {
let parts = device.split('[').collect::<Vec<_>>();
EnumerationResult {
serial: parts[0].to_string(),
variant: parts[1].trim_end_matches(']').to_string(),
}
})
.collect()
}
fn get_min_required_version(driver: Driver) -> &'static str {
match driver {
Driver::PS2000 => "3.0.30.1878",
Driver::PS2000A
| Driver::PS3000A
| Driver::PS4000
| Driver::PS4000A
| Driver::PS5000A
| Driver::PS6000 => "2.1.30.1878",
Driver::PS6000A => "1.0.54.2438",
_ => panic!(
"We don't know the minimum required version for the {:?} driver!",
driver,
),
}
}
pub trait LoadDriverExt {
fn try_load(&self) -> Result<ArcDriver, DriverLoadError>;
fn try_load_with_resolution(
&self,
resolution: &Resolution,
) -> Result<ArcDriver, DriverLoadError>;
}
impl LoadDriverExt for Driver {
fn try_load(&self) -> Result<ArcDriver, DriverLoadError> {
self.try_load_with_resolution(&Default::default())
}
fn try_load_with_resolution(
&self,
resolution: &Resolution,
) -> Result<ArcDriver, DriverLoadError> {
let path = resolution.get_path(*self);
Ok(match self {
Driver::PS2000 => Arc::new(ps2000::PS2000Driver::new(path)?),
Driver::PS2000A => Arc::new(ps2000a::PS2000ADriver::new(path)?),
Driver::PS3000A => Arc::new(ps3000a::PS3000ADriver::new(path)?),
Driver::PS4000 => Arc::new(ps4000::PS4000Driver::new(path)?),
Driver::PS4000A => Arc::new(ps4000a::PS4000ADriver::new(path)?),
Driver::PS5000A => Arc::new(ps5000a::PS5000ADriver::new(path)?),
Driver::PS6000 => Arc::new(ps6000::PS6000Driver::new(path)?),
Driver::PS6000A => Arc::new(ps6000a::PS6000ADriver::new(path)?),
Driver::PicoIPP | Driver::IOMP5 => {
panic!("These are libraries used by Pico drivers and cannot be loaded directly")
}
})
}
}