1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
//! Common, safe wrappers for Pico Technology oscilloscope drivers.
//!
//! This is a sub crate that you probably don't want to use directly. Try the top level
//! [`pico-sdk`](https://crates.io/crates/pico-sdk) crate which exposes everything from here.
//!
//! Each Pico Technology oscilloscope relies on a native driver for communication and this driver will
//! vary depending on the device product range. Each of these drivers has an interface which differs by
//! either a few function arguments or a vastly differing API.
//!
//! `PS2000Driver`, `PS2000ADriver`, `PS3000ADriver`, `PS4000Driver`,
//! `PS4000ADriver`, `PS5000ADriver`, `PS6000Driver` and `PS6000ADriver` wrap
//! their corresponding loaders and expose a safe, common API by implementing
//! the `PicoDriver` trait. These can be constructed with a `Resolution` which tells the wrapper where
//! to resolve the dynamic library from. The `LoadDriverExt` trait supplies a shortcut to load a driver
//! directly from the `Driver` enum via `try_load` and `try_load_with_resolution`.
//!
//! # Examples
//! Using the raw safe bindings to open and configure the first available device:
//! ```no_run
//! # fn run() -> Result<(),Box<dyn std::error::Error>> {
//! use pico_common::{ChannelConfig, Driver, PicoChannel, PicoCoupling, PicoInfo, PicoRange};
//! use pico_driver::{LoadDriverExt, Resolution};
//!
//! // Load the ps2000 driver library with the default resolution
//! let driver = Driver::PS2000.try_load()?;
//! // Load the ps4000a driver library from the applications root directory
//! let driver = Driver::PS4000A.try_load_with_resolution(&Resolution::AppRoot)?;
//!
//! // Open the first device
//! let handle = driver.open_unit(None)?;
//! let variant = driver.get_unit_info(handle, PicoInfo::VARIANT_INFO)?;
//!
//! let ch_config = ChannelConfig {
//!     coupling: PicoCoupling::DC,
//!     range: PicoRange::X1_PROBE_2V,
//!     offset: 0.0
//! };
//!
//! driver.enable_channel(handle, PicoChannel::A, &ch_config)?;
//! # Ok(())
//! # }
//! ```

#![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;

/// Covers the various errors encountered when attempting to load drivers
#[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 },
}

/// Serial and variant pairs returned by driver enumeration
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug)]
pub struct EnumerationResult {
    pub variant: String,
    pub serial: String,
}

/// Common trait implemented for every driver
pub trait PicoDriver: fmt::Debug + Send + Sync {
    /// Gets the underlying driver type
    fn get_driver(&self) -> Driver;
    /// Gets the driver version string
    fn get_version(&self) -> PicoResult<String>;
    /// Gets the path to the loaded driver
    fn get_path(&self) -> PicoResult<Option<String>>;
    /// Returns a list of discovered serial numbers
    fn enumerate_units(&self) -> PicoResult<Vec<EnumerationResult>>;
    /// Opens a device, optionally with a specific serial number
    fn open_unit(&self, serial: Option<&str>) -> PicoResult<i16>;
    /// Ping a unit to see if it's still connected
    fn ping_unit(&self, handle: i16) -> PicoResult<()>;
    /// Get the maximum expected ADC value. This is required to scale to volts
    fn maximum_value(&self, handle: i16) -> PicoResult<i16>;
    /// Close the specified unit
    fn close(&self, handle: i16) -> PicoResult<()>;
    /// Get one of the unit info strings
    fn get_unit_info(&self, handle: i16, info_type: PicoInfo) -> PicoResult<String>;
    /// Get valid ranges for the specified channel
    fn get_channel_ranges(&self, handle: i16, channel: PicoChannel) -> PicoResult<Vec<PicoRange>>;
    /// Set up a channel with the supplied config
    fn enable_channel(
        &self,
        handle: i16,
        channel: PicoChannel,
        config: &ChannelConfig,
    ) -> PicoResult<()>;
    /// Disable a channel
    fn disable_channel(&self, handle: i16, channel: PicoChannel) -> PicoResult<()>;
    /// Give the driver a buffer to write data into
    fn set_data_buffer(
        &self,
        handle: i16,
        channel: PicoChannel,
        buffer: Arc<RwLock<Pin<Vec<i16>>>>,
        buffer_len: usize,
    ) -> PicoResult<()>;
    /// Starts the device streaming
    fn start_streaming(
        &self,
        handle: i16,
        sample_config: &SampleConfig,
    ) -> PicoResult<SampleConfig>;
    /// Gets the latest streaming values
    fn get_latest_streaming_values<'a>(
        &self,
        handle: i16,
        channels: &[PicoChannel],
        callback: Box<dyn FnMut(usize, usize) + 'a>,
    ) -> PicoResult<()>;
    /// Stops the device streaming
    fn stop(&self, handle: i16) -> PicoResult<()>;
    /// Check that the driver meets the minimum version tested with these wrappers
    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()
}

/// Gets the minimum supported driver version
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,
        ),
    }
}

/// Shortcuts for loading drivers directly from the `Driver` enum.
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")
            }
        })
    }
}