mirror of
https://github.com/esphome/esphome.git
synced 2026-08-22 22:26:21 +00:00
[esp32] refactor esp32-vfs default configs to FINAL co-routine & add some defaults (#17337)
Co-authored-by: Oliver Kleinecke <kleinecke.oliver@googlemail.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: storage split <ai@local> Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
Oliver Kleinecke
Claude Sonnet 4.6
Copilot Autofix powered by AI
storage split
pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
parent
c0e70d9beb
commit
b326cefea7
@@ -2248,6 +2248,62 @@ async def _add_yaml_idf_components(components: list[ConfigType]):
|
||||
)
|
||||
|
||||
|
||||
@coroutine_with_priority(CoroPriority.FINAL)
|
||||
async def _reconcile_vfs_fatfs_sdkconfig(
|
||||
disable_vfs_termios: bool,
|
||||
disable_vfs_select: bool,
|
||||
disable_vfs_dir: bool,
|
||||
disable_fatfs: bool,
|
||||
) -> None:
|
||||
"""Reconcile VFS/FATFS sdkconfig flags after all require_*() calls; user sdkconfig_options win."""
|
||||
opts = CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS]
|
||||
|
||||
def set_opt(name: str, value: SdkconfigValueType) -> None:
|
||||
# User sdkconfig_options (applied during to_code) win.
|
||||
if name not in opts:
|
||||
add_idf_sdkconfig_option(name, value)
|
||||
|
||||
# USB Serial JTAG VFS needs termios (require_vfs_termios(), e.g. logger). ~1.8KB flash when off.
|
||||
if CORE.data.get(KEY_VFS_TERMIOS_REQUIRED, False):
|
||||
set_opt("CONFIG_VFS_SUPPORT_TERMIOS", True)
|
||||
else:
|
||||
set_opt("CONFIG_VFS_SUPPORT_TERMIOS", not disable_vfs_termios)
|
||||
|
||||
# VFS select is only needed for UART/eventfd fds (require_vfs_select(), e.g. openthread);
|
||||
# sockets use lwip_select() either way. ~2.7KB flash when off.
|
||||
if CORE.data.get(KEY_VFS_SELECT_REQUIRED, False):
|
||||
set_opt("CONFIG_VFS_SUPPORT_SELECT", True)
|
||||
else:
|
||||
set_opt("CONFIG_VFS_SUPPORT_SELECT", not disable_vfs_select)
|
||||
|
||||
# Directory functions: opendir/readdir/mkdir etc. (require_vfs_dir()). ~0.5KB flash when off.
|
||||
if CORE.data.get(KEY_VFS_DIR_REQUIRED, False):
|
||||
set_opt("CONFIG_VFS_SUPPORT_DIR", True)
|
||||
else:
|
||||
set_opt("CONFIG_VFS_SUPPORT_DIR", not disable_vfs_dir)
|
||||
|
||||
# FATFS (require_fatfs()): LFN + one volume per esp_vfs_fat mount. Defaults only;
|
||||
# sdkconfig_options override. FATFS_LONG_FILENAMES is a Kconfig choice -- if the user set
|
||||
# any member, leave the group alone. LFN_HEAP allocates per LFN op; LFN_STACK uses stack.
|
||||
lfn_keys = (
|
||||
"CONFIG_FATFS_LFN_NONE",
|
||||
"CONFIG_FATFS_LFN_HEAP",
|
||||
"CONFIG_FATFS_LFN_STACK",
|
||||
)
|
||||
user_picked_lfn = any(k in opts for k in lfn_keys)
|
||||
if CORE.data[KEY_ESP32].get(KEY_FATFS_REQUIRED, False):
|
||||
if not user_picked_lfn:
|
||||
set_opt("CONFIG_FATFS_LFN_NONE", False)
|
||||
set_opt("CONFIG_FATFS_LFN_HEAP", True)
|
||||
set_opt("CONFIG_FATFS_MAX_LFN", 255)
|
||||
set_opt("CONFIG_FATFS_VOLUME_COUNT", 4)
|
||||
elif disable_fatfs:
|
||||
if not user_picked_lfn:
|
||||
set_opt("CONFIG_FATFS_LFN_NONE", True)
|
||||
# Kconfig range is [1,10]; 0 gets clamped to the default.
|
||||
set_opt("CONFIG_FATFS_VOLUME_COUNT", 1)
|
||||
|
||||
|
||||
@coroutine_with_priority(CoroPriority.FINAL - 1)
|
||||
async def _finalize_arduino_aware_flags():
|
||||
"""Build flags that depend on whether arduino-esp32 is linked in.
|
||||
@@ -2603,47 +2659,6 @@ async def to_code(config):
|
||||
if advanced[CONF_DISABLE_LIBC_LOCKS_IN_IRAM]:
|
||||
add_idf_sdkconfig_option("CONFIG_LIBC_LOCKS_PLACE_IN_IRAM", False)
|
||||
|
||||
# Disable VFS support for termios (terminal I/O functions)
|
||||
# USB Serial JTAG VFS functions require termios support.
|
||||
# Components that need it (e.g., logger when USB_SERIAL_JTAG is supported but not selected
|
||||
# as the logger output) call require_vfs_termios().
|
||||
# Saves approximately 1.8KB of flash when disabled (default).
|
||||
if CORE.data.get(KEY_VFS_TERMIOS_REQUIRED, False):
|
||||
# Component requires VFS termios - force enable regardless of user setting
|
||||
add_idf_sdkconfig_option("CONFIG_VFS_SUPPORT_TERMIOS", True)
|
||||
else:
|
||||
# No component needs it - allow user to control (default: disabled)
|
||||
add_idf_sdkconfig_option(
|
||||
"CONFIG_VFS_SUPPORT_TERMIOS", not advanced[CONF_DISABLE_VFS_SUPPORT_TERMIOS]
|
||||
)
|
||||
|
||||
# Disable VFS support for select() with file descriptors
|
||||
# ESPHome only uses select() with sockets via lwip_select(), which still works.
|
||||
# VFS select is only needed for UART/eventfd file descriptors.
|
||||
# Components that need it (e.g., openthread) call require_vfs_select().
|
||||
# Saves approximately 2.7KB of flash when disabled (default).
|
||||
if CORE.data.get(KEY_VFS_SELECT_REQUIRED, False):
|
||||
# Component requires VFS select - force enable regardless of user setting
|
||||
add_idf_sdkconfig_option("CONFIG_VFS_SUPPORT_SELECT", True)
|
||||
else:
|
||||
# No component needs it - allow user to control (default: disabled)
|
||||
add_idf_sdkconfig_option(
|
||||
"CONFIG_VFS_SUPPORT_SELECT", not advanced[CONF_DISABLE_VFS_SUPPORT_SELECT]
|
||||
)
|
||||
|
||||
# Disable VFS support for directory functions (opendir, readdir, mkdir, etc.)
|
||||
# ESPHome doesn't use directory functions on ESP32.
|
||||
# Components that need it (e.g., storage components) call require_vfs_dir().
|
||||
# Saves approximately 0.5KB+ of flash when disabled (default).
|
||||
if CORE.data.get(KEY_VFS_DIR_REQUIRED, False):
|
||||
# Component requires VFS directory support - force enable regardless of user setting
|
||||
add_idf_sdkconfig_option("CONFIG_VFS_SUPPORT_DIR", True)
|
||||
else:
|
||||
# No component needs it - allow user to control (default: disabled)
|
||||
add_idf_sdkconfig_option(
|
||||
"CONFIG_VFS_SUPPORT_DIR", not advanced[CONF_DISABLE_VFS_SUPPORT_DIR]
|
||||
)
|
||||
|
||||
if use_platformio:
|
||||
cg.add_platformio_option("board_build.partitions", "partitions.csv")
|
||||
if CONF_PARTITIONS in config:
|
||||
@@ -2878,6 +2893,16 @@ async def to_code(config):
|
||||
# FINAL priority: runs after every network/coexistence request_*() call
|
||||
CORE.add_job(_reconcile_network_sdkconfig)
|
||||
|
||||
# FINAL: require_*() calls can come from to_code at or below this priority, so an
|
||||
# inline read would be iteration-order-dependent; reconcile once after every job ran.
|
||||
CORE.add_job(
|
||||
_reconcile_vfs_fatfs_sdkconfig,
|
||||
advanced[CONF_DISABLE_VFS_SUPPORT_TERMIOS],
|
||||
advanced[CONF_DISABLE_VFS_SUPPORT_SELECT],
|
||||
advanced[CONF_DISABLE_VFS_SUPPORT_DIR],
|
||||
advanced[CONF_DISABLE_FATFS],
|
||||
)
|
||||
|
||||
# Disable regi2c control functions in IRAM
|
||||
# Only needed if using analog peripherals (ADC, DAC, etc.) from ISRs while cache is disabled
|
||||
if advanced[CONF_DISABLE_REGI2C_IN_IRAM]:
|
||||
@@ -2893,17 +2918,6 @@ async def to_code(config):
|
||||
):
|
||||
add_idf_sdkconfig_option("CONFIG_ADC_ONESHOT_CTRL_FUNC_IN_IRAM", True)
|
||||
|
||||
# Disable FATFS support
|
||||
# Components that need FATFS (SD card, etc.) can call require_fatfs()
|
||||
if CORE.data[KEY_ESP32].get(KEY_FATFS_REQUIRED, False):
|
||||
# Component called require_fatfs() - enable regardless of user setting
|
||||
add_idf_sdkconfig_option("CONFIG_FATFS_LFN_NONE", False)
|
||||
add_idf_sdkconfig_option("CONFIG_FATFS_VOLUME_COUNT", 2)
|
||||
elif advanced[CONF_DISABLE_FATFS]:
|
||||
add_idf_sdkconfig_option("CONFIG_FATFS_LFN_NONE", True)
|
||||
# Kconfig range is [1,10]; 0 gets clamped to the default.
|
||||
add_idf_sdkconfig_option("CONFIG_FATFS_VOLUME_COUNT", 1)
|
||||
|
||||
for name, value in conf[CONF_SDKCONFIG_OPTIONS].items():
|
||||
add_idf_sdkconfig_option(name, RawSdkconfigValue(value))
|
||||
|
||||
|
||||
@@ -10,11 +10,16 @@ from typing import Any
|
||||
import pytest
|
||||
|
||||
from esphome.components.esp32 import (
|
||||
KEY_FATFS_REQUIRED,
|
||||
KEY_VFS_DIR_REQUIRED,
|
||||
KEY_VFS_SELECT_REQUIRED,
|
||||
KEY_VFS_TERMIOS_REQUIRED,
|
||||
VARIANT_ESP32,
|
||||
VARIANTS,
|
||||
NetworkSdkconfigData,
|
||||
_ota_downgrade_protection_errors,
|
||||
_reconcile_network_sdkconfig,
|
||||
_reconcile_vfs_fatfs_sdkconfig,
|
||||
)
|
||||
from esphome.components.esp32.const import (
|
||||
KEY_ESP32,
|
||||
@@ -614,6 +619,160 @@ def test_reconcile_network_sdkconfig(
|
||||
assert CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS] == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("requires", "fatfs_required", "disables", "preset", "expected"),
|
||||
[
|
||||
# Nothing required and every disable_* flag off (NOT the shipped defaults, which
|
||||
# disable everything): VFS enabled, FATFS left untouched entirely.
|
||||
pytest.param(
|
||||
{},
|
||||
False,
|
||||
(False, False, False, False),
|
||||
{},
|
||||
{
|
||||
"CONFIG_VFS_SUPPORT_TERMIOS": True,
|
||||
"CONFIG_VFS_SUPPORT_SELECT": True,
|
||||
"CONFIG_VFS_SUPPORT_DIR": True,
|
||||
},
|
||||
id="nothing_disabled_nothing_required",
|
||||
),
|
||||
# The shipped out-of-the-box path: every disable_* flag defaults to True and nothing
|
||||
# is required -- VFS off, FATFS at the smallest footprint (8.3 names, one volume).
|
||||
pytest.param(
|
||||
{},
|
||||
False,
|
||||
(True, True, True, True),
|
||||
{},
|
||||
{
|
||||
"CONFIG_VFS_SUPPORT_TERMIOS": False,
|
||||
"CONFIG_VFS_SUPPORT_SELECT": False,
|
||||
"CONFIG_VFS_SUPPORT_DIR": False,
|
||||
"CONFIG_FATFS_LFN_NONE": True,
|
||||
"CONFIG_FATFS_VOLUME_COUNT": 1,
|
||||
},
|
||||
id="all_disabled_fatfs_fallback",
|
||||
),
|
||||
# A component's require_* beats the user's disable_* flag for every VFS feature.
|
||||
pytest.param(
|
||||
{
|
||||
KEY_VFS_TERMIOS_REQUIRED: True,
|
||||
KEY_VFS_SELECT_REQUIRED: True,
|
||||
KEY_VFS_DIR_REQUIRED: True,
|
||||
},
|
||||
False,
|
||||
(True, True, True, False),
|
||||
{},
|
||||
{
|
||||
"CONFIG_VFS_SUPPORT_TERMIOS": True,
|
||||
"CONFIG_VFS_SUPPORT_SELECT": True,
|
||||
"CONFIG_VFS_SUPPORT_DIR": True,
|
||||
},
|
||||
id="require_beats_disable",
|
||||
),
|
||||
# A user sdkconfig_options preset wins over a require (the set_opt guard).
|
||||
pytest.param(
|
||||
{KEY_VFS_SELECT_REQUIRED: True},
|
||||
False,
|
||||
(False, False, False, False),
|
||||
{"CONFIG_VFS_SUPPORT_SELECT": False},
|
||||
{
|
||||
"CONFIG_VFS_SUPPORT_TERMIOS": True,
|
||||
"CONFIG_VFS_SUPPORT_SELECT": False,
|
||||
"CONFIG_VFS_SUPPORT_DIR": True,
|
||||
},
|
||||
id="user_preset_wins_over_require",
|
||||
),
|
||||
# require_fatfs() with no user preset: long filenames on the heap, 255 chars,
|
||||
# four volumes.
|
||||
pytest.param(
|
||||
{},
|
||||
True,
|
||||
(False, False, False, False),
|
||||
{},
|
||||
{
|
||||
"CONFIG_VFS_SUPPORT_TERMIOS": True,
|
||||
"CONFIG_VFS_SUPPORT_SELECT": True,
|
||||
"CONFIG_VFS_SUPPORT_DIR": True,
|
||||
"CONFIG_FATFS_LFN_NONE": False,
|
||||
"CONFIG_FATFS_LFN_HEAP": True,
|
||||
"CONFIG_FATFS_MAX_LFN": 255,
|
||||
"CONFIG_FATFS_VOLUME_COUNT": 4,
|
||||
},
|
||||
id="fatfs_required_defaults",
|
||||
),
|
||||
# CONFIG_FATFS_LONG_FILENAMES is a Kconfig choice: a user picking any member
|
||||
# (here LFN_STACK) leaves the whole group untouched -- no second =y in the choice.
|
||||
pytest.param(
|
||||
{},
|
||||
True,
|
||||
(False, False, False, False),
|
||||
{"CONFIG_FATFS_LFN_STACK": "y"},
|
||||
{
|
||||
"CONFIG_VFS_SUPPORT_TERMIOS": True,
|
||||
"CONFIG_VFS_SUPPORT_SELECT": True,
|
||||
"CONFIG_VFS_SUPPORT_DIR": True,
|
||||
"CONFIG_FATFS_LFN_STACK": "y",
|
||||
"CONFIG_FATFS_VOLUME_COUNT": 4,
|
||||
},
|
||||
id="fatfs_user_lfn_stack_untouched",
|
||||
),
|
||||
# disable_fatfs (the shipped default) with a user LFN pick: the choice group is the
|
||||
# user's -- no LFN_NONE=y written next to their member, only the volume fallback.
|
||||
pytest.param(
|
||||
{},
|
||||
False,
|
||||
(False, False, False, True),
|
||||
{"CONFIG_FATFS_LFN_HEAP": "y"},
|
||||
{
|
||||
"CONFIG_VFS_SUPPORT_TERMIOS": True,
|
||||
"CONFIG_VFS_SUPPORT_SELECT": True,
|
||||
"CONFIG_VFS_SUPPORT_DIR": True,
|
||||
"CONFIG_FATFS_LFN_HEAP": "y",
|
||||
"CONFIG_FATFS_VOLUME_COUNT": 1,
|
||||
},
|
||||
id="disable_fatfs_user_lfn_untouched",
|
||||
),
|
||||
# Same for an explicit LFN_NONE preset: the group is the user's, only the volume
|
||||
# count default is added.
|
||||
pytest.param(
|
||||
{},
|
||||
True,
|
||||
(False, False, False, False),
|
||||
{"CONFIG_FATFS_LFN_NONE": "y"},
|
||||
{
|
||||
"CONFIG_VFS_SUPPORT_TERMIOS": True,
|
||||
"CONFIG_VFS_SUPPORT_SELECT": True,
|
||||
"CONFIG_VFS_SUPPORT_DIR": True,
|
||||
"CONFIG_FATFS_LFN_NONE": "y",
|
||||
"CONFIG_FATFS_VOLUME_COUNT": 4,
|
||||
},
|
||||
id="fatfs_user_lfn_none_untouched",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_reconcile_vfs_fatfs_sdkconfig(
|
||||
set_core_config: SetCoreConfigCallable,
|
||||
requires: dict[str, bool],
|
||||
fatfs_required: bool,
|
||||
disables: tuple[bool, bool, bool, bool],
|
||||
preset: dict[str, Any],
|
||||
expected: dict[str, Any],
|
||||
) -> None:
|
||||
"""The FINAL-priority reconciler resolves the VFS feature flags and the FATFS
|
||||
defaults from the recorded require_* calls, with user sdkconfig_options winning
|
||||
and the LFN Kconfig choice treated as one group."""
|
||||
set_core_config(PlatformFramework.ESP32_IDF)
|
||||
CORE.data[KEY_ESP32] = {KEY_SDKCONFIG_OPTIONS: dict(preset)}
|
||||
if fatfs_required:
|
||||
CORE.data[KEY_ESP32][KEY_FATFS_REQUIRED] = True
|
||||
for key, value in requires.items():
|
||||
CORE.data[key] = value
|
||||
|
||||
asyncio.run(_reconcile_vfs_fatfs_sdkconfig(*disables))
|
||||
|
||||
assert CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS] == expected
|
||||
|
||||
|
||||
def test_network_wifi_only_reconciles_end_to_end(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
component_config_path: Callable[[str], Path],
|
||||
|
||||
Reference in New Issue
Block a user