gpio_mqtt_bridge/
lib.rs

1// Copyright 2021 Grayson Hay.
2// SPDX-License-Identifier: MIT
3
4pub mod config;
5
6use rppal::{gpio::Gpio, gpio::InputPin};
7
8use config::GpioConfig;
9use serde_derive::Serialize;
10use std::collections::HashMap;
11
12#[derive(Debug)]
13pub struct InterruptCtrl {
14    gpio: Gpio,
15    pins: Vec<InputPin>,
16    topic_map: HashMap<u8, String>,
17}
18
19#[derive(Debug)]
20pub enum ICError {
21    GenericError(String),
22}
23
24impl std::error::Error for ICError {}
25
26impl From<rppal::gpio::Error> for ICError {
27    fn from(gpioe: rppal::gpio::Error) -> ICError {
28        ICError::GenericError(format!("{:?}", gpioe))
29    }
30}
31
32impl std::fmt::Display for ICError {
33    fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
34        use ICError::*;
35        match self {
36            GenericError(e) => write!(f, "InterruptCtrl Error: {}", e),
37        }
38    }
39}
40
41#[derive(Debug, Serialize)]
42struct Message {
43    pin: u8,
44    message: String,
45}
46
47impl Message {
48    pub fn new(pin: u8, message: String) -> Message {
49        Message { pin, message }
50    }
51}
52
53impl ToString for Message {
54    fn to_string(&self) -> String {
55        serde_json::to_string(self).unwrap_or_else(|_| "Unknown Payload".to_string())
56    }
57}
58
59impl InterruptCtrl {
60    pub fn from_gpio_config(configs: &[GpioConfig]) -> Result<Self, ICError> {
61        let gpio = Gpio::new()?;
62
63        let pins: Result<Vec<InputPin>, _> = configs
64            .iter()
65            .map(|c| {
66                gpio.get(c.pin)
67                    .map(|p| match c.with_pullup() {
68                        true => p.into_input_pullup(),
69                        false => p.into_input(),
70                    })
71                    .map(|mut p| {
72                        p.set_interrupt(c.trigger.into())
73                            .unwrap_or_else(|_| panic!());
74                        p
75                    })
76            })
77            .collect();
78        let topics: HashMap<u8, String> = configs
79            .iter()
80            .map(|c| (c.pin, c.topic.to_string()))
81            .collect();
82
83        let pins = pins?;
84        let retval = InterruptCtrl {
85            gpio,
86            pins,
87            topic_map: topics,
88        };
89
90        Ok(retval)
91    }
92
93    pub fn poll<C>(&self, mut callback: C) -> Result<(), ICError>
94    where
95        C: FnMut(&str, &str),
96    {
97        let pin_refs: Vec<_> = self.pins.iter().collect();
98        let result = self.gpio.poll_interrupts(&pin_refs, false, None)?; // TODO: Maybe implement a timeout
99        if let Some((pin, level)) = result {
100            let topic = self.topic_map.get(&pin.pin());
101            if let Some(topic) = topic {
102                let message = Message::new(pin.pin(), format!("{:?}", level)).to_string();
103                callback(topic, &message);
104            } else {
105                unreachable!();
106            }
107        };
108        Ok(())
109    }
110}
111
112#[cfg(test)]
113mod test {
114    use crate::config::TriggerType;
115
116    use super::*;
117
118    #[test]
119    fn can_create_interrupt_ctrl() {
120        let gpios = vec![GpioConfig::new(1, "foo", TriggerType::Falling)];
121        let _ctrl = InterruptCtrl::from_gpio_config(&gpios).expect("Unable to create Ctrl");
122    }
123
124    #[test]
125    fn cannot_create_same_pin_twice() {
126        let gpios = vec![
127            GpioConfig::new(1, "yes", TriggerType::Falling),
128            GpioConfig::new(1, "no", TriggerType::Rising),
129        ];
130        let ctrl = InterruptCtrl::from_gpio_config(&gpios);
131        assert!(ctrl.is_err());
132    }
133}