phidget/
lib.rs

1// phidget-rs/src/lib.rs
2//
3// Copyright (c) 2023, Frank Pagliughi
4//
5// This file is part of the 'phidget-rs' library.
6//
7// Licensed under the MIT license:
8//   <LICENSE or http://opensource.org/licenses/MIT>
9// This file may not be copied, modified, or distributed except according
10// to those terms.
11//
12
13//! Safe Rust bindings to the phidget22 library.
14//!
15//! # Basic usage
16//!
17//! This example shows how to access a simple Digital Input, connected to the first available channel of a Vint HUB.
18//! See the `examples` directory for more thorough code snippets.
19//! ```rust,no_run
20//! use phidget::{DigitalOutput, Phidget};
21//! # use std::time::Duration;
22//!
23//! // Create a handle to a Digital Output device
24//! let mut out = DigitalOutput::new();
25//! // Before opening the device, set its VINT hub port
26//! out.set_is_hub_port_device(true).unwrap();
27//! out.set_hub_port(0).unwrap();
28//!
29//! // Start connection. Make sure to handle the result
30//! // to check the device is available
31//! out.open_wait_default().unwrap();
32//!
33//! // Control the output device
34//! loop {
35//!     println!("Turn on LED");
36//!     out.set_state(1).unwrap();
37//!     std::thread::sleep(Duration::from_secs(3));
38//!
39//!     println!("Turn off LED");
40//!     out.set_state(0).unwrap();
41//!     std::thread::sleep(Duration::from_secs(3));
42//! }
43//! ```
44//!
45//! # Callbacks
46//! In order to activate an output phidget device depending on the state of other sensors,
47//! for instance by turning on an LED whenever another sensor detects something,
48//! you need to set a callback listening for sensor value changes, and keep a valid handle to the output device to set its state.
49//!
50//! The problem is, Phidget callbacks do run in a different thread. A Phidget handle can already be sent
51//! to a different thread, as it implements [Send], but it doesn't implement [Sync].
52//! Hence, if you desire to access the same handle from different callbacks, it has to be wrapped in a
53//! Sync container, such as a [Mutex](std::sync::Mutex).
54//!
55//! ```rust,no_run
56//! # use phidget::{Phidget, DigitalOutput, DigitalInput};
57//! # use std::sync::Mutex;
58//! # fn main()
59//! # {
60//! #    // Open a digitalInput to detect a button
61//!     let mut button = DigitalInput::new();
62//! #   button.set_channel(0).unwrap();
63//!     // Open the digital output where
64//!     // a LED is connected to.
65//!     // In this example, it is initialized
66//!     // and wrapped in a Mutex
67//!     let led = Mutex::new({
68//!         let mut tmp = DigitalOutput::new();
69//!         tmp.set_channel(1).unwrap();
70//!         tmp.open_wait_default().unwrap();
71//!         tmp
72//!     });
73//!
74//!     // Make the button alternate the LED state
75//!     button.set_on_state_change_handler(move |_, s: u8| {
76//!         let lock = led.lock().unwrap();
77//!         match s {
78//!             // Access the device inside the Mutex and change its state
79//!             0 => lock.set_state(0).unwrap(),
80//!             _ => lock.set_state(1).unwrap()
81//!         }
82//!     }).unwrap();
83//! # }
84//! ```
85
86// Platform dependent, whether this is necessary
87#![allow(clippy::unnecessary_cast)]
88// Lints
89#![deny(
90    missing_docs,
91    missing_copy_implementations,
92    trivial_casts,
93    unstable_features,
94    unused_import_braces,
95    unused_qualifications
96)]
97
98use std::{
99    ffi::CStr,
100    os::raw::{c_char, c_uint, c_void},
101    ptr,
102    time::Duration,
103};
104
105pub use phidget_sys::{
106    self as ffi, PHIDGET_CHANNEL_ANY, PHIDGET_HUBPORTSPEED_AUTO, PHIDGET_HUBPORT_ANY,
107    PHIDGET_SERIALNUMBER_ANY, PHIDGET_TIMEOUT_DEFAULT, PHIDGET_TIMEOUT_INFINITE,
108};
109
110/// The error types for the crate
111pub mod errors;
112pub use crate::errors::*;
113
114/// The enumerated types for the crate
115pub mod types;
116pub use crate::types::*;
117
118/// The main Phidget trait
119pub mod phidget;
120pub use crate::phidget::{
121    AttachCallback, DetachCallback, GenericPhidget, Phidget, PhidgetFilter, PhidgetInfo, PhidgetRef,
122};
123
124/// Network API
125pub mod net;
126pub use crate::net::ServerType;
127
128/// Module containing all implemented devices
129pub mod devices;
130pub use crate::devices::{
131    current_input::CurrentInput, digital_input::DigitalInput, digital_output::DigitalOutput,
132    hub::Hub, humidity_sensor::HumiditySensor, pressure_sensor::PressureSensor,
133    temperature_sensor::TemperatureSensor, voltage_input::VoltageInput,
134    voltage_output::VoltageOutput, voltage_ratio_input::VoltageRatioInput,
135};
136
137pub mod manager;
138pub use crate::manager::{ManagerAttachCallback, ManagerDetachCallback, PhidgetManager};
139
140/// An infinite timeout (wait forever)
141pub const TIMEOUT_INFINITE: Duration = Duration::from_millis(PHIDGET_TIMEOUT_INFINITE as u64);
142
143/// The default timeout for the library
144pub const TIMEOUT_DEFAULT: Duration = Duration::from_millis(PHIDGET_TIMEOUT_DEFAULT as u64);
145
146/////////////////////////////////////////////////////////////////////////////
147/// Gets a string from a phidget22 call.
148/// This can be any function that takes a pointer to a c-str as the lone
149/// argument.
150pub(crate) fn get_ffi_string<F>(mut f: F) -> Result<String>
151where
152    F: FnMut(*mut *const c_char) -> c_uint,
153{
154    unsafe {
155        let mut ver: *const c_char = ptr::null_mut();
156        ReturnCode::result(f(&mut ver))?;
157        if ver.is_null() {
158            return Err(ReturnCode::NoMemory);
159        }
160        let s = CStr::from_ptr(ver);
161        Ok(s.to_string_lossy().into())
162    }
163}
164
165/// Release the memory held in a double-boxed callback function/lambda.
166pub(crate) fn drop_cb<P: ?Sized>(cb: Option<*mut c_void>) {
167    if let Some(ctx) = cb {
168        let _: Box<Box<P>> = unsafe { Box::from_raw(ctx as *mut _) };
169    }
170}
171
172/////////////////////////////////////////////////////////////////////////////
173
174/// The the full version of the phidget22 library as a string.
175/// This is something like, "Phidget22 - Version 1.14 - Built Mar 31 2023 22:44:59"
176pub fn library_version() -> Result<String> {
177    get_ffi_string(|s| unsafe { ffi::Phidget_getLibraryVersion(s) })
178}
179
180/// Gets just the version number of the phidget22 library as a string.
181/// This is something like, "1.14"
182pub fn library_version_number() -> Result<String> {
183    get_ffi_string(|s| unsafe { ffi::Phidget_getLibraryVersionNumber(s) })
184}
185
186/// Closes all channels, and stops all threads. The library is reset to a
187/// newly loaded state. All channel handles have been freed.
188///
189/// This function is intended for use in special cases where the library
190/// cannot be unloaded between program runs, such as LabVIEW and Unity
191/// Editor.
192///
193/// # Safety
194///
195/// This invalidates all Phidget objects that are running.
196///
197pub unsafe fn reset_library() -> Result<()> {
198    ReturnCode::result(unsafe { ffi::Phidget_resetLibrary() })
199}
200
201/////////////////////////////////////////////////////////////////////////////
202
203#[cfg(test)]
204mod tests {
205    use super::*;
206
207    #[test]
208    fn test_library_queries() {
209        assert!(library_version().is_ok());
210        assert!(library_version_number().is_ok());
211    }
212}