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)
|
@coroutine_with_priority(CoroPriority.FINAL - 1)
|
||||||
async def _finalize_arduino_aware_flags():
|
async def _finalize_arduino_aware_flags():
|
||||||
"""Build flags that depend on whether arduino-esp32 is linked in.
|
"""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]:
|
if advanced[CONF_DISABLE_LIBC_LOCKS_IN_IRAM]:
|
||||||
add_idf_sdkconfig_option("CONFIG_LIBC_LOCKS_PLACE_IN_IRAM", False)
|
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:
|
if use_platformio:
|
||||||
cg.add_platformio_option("board_build.partitions", "partitions.csv")
|
cg.add_platformio_option("board_build.partitions", "partitions.csv")
|
||||||
if CONF_PARTITIONS in config:
|
if CONF_PARTITIONS in config:
|
||||||
@@ -2878,6 +2893,16 @@ async def to_code(config):
|
|||||||
# FINAL priority: runs after every network/coexistence request_*() call
|
# FINAL priority: runs after every network/coexistence request_*() call
|
||||||
CORE.add_job(_reconcile_network_sdkconfig)
|
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
|
# Disable regi2c control functions in IRAM
|
||||||
# Only needed if using analog peripherals (ADC, DAC, etc.) from ISRs while cache is disabled
|
# Only needed if using analog peripherals (ADC, DAC, etc.) from ISRs while cache is disabled
|
||||||
if advanced[CONF_DISABLE_REGI2C_IN_IRAM]:
|
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)
|
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():
|
for name, value in conf[CONF_SDKCONFIG_OPTIONS].items():
|
||||||
add_idf_sdkconfig_option(name, RawSdkconfigValue(value))
|
add_idf_sdkconfig_option(name, RawSdkconfigValue(value))
|
||||||
|
|
||||||
|
|||||||
@@ -10,11 +10,16 @@ from typing import Any
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from esphome.components.esp32 import (
|
from esphome.components.esp32 import (
|
||||||
|
KEY_FATFS_REQUIRED,
|
||||||
|
KEY_VFS_DIR_REQUIRED,
|
||||||
|
KEY_VFS_SELECT_REQUIRED,
|
||||||
|
KEY_VFS_TERMIOS_REQUIRED,
|
||||||
VARIANT_ESP32,
|
VARIANT_ESP32,
|
||||||
VARIANTS,
|
VARIANTS,
|
||||||
NetworkSdkconfigData,
|
NetworkSdkconfigData,
|
||||||
_ota_downgrade_protection_errors,
|
_ota_downgrade_protection_errors,
|
||||||
_reconcile_network_sdkconfig,
|
_reconcile_network_sdkconfig,
|
||||||
|
_reconcile_vfs_fatfs_sdkconfig,
|
||||||
)
|
)
|
||||||
from esphome.components.esp32.const import (
|
from esphome.components.esp32.const import (
|
||||||
KEY_ESP32,
|
KEY_ESP32,
|
||||||
@@ -614,6 +619,160 @@ def test_reconcile_network_sdkconfig(
|
|||||||
assert CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS] == expected
|
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(
|
def test_network_wifi_only_reconciles_end_to_end(
|
||||||
generate_main: Callable[[str | Path], str],
|
generate_main: Callable[[str | Path], str],
|
||||||
component_config_path: Callable[[str], Path],
|
component_config_path: Callable[[str], Path],
|
||||||
|
|||||||
Reference in New Issue
Block a user