ebyte_e32_ui/
config.rs

1use anyhow::{Context, Result};
2use linux_embedded_hal::serial_core;
3use serde_derive::{Deserialize, Serialize};
4use std::{
5    fs::read_to_string,
6    path::{Path, PathBuf},
7};
8
9/// Load a configuration from the given file path,
10/// returning an error if something goes wrong.
11///
12/// # Errors
13/// Opening the file and parsing it may fail, returning error.
14pub fn load(config_path: impl AsRef<Path>) -> Result<Config> {
15    let path = read_to_string(&config_path).with_context(|| {
16        format!(
17            "Failed to open config file {}",
18            config_path.as_ref().display()
19        )
20    })?;
21    toml::from_str(&path).map_err(|e| {
22        eprintln!(
23            "Failed to parse configuration file. Here's an example:\n{}",
24            toml::to_string(&Config::example()).unwrap()
25        );
26        anyhow::anyhow!(e)
27    })
28}
29/// Configuration for connecting to the Ebyte module.
30#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
31pub struct Config {
32    pub(crate) serial_path: PathBuf,
33    pub(crate) baudrate: u32,
34    pub(crate) parity: Parity,
35    pub(crate) data_bits: u8,
36    pub(crate) stop_bits: u8,
37    pub(crate) gpiochip_path: PathBuf,
38    pub(crate) aux_pin: u32,
39    pub(crate) m0_pin: u32,
40    pub(crate) m1_pin: u32,
41}
42
43impl Config {
44    /// Example configuration
45    #[must_use]
46    pub fn example() -> Self {
47        Self {
48            serial_path: "dev/ttyAMA0".into(),
49            baudrate: 9600,
50            parity: Parity::None,
51            data_bits: 8,
52            stop_bits: 1,
53            gpiochip_path: "/dev/gpiochip0".into(),
54            aux_pin: 18,
55            m0_pin: 23,
56            m1_pin: 24,
57        }
58    }
59}
60
61/// Same as [`linux_embedded_hal::serial_core::Parity`],
62/// copied only for serde purposes.
63#[derive(Debug, PartialEq, Eq, Copy, Clone, Deserialize, Serialize)]
64pub(crate) enum Parity {
65    /// No parity bit.
66    None,
67    /// Even parity.
68    Even,
69    /// Odd parity.
70    Odd,
71}
72
73impl From<Parity> for serial_core::Parity {
74    fn from(value: Parity) -> Self {
75        match value {
76            Parity::None => Self::ParityNone,
77            Parity::Even => Self::ParityEven,
78            Parity::Odd => Self::ParityOdd,
79        }
80    }
81}
82
83/// Same as [`linux_embedded_hal::serial_core::StopBits`],
84/// copied only for serde purposes.
85#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
86pub(crate) enum StopBits {
87    /// One stop bit.
88    Stop1,
89
90    /// Two stop bits.
91    Stop2,
92}
93
94impl TryFrom<u8> for StopBits {
95    type Error = anyhow::Error;
96
97    fn try_from(value: u8) -> Result<Self, Self::Error> {
98        match value {
99            1 => Ok(Self::Stop1),
100            2 => Ok(Self::Stop2),
101            n => anyhow::bail!(
102                "'{n}' is not a valid value for stop bits. Valid values are '1' and '2'."
103            ),
104        }
105    }
106}
107
108impl From<StopBits> for serial_core::StopBits {
109    fn from(value: StopBits) -> Self {
110        match value {
111            StopBits::Stop1 => Self::Stop1,
112            StopBits::Stop2 => Self::Stop2,
113        }
114    }
115}
116
117#[cfg(test)]
118mod test {
119    use super::*;
120
121    #[test]
122    fn parses_example_config() {
123        let config = std::fs::read_to_string("Config.toml").unwrap();
124        assert!(toml::from_str::<Config>(&config).is_ok());
125    }
126
127    #[test]
128    fn show_config() {
129        let config = Config {
130            serial_path: "/dev/ttyAMA0".into(),
131            parity: Parity::None,
132            baudrate: 115200,
133            data_bits: 8,
134            stop_bits: 1,
135            gpiochip_path: "/dev/gpiochip0".into(),
136            aux_pin: 0,
137            m0_pin: 1,
138            m1_pin: 2,
139        };
140        println!("{}", toml::to_string(&config).unwrap());
141    }
142}