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>, #[serde(default)]
126 pub uart: HashMap<String, Value>, }
128
129impl Esp32Config {
130 pub fn contains_resource(&self, name: &str) -> bool {
132 self.gpio.contains_key(name) ||
133 self.spi.contains_key(name) ||
134 self.i2c.contains_key(name) ||
136 self.uart.contains_key(name)
137 }
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}