gpio_mqtt_bridge/
config.rs

1// Copyright 2021 Grayson Hay.
2// SPDX-License-Identifier: MIT
3
4use rumqttc::MqttOptions;
5use serde_derive::Deserialize;
6use std::fs;
7use std::path::Path;
8
9use rppal::gpio::Trigger;
10
11#[derive(Debug, Deserialize)]
12pub struct MqttConfig {
13    pub host: String,
14    pub username: Option<String>,
15    pub(crate) password: Option<String>,
16    pub topic_root: Option<String>,
17}
18
19impl MqttConfig {
20    pub fn as_mqtt_options(&self) -> MqttOptions {
21        let mut opts = MqttOptions::new("waterland-ctrl", &self.host, 1883);
22        if let (Some(username), Some(password)) = (self.username.as_ref(), self.password.as_ref()) {
23            opts.set_credentials(username, password);
24        };
25
26        opts
27    }
28}
29
30#[derive(Debug, Deserialize, Copy, Clone)]
31pub enum TriggerType {
32    #[serde(rename = "rising")]
33    Rising,
34    #[serde(rename = "falling")]
35    Falling,
36    #[serde(rename = "transition")]
37    AnyTransition,
38}
39
40impl From<TriggerType> for Trigger {
41    fn from(item: TriggerType) -> Self {
42        use TriggerType::*;
43        match item {
44            Rising => Trigger::RisingEdge,
45            Falling => Trigger::FallingEdge,
46            AnyTransition => Trigger::Both,
47        }
48    }
49}
50
51#[derive(Debug, Deserialize)]
52pub struct GpioConfig {
53    pub pin: u8,
54    pub topic: String,
55    pub trigger: TriggerType,
56    with_pullup: Option<bool>,
57}
58
59impl GpioConfig {
60    pub fn new(pin: u8, topic: &str, trigger: TriggerType) -> Self {
61        Self {
62            pin,
63            topic: topic.to_owned(),
64            trigger,
65            with_pullup: None,
66        }
67    }
68    pub fn with_pullup(&self) -> bool {
69        self.with_pullup.unwrap_or(false)
70    }
71}
72
73#[derive(Debug, Deserialize)]
74pub struct Config {
75    pub mqtt: MqttConfig,
76    pub gpio: Vec<GpioConfig>,
77}
78
79#[derive(Debug)]
80pub enum ConfigError {
81    IoError(std::io::Error),
82    ParseError(toml::de::Error),
83}
84
85impl std::fmt::Display for ConfigError {
86    fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
87        use ConfigError::*;
88        match self {
89            IoError(e) => write!(f, "IoError: {}", e),
90            ParseError(e) => write!(f, "ParseError: {}", e),
91        }
92    }
93}
94
95impl std::error::Error for ConfigError {}
96
97impl From<std::io::Error> for ConfigError {
98    fn from(e: std::io::Error) -> Self {
99        ConfigError::IoError(e)
100    }
101}
102
103impl From<toml::de::Error> for ConfigError {
104    fn from(e: toml::de::Error) -> Self {
105        ConfigError::ParseError(e)
106    }
107}
108
109impl Config {
110    pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, ConfigError> {
111        let config_data = fs::read_to_string(path)?;
112
113        Ok(toml::from_str(&config_data)?)
114    }
115}
116
117#[cfg(test)]
118mod test {
119    use super::*;
120
121    #[test]
122    fn can_load_config() {
123        let config = Config::from_file("config.toml").expect("config.toml should load");
124        assert_eq!(config.mqtt.host, "broker.hivemq.com");
125        assert_eq!(config.mqtt.topic_root, Some("waterland".to_owned()));
126        assert_eq!(config.mqtt.username, None);
127        assert_eq!(config.mqtt.password, None);
128        assert_eq!(config.gpio.len(), 3);
129        assert_eq!(config.gpio[0].pin, 23);
130        assert!(config.gpio[0].with_pullup());
131        assert_eq!(config.gpio[1].pin, 24);
132        assert!(!config.gpio[1].with_pullup());
133        assert_eq!(config.gpio[2].pin, 25);
134    }
135}