pico_driver/
lib.rs

1//! Common, safe wrappers for Pico Technology oscilloscope drivers.
2//!
3//! This is a sub crate that you probably don't want to use directly. Try the top level
4//! [`pico-sdk`](https://crates.io/crates/pico-sdk) crate which exposes everything from here.
5//!
6//! Each Pico Technology oscilloscope relies on a native driver for communication and this driver will
7//! vary depending on the device product range. Each of these drivers has an interface which differs by
8//! either a few function arguments or a vastly differing API.
9//!
10//! `PS2000Driver`, `PS2000ADriver`, `PS3000ADriver`, `PS4000Driver`,
11//! `PS4000ADriver`, `PS5000ADriver`, `PS6000Driver` and `PS6000ADriver` wrap
12//! their corresponding loaders and expose a safe, common API by implementing
13//! the `PicoDriver` trait. These can be constructed with a `Resolution` which tells the wrapper where
14//! to resolve the dynamic library from. The `LoadDriverExt` trait supplies a shortcut to load a driver
15//! directly from the `Driver` enum via `try_load` and `try_load_with_resolution`.
16//!
17//! # Examples
18//! Using the raw safe bindings to open and configure the first available device:
19//! ```no_run
20//! # fn run() -> Result<(),Box<dyn std::error::Error>> {
21//! use pico_common::{ChannelConfig, Driver, PicoChannel, PicoCoupling, PicoInfo, PicoRange};
22//! use pico_driver::{LoadDriverExt, Resolution};
23//!
24//! // Load the ps2000 driver library with the default resolution
25//! let driver = Driver::PS2000.try_load()?;
26//! // Load the ps4000a driver library from the applications root directory
27//! let driver = Driver::PS4000A.try_load_with_resolution(&Resolution::AppRoot)?;
28//!
29//! // Open the first device
30//! let handle = driver.open_unit(None)?;
31//! let variant = driver.get_unit_info(handle, PicoInfo::VARIANT_INFO)?;
32//!
33//! let ch_config = ChannelConfig {
34//!     coupling: PicoCoupling::DC,
35//!     range: PicoRange::X1_PROBE_2V,
36//!     offset: 0.0
37//! };
38//!
39//! driver.enable_channel(handle, PicoChannel::A, &ch_config)?;
40//! # Ok(())
41//! # }
42//! ```
43
44#![allow(clippy::upper_case_acronyms)]
45
46use parking_lot::RwLock;
47use pico_common::{
48    ChannelConfig, Driver, FromPicoStr, PicoChannel, PicoError, PicoInfo, PicoRange, PicoResult,
49    SampleConfig,
50};
51pub use resolution::Resolution;
52use std::{fmt, pin::Pin, sync::Arc};
53use thiserror::Error;
54use version_compare::Version;
55
56mod dependencies;
57pub mod kernel_driver;
58pub mod ps2000;
59pub mod ps2000a;
60pub mod ps3000a;
61pub mod ps4000;
62pub mod ps4000a;
63pub mod ps5000a;
64pub mod ps6000;
65pub mod ps6000a;
66mod resolution;
67mod trampoline;
68
69/// Covers the various errors encountered when attempting to load drivers
70#[derive(Error, Debug)]
71pub enum DriverLoadError {
72    #[error("Pico driver error: {0}")]
73    DriverError(#[from] PicoError),
74
75    #[error("Library load error: {0}")]
76    LibloadingError(#[from] libloading::Error),
77
78    #[error("Invalid Driver Version: Requires >= {required}, Found: {found}")]
79    VersionError { found: String, required: String },
80}
81
82/// Serial and variant pairs returned by driver enumeration
83#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
84#[derive(Clone, Debug)]
85pub struct EnumerationResult {
86    pub variant: String,
87    pub serial: String,
88}
89
90/// Common trait implemented for every driver
91pub trait PicoDriver: fmt::Debug + Send + Sync {
92    /// Gets the underlying driver type
93    fn get_driver(&self) -> Driver;
94    /// Gets the driver version string
95    fn get_version(&self) -> PicoResult<String>;
96    /// Gets the path to the loaded driver
97    fn get_path(&self) -> PicoResult<Option<String>>;
98    /// Returns a list of discovered serial numbers
99    fn enumerate_units(&self) -> PicoResult<Vec<EnumerationResult>>;
100    /// Opens a device, optionally with a specific serial number
101    fn open_unit(&self, serial: Option<&str>) -> PicoResult<i16>;
102    /// Ping a unit to see if it's still connected
103    fn ping_unit(&self, handle: i16) -> PicoResult<()>;
104    /// Get the maximum expected ADC value. This is required to scale to volts
105    fn maximum_value(&self, handle: i16) -> PicoResult<i16>;
106    /// Close the specified unit
107    fn close(&self, handle: i16) -> PicoResult<()>;
108    /// Get one of the unit info strings
109    fn get_unit_info(&self, handle: i16, info_type: PicoInfo) -> PicoResult<String>;
110    /// Get valid ranges for the specified channel
111    fn get_channel_ranges(&self, handle: i16, channel: PicoChannel) -> PicoResult<Vec<PicoRange>>;
112    /// Set up a channel with the supplied config
113    fn enable_channel(
114        &self,
115        handle: i16,
116        channel: PicoChannel,
117        config: &ChannelConfig,
118    ) -> PicoResult<()>;
119    /// Disable a channel
120    fn disable_channel(&self, handle: i16, channel: PicoChannel) -> PicoResult<()>;
121    /// Give the driver a buffer to write data into
122    fn set_data_buffer(
123        &self,
124        handle: i16,
125        channel: PicoChannel,
126        buffer: Arc<RwLock<Pin<Vec<i16>>>>,
127        buffer_len: usize,
128    ) -> PicoResult<()>;
129    /// Starts the device streaming
130    fn start_streaming(
131        &self,
132        handle: i16,
133        sample_config: &SampleConfig,
134    ) -> PicoResult<SampleConfig>;
135    /// Gets the latest streaming values
136    fn get_latest_streaming_values<'a>(
137        &self,
138        handle: i16,
139        channels: &[PicoChannel],
140        callback: Box<dyn FnMut(usize, usize) + 'a>,
141    ) -> PicoResult<()>;
142    /// Stops the device streaming
143    fn stop(&self, handle: i16) -> PicoResult<()>;
144    /// Check that the driver meets the minimum version tested with these wrappers
145    fn check_version(&self) -> Result<(), DriverLoadError> {
146        let loaded_str = &self.get_version()?;
147        let loaded_version = Version::from(loaded_str);
148
149        #[allow(clippy::expect_fun_call)]
150        let required_str = get_min_required_version(self.get_driver());
151        let required_version = Version::from(required_str);
152
153        if loaded_version < required_version {
154            Err(DriverLoadError::VersionError {
155                found: loaded_str.to_string(),
156                required: required_str.to_string(),
157            })
158        } else {
159            Ok(())
160        }
161    }
162}
163
164pub type ArcDriver = Arc<dyn PicoDriver>;
165
166pub(crate) fn get_version_string(input: &str) -> String {
167    input
168        .split(|s| s == ' ' || s == ',')
169        .last()
170        .expect("Invalid version string")
171        .to_string()
172}
173
174pub(crate) fn parse_enum_result(buffer: &[i8], len: usize) -> Vec<EnumerationResult> {
175    let serials_list = buffer.from_pico_i8_string(len);
176
177    serials_list
178        .split(',')
179        .map(String::from)
180        .map(|device| {
181            let parts = device.split('[').collect::<Vec<_>>();
182
183            EnumerationResult {
184                serial: parts[0].to_string(),
185                variant: parts[1].trim_end_matches(']').to_string(),
186            }
187        })
188        .collect()
189}
190
191/// Gets the minimum supported driver version
192fn get_min_required_version(driver: Driver) -> &'static str {
193    match driver {
194        Driver::PS2000 => "3.0.30.1878",
195        Driver::PS2000A
196        | Driver::PS3000A
197        | Driver::PS4000
198        | Driver::PS4000A
199        | Driver::PS5000A
200        | Driver::PS6000 => "2.1.30.1878",
201        Driver::PS6000A => "1.0.54.2438",
202        _ => panic!(
203            "We don't know the minimum required version for the {:?} driver!",
204            driver,
205        ),
206    }
207}
208
209/// Shortcuts for loading drivers directly from the `Driver` enum.
210pub trait LoadDriverExt {
211    fn try_load(&self) -> Result<ArcDriver, DriverLoadError>;
212    fn try_load_with_resolution(
213        &self,
214        resolution: &Resolution,
215    ) -> Result<ArcDriver, DriverLoadError>;
216}
217
218impl LoadDriverExt for Driver {
219    fn try_load(&self) -> Result<ArcDriver, DriverLoadError> {
220        self.try_load_with_resolution(&Default::default())
221    }
222
223    fn try_load_with_resolution(
224        &self,
225        resolution: &Resolution,
226    ) -> Result<ArcDriver, DriverLoadError> {
227        let path = resolution.get_path(*self);
228        Ok(match self {
229            Driver::PS2000 => Arc::new(ps2000::PS2000Driver::new(path)?),
230            Driver::PS2000A => Arc::new(ps2000a::PS2000ADriver::new(path)?),
231            Driver::PS3000A => Arc::new(ps3000a::PS3000ADriver::new(path)?),
232            Driver::PS4000 => Arc::new(ps4000::PS4000Driver::new(path)?),
233            Driver::PS4000A => Arc::new(ps4000a::PS4000ADriver::new(path)?),
234            Driver::PS5000A => Arc::new(ps5000a::PS5000ADriver::new(path)?),
235            Driver::PS6000 => Arc::new(ps6000::PS6000Driver::new(path)?),
236            Driver::PS6000A => Arc::new(ps6000a::PS6000ADriver::new(path)?),
237            Driver::PicoIPP | Driver::IOMP5 => {
238                panic!("These are libraries used by Pico drivers and cannot be loaded directly")
239            }
240        })
241    }
242}