espforge_lib/config/
mod.rs

1use std::{collections::HashMap, fmt};
2
3use serde::{Deserialize, Serialize};
4use serde_yaml_ng::Value;
5
6#[derive(Debug, Deserialize, Serialize)]
7pub struct EspforgeConfiguration {
8    pub espforge: EspforgeConfig,
9    #[serde(default)]
10    pub example: Option<ExampleConfig>,
11    pub esp32: Option<Esp32Config>,
12    pub components: Option<HashMap<String, ComponentConfig>>,
13    pub devices: Option<HashMap<String, DeviceConfig>>,
14    pub app: Option<AppConfig>,
15}
16
17#[derive(Debug, Deserialize, Serialize)]
18pub struct DeviceConfig {
19    pub using: String,
20    #[serde(default)]
21    pub with: HashMap<String, Value>,
22}
23
24#[derive(Debug, Deserialize, Serialize)]
25pub struct EspforgeConfig {
26    pub name: String,
27    pub platform: PlatformConfig,
28    #[serde(default)]
29    pub wokwi_board: Option<WokwiBoard>,
30    #[serde(default)]
31    pub wokwi: Option<WokwiConfig>,
32    #[serde(default)]
33    pub enable_async: bool,
34
35}
36
37#[derive(Debug, Deserialize, Serialize)]
38pub struct WokwiConfig {
39    pub diagram: Option<String>,
40    pub config: Option<String>,
41}
42
43#[derive(Debug, Deserialize, Serialize)]
44#[serde(rename_all = "kebab-case")]
45pub enum WokwiBoard {
46    BoardEsp32DevkitCV4,
47    BoardEsp32S2Devkitm1,
48    BoardEsp32S3Devkitc1,
49    BoardEsp32C3Devkitm1,
50    BoardEsp32C6Devkitc1,
51    BoardEsp32H2Devkitm1,
52    BoardXiaoEsp32C3,
53    BoardXiaoEsp32C6,
54    BoardXiaoEsp32S3,
55}
56
57#[derive(Debug, Deserialize, Serialize)]
58pub struct ExampleConfig {
59    pub name: String,
60    #[serde(flatten)]
61    pub example_properties: HashMap<String, Value>,
62}
63
64#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)]
65#[serde(rename_all = "lowercase")]
66pub enum PlatformConfig {
67    ESP32,
68    ESP32C2,
69    ESP32C3,
70    ESP32C6,
71    ESP32H2,
72    ESP32S2,
73    ESP32S3,
74}
75
76impl PlatformConfig {
77    pub fn target(&self) -> &str {
78        match self {
79            PlatformConfig::ESP32 => "xtensa-esp32-none-elf",
80            PlatformConfig::ESP32C2 => "riscv32imc-unknown-none-elf",
81            PlatformConfig::ESP32C3 => "riscv32imc-unknown-none-elf",
82            PlatformConfig::ESP32C6 => "riscv32imac-unknown-none-elf",
83            PlatformConfig::ESP32H2 => "riscv32imac-unknown-none-elf",
84            PlatformConfig::ESP32S2 => "xtensa-esp32s2-none-elf",
85            PlatformConfig::ESP32S3 => "xtensa-esp32s3-none-elf",
86        }
87    }
88}
89
90impl EspforgeConfiguration {
91    pub fn get_name(&self) -> &str {
92        &self.espforge.name
93    }
94
95    pub fn get_platform(&self) -> String {
96        self.espforge.platform.to_string()
97    }
98
99    pub fn get_template(&self) -> Option<String> {
100        self.example.as_ref().map(|e| e.name.clone())
101    }
102}
103
104impl fmt::Display for PlatformConfig {
105    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
106        match self {
107            PlatformConfig::ESP32 => write!(f, "esp32"),
108            PlatformConfig::ESP32C2 => write!(f, "esp32c2"),
109            PlatformConfig::ESP32C3 => write!(f, "esp32c3"),
110            PlatformConfig::ESP32C6 => write!(f, "esp32c6"),
111            PlatformConfig::ESP32H2 => write!(f, "esp32h2"),
112            PlatformConfig::ESP32S2 => write!(f, "esp32s2"),
113            PlatformConfig::ESP32S3 => write!(f, "esp32s3"),
114        }
115    }
116}
117
118#[derive(Debug, Deserialize, Serialize)]
119pub struct Esp32Config {
120    #[serde(default)]
121    pub gpio: HashMap<String, GpioPinConfig>,
122    #[serde(default)]
123    pub spi: HashMap<String, SpiConfig>,
124    #[serde(default)]
125    pub i2c: HashMap<String, Value>,
126    #[serde(default)]
127    pub uart: HashMap<String, Value>, 
128}
129
130impl Esp32Config {
131    /// Checks if a resource name exists in any of the configured peripheral maps
132    pub fn contains_resource(&self, name: &str) -> bool {
133        self.gpio.contains_key(name) ||
134        self.spi.contains_key(name) ||
135        self.i2c.contains_key(name) ||
136        self.uart.contains_key(name)
137    }
138}
139
140#[derive(Debug, Deserialize, Serialize)]
141pub struct SpiConfig {
142    pub miso: u8,
143    pub mosi: u8,
144    pub sck: u8,
145    #[serde(default)]
146    pub cs: Option<u8>,
147}
148
149#[derive(Debug, Deserialize, Serialize)]
150pub struct GpioPinConfig {
151    pub pin: u8,
152    pub direction: PinDirection,
153    #[serde(default)]
154    pub pullup: bool,
155    #[serde(default)]
156    pub pulldown: bool,
157}
158
159#[derive(Debug, Deserialize, Serialize, PartialEq)]
160#[serde(rename_all = "lowercase")]
161pub enum PinDirection {
162    Input,
163    Output,
164}
165
166#[derive(Debug, Deserialize, Serialize)]
167pub struct ComponentConfig {
168    pub using: String,
169    #[serde(default)]
170    pub with: HashMap<String, Value>,
171}
172
173#[derive(Debug, Deserialize, Serialize)]
174pub struct AppConfig {
175    #[serde(default)]
176    pub variables: HashMap<String, VariableConfig>,
177
178    #[serde(default)]
179    pub setup: Vec<HashMap<String, Value>>,
180
181    #[serde(default, rename = "loop")]
182    pub loop_fn: Vec<HashMap<String, Value>>,
183}
184
185#[derive(Debug, Deserialize, Serialize)]
186pub struct VariableConfig {
187    #[serde(rename = "type")]
188    pub type_name: String,
189    pub initial: Value,
190}
191
192#[cfg(test)]
193mod tests {
194    use super::*;
195    use serde_yaml_ng;
196
197    #[test]
198    fn parse_minimal() {
199        let yaml = r#"
200            espforge:
201              name: minimum
202              platform: esp32c3
203        "#;
204
205        let config: EspforgeConfiguration =
206            serde_yaml_ng::from_str(yaml).expect("YAML parse failed");
207
208        assert_eq!(config.espforge.name, "minimum");
209        assert_eq!(config.espforge.platform, PlatformConfig::ESP32C3);
210        assert!(config.example.is_none());
211    }
212
213    #[test]
214    fn parse_wokwi_config() {
215        let yaml = r#"
216            espforge:
217              name: wokwi_test
218              platform: esp32c3
219              wokwi:
220                diagram: diagram.json
221                config: wokwi.toml
222        "#;
223
224        let config: EspforgeConfiguration =
225            serde_yaml_ng::from_str(yaml).expect("YAML parse failed");
226
227        let wokwi = config.espforge.wokwi.expect("Wokwi config should exist");
228        assert_eq!(wokwi.diagram, Some("diagram.json".to_string()));
229        assert_eq!(wokwi.config, Some("wokwi.toml".to_string()));
230    }
231
232    #[test]
233    fn invalid_platform() {
234        let yaml = r#"
235            espforge:
236              name: minimum
237              platform: invalid
238        "#;
239
240        let result: Result<EspforgeConfiguration, _> = serde_yaml_ng::from_str(yaml);
241        assert!(
242            result.is_err(),
243            "expected invalid_platform to fail deserialization"
244        );
245    }
246
247    #[test]
248    fn parse_blink_example() {
249        let yaml = r#"
250            espforge:
251              name: blink
252              platform: esp32c3
253            example:
254              name: blink            
255        "#;
256
257        let config: EspforgeConfiguration =
258            serde_yaml_ng::from_str(yaml).expect("YAML parse failed");
259
260        assert_eq!(config.espforge.name, "blink");
261        if let Some(example) = config.example {
262            assert_eq!(example.name, "blink");
263        } else {
264            panic!("Example was None.");
265        }
266    }
267}