espforge_lib/config/
mod.rs

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