Define configs in YAML files (#3504)

* Define configs in YAML files

* Fix error message string

* Cleanup

* Fix rebase

* Experiment: Value is String

* More i64 -> i128

* More i64 -> i128

* yml -> yaml

* Clippy

* Expect

* Test more

* Explicit `trunc`

* fmt

* Typo

* `is_tooling` -> `ignore_feature_gates`

* Fix

* Briefly explain the config format

* Evaluate conditions in order, first match wins

* Address review

* Move evalexpr I128 support into separate file
This commit is contained in:
Björn Quentin 2025-06-17 10:13:15 +02:00 committed by GitHub
parent 891a5a4a8c
commit b87cd34456
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 1213 additions and 601 deletions

View File

@ -1,5 +1,5 @@
use esp_build::assert_unique_used_features;
use esp_config::{ConfigOption, generate_config};
use esp_config::generate_config_from_yaml_definition;
fn main() {
// Ensure that only a single chip is specified:
@ -13,14 +13,8 @@ fn main() {
}
// emit config
generate_config(
"esp_backtrace",
&[ConfigOption::new(
"backtrace-frames",
"The maximum number of frames that will be printed in a backtrace",
10,
)],
true,
true,
);
println!("cargo:rerun-if-changed=./esp_config.yml");
let cfg_yaml = std::fs::read_to_string("./esp_config.yml")
.expect("Failed to read esp_config.yml for esp-backtrace");
generate_config_from_yaml_definition(&cfg_yaml, true, true, None).unwrap();
}

View File

@ -0,0 +1,7 @@
crate: esp-backtrace
options:
- name: backtrace_frames
description: The maximum number of frames that will be printed in a backtrace.
default:
- value: 10

View File

@ -1,7 +1,7 @@
use std::env;
use chrono::{TimeZone, Utc};
use esp_config::{ConfigOption, Validator, generate_config};
use esp_config::generate_config_from_yaml_definition;
fn main() {
println!("cargo::rustc-check-cfg=cfg(embedded_test)");
@ -18,38 +18,8 @@ fn main() {
println!("cargo::rustc-env=ESP_BOOTLOADER_BUILD_DATE={build_date_formatted}");
// emit config
generate_config(
"esp-bootloader-esp-idf",
&[
ConfigOption::new(
"mmu_page_size",
"ESP32-C2, ESP32-C6 and ESP32-H2 support configurable page sizes. \
This is currently only used to populate the app descriptor.",
"64k",
)
.constraint(Validator::Enumeration(vec![
String::from("8k"),
String::from("16k"),
String::from("32k"),
String::from("64k"),
])), // .active(true) TODO we need to know the device here
ConfigOption::new(
"esp_idf_version",
"ESP-IDF version used in the application descriptor. Currently it's \
not checked by the bootloader.",
"0.0.0",
),
ConfigOption::new(
"partition-table-offset",
"The address of partition table (by default 0x8000). Allows you to \
move the partition table, it gives more space for the bootloader. Note that the \
bootloader and app will both need to be compiled with the same \
PARTITION_TABLE_OFFSET value.",
0x8000,
)
.constraint(Validator::PositiveInteger),
],
true,
true,
);
println!("cargo:rerun-if-changed=./esp_config.yml");
let cfg_yaml = std::fs::read_to_string("./esp_config.yml")
.expect("Failed to read esp_config.yml for esp-bootloader-esp-idf");
generate_config_from_yaml_definition(&cfg_yaml, true, true, None).unwrap();
}

View File

@ -0,0 +1,28 @@
crate: esp-bootloader-esp-idf
options:
- name: mmu_page_size
description: ESP32-C2, ESP32-C6 and ESP32-H2 support configurable page sizes. This is currently only used to populate the app descriptor.
default:
- value: '"64k"'
constraints:
- type:
validator: enumeration
value:
- 8k
- 16k
- 32k
- 64k
- name: esp_idf_version
description: ESP-IDF version used in the application descriptor. Currently it's not checked by the bootloader.
default:
- value: '"0.0.0"'
- name: partition-table-offset
description: "The address of partition table (by default 0x8000). Allows you to
move the partition table, it gives more space for the bootloader. Note that the
bootloader and app will both need to be compiled with the same
PARTITION_TABLE_OFFSET value."
default:
- value: 32768

View File

@ -15,7 +15,9 @@ test = true
[dependencies]
document-features = "0.2.11"
serde = { version = "1.0.197", features = ["derive"], optional = true }
serde_json = { version = "1.0.0", optional = true }
serde_yaml = { version = "0.9", optional = true }
evalexpr = { version = "12.0.2", optional = true }
esp-metadata = { version = "0.7.0", path = "../esp-metadata", default-features = true, optional = true }
[dev-dependencies]
temp-env = "0.3.6"
@ -23,4 +25,4 @@ pretty_assertions = "1.4.1"
[features]
## Enable the generation and parsing of a config
build = ["dep:serde","dep:serde_json"]
build = ["dep:serde", "dep:serde_yaml", "dep:evalexpr", "dep:esp-metadata"]

View File

@ -44,6 +44,75 @@ For all supported data types, there are helper macros that emit `const` code for
In addition to environment variables, for boolean types rust `cfg`'s are emitted in snake case _without_ the prefix.
## Defining Configuration Options
Config options should be defined declaratively in a file called `esp_config.yml`.
Such a file looks like this:
```yaml
crate: esp-bootloader-esp-idf
options:
- name: mmu_page_size
description: ESP32-C2, ESP32-C6 and ESP32-H2 support configurable page sizes. This is currently only used to populate the app descriptor.
default:
- value: '"64k"'
stability: !Stable stable-since-version
constraints:
- if: true
type:
validator: enumeration
value:
- 8k
- 16k
- 32k
- 64k
- name: esp_idf_version
description: ESP-IDF version used in the application descriptor. Currently it's not checked by the bootloader.
default:
- if: 'chip == "esp32c6"'
value: '"esp32c6"'
- if: 'chip == "esp32"'
value: '"other"'
active: true
- name: partition-table-offset
description: "The address of partition table (by default 0x8000). Allows you to \
move the partition table, it gives more space for the bootloader. Note that the \
bootloader and app will both need to be compiled with the same \
PARTITION_TABLE_OFFSET value."
default:
- if: true
value: 32768
stability: Unstable
active: 'chip == "esp32c6"'
checks:
- 'ESP_BOOTLOADER_ESP_IDF_CONFIG_PARTITION_TABLE_OFFSET >= 32768'
```
`if` and `active` are [evalexpr](https://crates.io/crates/evalexpr) expressions returning a boolean.
The expression supports these custom functions:
|Function|Description|
|---|---|
|feature(String)|`true` if the given chip feature is present|
|cargo_feature(String)|`true` if the given Cargo feature is active|
|ignore_feature_gates()|Usually `false` but tooling will set this to `true` to hint that the expression is evaluated by e.g. a TUI|
`ignore_feature_gates()` is useful to enable otherwise disabled functionality - e.g. to offer all possible options regardless of any active / non-active features.
The `chip` variable is populated with the name of the targeted chip (if the crate is using chip specific features).
The conditions for `default` and `constraints` are evaluated in order and the first match is taken no matter if there is more.
This way you could have a catch-all condition as the last item by just specifying `true`.
`checks` is a list of checks which needs to pass for a valid configuration and is checked after all config values for the current config are applied.
You can access the currently configured values to check them.
For more examples see the various `esp_config.yml` files in this repository.
## Minimum Supported Rust Version (MSRV)
This crate is guaranteed to compile when using the latest stable Rust version at the time of the crate's release. It _might_ compile with older versions, but that may change in any new release, including patches.

View File

@ -0,0 +1,163 @@
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct I128NumericTypes;
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct I128(pub i128);
impl FromStr for I128 {
type Err = core::num::ParseIntError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(I128(s.parse()?))
}
}
impl core::fmt::Display for I128 {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", self.0)
}
}
use core::str::FromStr;
use evalexpr::{EvalexprError, EvalexprResult};
impl<NumericTypes: evalexpr::EvalexprNumericTypes<Int = Self>> evalexpr::EvalexprInt<NumericTypes>
for I128
{
const MIN: Self = I128(i128::MIN);
const MAX: Self = I128(i128::MAX);
fn from_usize(int: usize) -> EvalexprResult<Self, NumericTypes> {
Ok(I128(int.try_into().map_err(|_| {
EvalexprError::IntFromUsize { usize_int: int }
})?))
}
fn into_usize(&self) -> EvalexprResult<usize, NumericTypes> {
if self.0 >= 0 {
(self.0 as u64)
.try_into()
.map_err(|_| EvalexprError::IntIntoUsize { int: *self })
} else {
Err(EvalexprError::IntIntoUsize { int: *self })
}
}
fn from_hex_str(literal: &str) -> Result<Self, ()> {
Ok(I128(i128::from_str_radix(literal, 16).map_err(|_| ())?))
}
fn checked_add(&self, rhs: &Self) -> EvalexprResult<Self, NumericTypes> {
let result = (self.0).checked_add(rhs.0);
if let Some(result) = result {
Ok(I128(result))
} else {
Err(EvalexprError::AdditionError {
augend: evalexpr::Value::<NumericTypes>::from_int(*self),
addend: evalexpr::Value::<NumericTypes>::from_int(*rhs),
})
}
}
fn checked_sub(&self, rhs: &Self) -> EvalexprResult<Self, NumericTypes> {
let result = (self.0).checked_sub(rhs.0);
if let Some(result) = result {
Ok(I128(result))
} else {
Err(EvalexprError::SubtractionError {
minuend: evalexpr::Value::<NumericTypes>::from_int(*self),
subtrahend: evalexpr::Value::<NumericTypes>::from_int(*rhs),
})
}
}
fn checked_neg(&self) -> EvalexprResult<Self, NumericTypes> {
let result = (self.0).checked_neg();
if let Some(result) = result {
Ok(I128(result))
} else {
Err(EvalexprError::NegationError {
argument: evalexpr::Value::<NumericTypes>::from_int(*self),
})
}
}
fn checked_mul(&self, rhs: &Self) -> EvalexprResult<Self, NumericTypes> {
let result = (self.0).checked_mul(rhs.0);
if let Some(result) = result {
Ok(I128(result))
} else {
Err(EvalexprError::MultiplicationError {
multiplicand: evalexpr::Value::<NumericTypes>::from_int(*self),
multiplier: evalexpr::Value::<NumericTypes>::from_int(*rhs),
})
}
}
fn checked_div(&self, rhs: &Self) -> EvalexprResult<Self, NumericTypes> {
let result = (self.0).checked_div(rhs.0);
if let Some(result) = result {
Ok(I128(result))
} else {
Err(EvalexprError::DivisionError {
dividend: evalexpr::Value::<NumericTypes>::from_int(*self),
divisor: evalexpr::Value::<NumericTypes>::from_int(*rhs),
})
}
}
fn checked_rem(&self, rhs: &Self) -> EvalexprResult<Self, NumericTypes> {
let result = (self.0).checked_rem(rhs.0);
if let Some(result) = result {
Ok(I128(result))
} else {
Err(EvalexprError::ModulationError {
dividend: evalexpr::Value::<NumericTypes>::from_int(*self),
divisor: evalexpr::Value::<NumericTypes>::from_int(*rhs),
})
}
}
fn abs(&self) -> EvalexprResult<Self, NumericTypes> {
Ok(I128(self.0.abs()))
}
fn bitand(&self, rhs: &Self) -> Self {
I128(std::ops::BitAnd::bitand(self.0, rhs.0))
}
fn bitor(&self, rhs: &Self) -> Self {
I128(std::ops::BitOr::bitor(self.0, rhs.0))
}
fn bitxor(&self, rhs: &Self) -> Self {
I128(std::ops::BitXor::bitxor(self.0, rhs.0))
}
fn bitnot(&self) -> Self {
I128(std::ops::Not::not(self.0))
}
fn bit_shift_left(&self, rhs: &Self) -> Self {
I128(std::ops::Shl::shl(self.0, rhs.0))
}
fn bit_shift_right(&self, rhs: &Self) -> Self {
I128(std::ops::Shr::shr(self.0, rhs.0))
}
}
impl evalexpr::EvalexprNumericTypes for I128NumericTypes {
type Int = I128;
type Float = f64;
fn int_as_float(int: &Self::Int) -> Self::Float {
int.0 as Self::Float
}
fn float_as_int(float: &Self::Float) -> Self::Int {
I128(float.trunc() as i128)
}
}

View File

@ -1,10 +1,16 @@
use core::fmt::Display;
use std::{collections::HashMap, env, fmt, fs, io::Write, path::PathBuf};
use evalexpr::{ContextWithMutableFunctions, ContextWithMutableVariables};
use serde::{Deserialize, Serialize};
use crate::generate::{validator::Validator, value::Value};
use crate::generate::{
evalexpr_extensions::{I128, I128NumericTypes},
validator::Validator,
value::Value,
};
pub(crate) mod evalexpr_extensions;
mod markdown;
pub(crate) mod validator;
pub(crate) mod value;
@ -45,8 +51,287 @@ impl fmt::Display for Error {
}
}
/// Generate and parse config from a prefix, and an array tuples containing the
/// name, description, default value, and an optional validator.
/// The root node of a configuration.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
pub struct Config {
/// The crate name.
#[serde(rename = "crate")]
pub krate: String,
/// The config options for this crate.
pub options: Vec<CfgOption>,
/// Optionally additional checks.
pub checks: Option<Vec<String>>,
}
fn true_default() -> String {
"true".to_string()
}
fn unstable_default() -> Stability {
Stability::Unstable
}
/// A default value for a configuration option.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
pub struct CfgDefaultValue {
/// Condition which makes this default value used.
/// You can and have to have exactly one active default value.
#[serde(rename = "if")]
#[serde(default = "true_default")]
pub if_: String,
/// The default value.
pub value: Value,
}
/// A configuration option.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
pub struct CfgOption {
/// Name of the configuration option
pub name: String,
/// Description of the configuration option.
/// This will be visible in the documentation and in the tooling.
pub description: String,
/// A condition which specified when this option is active.
#[serde(default = "true_default")]
pub active: String,
/// The default value.
/// Exactly one of the items needs to be active at any time.
pub default: Vec<CfgDefaultValue>,
/// Constraints (Validators) to use.
/// If given at most one item is allowed to be active at any time.
pub constraints: Option<Vec<CfgConstraint>>,
/// A display hint for the value.
/// This is meant for tooling and/or documentation.
pub display_hint: Option<DisplayHint>,
/// The stability guarantees of this option.
#[serde(default = "unstable_default")]
pub stability: Stability,
}
/// A conditional constraint / validator for a config option.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
pub struct CfgConstraint {
/// Condition which makes this validator used.
#[serde(rename = "if")]
#[serde(default = "true_default")]
if_: String,
/// The validator to be used.
#[serde(rename = "type")]
type_: Validator,
}
/// Generate the config from a YAML definition.
///
/// The YAML follows the format outlined by [Config].
///
/// After deserializing the config and normalizing it, this will call
/// [generate_config] to finally get the currently active configuration.
pub fn generate_config_from_yaml_definition(
yaml: &str,
enable_unstable: bool,
emit_md_tables: bool,
chip: Option<esp_metadata::Config>,
) -> Result<HashMap<String, Value>, Error> {
let features: Vec<String> = env::vars()
.filter(|(k, _)| k.starts_with("CARGO_FEATURE_"))
.map(|(k, _)| k)
.map(|v| {
v.strip_prefix("CARGO_FEATURE_")
.unwrap_or_default()
.to_string()
})
.collect();
let (config, options) = evaluate_yaml_config(yaml, chip, features, false)?;
let cfg = generate_config(&config.krate, &options, enable_unstable, emit_md_tables);
if let Some(checks) = config.checks {
let mut eval_ctx = evalexpr::HashMapContext::<I128NumericTypes>::new();
for (k, v) in cfg.iter() {
eval_ctx
.set_value(
k.clone(),
match v {
Value::Bool(v) => evalexpr::Value::Boolean(*v),
Value::Integer(v) => evalexpr::Value::Int(I128(*v)),
Value::String(v) => evalexpr::Value::String(v.clone()),
},
)
.map_err(|err| Error::Parse(format!("Error setting value for {k} ({err})")))?;
}
for check in checks {
if !evalexpr::eval_with_context(&check, &eval_ctx)
.and_then(|v| v.as_boolean())
.map_err(|err| Error::Validation(format!("Validation error: '{check}' ({err})")))?
{
return Err(Error::Validation(format!("Validation error: '{check}'")));
}
}
}
Ok(cfg)
}
/// Evaluate the given YAML representation of a config definition.
pub fn evaluate_yaml_config(
yaml: &str,
chip: Option<esp_metadata::Config>,
features: Vec<String>,
ignore_feature_gates: bool,
) -> Result<(Config, Vec<ConfigOption>), Error> {
let config: Config = serde_yaml::from_str(yaml).map_err(|err| Error::Parse(err.to_string()))?;
let mut options = Vec::new();
let mut eval_ctx = evalexpr::HashMapContext::<evalexpr::DefaultNumericTypes>::new();
if let Some(config) = chip {
eval_ctx
.set_value("chip".into(), evalexpr::Value::String(config.name()))
.map_err(|err| Error::Parse(err.to_string()))?;
eval_ctx
.set_function(
"feature".into(),
evalexpr::Function::<evalexpr::DefaultNumericTypes>::new(move |arg| {
if let evalexpr::Value::String(which) = arg {
let res = config.contains(which);
Ok(evalexpr::Value::Boolean(res))
} else {
Err(evalexpr::EvalexprError::CustomMessage(format!(
"Bad argument: {arg:?}"
)))
}
}),
)
.map_err(|err| Error::Parse(err.to_string()))?;
eval_ctx
.set_function(
"cargo_feature".into(),
evalexpr::Function::<evalexpr::DefaultNumericTypes>::new(move |arg| {
if let evalexpr::Value::String(which) = arg {
let res = features.contains(&which.to_uppercase().replace("-", "_"));
Ok(evalexpr::Value::Boolean(res))
} else {
Err(evalexpr::EvalexprError::CustomMessage(format!(
"Bad argument: {arg:?}"
)))
}
}),
)
.map_err(|err| Error::Parse(err.to_string()))?;
eval_ctx
.set_function(
"ignore_feature_gates".into(),
evalexpr::Function::<evalexpr::DefaultNumericTypes>::new(move |arg| {
if let evalexpr::Value::Empty = arg {
Ok(evalexpr::Value::Boolean(ignore_feature_gates))
} else {
Err(evalexpr::EvalexprError::CustomMessage(format!(
"Bad argument: {arg:?}"
)))
}
}),
)
.map_err(|err| Error::Parse(err.to_string()))?;
}
for option in config.options.clone() {
let active = evalexpr::eval_with_context(&option.active, &eval_ctx)
.map_err(|err| {
Error::Parse(format!(
"Error evaluating '{}', error = {:?}",
option.active, err
))
})?
.as_boolean()
.map_err(|err| {
Error::Parse(format!(
"Error evaluating '{}', error = {:?}",
option.active, err
))
})?;
let constraint = {
let mut active_constraint = None;
if let Some(constraints) = &option.constraints {
for constraint in constraints {
if evalexpr::eval_with_context(&constraint.if_, &eval_ctx)
.map_err(|err| {
Error::Parse(format!(
"Error evaluating '{}', error = {err:?}",
constraint.if_
))
})?
.as_boolean()
.map_err(|err| {
Error::Parse(format!(
"Error evaluating '{}', error = {:?}",
constraint.if_, err
))
})?
{
active_constraint = Some(constraint.type_.clone());
break;
}
}
};
if option.constraints.is_some() && active_constraint.is_none() {
panic!(
"No constraint active for crate {}, option {}",
config.krate, option.name
);
}
active_constraint
};
let default_value = {
let mut default_value = None;
for value in option.default.clone() {
if evalexpr::eval_with_context(&value.if_, &eval_ctx)
.and_then(|v| v.as_boolean())
.map_err(|err| {
Error::Parse(format!("Error evaluating '{}', error = {err:?}", value.if_))
})?
{
default_value = Some(value.value);
break;
}
}
if default_value.is_none() {
panic!(
"No default value active for crate {}, option {}",
config.krate, option.name
);
}
default_value
};
let option = ConfigOption {
name: option.name.clone(),
description: option.description,
default_value: default_value.ok_or(Error::Parse(format!(
"No default value found for {}",
option.name
)))?,
constraint,
stability: option.stability,
active,
display_hint: option.display_hint.unwrap_or(DisplayHint::None),
};
options.push(option);
}
Ok((config, options))
}
/// Generate and parse config from a prefix, and an array of [ConfigOption].
///
/// This function will parse any `SCREAMING_SNAKE_CASE` environment variables
/// that match the given prefix. It will then attempt to parse the [`Value`] and
@ -125,37 +410,9 @@ pub fn generate_config_internal<'a>(
emit_configuration(&mut stdout, &configs);
#[cfg(not(test))]
{
let config_json = config_json(&configs, false);
write_out_file(format!("{crate_name}_config_data.json"), config_json);
}
configs
}
fn config_json(config: &[(String, &ConfigOption, Value)], pretty: bool) -> String {
#[derive(Serialize)]
struct Item<'a> {
option: &'a ConfigOption,
actual_value: Value,
}
let mut to_write = Vec::new();
for (_, option, value) in config.iter() {
to_write.push(Item {
actual_value: value.clone(),
option,
})
}
if pretty {
serde_json::to_string_pretty(&to_write).unwrap()
} else {
serde_json::to_string(&to_write).unwrap()
}
}
/// The stability of the configuration option.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub enum Stability {
@ -224,51 +481,6 @@ pub struct ConfigOption {
}
impl ConfigOption {
/// Create a new config option.
///
/// Unstable, active, no display-hint and not constrained by default.
pub fn new(name: &str, description: &str, default_value: impl Into<Value>) -> Self {
Self {
name: name.to_string(),
description: description.to_string(),
default_value: default_value.into(),
constraint: None,
stability: Stability::Unstable,
active: true,
display_hint: DisplayHint::None,
}
}
/// Constrain the config option
pub fn constraint(mut self, validator: Validator) -> Self {
self.constraint = Some(validator);
self
}
/// Constrain the config option
pub fn constraint_by(mut self, validator: Option<Validator>) -> Self {
self.constraint = validator;
self
}
/// Mark this config option as stable
pub fn stable(mut self, version: &str) -> Self {
self.stability = Stability::Stable(version.to_string());
self
}
/// Sets the active flag of this config option
pub fn active(mut self, active: bool) -> Self {
self.active = active;
self
}
/// Sets the display hint
pub fn display_hint(mut self, display_hint: DisplayHint) -> Self {
self.display_hint = display_hint;
self
}
fn env_var(&self, prefix: &str) -> String {
format!("{}{}", prefix, screaming_snake_case(&self.name))
}
@ -708,85 +920,6 @@ mod test {
assert!(cargo_lines.contains(&"cargo:rustc-cfg=some_key_variant_0"));
}
#[test]
fn json_output() {
let mut stdout = Vec::new();
let config = [
ConfigOption {
name: String::from("some-key"),
description: String::from("NA"),
default_value: Value::String("variant-0".to_string()),
constraint: Some(Validator::Enumeration(vec![
"variant-0".to_string(),
"variant-1".to_string(),
])),
stability: Stability::Stable(String::from("testing")),
active: true,
display_hint: DisplayHint::None,
},
ConfigOption {
name: String::from("some-key2"),
description: String::from("NA"),
default_value: Value::Bool(true),
constraint: None,
stability: Stability::Unstable,
active: true,
display_hint: DisplayHint::None,
},
];
let configs =
temp_env::with_vars([("ESP_TEST_CONFIG_SOME_KEY", Some("variant-0"))], || {
generate_config_internal(&mut stdout, "esp-test", &config, false)
});
let json_output = config_json(&configs, true);
println!("{json_output}");
pretty_assertions::assert_eq!(
r#"[
{
"option": {
"name": "some-key",
"description": "NA",
"default_value": {
"String": "variant-0"
},
"constraint": {
"Enumeration": [
"variant-0",
"variant-1"
]
},
"stability": {
"Stable": "testing"
},
"active": true,
"display_hint": "None"
},
"actual_value": {
"String": "variant-0"
}
},
{
"option": {
"name": "some-key2",
"description": "NA",
"default_value": {
"Bool": true
},
"constraint": null,
"stability": "Unstable",
"active": true,
"display_hint": "None"
},
"actual_value": {
"Bool": true
}
}
]"#,
json_output
);
}
#[test]
#[should_panic]
fn unstable_option_panics_unless_enabled() {
@ -838,61 +971,190 @@ mod test {
}
#[test]
fn convenience_constructors() {
fn deserialization() {
let yml = r#"
crate: esp-bootloader-esp-idf
options:
- name: mmu_page_size
description: ESP32-C2, ESP32-C6 and ESP32-H2 support configurable page sizes. This is currently only used to populate the app descriptor.
default:
- value: '"64k"'
stability: !Stable xxxx
constraints:
- if: true
type:
validator: enumeration
value:
- 8k
- 16k
- 32k
- 64k
- name: esp_idf_version
description: ESP-IDF version used in the application descriptor. Currently it's not checked by the bootloader.
default:
- if: 'chip == "esp32c6"'
value: '"esp32c6"'
- if: 'chip == "esp32"'
value: '"other"'
active: true
- name: partition-table-offset
description: "The address of partition table (by default 0x8000). Allows you to \
move the partition table, it gives more space for the bootloader. Note that the \
bootloader and app will both need to be compiled with the same \
PARTITION_TABLE_OFFSET value."
default:
- if: true
value: 32768
stability: Unstable
active: 'chip == "esp32c6"'
"#;
let (cfg, options) = evaluate_yaml_config(
yml,
Some(esp_metadata::Config::for_chip(&esp_metadata::Chip::Esp32c6).clone()),
vec![],
false,
)
.unwrap();
assert_eq!("esp-bootloader-esp-idf", cfg.krate);
assert_eq!(
ConfigOption {
name: String::from("number"),
description: String::from("NA"),
default_value: Value::Integer(999),
constraint: None,
vec![
ConfigOption {
name: "mmu_page_size".to_string(),
description: "ESP32-C2, ESP32-C6 and ESP32-H2 support configurable page sizes. This is currently only used to populate the app descriptor.".to_string(),
default_value: Value::String("64k".to_string()),
constraint: Some(
Validator::Enumeration(
vec![
"8k".to_string(),
"16k".to_string(),
"32k".to_string(),
"64k".to_string(),
],
),
),
stability: Stability::Stable("xxxx".to_string()),
active: true,
display_hint: DisplayHint::None,
},
ConfigOption {
name: "esp_idf_version".to_string(),
description: "ESP-IDF version used in the application descriptor. Currently it's not checked by the bootloader.".to_string(),
default_value: Value::String("esp32c6".to_string()),
constraint: None,
stability: Stability::Unstable,
active: true,
display_hint: DisplayHint::None,
},
ConfigOption {
name: "partition-table-offset".to_string(),
description: "The address of partition table (by default 0x8000). Allows you to move the partition table, it gives more space for the bootloader. Note that the bootloader and app will both need to be compiled with the same PARTITION_TABLE_OFFSET value.".to_string(),
default_value: Value::Integer(32768),
constraint: None,
stability: Stability::Unstable,
active: true,
display_hint: DisplayHint::None,
},
],
options
);
}
#[test]
fn deserialization_fallback_default() {
let yml = r#"
crate: esp-bootloader-esp-idf
options:
- name: esp_idf_version
description: ESP-IDF version used in the application descriptor. Currently it's not checked by the bootloader.
default:
- if: 'chip == "esp32c6"'
value: '"esp32c6"'
- if: 'chip == "esp32"'
value: '"other"'
- value: '"default"'
active: true
"#;
let (cfg, options) = evaluate_yaml_config(
yml,
Some(esp_metadata::Config::for_chip(&esp_metadata::Chip::Esp32c3).clone()),
vec![],
false,
)
.unwrap();
assert_eq!("esp-bootloader-esp-idf", cfg.krate);
assert_eq!(
vec![
ConfigOption {
name: "esp_idf_version".to_string(),
description: "ESP-IDF version used in the application descriptor. Currently it's not checked by the bootloader.".to_string(),
default_value: Value::String("default".to_string()),
constraint: None,
stability: Stability::Unstable,
active: true,
display_hint: DisplayHint::None,
},
],
options
);
}
#[test]
fn deserialization_fallback_contraint() {
let yml = r#"
crate: esp-bootloader-esp-idf
options:
- name: option
description: Desc
default:
- value: 100
constraints:
- if: 'chip == "esp32c6"'
type:
validator: integer_in_range
value:
start: 0
end: 100
- if: true
type:
validator: integer_in_range
value:
start: 0
end: 50
active: true
"#;
let (cfg, options) = evaluate_yaml_config(
yml,
Some(esp_metadata::Config::for_chip(&esp_metadata::Chip::Esp32).clone()),
vec![],
false,
)
.unwrap();
assert_eq!("esp-bootloader-esp-idf", cfg.krate);
assert_eq!(
vec![ConfigOption {
name: "option".to_string(),
description: "Desc".to_string(),
default_value: Value::Integer(100),
constraint: Some(Validator::IntegerInRange(0..50)),
stability: Stability::Unstable,
active: true,
display_hint: DisplayHint::None,
},
ConfigOption::new("number", "NA", 999)
);
assert_eq!(
ConfigOption {
name: String::from("string"),
description: String::from("descr"),
default_value: Value::String("some string".to_string()),
constraint: None,
stability: Stability::Stable("1.0.0".to_string()),
active: true,
display_hint: DisplayHint::None,
},
ConfigOption::new("string", "descr", "some string").stable("1.0.0")
);
assert_eq!(
ConfigOption {
name: String::from("number"),
description: String::from("NA"),
default_value: Value::Integer(999),
constraint: Some(Validator::PositiveInteger),
stability: Stability::Unstable,
active: false,
display_hint: DisplayHint::None,
},
ConfigOption::new("number", "NA", 999)
.active(false)
.constraint(Validator::PositiveInteger)
);
assert_eq!(
ConfigOption {
name: String::from("number"),
description: String::from("NA"),
default_value: Value::Integer(999),
constraint: Some(Validator::PositiveInteger),
stability: Stability::Unstable,
active: true,
display_hint: DisplayHint::Hex,
},
ConfigOption::new("number", "NA", 999)
.constraint_by(Some(Validator::PositiveInteger))
.display_hint(DisplayHint::Hex)
},],
options
);
}
}

View File

@ -6,6 +6,7 @@ use super::{Error, snake_case, value::Value};
/// Configuration value validation functions.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(tag = "validator", content = "value", rename_all = "snake_case")]
pub enum Validator {
/// Only allow negative integers, i.e. any values less than 0.
NegativeInteger,

View File

@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
use super::Error;
/// Supported configuration value types.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Value {
/// Booleans.
Bool(bool),
@ -15,6 +15,70 @@ pub enum Value {
String(String),
}
impl Serialize for Value {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self {
Value::String(s) => serializer.serialize_str(&format!("\"{s}\"")),
Value::Integer(n) => serializer.serialize_str(&format!("{n}")),
Value::Bool(b) => serializer.serialize_str(&format!("{b}")),
}
}
}
impl<'de> Deserialize<'de> for Value {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct ValueVisitor;
impl serde::de::Visitor<'_> for ValueVisitor {
type Value = String;
fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str("a String representing the Value")
}
fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(v)
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(v.to_string())
}
}
let str_repr = deserializer.deserialize_string(ValueVisitor)?;
let str_repr = str_repr.as_str();
if let Some(remaining) = str_repr.strip_prefix("\"") {
let s = &remaining[..remaining.len() - 1];
return Ok(Value::String(s.to_string()));
}
if str_repr == "true" {
return Ok(Value::Bool(true));
}
if str_repr == "false" {
return Ok(Value::Bool(false));
}
Ok(Value::Integer(str_repr.parse().map_err(
|e: core::num::ParseIntError| serde::de::Error::custom(e.to_string()),
)?))
}
}
// TODO: Do we want to handle negative values for non-decimal values?
impl Value {
pub(crate) fn parse_in_place(&mut self, s: &str) -> Result<(), Error> {
@ -118,3 +182,31 @@ impl From<String> for Value {
Value::String(value)
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn deserialization_number() {
assert_eq!(
serde_yaml::from_str::<Value>("128").unwrap(),
Value::Integer(128)
);
assert_eq!(
serde_yaml::from_str::<Value>(&format!("{}", i128::MAX)).unwrap(),
Value::Integer(i128::MAX)
);
assert_eq!(
serde_yaml::from_str::<Value>(&format!("{}", i128::MIN)).unwrap(),
Value::Integer(i128::MIN)
);
}
#[test]
fn deserialization_string() {
let yml = "'\"Hello\"'";
let value: Value = serde_yaml::from_str(yml).unwrap();
assert_eq!(value, Value::String("Hello".to_string()));
}
}

View File

@ -14,6 +14,7 @@ pub use generate::{
Error,
Stability,
generate_config,
generate_config_from_yaml_definition,
validator::Validator,
value::Value,
};

View File

@ -1,6 +1,6 @@
use std::error::Error as StdError;
use esp_config::{ConfigOption, Validator, Value, generate_config};
use esp_config::{Value, generate_config_from_yaml_definition};
use esp_metadata::{Chip, Config};
fn main() -> Result<(), Box<dyn StdError>> {
@ -12,51 +12,11 @@ fn main() -> Result<(), Box<dyn StdError>> {
config.define_symbols();
// emit config
let crate_config = generate_config(
"esp_hal_embassy",
&[
ConfigOption::new(
"low-power-wait",
"Enables the lower-power wait if no tasks are ready to run on the \
thread-mode executor. This allows the MCU to use less power if the workload allows. \
Recommended for battery-powered systems. May impact analog performance.",
true,
),
ConfigOption::new(
"timer-queue",
"The flavour of the timer queue provided by this crate. Integrated \
queues require the `executors` feature to be enabled.</p><p>If you use \
embassy-executor, the `single-integrated` queue is recommended for ease of use, \
while the `multiple-integrated` queue is recommended for performance. The \
`multiple-integrated` option needs one timer per executor.</p><p>The `generic` \
queue allows using embassy-time without the embassy executors.",
if cfg!(feature = "executors") {
"single-integrated"
} else {
"generic"
},
)
.constraint(if cfg!(feature = "executors") {
Validator::Enumeration(vec![
String::from("generic"),
String::from("single-integrated"),
String::from("multiple-integrated"),
])
} else {
Validator::Enumeration(vec![String::from("generic")])
})
.active(cfg!(feature = "executors")),
ConfigOption::new(
"generic-queue-size",
"The capacity of the queue when the `generic` timer \
queue flavour is selected.",
64,
)
.constraint(Validator::PositiveInteger),
],
true,
true,
);
println!("cargo:rerun-if-changed=./esp_config.yml");
let cfg_yaml = std::fs::read_to_string("./esp_config.yml")
.expect("Failed to read esp_config.yml for esp-hal-embassy");
let crate_config =
generate_config_from_yaml_definition(&cfg_yaml, true, true, Some(config.clone())).unwrap();
println!("cargo:rustc-check-cfg=cfg(integrated_timers)");
println!("cargo:rustc-check-cfg=cfg(single_queue)");

View File

@ -0,0 +1,47 @@
crate: esp-hal-embassy
options:
- name: low-power-wait
description: "Enables the lower-power wait if no tasks are ready to run on the
thread-mode executor. This allows the MCU to use less power if the workload allows.
Recommended for battery-powered systems. May impact analog performance."
default:
- value: true
- name: timer-queue
description: "The flavour of the timer queue provided by this crate. Integrated
queues require the `executors` feature to be enabled.</p><p>If you use
embassy-executor, the `single-integrated` queue is recommended for ease of use,
while the `multiple-integrated` queue is recommended for performance. The
`multiple-integrated` option needs one timer per executor.</p><p>The `generic`
queue allows using embassy-time without the embassy executors."
default:
- if: 'ignore_feature_gates()'
value: '"single-integrated"'
- if: 'cargo_feature("executors")'
value: '"single-integrated"'
- if: 'true'
value: '"generic"'
constraints:
- if: 'cargo_feature("executors") || ignore_feature_gates()'
type:
validator: enumeration
value:
- 'generic'
- 'single-integrated'
- 'multiple-integrated'
- if: 'true'
type:
validator: enumeration
value:
- 'generic'
stability: Unstable
active: 'cargo_feature("executors") || ignore_feature_gates()'
- name: generic-queue-size
description: The capacity of the queue when the `generic` timer queue flavour is selected.
default:
- value: 64
constraints:
- type:
validator: positive_integer

View File

@ -8,7 +8,7 @@ use std::{
};
use esp_build::assert_unique_features;
use esp_config::{ConfigOption, DisplayHint, Validator, Value, generate_config};
use esp_config::{Value, generate_config_from_yaml_definition};
use esp_metadata::{Chip, Config};
fn main() -> Result<(), Box<dyn Error>> {
@ -49,116 +49,11 @@ fn main() -> Result<(), Box<dyn Error>> {
println!("cargo:rustc-link-search={}", out.display());
// emit config
let cfg = generate_config(
"esp_hal",
&[
ConfigOption::new(
"place-spi-master-driver-in-ram",
"Places the SPI master driver in RAM for better performance",
false,
),
ConfigOption::new(
"place-switch-tables-in-ram",
"Places switch-tables, some lookup tables and constants related to \
interrupt handling into RAM - resulting in better performance but slightly more \
RAM consumption.",
true,
)
.stable("1.0.0-beta.0"),
ConfigOption::new(
"place-anon-in-ram",
"Places anonymous symbols into RAM - resulting in better performance \
at the cost of significant more RAM consumption. Best to be combined with \
`place-switch-tables-in-ram`.",
false,
)
.stable("1.0.0-beta.0"),
// Ideally, we should be able to set any clock frequency for any chip. However,
// currently only the 32 and C2 implements any sort of configurability, and
// the rest have a fixed clock frequeny.
ConfigOption::new(
"xtal-frequency",
"The frequency of the crystal oscillator, in MHz. Set to `auto` to \
automatically detect the frequency. `auto` may not be able to identify the clock \
frequency in some cases. Also, configuring a specific frequency may increase \
performance slightly.",
match chip {
Chip::Esp32 | Chip::Esp32c2 => "auto",
// The rest has only one option
Chip::Esp32c3 | Chip::Esp32c6 | Chip::Esp32s2 | Chip::Esp32s3 => "40",
Chip::Esp32h2 => "32",
},
)
.constraint_by(match chip {
Chip::Esp32 | Chip::Esp32c2 => Some(Validator::Enumeration(vec![
String::from("auto"),
String::from("26"),
String::from("40"),
])),
// The rest has only one option
_ => None,
})
.active([Chip::Esp32, Chip::Esp32c2].contains(&chip)),
ConfigOption::new(
"spi-address-workaround",
"Enables a workaround for the issue where SPI in \
half-duplex mode incorrectly transmits the address on a single line if the \
data buffer is empty.",
true,
)
.active(chip == Chip::Esp32),
ConfigOption::new(
"flip-link",
"Move the stack to start of RAM to get zero-cost stack overflow protection.",
false,
)
.active([Chip::Esp32c6, Chip::Esp32h2].contains(&chip)),
// TODO: automate "enum of single choice" handling - they don't need
// to be presented to the user
ConfigOption::new("psram-mode", "SPIRAM chip mode", "quad")
.constraint(Validator::Enumeration(
if config
.symbols()
.iter()
.any(|s| s.eq_ignore_ascii_case("octal_psram"))
{
vec![String::from("quad"), String::from("octal")]
} else {
vec![String::from("quad")]
},
))
.active(
config
.symbols()
.iter()
.any(|s| s.eq_ignore_ascii_case("psram")),
),
// Rust's stack smashing protection configuration
ConfigOption::new(
"stack-guard-offset",
"The stack guard variable will be placed this many bytes from \
the stack's end.",
4096,
)
.stable("1.0.0-beta.0"),
ConfigOption::new(
"stack-guard-value",
"The value to be written to the stack guard variable.",
0xDEED_BAAD,
)
.stable("1.0.0-beta.0")
.display_hint(DisplayHint::Hex),
ConfigOption::new(
"impl-critical-section",
"Provide a `critical-section` implementation. Note that if disabled, \
you will need to provide a `critical-section` implementation which is \
using `restore-state-u32`.",
true,
),
],
cfg!(feature = "unstable"),
true,
);
println!("cargo:rerun-if-changed=./esp_config.yml");
let cfg_yaml = std::fs::read_to_string("./esp_config.yml")
.expect("Failed to read esp_config.yml for esp-hal");
let cfg =
generate_config_from_yaml_definition(&cfg_yaml, true, true, Some(config.clone())).unwrap();
// RISC-V and Xtensa devices each require some special handling and processing
// of linker scripts:

110
esp-hal/esp_config.yml Normal file
View File

@ -0,0 +1,110 @@
crate: esp-hal
options:
- name: place-spi-master-driver-in-ram
description: Places the SPI master driver in RAM for better performance
default:
- value: false
- name: place-switch-tables-in-ram
description: "Places switch-tables, some lookup tables and constants related to
interrupt handling into RAM - resulting in better performance but slightly more
RAM consumption."
default:
- value: true
stability: !Stable '1.0.0-beta.0'
- name: place-anon-in-ram
description: "Places anonymous symbols into RAM - resulting in better performance
at the cost of significant more RAM consumption. Best to be combined with
`place-switch-tables-in-ram`."
default:
- value: false
stability: !Stable '1.0.0-beta.0'
# Ideally, we should be able to set any clock frequency for any chip. However,
# currently only the 32 and C2 implements any sort of configurability, and
# the rest have a fixed clock frequency.
- name: xtal-frequency
description: "The frequency of the crystal oscillator, in MHz. Set to `auto` to
automatically detect the frequency. `auto` may not be able to identify the clock
frequency in some cases. Also, configuring a specific frequency may increase
performance slightly."
default:
- if: 'chip == "esp32" || chip == "esp32c2"'
value: '"auto"'
- if: 'chip == "esp32c3" || chip == "esp32c6" || chip == "esp32s2" || chip == "esp32s3"'
value: '"40"'
- if: 'chip == "esp32h2"'
value: '"32"'
constraints:
- if: 'chip == "esp32" || chip == "esp32c2"'
type:
validator: enumeration
value:
- 'auto'
- '26'
- '40'
- if: 'chip == "esp32c3" || chip == "esp32c6" || chip == "esp32s2" || chip == "esp32s3"'
type:
validator: enumeration
value:
- '40'
- if: 'chip == "esp32h2"'
type:
validator: enumeration
value:
- '32'
- name: spi-address-workaround
description: "Enables a workaround for the issue where SPI in
half-duplex mode incorrectly transmits the address on a single line if the
data buffer is empty."
default:
- value: true
active: 'chip == "esp32"'
- name: flip-link
description: Move the stack to start of RAM to get zero-cost stack overflow protection.
default:
- value: false
active: 'chip == "esp32c6" || chip == "esp32h2"'
- name: psram-mode
description: SPIRAM chip mode
default:
- value: '"quad"'
constraints:
- if: 'feature("octal_psram")'
type:
validator: enumeration
value:
- 'quad'
- 'octal'
- if: '!feature("octal_psram")'
type:
validator: enumeration
value:
- 'quad'
active: 'feature("psram")'
- name: stack-guard-offset
description: The stack guard variable will be placed this many bytes from the stack's end.
default:
- value: 4096
stability: !Stable '1.0.0-beta.0'
active: 'true'
- name: stack-guard-value
description: The value to be written to the stack guard variable.
default:
- value: 3740121773
stability: !Stable '1.0.0-beta.0'
display_hint: Hex
- name: impl-critical-section
description: "Provide a `critical-section` implementation. Note that if disabled,
you will need to provide a `critical-section` implementation which is
using `restore-state-u32`."
default:
- value: true

View File

@ -1,19 +1,14 @@
use std::{env, path::PathBuf};
use esp_config::{ConfigOption, Validator, generate_config};
use esp_config::generate_config_from_yaml_definition;
fn main() {
let out = PathBuf::from(env::var_os("OUT_DIR").unwrap());
println!("cargo:rustc-link-search={}", out.display());
// emit config
generate_config(
"esp_ieee802154",
&[
ConfigOption::new("rx_queue_size", "Size of the RX queue in frames", 50)
.constraint(Validator::PositiveInteger),
],
true,
true,
);
println!("cargo:rerun-if-changed=./esp_config.yml");
let cfg_yaml = std::fs::read_to_string("./esp_config.yml")
.expect("Failed to read esp_config.yml for esp-ieee802154");
generate_config_from_yaml_definition(&cfg_yaml, true, true, None).unwrap();
}

View File

@ -0,0 +1,10 @@
crate: esp-ieee802154
options:
- name: rx_queue_size
description: Size of the RX queue in frames
default:
- value: 10
constraints:
- type:
validator: positive_integer

View File

@ -1,6 +1,6 @@
use std::error::Error;
use esp_config::{ConfigOption, Validator, generate_config};
use esp_config::generate_config_from_yaml_definition;
use esp_metadata::{Chip, Config};
fn main() -> Result<(), Box<dyn Error>> {
@ -64,169 +64,10 @@ fn main() -> Result<(), Box<dyn Error>> {
//
// keep the defaults aligned with `esp_wifi_sys::include::*` e.g.
// `esp_wifi_sys::include::CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM`
generate_config(
"esp_wifi",
&[
ConfigOption::new("rx_queue_size", "Size of the RX queue in frames", 5)
.constraint(Validator::PositiveInteger),
ConfigOption::new("tx_queue_size", "Size of the TX queue in frames", 3)
.constraint(Validator::PositiveInteger),
ConfigOption::new(
"static_rx_buf_num",
"WiFi static RX buffer number. See [ESP-IDF Programming Guide]\
(https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/\
network/esp_wifi.html#_CPPv418wifi_init_config_t)",
10,
)
.constraint(Validator::PositiveInteger),
ConfigOption::new(
"dynamic_rx_buf_num",
"WiFi dynamic RX buffer number. See [ESP-IDF Programming Guide]\
(https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/\
network/esp_wifi.html#_CPPv418wifi_init_config_t)",
32,
)
.constraint(Validator::PositiveInteger),
ConfigOption::new(
"static_tx_buf_num",
"WiFi static TX buffer number. See [ESP-IDF Programming Guide]\
(https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/\
network/esp_wifi.html#_CPPv418wifi_init_config_t)",
0,
),
ConfigOption::new(
"dynamic_tx_buf_num",
"WiFi dynamic TX buffer number. See [ESP-IDF Programming Guide]\
(https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/\
network/esp_wifi.html#_CPPv418wifi_init_config_t)",
32,
),
ConfigOption::new(
"ampdu_rx_enable",
"WiFi AMPDU RX feature enable flag. See [ESP-IDF Programming Guide]\
(https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/\
network/esp_wifi.html#_CPPv418wifi_init_config_t)",
true,
),
ConfigOption::new(
"ampdu_tx_enable",
"WiFi AMPDU TX feature enable flag. See [ESP-IDF Programming Guide]\
(https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/\
network/esp_wifi.html#_CPPv418wifi_init_config_t)",
true,
),
ConfigOption::new(
"amsdu_tx_enable",
"WiFi AMSDU TX feature enable flag. See [ESP-IDF Programming Guide]\
(https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/\
network/esp_wifi.html#_CPPv418wifi_init_config_t)",
false,
),
ConfigOption::new(
"rx_ba_win",
"WiFi Block Ack RX window size. See [ESP-IDF Programming Guide]\
(https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/\
network/esp_wifi.html#_CPPv418wifi_init_config_t)",
6,
),
ConfigOption::new(
"max_burst_size",
"See [smoltcp's documentation]\
(https://docs.rs/smoltcp/0.10.0/smoltcp/phy/struct.DeviceCapabilities.html\
#structfield.max_burst_size)",
1,
),
ConfigOption::new(
"country_code",
"Country code. See [ESP-IDF Programming Guide]\
(https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/\
wifi.html#wi-fi-country-code)",
"CN",
),
ConfigOption::new(
"country_code_operating_class",
"If not 0: Operating Class table number. See [ESP-IDF Programming Guide]\
(https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/\
wifi.html#wi-fi-country-code)",
0,
),
ConfigOption::new(
"mtu",
"MTU, see [smoltcp's documentation]\
(https://docs.rs/smoltcp/0.10.0/smoltcp/phy/struct.DeviceCapabilities.html\
#structfield.max_transmission_unit)",
1492,
)
.constraint(Validator::PositiveInteger),
ConfigOption::new(
"tick_rate_hz",
"Tick rate of the internal task scheduler in hertz",
100,
)
.constraint(Validator::PositiveInteger),
ConfigOption::new(
"listen_interval",
"Interval for station to listen to beacon from AP.
The unit of listen interval is one beacon interval.
For example, if beacon interval is 100 ms and listen interval is 3,
the interval for station to listen to beacon is 300 ms",
3,
),
ConfigOption::new(
"beacon_timeout",
"For Station, If the station does not receive a beacon frame
from the connected SoftAP during the inactive time, disconnect from SoftAP.
Default 6s. Range 6-30",
6,
)
.constraint(Validator::IntegerInRange(6..30)),
ConfigOption::new(
"ap_beacon_timeout",
"For SoftAP, If the SoftAP doesn't receive any data from the connected STA
during inactive time, the SoftAP will force deauth the STA. Default is 300s",
300,
),
ConfigOption::new(
"failure_retry_cnt",
"Number of connection retries station will do before moving to next AP.
scan_method should be set as WIFI_ALL_CHANNEL_SCAN to use this config.
Note: Enabling this may cause connection time to increase incase best AP
doesn't behave properly. Defaults to 1",
1,
)
.constraint(Validator::PositiveInteger),
ConfigOption::new(
"scan_method",
"0 = WIFI_FAST_SCAN, 1 = WIFI_ALL_CHANNEL_SCAN, defaults to 0",
0,
)
.constraint(Validator::IntegerInRange(0..2)),
ConfigOption::new(
"dump_packets",
"Dump packets via an info log statement",
false,
),
ConfigOption::new(
"phy_enable_usb",
"Keeps USB running when using WiFi.
This allows debugging and log messages via USB Serial JTAG.
Turn off for best WiFi performance.",
true,
),
ConfigOption::new(
"phy_skip_calibration_after_deep_sleep",
"Use PHY_RF_CAL_NONE after deep sleep.",
false,
),
ConfigOption::new(
"phy_full_calibration",
"Use PHY_RF_CAL_FULL instead of PHY_RF_CAL_PARTIAL.",
true,
),
],
true,
true,
);
println!("cargo:rerun-if-changed=./esp_config.yml");
let cfg_yaml = std::fs::read_to_string("./esp_config.yml")
.expect("Failed to read esp_config.yml for esp-wifi");
generate_config_from_yaml_definition(&cfg_yaml, true, true, Some(config.clone())).unwrap();
Ok(())
}

180
esp-wifi/esp_config.yml Normal file
View File

@ -0,0 +1,180 @@
crate: esp-wifi
options:
- name: rx_queue_size
description: Size of the RX queue in frames
default:
- value: 5
constraints:
- type:
validator: positive_integer
- name: tx_queue_size
description: Size of the TX queue in frames
default:
- value: 3
constraints:
- type:
validator: positive_integer
- name: static_rx_buf_num
description: "WiFi static RX buffer number. See
[ESP-IDF Programming Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_wifi.html#_CPPv418wifi_init_config_t)"
default:
- value: 10
constraints:
- type:
validator: positive_integer
- name: dynamic_rx_buf_num
description: "WiFi dynamic RX buffer number. See
[ESP-IDF Programming Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_wifi.html#_CPPv418wifi_init_config_t)"
default:
- value: 32
constraints:
- type:
validator: positive_integer
- name: static_tx_buf_num
description: "WiFi static TX buffer number. See
[ESP-IDF Programming Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_wifi.html#_CPPv418wifi_init_config_t)"
default:
- value: 0
- name: dynamic_tx_buf_num
description: "WiFi dynamic TX buffer number. See
[ESP-IDF Programming Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_wifi.html#_CPPv418wifi_init_config_t)"
default:
- value: 32
- name: ampdu_rx_enable
description: "WiFi AMPDU RX feature enable flag.
See [ESP-IDF Programming Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_wifi.html#_CPPv418wifi_init_config_t)"
default:
- value: true
- name: ampdu_tx_enable
description: "WiFi AMPDU TX feature enable flag. See
[ESP-IDF Programming Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_wifi.html#_CPPv418wifi_init_config_t)"
default:
- value: true
- name: amsdu_tx_enable
description: "WiFi AMSDU TX feature enable flag. See
[ESP-IDF Programming Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_wifi.html#_CPPv418wifi_init_config_t)"
default:
- value: false
- name: rx_ba_win
description: "WiFi Block Ack RX window size. See
[ESP-IDF Programming Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_wifi.html#_CPPv418wifi_init_config_t)"
default:
- value: 6
- name: max_burst_size
description: See [smoltcp's documentation](https://docs.rs/smoltcp/0.10.0/smoltcp/phy/struct.DeviceCapabilities.html#structfield.max_burst_size)
default:
- value: 1
- name: country_code
description: "Country code. See
[ESP-IDF Programming Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/wifi.html#wi-fi-country-code)"
default:
- value: '"CN"'
- name: country_code_operating_class
description: 'If not 0: Operating Class table number. See
[ESP-IDF Programming Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/wifi.html#wi-fi-country-code)'
default:
- value: 0
- name: mtu
description: "MTU, see [smoltcp's documentation](https://docs.rs/smoltcp/0.10.0/smoltcp/phy/struct.DeviceCapabilities.html#structfield.max_transmission_unit)"
default:
- value: 1492
constraints:
- type:
validator: positive_integer
- name: tick_rate_hz
description: 'Tick rate of the internal task scheduler in hertz'
default:
- value: 100
constraints:
- type:
validator: positive_integer
- name: listen_interval
description: 'Interval for station to listen to beacon from AP.
The unit of listen interval is one beacon interval.
For example, if beacon interval is 100 ms and listen interval is 3,
the interval for station to listen to beacon is 300 ms'
default:
- value: 3
- name: beacon_timeout
description: 'For Station, If the station does not receive a beacon frame
from the connected SoftAP during the inactive time, disconnect from SoftAP.
Default 6s. Range 6-30'
default:
- value: 6
constraints:
- type:
validator: integer_in_range
value:
start: 6
end: 31
- name: ap_beacon_timeout
description: "For SoftAP, If the SoftAP doesn't receive any data from the connected STA
during inactive time, the SoftAP will force deauth the STA. Default is 300s"
default:
- value: 300
- name: failure_retry_cnt
description: "Number of connection retries station will do before moving to next AP.
scan_method should be set as WIFI_ALL_CHANNEL_SCAN to use this config.
Note: Enabling this may cause connection time to increase incase best AP
doesn't behave properly. Defaults to 1"
default:
- value: 1
constraints:
- type:
validator: positive_integer
- name: scan_method
description: "0 = WIFI_FAST_SCAN, 1 = WIFI_ALL_CHANNEL_SCAN, defaults to 0"
default:
- value: 0
constraints:
- type:
validator: integer_in_range
value:
start: 0
end: 2
- name: dump_packets
description: "Dump packets via an info log statement"
default:
- value: false
- name: phy_enable_usb
description: "Keeps USB running when using WiFi.
This allows debugging and log messages via USB Serial JTAG.
Turn off for best WiFi performance."
default:
- value: true
- name: phy_skip_calibration_after_deep_sleep
description: "Use PHY_RF_CAL_NONE after deep sleep."
default:
- value: false
- name: phy_full_calibration
description: "Use PHY_RF_CAL_FULL instead of PHY_RF_CAL_PARTIAL."
default:
- value: true
checks:
- 'ESP_WIFI_CONFIG_RX_BA_WIN < ESP_WIFI_CONFIG_DYNAMIC_RX_BUF_NUM'
- 'ESP_WIFI_CONFIG_RX_BA_WIN < (ESP_WIFI_CONFIG_STATIC_RX_BUF_NUM * 2)'

View File

@ -227,21 +227,6 @@ pub(crate) const CONFIG: config::EspWifiConfig = config::EspWifiConfig {
scan_method: esp_config_int!(u32, "ESP_WIFI_CONFIG_SCAN_METHOD"),
};
// Validate the configuration at compile time
#[allow(clippy::assertions_on_constants)]
const _: () = {
// We explicitely use `core` assert here because this evaluation happens at
// compile time and won't bloat the binary
core::assert!(
CONFIG.rx_ba_win < CONFIG.dynamic_rx_buf_num,
"WiFi configuration check: rx_ba_win should not be larger than dynamic_rx_buf_num!"
);
core::assert!(
CONFIG.rx_ba_win < (CONFIG.static_rx_buf_num * 2),
"WiFi configuration check: rx_ba_win should not be larger than double of the static_rx_buf_num!"
);
};
type TimeBase = PeriodicTimer<'static, Blocking>;
#[derive(Debug, PartialEq, PartialOrd)]