wiringx/
lib.rs

1//! Safe WiringX Rust bindings.
2//!
3//! Example Blinker on pin `0` for [`Milk-V Duo S`](Platform::MilkVDuoS):
4//! ```
5//! use wiringx::{Output, Platform, WiringX};
6//!
7//! use std::{thread, time::Duration};
8//!
9//! // Replace `Platform` with your platform
10//! let wiringx = WiringX::new(Platform::MilkVDuoS).unwrap();
11//!
12//! // We use pin `0`, for the built in LED
13//! let mut pin = wiringx.gpio_pin::<Output>(0).unwrap();
14//!
15//! loop {
16//!  pin.toggle();
17//!  thread::sleep(Duration::from_secs(1));
18//! }
19//! ```
20
21mod platform;
22pub use platform::*;
23
24mod gpio;
25pub use gpio::*;
26
27mod i2c;
28pub use i2c::*;
29
30mod pwm;
31pub use pwm::*;
32
33mod spi;
34pub use spi::*;
35
36pub use uart::*;
37mod uart;
38
39use thiserror::Error;
40
41use std::{
42    any::TypeId,
43    collections::HashSet,
44    io,
45    os::fd::RawFd,
46    path::PathBuf,
47    sync::{Arc, OnceLock},
48    time::Duration,
49};
50
51use parking_lot::Mutex;
52
53use wiringx_sys::{
54    pinMode, pinmode_t_PINMODE_INPUT, pinmode_t_PINMODE_OUTPUT, wiringXGC, wiringXSelectableFd,
55    wiringXSetup, wiringXValidGPIO,
56};
57
58static WIRINGX: OnceLock<WiringX> = OnceLock::new();
59
60/// A pin handle
61type Hand<T> = Arc<Mutex<HashSet<T>>>;
62
63/// Instance of WiringX
64///
65/// Used to register pins and interfaces to be safely used.
66///
67/// Keeps track of all pins and protocol instances.
68///
69/// Can be shared across threads safely.
70#[derive(Clone, Debug)]
71pub struct WiringX {
72    platform: Platform,
73    gpio_handles: Hand<i32>,
74    pwm_handles: Hand<i32>,
75    i2c_handles: Hand<(PathBuf, i32)>,
76    spi_handles: Hand<i32>,
77    uart_handles: Hand<PathBuf>,
78}
79
80impl WiringX {
81    /// Sets up WiringX for the given board.
82    ///
83    /// When called a second time, the platform argument does not do anything.
84    /// Instead the same instance will be returned.
85    pub fn new(platform: Platform) -> Result<&'static Self, WiringXError> {
86        let error = OnceLock::new();
87
88        let wiringx = WIRINGX.get_or_init(|| {
89            let result = unsafe { wiringXSetup(platform.as_c_addr(), None) };
90
91            if result != 0 {
92                error.get_or_init(|| "Failed to initialize WiringX");
93            };
94
95            WiringX {
96                platform,
97                gpio_handles: Mutex::new(HashSet::new()).into(),
98                pwm_handles: Mutex::new(HashSet::new()).into(),
99                i2c_handles: Mutex::new(HashSet::new()).into(),
100                spi_handles: Mutex::new(HashSet::new()).into(),
101                uart_handles: Mutex::new(HashSet::new()).into(),
102            }
103        });
104
105        if let Some(error) = error.get() {
106            Err(WiringXError::InitError(error.to_string()))
107        } else {
108            Ok(wiringx)
109        }
110    }
111
112    /// Returns the WiringX platform of this instance.
113    #[inline]
114    pub fn platform(&self) -> Platform {
115        self.platform
116    }
117
118    /// Returns true if the given GPIO number is valid for this platform.
119    pub fn valid_gpio(&self, gpio_pin: i32) -> bool {
120        let result = unsafe { wiringXValidGPIO(gpio_pin) };
121
122        result == 0
123    }
124
125    /// Returns a raw file descriptor to the given GPIO pin.
126    pub fn selectable_fd(&self, gpio_pin: i32) -> Result<RawFd, WiringXError> {
127        if !self.valid_gpio(gpio_pin) {
128            return Err(WiringXError::InvalidPin);
129        }
130
131        let fd = unsafe { wiringXSelectableFd(gpio_pin) };
132        if fd < 0 {
133            Err(WiringXError::Io(io::Error::last_os_error()))
134        } else {
135            Ok(fd)
136        }
137    }
138
139    /// Returns a handle to a pin marked either as [`Input`] or [`Output`]
140    pub fn gpio_pin<State: 'static + Default>(
141        &self,
142        pin_number: i32,
143    ) -> Result<Pin<State>, WiringXError> {
144        if self.gpio_handles.lock().contains(&pin_number) {
145            return Err(WiringXError::PinUsed);
146        }
147
148        if !self.valid_gpio(pin_number) {
149            return Err(WiringXError::InvalidPin);
150        }
151
152        let type_id = TypeId::of::<State>();
153
154        if type_id == TypeId::of::<Input>() {
155            unsafe { pinMode(pin_number, pinmode_t_PINMODE_INPUT) }
156        } else if type_id == TypeId::of::<Output>() {
157            unsafe { pinMode(pin_number, pinmode_t_PINMODE_OUTPUT) }
158        } else {
159            return Err(WiringXError::InvalidStateType);
160        };
161
162        self.gpio_handles.lock().insert(pin_number);
163
164        Ok(Pin::new(pin_number, self.gpio_handles.clone()))
165    }
166
167    /// Enables and returns a handle to a pulse-width modulated pin, if supported.
168    #[inline]
169    pub fn pwm_pin(
170        &self,
171        pin_number: i32,
172        period: Duration,
173        duty_cycle: f32,
174        polarity: Polarity,
175    ) -> Result<PwmPin, WiringXError> {
176        PwmPin::new(
177            pin_number,
178            self.pwm_handles.clone(),
179            period,
180            duty_cycle,
181            polarity,
182        )
183    }
184
185    /// Sets up an inter-integrated circuit instance for the given I2C device path, for example `/dev/i2c-1`, and the device address.
186    #[inline]
187    pub fn setup_i2c(&self, dev: PathBuf, addr: i32) -> Result<I2C, WiringXError> {
188        I2C::new(dev, addr, self.i2c_handles.clone())
189    }
190
191    /// Sets up an serial peripheral interface instance for the given device channel.
192    ///
193    /// Speed is measured in Hertz here.
194    #[inline]
195    pub fn setup_spi(&self, channel: i32, speed: u32) -> Result<Spi, WiringXError> {
196        Spi::new(channel, speed as i32, self.spi_handles.clone())
197    }
198
199    /// Sets up a universal asynchronous receiver-transmitter instance with the provided device path and configuration.
200    #[inline]
201    pub fn setup_uart(&self, dev: PathBuf, config: SerialConfig) -> Result<Uart, WiringXError> {
202        Uart::new(dev, config, self.uart_handles.clone())
203    }
204}
205
206impl Drop for WiringX {
207    #[inline]
208    fn drop(&mut self) {
209        unsafe {
210            wiringXGC();
211        }
212    }
213}
214
215/// Errors that can occur from wiringX.
216#[derive(Debug, Error)]
217pub enum WiringXError {
218    /// Gets returned when an error occurs when starting wiringX.
219    ///
220    /// Maybe the device this gets used on does not support wiringX.
221    #[error("Failed to initialize wiringX: {0}")]
222    InitError(String),
223    #[error("An unexpected error occured: {0}")]
224    Other(String),
225    /// A function was used with a pin that is not supported for the given platform.
226    #[error("The given pin does not exist for this platform.")]
227    InvalidPin,
228    /// The provided pin already has an instance. Pins can only exist once.
229    #[error("The given pin is already used. Pin instances can only exist once.")]
230    PinUsed,
231    /// When using the `pin` function of `WiringX` with a generic other than `Input` or `Output`.
232    #[error("A pin can not be created with generics other than `Input` or `Output`.")]
233    InvalidStateType,
234    /// Gets returned when a a function gets called that is not supported on the set platform.
235    #[error("The function you are trying to call is not supported on your platform.")]
236    Unsupported,
237    /// Gets returned if the provided config for UART is not valid.
238    #[error("The provided UART config is not valid: {0}")]
239    InvalidUARTConfig(InvalidUARTConfig),
240    /// Gets returned when a value is not accepted by the device.
241    #[error("Failed to write value: Invalid argument")]
242    InvalidArgument,
243    /// Io os error.
244    #[error("IO error: {0}")]
245    Io(io::Error),
246}