diff --git a/Cargo.lock b/Cargo.lock index fe7e318..339aae8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -137,6 +137,12 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + [[package]] name = "prettyplease" version = "0.2.25" @@ -314,5 +320,6 @@ dependencies = [ "bindgen", "cc", "glob", + "once_cell", "thiserror", ] diff --git a/Cargo.toml b/Cargo.toml index f2ae784..a5bd5ce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] +once_cell = "1.20.2" thiserror = "1.0.65" [build-dependencies] diff --git a/src/gpio.rs b/src/gpio.rs index f53d9a9..5557e38 100644 --- a/src/gpio.rs +++ b/src/gpio.rs @@ -2,35 +2,193 @@ use std::sync::Mutex; use thiserror::Error; -use crate::ffi; +use crate::{ + ensure_library_setup, + ffi::{self, HIGH, INPUT, LOW, OUTPUT, PUD_DOWN, PUD_OFF, PUD_UP, PWM_OUTPUT}, +}; static PINS_HANDED_OUT: Mutex> = Mutex::new(Vec::new()); +#[derive(Error, Debug)] +pub enum GpioError { + #[error("could not parse value {0} into type")] + ValueParseError(i32), + #[error("pin {0} is in mode {1:?} when it should be in mode {2:?}")] + WrongPinModeError(i32, PinMode, PinMode), + #[error("mode {0:?} is unsupported")] + UnsupportedModeError(PinMode), +} + #[derive(Error, Debug)] pub enum PinRetrievalError { #[error("pin {0} has already been retrieved")] AlreadyRetrieved(i32), } +#[derive(PartialEq, Eq, Debug, Clone, Copy)] +pub enum PinMode { + Input, + Output, + PwmOutput, + Uninitialized, +} + +impl TryFrom for PinMode { + type Error = GpioError; + + fn try_from(value: i32) -> Result { + match value as u32 { + INPUT => Ok(PinMode::Input), + OUTPUT => Ok(PinMode::Output), + PWM_OUTPUT => Ok(PinMode::PwmOutput), + _ => Err(GpioError::ValueParseError(value)), + } + } +} + +impl From for i32 { + fn from(value: PinMode) -> Self { + match value { + PinMode::Input => INPUT as i32, + PinMode::Output => OUTPUT as i32, + PinMode::PwmOutput => PWM_OUTPUT as i32, + PinMode::Uninitialized => panic!("tried to make use of uninitialized pin"), + } + } +} + +#[derive(PartialEq, Eq, Debug, Clone, Copy)] +pub enum PullUpMode { + Off, + Up, + Down, +} + +impl TryFrom for PullUpMode { + type Error = GpioError; + + fn try_from(value: i32) -> Result { + match value as u32 { + PUD_OFF => Ok(PullUpMode::Off), + PUD_UP => Ok(PullUpMode::Up), + PUD_DOWN => Ok(PullUpMode::Down), + _ => Err(GpioError::ValueParseError(value)), + } + } +} + +impl From for i32 { + fn from(value: PullUpMode) -> Self { + match value { + PullUpMode::Down => PUD_DOWN as i32, + PullUpMode::Up => PUD_UP as i32, + PullUpMode::Off => PUD_OFF as i32, + } + } +} + +#[derive(PartialEq, Eq, Debug, Clone, Copy)] +pub enum PinState { + Low, + High, +} + +impl TryFrom for PinState { + type Error = GpioError; + + fn try_from(value: i32) -> Result { + match value as u32 { + HIGH => Ok(PinState::High), + LOW => Ok(PinState::Low), + _ => Err(GpioError::ValueParseError(value)), + } + } +} + +impl From for i32 { + fn from(value: PinState) -> Self { + match value { + PinState::Low => LOW as i32, + PinState::High => HIGH as i32, + } + } +} + pub struct Pin { pin_id: i32, + pin_mode: PinMode, } impl Pin { pub fn new(pin_id: i32) -> Result { + ensure_library_setup!(); + let can_retrieve = !PINS_HANDED_OUT .lock() .expect("failed to obtain pin list lock") .contains(&pin_id); if can_retrieve { - Ok(Self { pin_id }) + Ok(Self { + pin_id, + pin_mode: PinMode::Uninitialized, + }) } else { Err(PinRetrievalError::AlreadyRetrieved(pin_id)) } } - pub fn get_gpio_mode(&mut self) -> i32 { - unsafe { ffi::orangepi_get_gpio_mode(self.pin_id) } + fn ensure_pin_mode(&mut self, mode: PinMode) -> Result<(), GpioError> { + if self.pin_mode != mode { + Err(GpioError::WrongPinModeError( + self.pin_id, + self.pin_mode, + mode, + )) + } else { + Ok(()) + } + } + + pub fn get_gpio_mode(&mut self) -> Result { + ensure_library_setup!(); + + unsafe { ffi::orangepi_get_gpio_mode(self.pin_id).try_into() } + } + + pub fn set_gpio_mode(&mut self, mode: PinMode) -> Result<(), GpioError> { + ensure_library_setup!(); + + if mode == PinMode::PwmOutput || mode == PinMode::Uninitialized { + return Err(GpioError::UnsupportedModeError(mode)); + } + + unsafe { + ffi::pinMode(self.pin_id, mode.into()); + } + + Ok(()) + } + + pub fn digital_read(&mut self) -> Result { + ensure_library_setup!(); + + self.ensure_pin_mode(PinMode::Input)?; + + unsafe { ffi::orangepi_digitalRead(self.pin_id).try_into() } + } + + pub fn digital_write(&mut self, value: PinState) -> Result { + ensure_library_setup!(); + + self.ensure_pin_mode(PinMode::Output)?; + + unsafe { Ok(ffi::orangepi_digitalWrite(self.pin_id, value.into()) == 0) } + } + + pub fn pull_up_down_control(&mut self, mode: PullUpMode) { + ensure_library_setup!(); + + unsafe { ffi::pullUpDnControl(self.pin_id, mode.into()) } } } diff --git a/src/lib.rs b/src/lib.rs index ea4a206..b9f9ec0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,2 +1,15 @@ +use once_cell::sync::Lazy; + mod ffi; pub mod gpio; + +#[macro_export] +macro_rules! ensure_library_setup { + () => { + if !*$crate::LIBRARY_SETUP_COMPLETE { + panic!("wiringOP library failed to set up"); + } + }; +} + +static LIBRARY_SETUP_COMPLETE: Lazy = Lazy::new(|| unsafe { ffi::wiringPiSetupGpio() == 0 });