gpio_mqtt_bridge/
config.rs1use 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}