use std::path::Path;
use embedded_hal::digital::PinState;
use gpiocdev::{
line::{Config, Direction, Offset, Value},
Request,
};
#[cfg(any(feature = "async_tokio", feature = "async_io"))]
mod r#async;
#[cfg(feature = "async_io")]
pub use r#async::async_io;
#[cfg(feature = "async_tokio")]
pub use r#async::tokio;
#[derive(Debug)]
struct Pin {
req: Request,
offset: Offset,
config: Config,
}
impl Pin {
#[inline]
fn is_high(&mut self) -> Result<bool, Error> {
Ok(self.req.as_ref().value(self.offset)?
== state_to_value(PinState::High, self.config.active_low))
}
#[inline]
fn is_low(&mut self) -> Result<bool, Error> {
Ok(!self.is_high()?)
}
}
impl From<Pin> for Request {
fn from(pin: Pin) -> Self {
pin.req
}
}
#[derive(Debug)]
pub struct InputPin(Pin);
impl InputPin {
pub fn new<P>(chip: P, offset: u32) -> Result<Self, Error>
where
P: AsRef<Path>,
{
let req = Request::builder()
.on_chip(chip.as_ref())
.with_line(offset)
.as_input()
.request()?;
InputPin::try_from(req)
}
pub fn into_output_pin(mut self, state: PinState) -> Result<OutputPin, Error> {
let pin = &mut self.0;
let req = pin.req.as_ref();
let value = state_to_value(state, pin.config.active_low);
let mut config = req.config();
config.from_line_config(&pin.config).as_output(value);
req.reconfigure(&config)?;
pin.config.direction = Some(Direction::Output);
pin.config.value = Some(value);
Ok(OutputPin(self.0))
}
pub fn from_found_line(fl: gpiocdev::FoundLine) -> Result<Self, Error> {
let req = Request::builder()
.with_found_line(&fl)
.as_input()
.request()?;
let config = req.config();
let line_config = config.line_config(fl.info.offset).unwrap().clone();
Ok(InputPin(Pin {
req,
offset: fl.info.offset,
config: line_config,
}))
}
pub fn from_name(name: &str) -> Result<Self, Error> {
let line = gpiocdev::find_named_line(name)
.ok_or_else(|| Error::UnfoundLine(name.into()))?;
Self::from_found_line(line)
}
}
impl TryFrom<Request> for InputPin {
type Error = Error;
fn try_from(req: Request) -> Result<Self, Self::Error> {
let config = req.as_ref().config();
let offsets = config.lines();
if offsets.len() != 1 {
return Err(Error::MultipleLinesRequested);
}
let offset = offsets[0];
let line_config = config.line_config(offset).unwrap().clone();
if line_config.direction != Some(Direction::Input) {
return Err(Error::RequiresInputMode);
}
Ok(InputPin(Pin {
req,
offset,
config: line_config,
}))
}
}
impl From<InputPin> for Request {
fn from(pin: InputPin) -> Self {
pin.0.req
}
}
impl embedded_hal::digital::InputPin for InputPin {
#[inline]
fn is_high(&mut self) -> Result<bool, Self::Error> {
self.0.is_high()
}
#[inline]
fn is_low(&mut self) -> Result<bool, Self::Error> {
self.0.is_low()
}
}
impl embedded_hal::digital::ErrorType for InputPin {
type Error = Error;
}
#[derive(Debug)]
pub struct OutputPin(Pin);
impl OutputPin {
pub fn new<P>(chip: P, offset: u32, state: PinState) -> Result<Self, Error>
where
P: AsRef<Path>,
{
let req = Request::builder()
.on_chip(chip.as_ref())
.with_line(offset)
.as_output(state_to_value(state, false))
.request()?;
OutputPin::try_from(req)
}
pub fn into_input_pin(mut self) -> Result<InputPin, Error> {
let pin = &mut self.0;
let req = pin.req.as_ref();
let mut config = req.config();
config.from_line_config(&pin.config).as_input();
req.reconfigure(&config)?;
pin.config.direction = Some(Direction::Input);
pin.config.value = None;
Ok(InputPin(self.0))
}
pub fn from_found_line(fl: gpiocdev::FoundLine, state: PinState) -> Result<Self, Error> {
let req = Request::builder()
.with_found_line(&fl)
.as_output(state_to_value(state, false))
.request()?;
let config = req.config();
let line_config = config.line_config(fl.info.offset).unwrap().clone();
Ok(OutputPin(Pin {
req,
offset: fl.info.offset,
config: line_config,
}))
}
pub fn from_name(name: &str, state: PinState) -> Result<Self, Error> {
let line = gpiocdev::find_named_line(name)
.ok_or_else(|| Error::UnfoundLine(name.into()))?;
Self::from_found_line(line, state)
}
}
impl TryFrom<Request> for OutputPin {
type Error = Error;
fn try_from(req: Request) -> Result<Self, Self::Error> {
let config = req.as_ref().config();
let offsets = config.lines();
if offsets.len() != 1 {
return Err(Error::MultipleLinesRequested);
}
let offset = offsets[0];
let line_config = config.line_config(offset).unwrap().clone();
if line_config.direction != Some(Direction::Output) {
return Err(Error::RequiresOutputMode);
}
Ok(OutputPin(Pin {
req,
offset,
config: line_config,
}))
}
}
impl From<OutputPin> for Request {
fn from(pin: OutputPin) -> Self {
pin.0.req
}
}
impl embedded_hal::digital::InputPin for OutputPin {
#[inline]
fn is_high(&mut self) -> Result<bool, Self::Error> {
self.0.is_high()
}
#[inline]
fn is_low(&mut self) -> Result<bool, Self::Error> {
self.0.is_low()
}
}
impl embedded_hal::digital::OutputPin for OutputPin {
#[inline]
fn set_low(&mut self) -> Result<(), Self::Error> {
self.set_state(PinState::Low)
}
#[inline]
fn set_high(&mut self) -> Result<(), Self::Error> {
self.set_state(PinState::High)
}
fn set_state(&mut self, state: PinState) -> Result<(), Error> {
let pin = &mut self.0;
let value = state_to_value(state, pin.config.active_low);
if pin.config.value != Some(value) {
pin.req.as_ref().set_value(pin.offset, value)?;
pin.config.value = Some(value);
}
Ok(())
}
}
impl embedded_hal::digital::StatefulOutputPin for OutputPin {
fn is_set_high(&mut self) -> Result<bool, Self::Error> {
Ok(self.0.config.value == Some(Value::Active))
}
fn is_set_low(&mut self) -> Result<bool, Self::Error> {
Ok(self.0.config.value == Some(Value::Inactive))
}
fn toggle(&mut self) -> Result<(), Self::Error> {
let pin = &mut self.0;
let value = pin.config.value.unwrap_or_default().not();
pin.req.as_ref().set_value(pin.offset, value)?;
pin.config.value = Some(value);
Ok(())
}
}
impl embedded_hal::digital::ErrorType for OutputPin {
type Error = Error;
}
fn state_to_value(state: PinState, is_active_low: bool) -> Value {
let value = match state {
PinState::High => Value::Active,
PinState::Low => Value::Inactive,
};
if is_active_low {
return value.not();
}
value
}
#[derive(Clone, Debug, thiserror::Error, Eq, PartialEq)]
pub enum Error {
#[error("Request must not contain a multiple lines")]
MultipleLinesRequested,
#[error("Requested pin must be in input mode")]
RequiresInputMode,
#[error("Requested pin must be in output mode")]
RequiresOutputMode,
#[error("Cannot find a line named '{0}'")]
UnfoundLine(String),
#[error("gpiocdev returned: {0}")]
Cdev(#[source] gpiocdev::Error),
}
impl From<gpiocdev::Error> for Error {
fn from(err: gpiocdev::Error) -> Self {
Self::Cdev(err)
}
}
impl embedded_hal::digital::Error for Error {
fn kind(&self) -> embedded_hal::digital::ErrorKind {
embedded_hal::digital::ErrorKind::Other
}
}