From d32cf553c6b053884415975ea62c0f42f604fdca Mon Sep 17 00:00:00 2001 From: zackees Date: Fri, 19 Jun 2026 12:06:20 -0700 Subject: [PATCH] feat: support PlatformIO monitor filters Closes #644 --- crates/fbuild-config/src/board/loaders.rs | 36 ++++++++++++++++ crates/fbuild-config/src/board/methods.rs | 8 ++++ crates/fbuild-config/src/board/tests.rs | 42 +++++++++++++++++++ .../src/board/tests_enriched_json.rs | 17 ++++++++ crates/fbuild-config/src/board/types.rs | 6 +++ crates/fbuild-config/src/ini_parser/mod.rs | 2 + crates/fbuild-config/src/ini_parser/tests.rs | 22 ++++++++++ docs/reference/platformio-ini.md | 2 + 8 files changed, 135 insertions(+) diff --git a/crates/fbuild-config/src/board/loaders.rs b/crates/fbuild-config/src/board/loaders.rs index 26c51624..c366396f 100644 --- a/crates/fbuild-config/src/board/loaders.rs +++ b/crates/fbuild-config/src/board/loaders.rs @@ -76,6 +76,40 @@ fn resolve_max_flash( }) } +fn parse_monitor_filters(value: &str) -> Vec { + let trimmed = value.trim(); + if trimmed.is_empty() || trimmed == "[]" { + return Vec::new(); + } + + trimmed + .lines() + .flat_map(|line| line.split(',')) + .map(str::trim) + .filter(|filter| !filter.is_empty()) + .map(ToString::to_string) + .collect() +} + +fn resolve_monitor_filters( + overrides: &HashMap, + defaults: &HashMap, + is_esp32_family: bool, +) -> Option> { + overrides + .get("monitor_filters") + .map(|value| parse_monitor_filters(value)) + .or_else(|| { + defaults + .get("monitor_filters") + .map(|value| parse_monitor_filters(value)) + }) + .or_else(|| { + is_esp32_family + .then(|| vec!["default".to_string(), "esp32_exception_decoder".to_string()]) + }) +} + impl BoardConfig { /// Load board config from a boards.txt file. /// @@ -141,6 +175,7 @@ impl BoardConfig { upload_protocol: get("upload.protocol") .or_else(|| props.get("upload.protocol").cloned()), upload_speed: get("upload.speed").or_else(|| props.get("upload.speed").cloned()), + monitor_filters: resolve_monitor_filters(overrides, &props, is_esp32_family), max_flash: resolve_max_flash(overrides, &props), max_ram: get("maximum_data_size") .or_else(|| props.get("maximum_data_size").cloned()) @@ -308,6 +343,7 @@ impl BoardConfig { .get("upload.speed") .cloned() .or_else(|| defaults.get("upload.speed").cloned()), + monitor_filters: resolve_monitor_filters(overrides, &defaults, is_esp32_family), max_flash: resolve_max_flash(overrides, &defaults), max_ram: overrides .get("maximum_data_size") diff --git a/crates/fbuild-config/src/board/methods.rs b/crates/fbuild-config/src/board/methods.rs index 4f2876b6..40cbd1c1 100644 --- a/crates/fbuild-config/src/board/methods.rs +++ b/crates/fbuild-config/src/board/methods.rs @@ -43,6 +43,14 @@ impl BoardConfig { && EMULATOR_TOOL_NAMES.contains(&tool_name) } + /// PlatformIO `monitor_filters` value to emit for this board. + pub fn monitor_filters_ini_value(&self) -> Option { + self.monitor_filters + .as_ref() + .filter(|filters| !filters.is_empty()) + .map(|filters| filters.join(", ")) + } + /// Resolve the effective ESP32 SDK memory profile used for variant headers/libs. /// /// This keeps the SDK `sdkconfig.h` and memory-profile libraries aligned diff --git a/crates/fbuild-config/src/board/tests.rs b/crates/fbuild-config/src/board/tests.rs index 06beed3d..b301e7f5 100644 --- a/crates/fbuild-config/src/board/tests.rs +++ b/crates/fbuild-config/src/board/tests.rs @@ -283,6 +283,48 @@ fn test_platform_detection() { assert_eq!(rpi.platform(), Some(fbuild_core::Platform::RaspberryPi)); } +#[test] +fn test_non_esp32_monitor_filters_default_absent() { + let config = BoardConfig::from_board_id("uno", &HashMap::new()).unwrap(); + assert_eq!(config.monitor_filters, None); + assert_eq!(config.monitor_filters_ini_value(), None); +} + +#[test] +fn test_custom_monitor_filters_emit_in_platformio_format() { + let mut overrides = HashMap::new(); + overrides.insert( + "monitor_filters".to_string(), + "default, time, log2file".to_string(), + ); + + let config = BoardConfig::from_board_id("uno", &overrides).unwrap(); + + assert_eq!( + config.monitor_filters, + Some(vec![ + "default".to_string(), + "time".to_string(), + "log2file".to_string() + ]) + ); + assert_eq!( + config.monitor_filters_ini_value(), + Some("default, time, log2file".to_string()) + ); +} + +#[test] +fn test_empty_monitor_filters_suppresses_emit() { + let mut overrides = HashMap::new(); + overrides.insert("monitor_filters".to_string(), "[]".to_string()); + + let config = BoardConfig::from_board_id("esp32dev", &overrides).unwrap(); + + assert_eq!(config.monitor_filters, Some(Vec::new())); + assert_eq!(config.monitor_filters_ini_value(), None); +} + #[test] fn test_parse_boards_txt_with_comments() { let props = parse_boards_txt( diff --git a/crates/fbuild-config/src/board/tests_enriched_json.rs b/crates/fbuild-config/src/board/tests_enriched_json.rs index ecb3562d..024905b5 100644 --- a/crates/fbuild-config/src/board/tests_enriched_json.rs +++ b/crates/fbuild-config/src/board/tests_enriched_json.rs @@ -139,6 +139,23 @@ fn test_esp32dev_enriched_fields() { assert_eq!(config.upload_speed, Some("460800".to_string())); } +#[test] +fn test_esp32dev_defaults_monitor_filters() { + let config = BoardConfig::from_board_id("esp32dev", &HashMap::new()).unwrap(); + + assert_eq!( + config.monitor_filters, + Some(vec![ + "default".to_string(), + "esp32_exception_decoder".to_string() + ]) + ); + assert_eq!( + config.monitor_filters_ini_value(), + Some("default, esp32_exception_decoder".to_string()) + ); +} + #[test] fn test_esp32_flash_mode_env_override_honoured() { // The user can opt back into QIO via `board_build.flash_mode = qio` diff --git a/crates/fbuild-config/src/board/types.rs b/crates/fbuild-config/src/board/types.rs index 86286a82..b4889eb9 100644 --- a/crates/fbuild-config/src/board/types.rs +++ b/crates/fbuild-config/src/board/types.rs @@ -60,6 +60,12 @@ pub struct BoardConfig { pub upload_protocol: Option, /// Upload speed pub upload_speed: Option, + /// PlatformIO serial monitor filters. + /// + /// ESP32-family boards default to `default, esp32_exception_decoder` when + /// unset. An explicit empty list in project config suppresses that default. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub monitor_filters: Option>, /// Maximum flash size in bytes pub max_flash: Option, /// Maximum RAM size in bytes diff --git a/crates/fbuild-config/src/ini_parser/mod.rs b/crates/fbuild-config/src/ini_parser/mod.rs index 5fbc403c..01ec0c4a 100644 --- a/crates/fbuild-config/src/ini_parser/mod.rs +++ b/crates/fbuild-config/src/ini_parser/mod.rs @@ -348,6 +348,8 @@ impl PlatformIOConfig { overrides.insert(stripped.to_string(), value.clone()); } else if let Some(stripped) = key.strip_prefix("board_upload.") { overrides.insert(format!("upload.{}", stripped), value.clone()); + } else if key == "monitor_filters" { + overrides.insert(key.clone(), value.clone()); } } diff --git a/crates/fbuild-config/src/ini_parser/tests.rs b/crates/fbuild-config/src/ini_parser/tests.rs index 3d455208..1245227c 100644 --- a/crates/fbuild-config/src/ini_parser/tests.rs +++ b/crates/fbuild-config/src/ini_parser/tests.rs @@ -750,6 +750,28 @@ board_upload.flash_size = 4MB assert_eq!(overrides.get("upload.flash_size"), Some(&"4MB".to_string())); } +#[test] +fn test_get_board_overrides_preserves_monitor_filters() { + let f = write_ini( + "\ +[env:esp32dev] +platform = espressif32 +board = esp32dev +framework = arduino +monitor_filters = + default + esp32_exception_decoder +", + ); + let config = PlatformIOConfig::from_path(f.path()).unwrap(); + let overrides = config.get_board_overrides("esp32dev").unwrap(); + + assert_eq!( + overrides.get("monitor_filters"), + Some(&"default\nesp32_exception_decoder".to_string()) + ); +} + #[test] fn test_get_source_filter_prefers_build_src_filter() { let f = write_ini( diff --git a/docs/reference/platformio-ini.md b/docs/reference/platformio-ini.md index bcb6cca8..4ea67f34 100644 --- a/docs/reference/platformio-ini.md +++ b/docs/reference/platformio-ini.md @@ -30,6 +30,7 @@ board = uno framework = arduino upload_port = COM3 monitor_speed = 9600 +monitor_filters = default, esp32_exception_decoder build_flags = -DDEBUG -DLED_PIN=13 @@ -48,6 +49,7 @@ Common keys: | `[env:] framework` | Framework, usually `arduino`. | | `upload_port` | Preferred deploy port. | | `monitor_speed` | Serial monitor baud rate. | +| `monitor_filters` | Serial monitor filters. ESP32-family boards default to `default, esp32_exception_decoder` when unset; use `monitor_filters = []` to suppress. | | `build_flags` | Extra compiler flags. | | `lib_deps` | Library dependencies, including GitHub URLs. | | `build_type` | Build profile; `debug` preserves unwind metadata. |