1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
use crate::io::gpio::{GPIOError, GPIOManager, GPIOMode, GPIOState, PinUpdate};
use log::{debug, error, trace, warn};
use std::{collections::HashMap, thread::sleep, time::Duration};
use sysfs_gpio::{Direction, Error, Pin};
use tokio::sync::mpsc::Sender;

pub struct SysFsGPIO {
    gpios: HashMap<usize, Pin>,
    sender: Sender<PinUpdate>,
}

impl SysFsGPIO {
    pub fn new(sender: Sender<PinUpdate>) -> SysFsGPIO {
        SysFsGPIO {
            gpios: HashMap::new(),
            sender,
        }
    }
}

impl GPIOManager for SysFsGPIO {
    fn setup(&mut self, pin_id: usize, mode: &GPIOMode) -> Result<(), GPIOError> {
        debug!("Setting up pin {}", pin_id);
        let pin = sysfs_gpio::Pin::new(pin_id as u64);
        let direction = match mode {
            GPIOMode::Input => Direction::In,
            GPIOMode::Output => Direction::High,
        };
        pin.export()?;
        let direction_before = pin
            .get_direction()
            .expect("Expected to be able to read direction of pin");
        let already_at_mode = match direction_before {
            Direction::In => {
                matches!(mode, GPIOMode::Input)
            }
            Direction::Out => {
                matches!(mode, GPIOMode::Output)
            }
            Direction::High => {
                matches!(mode, GPIOMode::Output)
            }
            Direction::Low => {
                matches!(mode, GPIOMode::Output)
            }
        };
        if already_at_mode {
            self.gpios.insert(pin_id, pin);
            return Ok(());
        }

        const MAX_ATTEMPTS: usize = 5;
        let mut attempt = 0;
        while let Err(e) = pin.set_direction(direction) {
            warn!(
                "Failed to set direction of pin {} - Attempt {}",
                pin_id, attempt
            );
            if attempt >= MAX_ATTEMPTS {
                return Err(e.into());
            }
            attempt += 1;
            sleep(Duration::from_millis(400));
        }

        warn!("Set direction of pin {} on attempt {}", pin_id, attempt);
        self.gpios.insert(pin_id, pin);
        Ok(())
    }

    fn set_pin(&mut self, pin_id: usize, state: &GPIOState) -> Result<(), GPIOError> {
        trace!("Setting pin {} to {:?}", pin_id, state);
        let pin = self.gpios.get(&pin_id);
        if pin.is_none() {
            return Err(GPIOError::PinNotSetup);
        }
        let pin = pin.unwrap();
        let direction = pin.get_direction()?;
        if direction == Direction::In {
            return Err(GPIOError::PinInIncorrectMode {
                required_mode: GPIOMode::Output,
            });
        }
        let current_state = self.get_pin(pin_id)?;
        if current_state == *state {
            trace!("Pin {} was already {:?}", pin_id, state);
            return Ok(());
        }
        let bit_value = match state {
            GPIOState::High => 1,
            GPIOState::Low => 0,
        };
        let result = pin.set_value(bit_value);

        if result.is_ok() {
            let send_result = self.sender.try_send(PinUpdate::new(pin_id, state.clone()));
            if send_result.is_err() {
                error!("Error notifying sender of pin update {:?}", send_result);
            }
        }
        result.map_err(|err| err.into())
    }

    fn get_pin(&self, pin: usize) -> Result<GPIOState, GPIOError> {
        let pin = self.gpios.get(&pin);
        if pin.is_none() {
            return Err(GPIOError::PinNotSetup);
        }
        let pin = pin.unwrap();
        let value = pin.get_value();
        Ok(value.map(|x| match x {
            0 => GPIOState::Low,
            1 => GPIOState::High,
            _ => panic!("Breach of api contract / implementation"),
        })?)
    }
}

impl From<sysfs_gpio::Error> for GPIOError {
    fn from(err: sysfs_gpio::Error) -> Self {
        match err {
            Error::Io(err) => GPIOError::Io(err),
            Error::Unexpected(s) => GPIOError::Other(s),
            Error::InvalidPath(s) => GPIOError::Other(s),
            Error::Unsupported(s) => GPIOError::Other(s),
        }
    }
}