1
0
mirror of https://github.com/esphome/esphome.git synced 2025-09-22 21:22:22 +01:00

Merge remote-tracking branch 'upstream/dev' into integration

This commit is contained in:
J. Nick Koston
2025-09-17 17:25:21 -05:00
35 changed files with 534 additions and 803 deletions

View File

@@ -219,7 +219,7 @@ def has_mqtt_logging() -> bool:
if CONF_TOPIC not in log_topic: if CONF_TOPIC not in log_topic:
return False return False
return log_topic[CONF_LEVEL] != "NONE" return log_topic.get(CONF_LEVEL, None) != "NONE"
def has_mqtt() -> bool: def has_mqtt() -> bool:

View File

@@ -36,7 +36,6 @@ from esphome.const import (
__version__, __version__,
) )
from esphome.core import CORE, HexInt, TimePeriod from esphome.core import CORE, HexInt, TimePeriod
from esphome.cpp_generator import RawExpression
import esphome.final_validate as fv import esphome.final_validate as fv
from esphome.helpers import copy_file_if_changed, mkdir_p, write_file_if_changed from esphome.helpers import copy_file_if_changed, mkdir_p, write_file_if_changed
from esphome.types import ConfigType from esphome.types import ConfigType
@@ -157,8 +156,6 @@ def set_core_data(config):
conf = config[CONF_FRAMEWORK] conf = config[CONF_FRAMEWORK]
if conf[CONF_TYPE] == FRAMEWORK_ESP_IDF: if conf[CONF_TYPE] == FRAMEWORK_ESP_IDF:
CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "esp-idf" CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "esp-idf"
CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS] = {}
CORE.data[KEY_ESP32][KEY_COMPONENTS] = {}
elif conf[CONF_TYPE] == FRAMEWORK_ARDUINO: elif conf[CONF_TYPE] == FRAMEWORK_ARDUINO:
CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "arduino" CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "arduino"
if variant not in ARDUINO_ALLOWED_VARIANTS: if variant not in ARDUINO_ALLOWED_VARIANTS:
@@ -166,6 +163,8 @@ def set_core_data(config):
f"ESPHome does not support using the Arduino framework for the {variant}. Please use the ESP-IDF framework instead.", f"ESPHome does not support using the Arduino framework for the {variant}. Please use the ESP-IDF framework instead.",
path=[CONF_FRAMEWORK, CONF_TYPE], path=[CONF_FRAMEWORK, CONF_TYPE],
) )
CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS] = {}
CORE.data[KEY_ESP32][KEY_COMPONENTS] = {}
CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = cv.Version.parse( CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = cv.Version.parse(
config[CONF_FRAMEWORK][CONF_VERSION] config[CONF_FRAMEWORK][CONF_VERSION]
) )
@@ -236,8 +235,6 @@ SdkconfigValueType = bool | int | HexInt | str | RawSdkconfigValue
def add_idf_sdkconfig_option(name: str, value: SdkconfigValueType): def add_idf_sdkconfig_option(name: str, value: SdkconfigValueType):
"""Set an esp-idf sdkconfig value.""" """Set an esp-idf sdkconfig value."""
if not CORE.using_esp_idf:
raise ValueError("Not an esp-idf project")
CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS][name] = value CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS][name] = value
@@ -252,8 +249,6 @@ def add_idf_component(
submodules: list[str] | None = None, submodules: list[str] | None = None,
): ):
"""Add an esp-idf component to the project.""" """Add an esp-idf component to the project."""
if not CORE.using_esp_idf:
raise ValueError("Not an esp-idf project")
if not repo and not ref and not path: if not repo and not ref and not path:
raise ValueError("Requires at least one of repo, ref or path") raise ValueError("Requires at least one of repo, ref or path")
if refresh or submodules or components: if refresh or submodules or components:
@@ -367,47 +362,49 @@ SUPPORTED_PIOARDUINO_ESP_IDF_5X = [
] ]
def _arduino_check_versions(value): def _check_versions(value):
value = value.copy() value = value.copy()
lookups = { if value[CONF_TYPE] == FRAMEWORK_ARDUINO:
"dev": (cv.Version(3, 2, 1), "https://github.com/espressif/arduino-esp32.git"), lookups = {
"latest": (cv.Version(3, 2, 1), None), "dev": (
"recommended": (RECOMMENDED_ARDUINO_FRAMEWORK_VERSION, None), cv.Version(3, 2, 1),
} "https://github.com/espressif/arduino-esp32.git",
),
"latest": (cv.Version(3, 2, 1), None),
"recommended": (RECOMMENDED_ARDUINO_FRAMEWORK_VERSION, None),
}
if value[CONF_VERSION] in lookups: if value[CONF_VERSION] in lookups:
if CONF_SOURCE in value: if CONF_SOURCE in value:
raise cv.Invalid( raise cv.Invalid(
"Framework version needs to be explicitly specified when custom source is used." "Framework version needs to be explicitly specified when custom source is used."
) )
version, source = lookups[value[CONF_VERSION]] version, source = lookups[value[CONF_VERSION]]
else: else:
version = cv.Version.parse(cv.version_number(value[CONF_VERSION])) version = cv.Version.parse(cv.version_number(value[CONF_VERSION]))
source = value.get(CONF_SOURCE, None) source = value.get(CONF_SOURCE, None)
value[CONF_VERSION] = str(version) value[CONF_VERSION] = str(version)
value[CONF_SOURCE] = source or _format_framework_arduino_version(version) value[CONF_SOURCE] = source or _format_framework_arduino_version(version)
value[CONF_PLATFORM_VERSION] = value.get( value[CONF_PLATFORM_VERSION] = value.get(
CONF_PLATFORM_VERSION, _parse_platform_version(str(ARDUINO_PLATFORM_VERSION)) CONF_PLATFORM_VERSION,
) _parse_platform_version(str(ARDUINO_PLATFORM_VERSION)),
if value[CONF_SOURCE].startswith("http"):
# prefix is necessary or platformio will complain with a cryptic error
value[CONF_SOURCE] = f"framework-arduinoespressif32@{value[CONF_SOURCE]}"
if version != RECOMMENDED_ARDUINO_FRAMEWORK_VERSION:
_LOGGER.warning(
"The selected Arduino framework version is not the recommended one. "
"If there are connectivity or build issues please remove the manual version."
) )
return value if value[CONF_SOURCE].startswith("http"):
# prefix is necessary or platformio will complain with a cryptic error
value[CONF_SOURCE] = f"framework-arduinoespressif32@{value[CONF_SOURCE]}"
if version != RECOMMENDED_ARDUINO_FRAMEWORK_VERSION:
_LOGGER.warning(
"The selected Arduino framework version is not the recommended one. "
"If there are connectivity or build issues please remove the manual version."
)
return value
def _esp_idf_check_versions(value):
value = value.copy()
lookups = { lookups = {
"dev": (cv.Version(5, 4, 2), "https://github.com/espressif/esp-idf.git"), "dev": (cv.Version(5, 4, 2), "https://github.com/espressif/esp-idf.git"),
"latest": (cv.Version(5, 2, 2), None), "latest": (cv.Version(5, 2, 2), None),
@@ -589,24 +586,6 @@ def final_validate(config):
return config return config
ARDUINO_FRAMEWORK_SCHEMA = cv.All(
cv.Schema(
{
cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict,
cv.Optional(CONF_SOURCE): cv.string_strict,
cv.Optional(CONF_PLATFORM_VERSION): _parse_platform_version,
cv.Optional(CONF_ADVANCED, default={}): cv.Schema(
{
cv.Optional(
CONF_IGNORE_EFUSE_CUSTOM_MAC, default=False
): cv.boolean,
}
),
}
),
_arduino_check_versions,
)
CONF_SDKCONFIG_OPTIONS = "sdkconfig_options" CONF_SDKCONFIG_OPTIONS = "sdkconfig_options"
CONF_ENABLE_LWIP_DHCP_SERVER = "enable_lwip_dhcp_server" CONF_ENABLE_LWIP_DHCP_SERVER = "enable_lwip_dhcp_server"
CONF_ENABLE_LWIP_MDNS_QUERIES = "enable_lwip_mdns_queries" CONF_ENABLE_LWIP_MDNS_QUERIES = "enable_lwip_mdns_queries"
@@ -625,9 +604,14 @@ def _validate_idf_component(config: ConfigType) -> ConfigType:
return config return config
ESP_IDF_FRAMEWORK_SCHEMA = cv.All( FRAMEWORK_ESP_IDF = "esp-idf"
FRAMEWORK_ARDUINO = "arduino"
FRAMEWORK_SCHEMA = cv.All(
cv.Schema( cv.Schema(
{ {
cv.Optional(CONF_TYPE, default=FRAMEWORK_ARDUINO): cv.one_of(
FRAMEWORK_ESP_IDF, FRAMEWORK_ARDUINO
),
cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict, cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict,
cv.Optional(CONF_RELEASE): cv.string_strict, cv.Optional(CONF_RELEASE): cv.string_strict,
cv.Optional(CONF_SOURCE): cv.string_strict, cv.Optional(CONF_SOURCE): cv.string_strict,
@@ -691,7 +675,7 @@ ESP_IDF_FRAMEWORK_SCHEMA = cv.All(
), ),
} }
), ),
_esp_idf_check_versions, _check_versions,
) )
@@ -758,32 +742,18 @@ def _set_default_framework(config):
config = config.copy() config = config.copy()
variant = config[CONF_VARIANT] variant = config[CONF_VARIANT]
config[CONF_FRAMEWORK] = FRAMEWORK_SCHEMA({})
if variant in ARDUINO_ALLOWED_VARIANTS: if variant in ARDUINO_ALLOWED_VARIANTS:
config[CONF_FRAMEWORK] = ARDUINO_FRAMEWORK_SCHEMA({})
config[CONF_FRAMEWORK][CONF_TYPE] = FRAMEWORK_ARDUINO config[CONF_FRAMEWORK][CONF_TYPE] = FRAMEWORK_ARDUINO
# Show the migration message
_show_framework_migration_message( _show_framework_migration_message(
config.get(CONF_NAME, "This device"), variant config.get(CONF_NAME, "This device"), variant
) )
else: else:
config[CONF_FRAMEWORK] = ESP_IDF_FRAMEWORK_SCHEMA({})
config[CONF_FRAMEWORK][CONF_TYPE] = FRAMEWORK_ESP_IDF config[CONF_FRAMEWORK][CONF_TYPE] = FRAMEWORK_ESP_IDF
return config return config
FRAMEWORK_ESP_IDF = "esp-idf"
FRAMEWORK_ARDUINO = "arduino"
FRAMEWORK_SCHEMA = cv.typed_schema(
{
FRAMEWORK_ESP_IDF: ESP_IDF_FRAMEWORK_SCHEMA,
FRAMEWORK_ARDUINO: ARDUINO_FRAMEWORK_SCHEMA,
},
lower=True,
space="-",
)
FLASH_SIZES = [ FLASH_SIZES = [
"2MB", "2MB",
"4MB", "4MB",
@@ -851,139 +821,145 @@ async def to_code(config):
os.path.join(os.path.dirname(__file__), "post_build.py.script"), os.path.join(os.path.dirname(__file__), "post_build.py.script"),
) )
freq = config[CONF_CPU_FREQUENCY][:-3]
if conf[CONF_TYPE] == FRAMEWORK_ESP_IDF: if conf[CONF_TYPE] == FRAMEWORK_ESP_IDF:
cg.add_platformio_option("framework", "espidf") cg.add_platformio_option("framework", "espidf")
cg.add_build_flag("-DUSE_ESP_IDF") cg.add_build_flag("-DUSE_ESP_IDF")
cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ESP_IDF") cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ESP_IDF")
cg.add_build_flag("-Wno-nonnull-compare") else:
cg.add_platformio_option("framework", "arduino, espidf")
cg.add_platformio_option("platform_packages", [conf[CONF_SOURCE]])
add_idf_sdkconfig_option(f"CONFIG_IDF_TARGET_{variant}", True)
add_idf_sdkconfig_option(
f"CONFIG_ESPTOOLPY_FLASHSIZE_{config[CONF_FLASH_SIZE]}", True
)
add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_SINGLE_APP", False)
add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_CUSTOM", True)
add_idf_sdkconfig_option(
"CONFIG_PARTITION_TABLE_CUSTOM_FILENAME", "partitions.csv"
)
# Increase freertos tick speed from 100Hz to 1kHz so that delay() resolution is 1ms
add_idf_sdkconfig_option("CONFIG_FREERTOS_HZ", 1000)
# Setup watchdog
add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT", True)
add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT_PANIC", True)
add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0", False)
add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1", False)
# Disable dynamic log level control to save memory
add_idf_sdkconfig_option("CONFIG_LOG_DYNAMIC_LEVEL_CONTROL", False)
# Set default CPU frequency
add_idf_sdkconfig_option(f"CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_{freq}", True)
# Apply LWIP optimization settings
advanced = conf[CONF_ADVANCED]
# DHCP server: only disable if explicitly set to false
# WiFi component handles its own optimization when AP mode is not used
if (
CONF_ENABLE_LWIP_DHCP_SERVER in advanced
and not advanced[CONF_ENABLE_LWIP_DHCP_SERVER]
):
add_idf_sdkconfig_option("CONFIG_LWIP_DHCPS", False)
if not advanced.get(CONF_ENABLE_LWIP_MDNS_QUERIES, True):
add_idf_sdkconfig_option("CONFIG_LWIP_DNS_SUPPORT_MDNS_QUERIES", False)
if not advanced.get(CONF_ENABLE_LWIP_BRIDGE_INTERFACE, False):
add_idf_sdkconfig_option("CONFIG_LWIP_BRIDGEIF_MAX_PORTS", 0)
if advanced.get(CONF_EXECUTE_FROM_PSRAM, False):
add_idf_sdkconfig_option("CONFIG_SPIRAM_FETCH_INSTRUCTIONS", True)
add_idf_sdkconfig_option("CONFIG_SPIRAM_RODATA", True)
# Apply LWIP core locking for better socket performance
# This is already enabled by default in Arduino framework, where it provides
# significant performance benefits. Our benchmarks show socket operations are
# 24-200% faster with core locking enabled:
# - select() on 4 sockets: ~190μs (Arduino/core locking) vs ~235μs (ESP-IDF default)
# - Up to 200% slower under load when all operations queue through tcpip_thread
# Enabling this makes ESP-IDF socket performance match Arduino framework.
if advanced.get(CONF_ENABLE_LWIP_TCPIP_CORE_LOCKING, True):
add_idf_sdkconfig_option("CONFIG_LWIP_TCPIP_CORE_LOCKING", True)
if advanced.get(CONF_ENABLE_LWIP_CHECK_THREAD_SAFETY, True):
add_idf_sdkconfig_option("CONFIG_LWIP_CHECK_THREAD_SAFETY", True)
cg.add_platformio_option("board_build.partitions", "partitions.csv")
if CONF_PARTITIONS in config:
add_extra_build_file(
"partitions.csv", CORE.relative_config_path(config[CONF_PARTITIONS])
)
if assertion_level := advanced.get(CONF_ASSERTION_LEVEL):
for key, flag in ASSERTION_LEVELS.items():
add_idf_sdkconfig_option(flag, assertion_level == key)
add_idf_sdkconfig_option("CONFIG_COMPILER_OPTIMIZATION_DEFAULT", False)
compiler_optimization = advanced.get(CONF_COMPILER_OPTIMIZATION)
for key, flag in COMPILER_OPTIMIZATIONS.items():
add_idf_sdkconfig_option(flag, compiler_optimization == key)
add_idf_sdkconfig_option(
"CONFIG_LWIP_ESP_LWIP_ASSERT",
conf[CONF_ADVANCED][CONF_ENABLE_LWIP_ASSERT],
)
if advanced.get(CONF_IGNORE_EFUSE_MAC_CRC):
add_idf_sdkconfig_option("CONFIG_ESP_MAC_IGNORE_MAC_CRC_ERROR", True)
add_idf_sdkconfig_option(
"CONFIG_ESP_PHY_CALIBRATION_AND_DATA_STORAGE", False
)
if advanced.get(CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES):
_LOGGER.warning(
"Using experimental features in ESP-IDF may result in unexpected failures."
)
add_idf_sdkconfig_option("CONFIG_IDF_EXPERIMENTAL_FEATURES", True)
cg.add_define(
"USE_ESP_IDF_VERSION_CODE",
cg.RawExpression(
f"VERSION_CODE({framework_ver.major}, {framework_ver.minor}, {framework_ver.patch})"
),
)
add_idf_sdkconfig_option(
f"CONFIG_LOG_DEFAULT_LEVEL_{conf[CONF_LOG_LEVEL]}", True
)
for name, value in conf[CONF_SDKCONFIG_OPTIONS].items():
add_idf_sdkconfig_option(name, RawSdkconfigValue(value))
for component in conf[CONF_COMPONENTS]:
add_idf_component(
name=component[CONF_NAME],
repo=component.get(CONF_SOURCE),
ref=component.get(CONF_REF),
path=component.get(CONF_PATH),
)
elif conf[CONF_TYPE] == FRAMEWORK_ARDUINO:
cg.add_platformio_option("framework", "arduino")
cg.add_build_flag("-DUSE_ARDUINO") cg.add_build_flag("-DUSE_ARDUINO")
cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ARDUINO") cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ARDUINO")
cg.add_platformio_option("platform_packages", [conf[CONF_SOURCE]]) cg.add_platformio_option(
"board_build.embed_txtfiles",
if CONF_PARTITIONS in config: [
cg.add_platformio_option("board_build.partitions", config[CONF_PARTITIONS]) "managed_components/espressif__esp_insights/server_certs/https_server.crt",
else: "managed_components/espressif__esp_rainmaker/server_certs/rmaker_mqtt_server.crt",
cg.add_platformio_option("board_build.partitions", "partitions.csv") "managed_components/espressif__esp_rainmaker/server_certs/rmaker_claim_service_server.crt",
"managed_components/espressif__esp_rainmaker/server_certs/rmaker_ota_server.crt",
],
)
cg.add_define( cg.add_define(
"USE_ARDUINO_VERSION_CODE", "USE_ARDUINO_VERSION_CODE",
cg.RawExpression( cg.RawExpression(
f"VERSION_CODE({framework_ver.major}, {framework_ver.minor}, {framework_ver.patch})" f"VERSION_CODE({framework_ver.major}, {framework_ver.minor}, {framework_ver.patch})"
), ),
) )
cg.add(RawExpression(f"setCpuFrequencyMhz({freq})")) add_idf_sdkconfig_option("CONFIG_AUTOSTART_ARDUINO", True)
add_idf_sdkconfig_option("CONFIG_MBEDTLS_PSK_MODES", True)
add_idf_sdkconfig_option("CONFIG_MBEDTLS_CERTIFICATE_BUNDLE", True)
cg.add_build_flag("-Wno-nonnull-compare")
cg.add_platformio_option("platform_packages", [conf[CONF_SOURCE]])
add_idf_sdkconfig_option(f"CONFIG_IDF_TARGET_{variant}", True)
add_idf_sdkconfig_option(
f"CONFIG_ESPTOOLPY_FLASHSIZE_{config[CONF_FLASH_SIZE]}", True
)
add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_SINGLE_APP", False)
add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_CUSTOM", True)
add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_CUSTOM_FILENAME", "partitions.csv")
# Increase freertos tick speed from 100Hz to 1kHz so that delay() resolution is 1ms
add_idf_sdkconfig_option("CONFIG_FREERTOS_HZ", 1000)
# Setup watchdog
add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT", True)
add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT_PANIC", True)
add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0", False)
add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1", False)
# Disable dynamic log level control to save memory
add_idf_sdkconfig_option("CONFIG_LOG_DYNAMIC_LEVEL_CONTROL", False)
# Set default CPU frequency
add_idf_sdkconfig_option(
f"CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_{config[CONF_CPU_FREQUENCY][:-3]}", True
)
# Apply LWIP optimization settings
advanced = conf[CONF_ADVANCED]
# DHCP server: only disable if explicitly set to false
# WiFi component handles its own optimization when AP mode is not used
# When using Arduino with Ethernet, DHCP server functions must be available
# for the Network library to compile, even if not actively used
if (
CONF_ENABLE_LWIP_DHCP_SERVER in advanced
and not advanced[CONF_ENABLE_LWIP_DHCP_SERVER]
and not (
conf[CONF_TYPE] == FRAMEWORK_ARDUINO
and "ethernet" in CORE.loaded_integrations
)
):
add_idf_sdkconfig_option("CONFIG_LWIP_DHCPS", False)
if not advanced.get(CONF_ENABLE_LWIP_MDNS_QUERIES, True):
add_idf_sdkconfig_option("CONFIG_LWIP_DNS_SUPPORT_MDNS_QUERIES", False)
if not advanced.get(CONF_ENABLE_LWIP_BRIDGE_INTERFACE, False):
add_idf_sdkconfig_option("CONFIG_LWIP_BRIDGEIF_MAX_PORTS", 0)
if advanced.get(CONF_EXECUTE_FROM_PSRAM, False):
add_idf_sdkconfig_option("CONFIG_SPIRAM_FETCH_INSTRUCTIONS", True)
add_idf_sdkconfig_option("CONFIG_SPIRAM_RODATA", True)
# Apply LWIP core locking for better socket performance
# This is already enabled by default in Arduino framework, where it provides
# significant performance benefits. Our benchmarks show socket operations are
# 24-200% faster with core locking enabled:
# - select() on 4 sockets: ~190μs (Arduino/core locking) vs ~235μs (ESP-IDF default)
# - Up to 200% slower under load when all operations queue through tcpip_thread
# Enabling this makes ESP-IDF socket performance match Arduino framework.
if advanced.get(CONF_ENABLE_LWIP_TCPIP_CORE_LOCKING, True):
add_idf_sdkconfig_option("CONFIG_LWIP_TCPIP_CORE_LOCKING", True)
if advanced.get(CONF_ENABLE_LWIP_CHECK_THREAD_SAFETY, True):
add_idf_sdkconfig_option("CONFIG_LWIP_CHECK_THREAD_SAFETY", True)
cg.add_platformio_option("board_build.partitions", "partitions.csv")
if CONF_PARTITIONS in config:
add_extra_build_file(
"partitions.csv", CORE.relative_config_path(config[CONF_PARTITIONS])
)
if assertion_level := advanced.get(CONF_ASSERTION_LEVEL):
for key, flag in ASSERTION_LEVELS.items():
add_idf_sdkconfig_option(flag, assertion_level == key)
add_idf_sdkconfig_option("CONFIG_COMPILER_OPTIMIZATION_DEFAULT", False)
compiler_optimization = advanced.get(CONF_COMPILER_OPTIMIZATION)
for key, flag in COMPILER_OPTIMIZATIONS.items():
add_idf_sdkconfig_option(flag, compiler_optimization == key)
add_idf_sdkconfig_option(
"CONFIG_LWIP_ESP_LWIP_ASSERT",
conf[CONF_ADVANCED][CONF_ENABLE_LWIP_ASSERT],
)
if advanced.get(CONF_IGNORE_EFUSE_MAC_CRC):
add_idf_sdkconfig_option("CONFIG_ESP_MAC_IGNORE_MAC_CRC_ERROR", True)
add_idf_sdkconfig_option("CONFIG_ESP_PHY_CALIBRATION_AND_DATA_STORAGE", False)
if advanced.get(CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES):
_LOGGER.warning(
"Using experimental features in ESP-IDF may result in unexpected failures."
)
add_idf_sdkconfig_option("CONFIG_IDF_EXPERIMENTAL_FEATURES", True)
cg.add_define(
"USE_ESP_IDF_VERSION_CODE",
cg.RawExpression(
f"VERSION_CODE({framework_ver.major}, {framework_ver.minor}, {framework_ver.patch})"
),
)
add_idf_sdkconfig_option(f"CONFIG_LOG_DEFAULT_LEVEL_{conf[CONF_LOG_LEVEL]}", True)
for name, value in conf[CONF_SDKCONFIG_OPTIONS].items():
add_idf_sdkconfig_option(name, RawSdkconfigValue(value))
for component in conf[CONF_COMPONENTS]:
add_idf_component(
name=component[CONF_NAME],
repo=component.get(CONF_SOURCE),
ref=component.get(CONF_REF),
path=component.get(CONF_PATH),
)
APP_PARTITION_SIZES = { APP_PARTITION_SIZES = {
@@ -1057,6 +1033,7 @@ def _write_sdkconfig():
) )
+ "\n" + "\n"
) )
if write_file_if_changed(internal_path, contents): if write_file_if_changed(internal_path, contents):
# internal changed, update real one # internal changed, update real one
write_file_if_changed(sdk_path, contents) write_file_if_changed(sdk_path, contents)
@@ -1088,34 +1065,32 @@ def _write_idf_component_yml():
# Called by writer.py # Called by writer.py
def copy_files(): def copy_files():
if ( _write_sdkconfig()
CORE.using_arduino _write_idf_component_yml()
and "partitions.csv" not in CORE.data[KEY_ESP32][KEY_EXTRA_BUILD_FILES]
): if "partitions.csv" not in CORE.data[KEY_ESP32][KEY_EXTRA_BUILD_FILES]:
write_file_if_changed( if CORE.using_arduino:
CORE.relative_build_path("partitions.csv"), write_file_if_changed(
get_arduino_partition_csv( CORE.relative_build_path("partitions.csv"),
CORE.platformio_options.get("board_upload.flash_size") get_arduino_partition_csv(
), CORE.platformio_options.get("board_upload.flash_size")
) ),
if CORE.using_esp_idf: )
_write_sdkconfig() else:
_write_idf_component_yml()
if "partitions.csv" not in CORE.data[KEY_ESP32][KEY_EXTRA_BUILD_FILES]:
write_file_if_changed( write_file_if_changed(
CORE.relative_build_path("partitions.csv"), CORE.relative_build_path("partitions.csv"),
get_idf_partition_csv( get_idf_partition_csv(
CORE.platformio_options.get("board_upload.flash_size") CORE.platformio_options.get("board_upload.flash_size")
), ),
) )
# IDF build scripts look for version string to put in the build. # IDF build scripts look for version string to put in the build.
# However, if the build path does not have an initialized git repo, # However, if the build path does not have an initialized git repo,
# and no version.txt file exists, the CMake script fails for some setups. # and no version.txt file exists, the CMake script fails for some setups.
# Fix by manually pasting a version.txt file, containing the ESPHome version # Fix by manually pasting a version.txt file, containing the ESPHome version
write_file_if_changed( write_file_if_changed(
CORE.relative_build_path("version.txt"), CORE.relative_build_path("version.txt"),
__version__, __version__,
) )
for file in CORE.data[KEY_ESP32][KEY_EXTRA_BUILD_FILES].values(): for file in CORE.data[KEY_ESP32][KEY_EXTRA_BUILD_FILES].values():
if file[KEY_PATH].startswith("http"): if file[KEY_PATH].startswith("http"):

View File

@@ -12,7 +12,7 @@ from esphome.const import (
CONF_NAME, CONF_NAME,
CONF_NAME_ADD_MAC_SUFFIX, CONF_NAME_ADD_MAC_SUFFIX,
) )
from esphome.core import CORE, TimePeriod from esphome.core import TimePeriod
import esphome.final_validate as fv import esphome.final_validate as fv
DEPENDENCIES = ["esp32"] DEPENDENCIES = ["esp32"]
@@ -261,43 +261,40 @@ async def to_code(config):
cg.add(var.set_name(name)) cg.add(var.set_name(name))
await cg.register_component(var, config) await cg.register_component(var, config)
if CORE.using_esp_idf: add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True)
add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True) add_idf_sdkconfig_option("CONFIG_BT_BLE_42_FEATURES_SUPPORTED", True)
add_idf_sdkconfig_option("CONFIG_BT_BLE_42_FEATURES_SUPPORTED", True)
# Register the core BLE loggers that are always needed # Register the core BLE loggers that are always needed
register_bt_logger(BTLoggers.GAP, BTLoggers.BTM, BTLoggers.HCI) register_bt_logger(BTLoggers.GAP, BTLoggers.BTM, BTLoggers.HCI)
# Apply logger settings if log disabling is enabled # Apply logger settings if log disabling is enabled
if config.get(CONF_DISABLE_BT_LOGS, False): if config.get(CONF_DISABLE_BT_LOGS, False):
# Disable all Bluetooth loggers that are not required # Disable all Bluetooth loggers that are not required
for logger in BTLoggers: for logger in BTLoggers:
if logger not in _required_loggers: if logger not in _required_loggers:
add_idf_sdkconfig_option(f"{logger.value}_NONE", True) add_idf_sdkconfig_option(f"{logger.value}_NONE", True)
# Set BLE connection establishment timeout to match aioesphomeapi/bleak-retry-connector # Set BLE connection establishment timeout to match aioesphomeapi/bleak-retry-connector
# Default is 20 seconds instead of ESP-IDF's 30 seconds. Because there is no way to # Default is 20 seconds instead of ESP-IDF's 30 seconds. Because there is no way to
# cancel a BLE connection in progress, when aioesphomeapi times out at 20 seconds, # cancel a BLE connection in progress, when aioesphomeapi times out at 20 seconds,
# the connection slot remains occupied for the remaining time, preventing new connection # the connection slot remains occupied for the remaining time, preventing new connection
# attempts and wasting valuable connection slots. # attempts and wasting valuable connection slots.
if CONF_CONNECTION_TIMEOUT in config: if CONF_CONNECTION_TIMEOUT in config:
timeout_seconds = int(config[CONF_CONNECTION_TIMEOUT].total_seconds) timeout_seconds = int(config[CONF_CONNECTION_TIMEOUT].total_seconds)
add_idf_sdkconfig_option( add_idf_sdkconfig_option("CONFIG_BT_BLE_ESTAB_LINK_CONN_TOUT", timeout_seconds)
"CONFIG_BT_BLE_ESTAB_LINK_CONN_TOUT", timeout_seconds # Increase GATT client connection retry count for problematic devices
) # Default in ESP-IDF is 3, we increase to 10 for better reliability with
# Increase GATT client connection retry count for problematic devices # low-power/timing-sensitive devices
# Default in ESP-IDF is 3, we increase to 10 for better reliability with add_idf_sdkconfig_option("CONFIG_BT_GATTC_CONNECT_RETRY_COUNT", 10)
# low-power/timing-sensitive devices
add_idf_sdkconfig_option("CONFIG_BT_GATTC_CONNECT_RETRY_COUNT", 10)
# Set the maximum number of notification registrations # Set the maximum number of notification registrations
# This controls how many BLE characteristics can have notifications enabled # This controls how many BLE characteristics can have notifications enabled
# across all connections for a single GATT client interface # across all connections for a single GATT client interface
# https://github.com/esphome/issues/issues/6808 # https://github.com/esphome/issues/issues/6808
if CONF_MAX_NOTIFICATIONS in config: if CONF_MAX_NOTIFICATIONS in config:
add_idf_sdkconfig_option( add_idf_sdkconfig_option(
"CONFIG_BT_GATTC_NOTIF_REG_MAX", config[CONF_MAX_NOTIFICATIONS] "CONFIG_BT_GATTC_NOTIF_REG_MAX", config[CONF_MAX_NOTIFICATIONS]
) )
cg.add_define("USE_ESP32_BLE") cg.add_define("USE_ESP32_BLE")

View File

@@ -4,7 +4,7 @@ from esphome.components.esp32 import add_idf_sdkconfig_option
from esphome.components.esp32_ble import CONF_BLE_ID from esphome.components.esp32_ble import CONF_BLE_ID
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_TX_POWER, CONF_TYPE, CONF_UUID from esphome.const import CONF_ID, CONF_TX_POWER, CONF_TYPE, CONF_UUID
from esphome.core import CORE, TimePeriod from esphome.core import TimePeriod
AUTO_LOAD = ["esp32_ble"] AUTO_LOAD = ["esp32_ble"]
DEPENDENCIES = ["esp32"] DEPENDENCIES = ["esp32"]
@@ -86,6 +86,5 @@ async def to_code(config):
cg.add_define("USE_ESP32_BLE_ADVERTISING") cg.add_define("USE_ESP32_BLE_ADVERTISING")
if CORE.using_esp_idf: add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True)
add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True) add_idf_sdkconfig_option("CONFIG_BT_BLE_42_FEATURES_SUPPORTED", True)
add_idf_sdkconfig_option("CONFIG_BT_BLE_42_FEATURES_SUPPORTED", True)

View File

@@ -573,8 +573,7 @@ async def to_code(config):
) )
cg.add_define("USE_ESP32_BLE_SERVER") cg.add_define("USE_ESP32_BLE_SERVER")
cg.add_define("USE_ESP32_BLE_ADVERTISING") cg.add_define("USE_ESP32_BLE_ADVERTISING")
if CORE.using_esp_idf: add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True)
add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True)
@automation.register_action( @automation.register_action(

View File

@@ -342,19 +342,18 @@ async def to_code(config):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf) await automation.build_automation(trigger, [], conf)
if CORE.using_esp_idf: add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True)
add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True) if config.get(CONF_SOFTWARE_COEXISTENCE):
if config.get(CONF_SOFTWARE_COEXISTENCE): add_idf_sdkconfig_option("CONFIG_SW_COEXIST_ENABLE", True)
add_idf_sdkconfig_option("CONFIG_SW_COEXIST_ENABLE", True) # https://github.com/espressif/esp-idf/issues/4101
# https://github.com/espressif/esp-idf/issues/4101 # https://github.com/espressif/esp-idf/issues/2503
# https://github.com/espressif/esp-idf/issues/2503 # Match arduino CONFIG_BTU_TASK_STACK_SIZE
# Match arduino CONFIG_BTU_TASK_STACK_SIZE # https://github.com/espressif/arduino-esp32/blob/fd72cf46ad6fc1a6de99c1d83ba8eba17d80a4ee/tools/sdk/esp32/sdkconfig#L1866
# https://github.com/espressif/arduino-esp32/blob/fd72cf46ad6fc1a6de99c1d83ba8eba17d80a4ee/tools/sdk/esp32/sdkconfig#L1866 add_idf_sdkconfig_option("CONFIG_BT_BTU_TASK_STACK_SIZE", 8192)
add_idf_sdkconfig_option("CONFIG_BT_BTU_TASK_STACK_SIZE", 8192) add_idf_sdkconfig_option("CONFIG_BT_ACL_CONNECTIONS", 9)
add_idf_sdkconfig_option("CONFIG_BT_ACL_CONNECTIONS", 9) add_idf_sdkconfig_option(
add_idf_sdkconfig_option( "CONFIG_BTDM_CTRL_BLE_MAX_CONN", config[CONF_MAX_CONNECTIONS]
"CONFIG_BTDM_CTRL_BLE_MAX_CONN", config[CONF_MAX_CONNECTIONS] )
)
cg.add_define("USE_OTA_STATE_CALLBACK") # To be notified when an OTA update starts cg.add_define("USE_OTA_STATE_CALLBACK") # To be notified when an OTA update starts
cg.add_define("USE_ESP32_BLE_CLIENT") cg.add_define("USE_ESP32_BLE_CLIENT")

View File

@@ -21,7 +21,6 @@ from esphome.const import (
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_VSYNC_PIN, CONF_VSYNC_PIN,
) )
from esphome.core import CORE
from esphome.core.entity_helpers import setup_entity from esphome.core.entity_helpers import setup_entity
import esphome.final_validate as fv import esphome.final_validate as fv
@@ -344,8 +343,7 @@ async def to_code(config):
cg.add_define("USE_CAMERA") cg.add_define("USE_CAMERA")
if CORE.using_esp_idf: add_idf_component(name="espressif/esp32-camera", ref="2.1.1")
add_idf_component(name="espressif/esp32-camera", ref="2.1.1")
for conf in config.get(CONF_ON_STREAM_START, []): for conf in config.get(CONF_ON_STREAM_START, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)

View File

@@ -322,11 +322,8 @@ async def to_code(config):
cg.add(var.set_clock_speed(config[CONF_CLOCK_SPEED])) cg.add(var.set_clock_speed(config[CONF_CLOCK_SPEED]))
cg.add_define("USE_ETHERNET_SPI") cg.add_define("USE_ETHERNET_SPI")
if CORE.using_esp_idf: add_idf_sdkconfig_option("CONFIG_ETH_USE_SPI_ETHERNET", True)
add_idf_sdkconfig_option("CONFIG_ETH_USE_SPI_ETHERNET", True) add_idf_sdkconfig_option(f"CONFIG_ETH_SPI_ETHERNET_{config[CONF_TYPE]}", True)
add_idf_sdkconfig_option(
f"CONFIG_ETH_SPI_ETHERNET_{config[CONF_TYPE]}", True
)
elif config[CONF_TYPE] == "OPENETH": elif config[CONF_TYPE] == "OPENETH":
cg.add_define("USE_ETHERNET_OPENETH") cg.add_define("USE_ETHERNET_OPENETH")
add_idf_sdkconfig_option("CONFIG_ETH_USE_OPENETH", True) add_idf_sdkconfig_option("CONFIG_ETH_USE_OPENETH", True)
@@ -359,10 +356,9 @@ async def to_code(config):
cg.add_define("USE_ETHERNET") cg.add_define("USE_ETHERNET")
# Disable WiFi when using Ethernet to save memory # Disable WiFi when using Ethernet to save memory
if CORE.using_esp_idf: add_idf_sdkconfig_option("CONFIG_ESP_WIFI_ENABLED", False)
add_idf_sdkconfig_option("CONFIG_ESP_WIFI_ENABLED", False) # Also disable WiFi/BT coexistence since WiFi is disabled
# Also disable WiFi/BT coexistence since WiFi is disabled add_idf_sdkconfig_option("CONFIG_SW_COEXIST_ENABLE", False)
add_idf_sdkconfig_option("CONFIG_SW_COEXIST_ENABLE", False)
if CORE.using_arduino: if CORE.using_arduino:
cg.add_library("WiFi", None) cg.add_library("WiFi", None)

View File

@@ -262,8 +262,7 @@ async def to_code(config):
cg.add_define("USE_I2S_LEGACY") cg.add_define("USE_I2S_LEGACY")
# Helps avoid callbacks being skipped due to processor load # Helps avoid callbacks being skipped due to processor load
if CORE.using_esp_idf: add_idf_sdkconfig_option("CONFIG_I2S_ISR_IRAM_SAFE", True)
add_idf_sdkconfig_option("CONFIG_I2S_ISR_IRAM_SAFE", True)
cg.add(var.set_lrclk_pin(config[CONF_I2S_LRCLK_PIN])) cg.add(var.set_lrclk_pin(config[CONF_I2S_LRCLK_PIN]))
if CONF_I2S_BCLK_PIN in config: if CONF_I2S_BCLK_PIN in config:

View File

@@ -15,11 +15,10 @@ static const char *const TAG = "improv_serial";
void ImprovSerialComponent::setup() { void ImprovSerialComponent::setup() {
global_improv_serial_component = this; global_improv_serial_component = this;
#ifdef USE_ARDUINO #ifdef USE_ESP32
this->hw_serial_ = logger::global_logger->get_hw_serial();
#endif
#ifdef USE_ESP_IDF
this->uart_num_ = logger::global_logger->get_uart_num(); this->uart_num_ = logger::global_logger->get_uart_num();
#elif defined(USE_ARDUINO)
this->hw_serial_ = logger::global_logger->get_hw_serial();
#endif #endif
if (wifi::global_wifi_component->has_sta()) { if (wifi::global_wifi_component->has_sta()) {
@@ -34,13 +33,7 @@ void ImprovSerialComponent::dump_config() { ESP_LOGCONFIG(TAG, "Improv Serial:")
optional<uint8_t> ImprovSerialComponent::read_byte_() { optional<uint8_t> ImprovSerialComponent::read_byte_() {
optional<uint8_t> byte; optional<uint8_t> byte;
uint8_t data = 0; uint8_t data = 0;
#ifdef USE_ARDUINO #ifdef USE_ESP32
if (this->hw_serial_->available()) {
this->hw_serial_->readBytes(&data, 1);
byte = data;
}
#endif
#ifdef USE_ESP_IDF
switch (logger::global_logger->get_uart()) { switch (logger::global_logger->get_uart()) {
case logger::UART_SELECTION_UART0: case logger::UART_SELECTION_UART0:
case logger::UART_SELECTION_UART1: case logger::UART_SELECTION_UART1:
@@ -76,16 +69,18 @@ optional<uint8_t> ImprovSerialComponent::read_byte_() {
default: default:
break; break;
} }
#elif defined(USE_ARDUINO)
if (this->hw_serial_->available()) {
this->hw_serial_->readBytes(&data, 1);
byte = data;
}
#endif #endif
return byte; return byte;
} }
void ImprovSerialComponent::write_data_(std::vector<uint8_t> &data) { void ImprovSerialComponent::write_data_(std::vector<uint8_t> &data) {
data.push_back('\n'); data.push_back('\n');
#ifdef USE_ARDUINO #ifdef USE_ESP32
this->hw_serial_->write(data.data(), data.size());
#endif
#ifdef USE_ESP_IDF
switch (logger::global_logger->get_uart()) { switch (logger::global_logger->get_uart()) {
case logger::UART_SELECTION_UART0: case logger::UART_SELECTION_UART0:
case logger::UART_SELECTION_UART1: case logger::UART_SELECTION_UART1:
@@ -112,6 +107,8 @@ void ImprovSerialComponent::write_data_(std::vector<uint8_t> &data) {
default: default:
break; break;
} }
#elif defined(USE_ARDUINO)
this->hw_serial_->write(data.data(), data.size());
#endif #endif
} }

View File

@@ -9,10 +9,7 @@
#include <improv.h> #include <improv.h>
#include <vector> #include <vector>
#ifdef USE_ARDUINO #ifdef USE_ESP32
#include <HardwareSerial.h>
#endif
#ifdef USE_ESP_IDF
#include <driver/uart.h> #include <driver/uart.h>
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S3) || \ #if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S3) || \
defined(USE_ESP32_VARIANT_ESP32H2) defined(USE_ESP32_VARIANT_ESP32H2)
@@ -22,6 +19,8 @@
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) #if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
#include <esp_private/usb_console.h> #include <esp_private/usb_console.h>
#endif #endif
#elif defined(USE_ARDUINO)
#include <HardwareSerial.h>
#endif #endif
namespace esphome { namespace esphome {
@@ -60,11 +59,10 @@ class ImprovSerialComponent : public Component, public improv_base::ImprovBase {
optional<uint8_t> read_byte_(); optional<uint8_t> read_byte_();
void write_data_(std::vector<uint8_t> &data); void write_data_(std::vector<uint8_t> &data);
#ifdef USE_ARDUINO #ifdef USE_ESP32
Stream *hw_serial_{nullptr};
#endif
#ifdef USE_ESP_IDF
uart_port_t uart_num_; uart_port_t uart_num_;
#elif defined(USE_ARDUINO)
Stream *hw_serial_{nullptr};
#endif #endif
std::vector<uint8_t> rx_buffer_; std::vector<uint8_t> rx_buffer_;

View File

@@ -117,8 +117,6 @@ UART_SELECTION_LIBRETINY = {
COMPONENT_RTL87XX: [DEFAULT, UART0, UART1, UART2], COMPONENT_RTL87XX: [DEFAULT, UART0, UART1, UART2],
} }
ESP_ARDUINO_UNSUPPORTED_USB_UARTS = [USB_SERIAL_JTAG]
UART_SELECTION_RP2040 = [USB_CDC, UART0, UART1] UART_SELECTION_RP2040 = [USB_CDC, UART0, UART1]
UART_SELECTION_NRF52 = [USB_CDC, UART0] UART_SELECTION_NRF52 = [USB_CDC, UART0]
@@ -153,13 +151,7 @@ is_log_level = cv.one_of(*LOG_LEVELS, upper=True)
def uart_selection(value): def uart_selection(value):
if CORE.is_esp32: if CORE.is_esp32:
if CORE.using_arduino and value.upper() in ESP_ARDUINO_UNSUPPORTED_USB_UARTS:
raise cv.Invalid(f"Arduino framework does not support {value}.")
variant = get_esp32_variant() variant = get_esp32_variant()
if CORE.using_esp_idf and variant == VARIANT_ESP32C3 and value == USB_CDC:
raise cv.Invalid(
f"{value} is not supported for variant {variant} when using ESP-IDF."
)
if variant in UART_SELECTION_ESP32: if variant in UART_SELECTION_ESP32:
return cv.one_of(*UART_SELECTION_ESP32[variant], upper=True)(value) return cv.one_of(*UART_SELECTION_ESP32[variant], upper=True)(value)
if CORE.is_esp8266: if CORE.is_esp8266:
@@ -226,14 +218,11 @@ CONFIG_SCHEMA = cv.All(
esp8266=UART0, esp8266=UART0,
esp32=UART0, esp32=UART0,
esp32_s2=USB_CDC, esp32_s2=USB_CDC,
esp32_s3_arduino=USB_CDC, esp32_s3=USB_SERIAL_JTAG,
esp32_s3_idf=USB_SERIAL_JTAG, esp32_c3=USB_SERIAL_JTAG,
esp32_c3_arduino=USB_CDC, esp32_c5=USB_SERIAL_JTAG,
esp32_c3_idf=USB_SERIAL_JTAG, esp32_c6=USB_SERIAL_JTAG,
esp32_c5_idf=USB_SERIAL_JTAG, esp32_p4=USB_SERIAL_JTAG,
esp32_c6_arduino=USB_CDC,
esp32_c6_idf=USB_SERIAL_JTAG,
esp32_p4_idf=USB_SERIAL_JTAG,
rp2040=USB_CDC, rp2040=USB_CDC,
bk72xx=DEFAULT, bk72xx=DEFAULT,
ln882x=DEFAULT, ln882x=DEFAULT,
@@ -346,15 +335,7 @@ async def to_code(config):
if config.get(CONF_ESP8266_STORE_LOG_STRINGS_IN_FLASH): if config.get(CONF_ESP8266_STORE_LOG_STRINGS_IN_FLASH):
cg.add_build_flag("-DUSE_STORE_LOG_STR_IN_FLASH") cg.add_build_flag("-DUSE_STORE_LOG_STR_IN_FLASH")
if CORE.using_arduino and config[CONF_HARDWARE_UART] == USB_CDC: if CORE.is_esp32:
cg.add_build_flag("-DARDUINO_USB_CDC_ON_BOOT=1")
if CORE.is_esp32 and get_esp32_variant() in (
VARIANT_ESP32C3,
VARIANT_ESP32C6,
):
cg.add_build_flag("-DARDUINO_USB_MODE=1")
if CORE.using_esp_idf:
if config[CONF_HARDWARE_UART] == USB_CDC: if config[CONF_HARDWARE_UART] == USB_CDC:
add_idf_sdkconfig_option("CONFIG_ESP_CONSOLE_USB_CDC", True) add_idf_sdkconfig_option("CONFIG_ESP_CONSOLE_USB_CDC", True)
elif config[CONF_HARDWARE_UART] == USB_SERIAL_JTAG: elif config[CONF_HARDWARE_UART] == USB_SERIAL_JTAG:

View File

@@ -173,24 +173,8 @@ void Logger::init_log_buffer(size_t total_buffer_size) {
} }
#endif #endif
#ifndef USE_ZEPHYR #ifdef USE_ESPHOME_TASK_LOG_BUFFER
#if defined(USE_LOGGER_USB_CDC) || defined(USE_ESP32) void Logger::loop() { this->process_messages_(); }
void Logger::loop() {
#if defined(USE_LOGGER_USB_CDC) && defined(USE_ARDUINO)
if (this->uart_ == UART_SELECTION_USB_CDC) {
static bool opened = false;
if (opened == Serial) {
return;
}
if (false == opened) {
App.schedule_dump_config();
}
opened = !opened;
}
#endif
this->process_messages_();
}
#endif
#endif #endif
void Logger::process_messages_() { void Logger::process_messages_() {

View File

@@ -16,18 +16,18 @@
#endif #endif
#ifdef USE_ARDUINO #ifdef USE_ARDUINO
#if defined(USE_ESP8266) || defined(USE_ESP32) #if defined(USE_ESP8266)
#include <HardwareSerial.h> #include <HardwareSerial.h>
#endif // USE_ESP8266 || USE_ESP32 #endif // USE_ESP8266
#ifdef USE_RP2040 #ifdef USE_RP2040
#include <HardwareSerial.h> #include <HardwareSerial.h>
#include <SerialUSB.h> #include <SerialUSB.h>
#endif // USE_RP2040 #endif // USE_RP2040
#endif // USE_ARDUINO #endif // USE_ARDUINO
#ifdef USE_ESP_IDF #ifdef USE_ESP32
#include <driver/uart.h> #include <driver/uart.h>
#endif // USE_ESP_IDF #endif // USE_ESP32
#ifdef USE_ZEPHYR #ifdef USE_ZEPHYR
#include <zephyr/kernel.h> #include <zephyr/kernel.h>
@@ -110,19 +110,17 @@ class Logger : public Component {
#ifdef USE_ESPHOME_TASK_LOG_BUFFER #ifdef USE_ESPHOME_TASK_LOG_BUFFER
void init_log_buffer(size_t total_buffer_size); void init_log_buffer(size_t total_buffer_size);
#endif #endif
#if defined(USE_LOGGER_USB_CDC) || defined(USE_ESP32) || defined(USE_ZEPHYR) #if defined(USE_ESPHOME_TASK_LOG_BUFFER) || (defined(USE_ZEPHYR) && defined(USE_LOGGER_USB_CDC))
void loop() override; void loop() override;
#endif #endif
/// Manually set the baud rate for serial, set to 0 to disable. /// Manually set the baud rate for serial, set to 0 to disable.
void set_baud_rate(uint32_t baud_rate); void set_baud_rate(uint32_t baud_rate);
uint32_t get_baud_rate() const { return baud_rate_; } uint32_t get_baud_rate() const { return baud_rate_; }
#ifdef USE_ARDUINO #if defined(USE_ARDUINO) && !defined(USE_ESP32)
Stream *get_hw_serial() const { return hw_serial_; } Stream *get_hw_serial() const { return hw_serial_; }
#endif #endif
#ifdef USE_ESP_IDF
uart_port_t get_uart_num() const { return uart_num_; }
#endif
#ifdef USE_ESP32 #ifdef USE_ESP32
uart_port_t get_uart_num() const { return uart_num_; }
void create_pthread_key() { pthread_key_create(&log_recursion_key_, nullptr); } void create_pthread_key() { pthread_key_create(&log_recursion_key_, nullptr); }
#endif #endif
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR) #if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR)
@@ -232,7 +230,7 @@ class Logger : public Component {
// Group 4-byte aligned members first // Group 4-byte aligned members first
uint32_t baud_rate_; uint32_t baud_rate_;
char *tx_buffer_{nullptr}; char *tx_buffer_{nullptr};
#ifdef USE_ARDUINO #if defined(USE_ARDUINO) && !defined(USE_ESP32)
Stream *hw_serial_{nullptr}; Stream *hw_serial_{nullptr};
#endif #endif
#if defined(USE_ZEPHYR) #if defined(USE_ZEPHYR)
@@ -246,9 +244,7 @@ class Logger : public Component {
// - Main task uses a dedicated member variable for efficiency // - Main task uses a dedicated member variable for efficiency
// - Other tasks use pthread TLS with a dynamically created key via pthread_key_create // - Other tasks use pthread TLS with a dynamically created key via pthread_key_create
pthread_key_t log_recursion_key_; // 4 bytes pthread_key_t log_recursion_key_; // 4 bytes
#endif uart_port_t uart_num_; // 4 bytes (enum defaults to int size)
#ifdef USE_ESP_IDF
uart_port_t uart_num_; // 4 bytes (enum defaults to int size)
#endif #endif
// Large objects (internally aligned) // Large objects (internally aligned)
@@ -380,15 +376,7 @@ class Logger : public Component {
// will be processed on the next main loop iteration since: // will be processed on the next main loop iteration since:
// - disable_loop() takes effect immediately // - disable_loop() takes effect immediately
// - enable_loop_soon_any_context() sets a pending flag that's checked at loop start // - enable_loop_soon_any_context() sets a pending flag that's checked at loop start
#if defined(USE_LOGGER_USB_CDC) && defined(USE_ARDUINO)
// Only disable if not using USB CDC (which needs loop for connection detection)
if (this->uart_ != UART_SELECTION_USB_CDC) {
this->disable_loop();
}
#else
// No USB CDC support, always safe to disable
this->disable_loop(); this->disable_loop();
#endif
} }
#endif #endif
}; };

View File

@@ -1,11 +1,8 @@
#ifdef USE_ESP32 #ifdef USE_ESP32
#include "logger.h" #include "logger.h"
#if defined(USE_ESP32_FRAMEWORK_ARDUINO) || defined(USE_ESP_IDF)
#include <esp_log.h> #include <esp_log.h>
#endif // USE_ESP32_FRAMEWORK_ARDUINO || USE_ESP_IDF
#ifdef USE_ESP_IDF
#include <driver/uart.h> #include <driver/uart.h>
#ifdef USE_LOGGER_USB_SERIAL_JTAG #ifdef USE_LOGGER_USB_SERIAL_JTAG
@@ -25,16 +22,12 @@
#include <cstdint> #include <cstdint>
#include <cstdio> #include <cstdio>
#endif // USE_ESP_IDF
#include "esphome/core/log.h" #include "esphome/core/log.h"
namespace esphome::logger { namespace esphome::logger {
static const char *const TAG = "logger"; static const char *const TAG = "logger";
#ifdef USE_ESP_IDF
#ifdef USE_LOGGER_USB_SERIAL_JTAG #ifdef USE_LOGGER_USB_SERIAL_JTAG
static void init_usb_serial_jtag_() { static void init_usb_serial_jtag_() {
setvbuf(stdin, NULL, _IONBF, 0); // Disable buffering on stdin setvbuf(stdin, NULL, _IONBF, 0); // Disable buffering on stdin
@@ -89,42 +82,8 @@ void init_uart(uart_port_t uart_num, uint32_t baud_rate, int tx_buffer_size) {
uart_driver_install(uart_num, uart_buffer_size, uart_buffer_size, 10, nullptr, 0); uart_driver_install(uart_num, uart_buffer_size, uart_buffer_size, 10, nullptr, 0);
} }
#endif // USE_ESP_IDF
void Logger::pre_setup() { void Logger::pre_setup() {
if (this->baud_rate_ > 0) { if (this->baud_rate_ > 0) {
#ifdef USE_ARDUINO
switch (this->uart_) {
case UART_SELECTION_UART0:
#if ARDUINO_USB_CDC_ON_BOOT
this->hw_serial_ = &Serial0;
Serial0.begin(this->baud_rate_);
#else
this->hw_serial_ = &Serial;
Serial.begin(this->baud_rate_);
#endif
break;
case UART_SELECTION_UART1:
this->hw_serial_ = &Serial1;
Serial1.begin(this->baud_rate_);
break;
#ifdef USE_ESP32_VARIANT_ESP32
case UART_SELECTION_UART2:
this->hw_serial_ = &Serial2;
Serial2.begin(this->baud_rate_);
break;
#endif
#ifdef USE_LOGGER_USB_CDC
case UART_SELECTION_USB_CDC:
this->hw_serial_ = &Serial;
Serial.begin(this->baud_rate_);
break;
#endif
}
#endif // USE_ARDUINO
#ifdef USE_ESP_IDF
this->uart_num_ = UART_NUM_0; this->uart_num_ = UART_NUM_0;
switch (this->uart_) { switch (this->uart_) {
case UART_SELECTION_UART0: case UART_SELECTION_UART0:
@@ -151,21 +110,17 @@ void Logger::pre_setup() {
break; break;
#endif #endif
} }
#endif // USE_ESP_IDF
} }
global_logger = this; global_logger = this;
#if defined(USE_ESP_IDF) || defined(USE_ESP32_FRAMEWORK_ARDUINO)
esp_log_set_vprintf(esp_idf_log_vprintf_); esp_log_set_vprintf(esp_idf_log_vprintf_);
if (ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE) { if (ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE) {
esp_log_level_set("*", ESP_LOG_VERBOSE); esp_log_level_set("*", ESP_LOG_VERBOSE);
} }
#endif // USE_ESP_IDF || USE_ESP32_FRAMEWORK_ARDUINO
ESP_LOGI(TAG, "Log initialized"); ESP_LOGI(TAG, "Log initialized");
} }
#ifdef USE_ESP_IDF
void HOT Logger::write_msg_(const char *msg) { void HOT Logger::write_msg_(const char *msg) {
if ( if (
#if defined(USE_LOGGER_USB_CDC) && !defined(USE_LOGGER_USB_SERIAL_JTAG) #if defined(USE_LOGGER_USB_CDC) && !defined(USE_LOGGER_USB_SERIAL_JTAG)
@@ -186,9 +141,6 @@ void HOT Logger::write_msg_(const char *msg) {
uart_write_bytes(this->uart_num_, "\n", 1); uart_write_bytes(this->uart_num_, "\n", 1);
} }
} }
#else
void HOT Logger::write_msg_(const char *msg) { this->hw_serial_->println(msg); }
#endif
const LogString *Logger::get_uart_selection_() { const LogString *Logger::get_uart_selection_() {
switch (this->uart_) { switch (this->uart_) {

View File

@@ -12,8 +12,8 @@ namespace esphome::logger {
static const char *const TAG = "logger"; static const char *const TAG = "logger";
void Logger::loop() {
#ifdef USE_LOGGER_USB_CDC #ifdef USE_LOGGER_USB_CDC
void Logger::loop() {
if (this->uart_ != UART_SELECTION_USB_CDC || nullptr == this->uart_dev_) { if (this->uart_ != UART_SELECTION_USB_CDC || nullptr == this->uart_dev_) {
return; return;
} }
@@ -30,9 +30,8 @@ void Logger::loop() {
App.schedule_dump_config(); App.schedule_dump_config();
} }
opened = !opened; opened = !opened;
#endif
this->process_messages_();
} }
#endif
void Logger::pre_setup() { void Logger::pre_setup() {
if (this->baud_rate_ > 0) { if (this->baud_rate_ > 0) {

View File

@@ -47,9 +47,13 @@ async def to_code(config):
cg.add_define( cg.add_define(
"USE_NETWORK_MIN_IPV6_ADDR_COUNT", config[CONF_MIN_IPV6_ADDR_COUNT] "USE_NETWORK_MIN_IPV6_ADDR_COUNT", config[CONF_MIN_IPV6_ADDR_COUNT]
) )
if CORE.using_esp_idf: if CORE.is_esp32:
add_idf_sdkconfig_option("CONFIG_LWIP_IPV6", enable_ipv6) if CORE.using_esp_idf:
add_idf_sdkconfig_option("CONFIG_LWIP_IPV6_AUTOCONFIG", enable_ipv6) add_idf_sdkconfig_option("CONFIG_LWIP_IPV6", enable_ipv6)
add_idf_sdkconfig_option("CONFIG_LWIP_IPV6_AUTOCONFIG", enable_ipv6)
else:
add_idf_sdkconfig_option("CONFIG_LWIP_IPV6", True)
add_idf_sdkconfig_option("CONFIG_LWIP_IPV6_AUTOCONFIG", True)
elif enable_ipv6: elif enable_ipv6:
cg.add_build_flag("-DCONFIG_LWIP_IPV6") cg.add_build_flag("-DCONFIG_LWIP_IPV6")
cg.add_build_flag("-DCONFIG_LWIP_IPV6_AUTOCONFIG") cg.add_build_flag("-DCONFIG_LWIP_IPV6_AUTOCONFIG")

View File

@@ -153,10 +153,10 @@ async def to_code(config):
if CONF_TFT_URL in config: if CONF_TFT_URL in config:
cg.add_define("USE_NEXTION_TFT_UPLOAD") cg.add_define("USE_NEXTION_TFT_UPLOAD")
cg.add(var.set_tft_url(config[CONF_TFT_URL])) cg.add(var.set_tft_url(config[CONF_TFT_URL]))
if CORE.is_esp32 and CORE.using_arduino: if CORE.is_esp32:
cg.add_library("NetworkClientSecure", None) if CORE.using_arduino:
cg.add_library("HTTPClient", None) cg.add_library("NetworkClientSecure", None)
elif CORE.is_esp32 and CORE.using_esp_idf: cg.add_library("HTTPClient", None)
esp32.add_idf_sdkconfig_option("CONFIG_ESP_TLS_INSECURE", True) esp32.add_idf_sdkconfig_option("CONFIG_ESP_TLS_INSECURE", True)
esp32.add_idf_sdkconfig_option( esp32.add_idf_sdkconfig_option(
"CONFIG_ESP_TLS_SKIP_SERVER_CERT_VERIFY", True "CONFIG_ESP_TLS_SKIP_SERVER_CERT_VERIFY", True

View File

@@ -121,33 +121,30 @@ async def to_code(config):
if config[CONF_MODE] == TYPE_OCTAL: if config[CONF_MODE] == TYPE_OCTAL:
cg.add_platformio_option("board_build.arduino.memory_type", "qio_opi") cg.add_platformio_option("board_build.arduino.memory_type", "qio_opi")
if CORE.using_esp_idf: add_idf_sdkconfig_option(
add_idf_sdkconfig_option( f"CONFIG_{get_esp32_variant().upper()}_SPIRAM_SUPPORT", True
f"CONFIG_{get_esp32_variant().upper()}_SPIRAM_SUPPORT", True )
) add_idf_sdkconfig_option("CONFIG_SOC_SPIRAM_SUPPORTED", True)
add_idf_sdkconfig_option("CONFIG_SOC_SPIRAM_SUPPORTED", True) add_idf_sdkconfig_option("CONFIG_SPIRAM", True)
add_idf_sdkconfig_option("CONFIG_SPIRAM", True) add_idf_sdkconfig_option("CONFIG_SPIRAM_USE", True)
add_idf_sdkconfig_option("CONFIG_SPIRAM_USE", True) add_idf_sdkconfig_option("CONFIG_SPIRAM_USE_CAPS_ALLOC", True)
add_idf_sdkconfig_option("CONFIG_SPIRAM_USE_CAPS_ALLOC", True) add_idf_sdkconfig_option("CONFIG_SPIRAM_IGNORE_NOTFOUND", True)
add_idf_sdkconfig_option("CONFIG_SPIRAM_IGNORE_NOTFOUND", True)
add_idf_sdkconfig_option( add_idf_sdkconfig_option(f"CONFIG_SPIRAM_MODE_{SDK_MODES[config[CONF_MODE]]}", True)
f"CONFIG_SPIRAM_MODE_{SDK_MODES[config[CONF_MODE]]}", True
)
# Remove MHz suffix, convert to int # Remove MHz suffix, convert to int
speed = int(config[CONF_SPEED][:-3]) speed = int(config[CONF_SPEED][:-3])
add_idf_sdkconfig_option(f"CONFIG_SPIRAM_SPEED_{speed}M", True) add_idf_sdkconfig_option(f"CONFIG_SPIRAM_SPEED_{speed}M", True)
add_idf_sdkconfig_option("CONFIG_SPIRAM_SPEED", speed) add_idf_sdkconfig_option("CONFIG_SPIRAM_SPEED", speed)
if config[CONF_MODE] == TYPE_OCTAL and speed == 120: if config[CONF_MODE] == TYPE_OCTAL and speed == 120:
add_idf_sdkconfig_option("CONFIG_ESPTOOLPY_FLASHFREQ_120M", True) add_idf_sdkconfig_option("CONFIG_ESPTOOLPY_FLASHFREQ_120M", True)
add_idf_sdkconfig_option("CONFIG_BOOTLOADER_FLASH_DC_AWARE", True) add_idf_sdkconfig_option("CONFIG_BOOTLOADER_FLASH_DC_AWARE", True)
if CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] >= cv.Version(5, 4, 0): if CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] >= cv.Version(5, 4, 0):
add_idf_sdkconfig_option( add_idf_sdkconfig_option(
"CONFIG_SPIRAM_TIMING_TUNING_POINT_VIA_TEMPERATURE_SENSOR", True "CONFIG_SPIRAM_TIMING_TUNING_POINT_VIA_TEMPERATURE_SENSOR", True
) )
if config[CONF_ENABLE_ECC]: if config[CONF_ENABLE_ECC]:
add_idf_sdkconfig_option("CONFIG_SPIRAM_ECC_ENABLE", True) add_idf_sdkconfig_option("CONFIG_SPIRAM_ECC_ENABLE", True)
cg.add_define("USE_PSRAM") cg.add_define("USE_PSRAM")

View File

@@ -16,7 +16,6 @@ from esphome.const import (
CONF_DUMMY_RECEIVER_ID, CONF_DUMMY_RECEIVER_ID,
CONF_ID, CONF_ID,
CONF_INVERT, CONF_INVERT,
CONF_INVERTED,
CONF_LAMBDA, CONF_LAMBDA,
CONF_NUMBER, CONF_NUMBER,
CONF_PORT, CONF_PORT,
@@ -39,9 +38,6 @@ uart_ns = cg.esphome_ns.namespace("uart")
UARTComponent = uart_ns.class_("UARTComponent") UARTComponent = uart_ns.class_("UARTComponent")
IDFUARTComponent = uart_ns.class_("IDFUARTComponent", UARTComponent, cg.Component) IDFUARTComponent = uart_ns.class_("IDFUARTComponent", UARTComponent, cg.Component)
ESP32ArduinoUARTComponent = uart_ns.class_(
"ESP32ArduinoUARTComponent", UARTComponent, cg.Component
)
ESP8266UartComponent = uart_ns.class_( ESP8266UartComponent = uart_ns.class_(
"ESP8266UartComponent", UARTComponent, cg.Component "ESP8266UartComponent", UARTComponent, cg.Component
) )
@@ -53,7 +49,6 @@ HostUartComponent = uart_ns.class_("HostUartComponent", UARTComponent, cg.Compon
NATIVE_UART_CLASSES = ( NATIVE_UART_CLASSES = (
str(IDFUARTComponent), str(IDFUARTComponent),
str(ESP32ArduinoUARTComponent),
str(ESP8266UartComponent), str(ESP8266UartComponent),
str(RP2040UartComponent), str(RP2040UartComponent),
str(LibreTinyUARTComponent), str(LibreTinyUARTComponent),
@@ -119,20 +114,6 @@ def validate_rx_pin(value):
return value return value
def validate_invert_esp32(config):
if (
CORE.is_esp32
and CORE.using_arduino
and CONF_TX_PIN in config
and CONF_RX_PIN in config
and config[CONF_TX_PIN][CONF_INVERTED] != config[CONF_RX_PIN][CONF_INVERTED]
):
raise cv.Invalid(
"Different invert values for TX and RX pin are not supported for ESP32 when using Arduino."
)
return config
def validate_host_config(config): def validate_host_config(config):
if CORE.is_host: if CORE.is_host:
if CONF_TX_PIN in config or CONF_RX_PIN in config: if CONF_TX_PIN in config or CONF_RX_PIN in config:
@@ -151,10 +132,7 @@ def _uart_declare_type(value):
if CORE.is_esp8266: if CORE.is_esp8266:
return cv.declare_id(ESP8266UartComponent)(value) return cv.declare_id(ESP8266UartComponent)(value)
if CORE.is_esp32: if CORE.is_esp32:
if CORE.using_arduino: return cv.declare_id(IDFUARTComponent)(value)
return cv.declare_id(ESP32ArduinoUARTComponent)(value)
if CORE.using_esp_idf:
return cv.declare_id(IDFUARTComponent)(value)
if CORE.is_rp2040: if CORE.is_rp2040:
return cv.declare_id(RP2040UartComponent)(value) return cv.declare_id(RP2040UartComponent)(value)
if CORE.is_libretiny: if CORE.is_libretiny:
@@ -255,7 +233,6 @@ CONFIG_SCHEMA = cv.All(
} }
).extend(cv.COMPONENT_SCHEMA), ).extend(cv.COMPONENT_SCHEMA),
cv.has_at_least_one_key(CONF_TX_PIN, CONF_RX_PIN, CONF_PORT), cv.has_at_least_one_key(CONF_TX_PIN, CONF_RX_PIN, CONF_PORT),
validate_invert_esp32,
validate_host_config, validate_host_config,
) )
@@ -444,8 +421,10 @@ async def uart_write_to_code(config, action_id, template_arg, args):
FILTER_SOURCE_FILES = filter_source_files_from_platform( FILTER_SOURCE_FILES = filter_source_files_from_platform(
{ {
"uart_component_esp32_arduino.cpp": {PlatformFramework.ESP32_ARDUINO}, "uart_component_esp_idf.cpp": {
"uart_component_esp_idf.cpp": {PlatformFramework.ESP32_IDF}, PlatformFramework.ESP32_IDF,
PlatformFramework.ESP32_ARDUINO,
},
"uart_component_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO}, "uart_component_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO},
"uart_component_host.cpp": {PlatformFramework.HOST_NATIVE}, "uart_component_host.cpp": {PlatformFramework.HOST_NATIVE},
"uart_component_rp2040.cpp": {PlatformFramework.RP2040_ARDUINO}, "uart_component_rp2040.cpp": {PlatformFramework.RP2040_ARDUINO},

View File

@@ -1,214 +0,0 @@
#ifdef USE_ESP32_FRAMEWORK_ARDUINO
#include "uart_component_esp32_arduino.h"
#include "esphome/core/application.h"
#include "esphome/core/defines.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#ifdef USE_LOGGER
#include "esphome/components/logger/logger.h"
#endif
namespace esphome {
namespace uart {
static const char *const TAG = "uart.arduino_esp32";
static const uint32_t UART_PARITY_EVEN = 0 << 0;
static const uint32_t UART_PARITY_ODD = 1 << 0;
static const uint32_t UART_PARITY_ENABLE = 1 << 1;
static const uint32_t UART_NB_BIT_5 = 0 << 2;
static const uint32_t UART_NB_BIT_6 = 1 << 2;
static const uint32_t UART_NB_BIT_7 = 2 << 2;
static const uint32_t UART_NB_BIT_8 = 3 << 2;
static const uint32_t UART_NB_STOP_BIT_1 = 1 << 4;
static const uint32_t UART_NB_STOP_BIT_2 = 3 << 4;
static const uint32_t UART_TICK_APB_CLOCK = 1 << 27;
uint32_t ESP32ArduinoUARTComponent::get_config() {
uint32_t config = 0;
/*
* All bits numbers below come from
* framework-arduinoespressif32/cores/esp32/esp32-hal-uart.h
* And more specifically conf0 union in uart_dev_t.
*
* Below is bit used from conf0 union.
* <name>:<bits position> <values>
* parity:0 0:even 1:odd
* parity_en:1 Set this bit to enable uart parity check.
* bit_num:2-4 0:5bits 1:6bits 2:7bits 3:8bits
* stop_bit_num:4-6 stop bit. 1:1bit 2:1.5bits 3:2bits
* tick_ref_always_on:27 select the clock.1apb clockref_tick
*/
if (this->parity_ == UART_CONFIG_PARITY_EVEN) {
config |= UART_PARITY_EVEN | UART_PARITY_ENABLE;
} else if (this->parity_ == UART_CONFIG_PARITY_ODD) {
config |= UART_PARITY_ODD | UART_PARITY_ENABLE;
}
switch (this->data_bits_) {
case 5:
config |= UART_NB_BIT_5;
break;
case 6:
config |= UART_NB_BIT_6;
break;
case 7:
config |= UART_NB_BIT_7;
break;
case 8:
config |= UART_NB_BIT_8;
break;
}
if (this->stop_bits_ == 1) {
config |= UART_NB_STOP_BIT_1;
} else {
config |= UART_NB_STOP_BIT_2;
}
config |= UART_TICK_APB_CLOCK;
return config;
}
void ESP32ArduinoUARTComponent::setup() {
// Use Arduino HardwareSerial UARTs if all used pins match the ones
// preconfigured by the platform. For example if RX disabled but TX pin
// is 1 we still want to use Serial.
bool is_default_tx, is_default_rx;
#ifdef CONFIG_IDF_TARGET_ESP32C3
is_default_tx = tx_pin_ == nullptr || tx_pin_->get_pin() == 21;
is_default_rx = rx_pin_ == nullptr || rx_pin_->get_pin() == 20;
#else
is_default_tx = tx_pin_ == nullptr || tx_pin_->get_pin() == 1;
is_default_rx = rx_pin_ == nullptr || rx_pin_->get_pin() == 3;
#endif
static uint8_t next_uart_num = 0;
if (is_default_tx && is_default_rx && next_uart_num == 0) {
#if ARDUINO_USB_CDC_ON_BOOT
this->hw_serial_ = &Serial0;
#else
this->hw_serial_ = &Serial;
#endif
next_uart_num++;
} else {
#ifdef USE_LOGGER
bool logger_uses_hardware_uart = true;
#ifdef USE_LOGGER_USB_CDC
if (logger::global_logger->get_uart() == logger::UART_SELECTION_USB_CDC) {
// this is not a hardware UART, ignore it
logger_uses_hardware_uart = false;
}
#endif // USE_LOGGER_USB_CDC
#ifdef USE_LOGGER_USB_SERIAL_JTAG
if (logger::global_logger->get_uart() == logger::UART_SELECTION_USB_SERIAL_JTAG) {
// this is not a hardware UART, ignore it
logger_uses_hardware_uart = false;
}
#endif // USE_LOGGER_USB_SERIAL_JTAG
if (logger_uses_hardware_uart && logger::global_logger->get_baud_rate() > 0 &&
logger::global_logger->get_uart() == next_uart_num) {
next_uart_num++;
}
#endif // USE_LOGGER
if (next_uart_num >= SOC_UART_NUM) {
ESP_LOGW(TAG, "Maximum number of UART components created already.");
this->mark_failed();
return;
}
this->number_ = next_uart_num;
this->hw_serial_ = new HardwareSerial(next_uart_num++); // NOLINT(cppcoreguidelines-owning-memory)
}
this->load_settings(false);
}
void ESP32ArduinoUARTComponent::load_settings(bool dump_config) {
int8_t tx = this->tx_pin_ != nullptr ? this->tx_pin_->get_pin() : -1;
int8_t rx = this->rx_pin_ != nullptr ? this->rx_pin_->get_pin() : -1;
bool invert = false;
if (tx_pin_ != nullptr && tx_pin_->is_inverted())
invert = true;
if (rx_pin_ != nullptr && rx_pin_->is_inverted())
invert = true;
this->hw_serial_->setRxBufferSize(this->rx_buffer_size_);
this->hw_serial_->begin(this->baud_rate_, get_config(), rx, tx, invert);
if (dump_config) {
ESP_LOGCONFIG(TAG, "UART %u was reloaded.", this->number_);
this->dump_config();
}
}
void ESP32ArduinoUARTComponent::dump_config() {
ESP_LOGCONFIG(TAG, "UART Bus %d:", this->number_);
LOG_PIN(" TX Pin: ", tx_pin_);
LOG_PIN(" RX Pin: ", rx_pin_);
if (this->rx_pin_ != nullptr) {
ESP_LOGCONFIG(TAG, " RX Buffer Size: %u", this->rx_buffer_size_);
}
ESP_LOGCONFIG(TAG,
" Baud Rate: %u baud\n"
" Data Bits: %u\n"
" Parity: %s\n"
" Stop bits: %u",
this->baud_rate_, this->data_bits_, LOG_STR_ARG(parity_to_str(this->parity_)), this->stop_bits_);
this->check_logger_conflict();
}
void ESP32ArduinoUARTComponent::write_array(const uint8_t *data, size_t len) {
this->hw_serial_->write(data, len);
#ifdef USE_UART_DEBUGGER
for (size_t i = 0; i < len; i++) {
this->debug_callback_.call(UART_DIRECTION_TX, data[i]);
}
#endif
}
bool ESP32ArduinoUARTComponent::peek_byte(uint8_t *data) {
if (!this->check_read_timeout_())
return false;
*data = this->hw_serial_->peek();
return true;
}
bool ESP32ArduinoUARTComponent::read_array(uint8_t *data, size_t len) {
if (!this->check_read_timeout_(len))
return false;
this->hw_serial_->readBytes(data, len);
#ifdef USE_UART_DEBUGGER
for (size_t i = 0; i < len; i++) {
this->debug_callback_.call(UART_DIRECTION_RX, data[i]);
}
#endif
return true;
}
int ESP32ArduinoUARTComponent::available() { return this->hw_serial_->available(); }
void ESP32ArduinoUARTComponent::flush() {
ESP_LOGVV(TAG, " Flushing");
this->hw_serial_->flush();
}
void ESP32ArduinoUARTComponent::check_logger_conflict() {
#ifdef USE_LOGGER
if (this->hw_serial_ == nullptr || logger::global_logger->get_baud_rate() == 0) {
return;
}
if (this->hw_serial_ == logger::global_logger->get_hw_serial()) {
ESP_LOGW(TAG, " You're using the same serial port for logging and the UART component. Please "
"disable logging over the serial port by setting logger->baud_rate to 0.");
}
#endif
}
} // namespace uart
} // namespace esphome
#endif // USE_ESP32_FRAMEWORK_ARDUINO

View File

@@ -1,60 +0,0 @@
#pragma once
#ifdef USE_ESP32_FRAMEWORK_ARDUINO
#include <driver/uart.h>
#include <HardwareSerial.h>
#include <vector>
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
#include "uart_component.h"
namespace esphome {
namespace uart {
class ESP32ArduinoUARTComponent : public UARTComponent, public Component {
public:
void setup() override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::BUS; }
void write_array(const uint8_t *data, size_t len) override;
bool peek_byte(uint8_t *data) override;
bool read_array(uint8_t *data, size_t len) override;
int available() override;
void flush() override;
uint32_t get_config();
HardwareSerial *get_hw_serial() { return this->hw_serial_; }
uint8_t get_hw_serial_number() { return this->number_; }
/**
* Load the UART with the current settings.
* @param dump_config (Optional, default `true`): True for displaying new settings or
* false to change it quitely
*
* Example:
* ```cpp
* id(uart1).load_settings();
* ```
*
* This will load the current UART interface with the latest settings (baud_rate, parity, etc).
*/
void load_settings(bool dump_config) override;
void load_settings() override { this->load_settings(true); }
protected:
void check_logger_conflict() override;
HardwareSerial *hw_serial_{nullptr};
uint8_t number_{0};
};
} // namespace uart
} // namespace esphome
#endif // USE_ESP32_FRAMEWORK_ARDUINO

View File

@@ -1,4 +1,4 @@
#ifdef USE_ESP_IDF #ifdef USE_ESP32
#include "uart_component_esp_idf.h" #include "uart_component_esp_idf.h"
#include <cinttypes> #include <cinttypes>

View File

@@ -1,6 +1,6 @@
#pragma once #pragma once
#ifdef USE_ESP_IDF #ifdef USE_ESP32
#include <driver/uart.h> #include <driver/uart.h>
#include "esphome/core/component.h" #include "esphome/core/component.h"
@@ -55,4 +55,4 @@ class IDFUARTComponent : public UARTComponent, public Component {
} // namespace uart } // namespace uart
} // namespace esphome } // namespace esphome
#endif // USE_ESP_IDF #endif // USE_ESP32

View File

@@ -402,7 +402,7 @@ async def to_code(config):
add_idf_sdkconfig_option("CONFIG_LWIP_DHCPS", False) add_idf_sdkconfig_option("CONFIG_LWIP_DHCPS", False)
# Disable Enterprise WiFi support if no EAP is configured # Disable Enterprise WiFi support if no EAP is configured
if CORE.is_esp32 and CORE.using_esp_idf and not has_eap: if CORE.is_esp32 and not has_eap:
add_idf_sdkconfig_option("CONFIG_ESP_WIFI_ENTERPRISE_SUPPORT", False) add_idf_sdkconfig_option("CONFIG_ESP_WIFI_ENTERPRISE_SUPPORT", False)
cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT])) cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT]))

View File

@@ -118,7 +118,7 @@ async def to_code(config):
# Workaround for crash on IDF 5+ # Workaround for crash on IDF 5+
# See https://github.com/trombik/esp_wireguard/issues/33#issuecomment-1568503651 # See https://github.com/trombik/esp_wireguard/issues/33#issuecomment-1568503651
if CORE.using_esp_idf: if CORE.is_esp32:
add_idf_sdkconfig_option("CONFIG_LWIP_PPP_SUPPORT", True) add_idf_sdkconfig_option("CONFIG_LWIP_PPP_SUPPORT", True)
# This flag is added here because the esp_wireguard library statically # This flag is added here because the esp_wireguard library statically

View File

@@ -71,6 +71,8 @@ FILTER_PLATFORMIO_LINES = [
r" - tool-esptool.* \(.*\)", r" - tool-esptool.* \(.*\)",
r" - toolchain-.* \(.*\)", r" - toolchain-.* \(.*\)",
r"Creating BIN file .*", r"Creating BIN file .*",
r"Warning! Could not find file \".*.crt\"",
r"Warning! Arduino framework as an ESP-IDF component doesn't handle the `variant` field! The default `esp32` variant will be used.",
] ]

View File

@@ -1,6 +1,7 @@
import os import os
import random import random
import string import string
from typing import Literal, NotRequired, TypedDict, Unpack
import unicodedata import unicodedata
import voluptuous as vol import voluptuous as vol
@@ -103,11 +104,25 @@ HARDWARE_BASE_CONFIGS = {
} }
def sanitize_double_quotes(value): def sanitize_double_quotes(value: str) -> str:
return value.replace("\\", "\\\\").replace('"', '\\"') return value.replace("\\", "\\\\").replace('"', '\\"')
def wizard_file(**kwargs): class WizardFileKwargs(TypedDict):
"""Keyword arguments for wizard_file function."""
name: str
platform: Literal["ESP8266", "ESP32", "RP2040", "BK72XX", "LN882X", "RTL87XX"]
board: str
ssid: NotRequired[str]
psk: NotRequired[str]
password: NotRequired[str]
ota_password: NotRequired[str]
api_encryption_key: NotRequired[str]
friendly_name: NotRequired[str]
def wizard_file(**kwargs: Unpack[WizardFileKwargs]) -> str:
letters = string.ascii_letters + string.digits letters = string.ascii_letters + string.digits
ap_name_base = kwargs["name"].replace("_", " ").title() ap_name_base = kwargs["name"].replace("_", " ").title()
ap_name = f"{ap_name_base} Fallback Hotspot" ap_name = f"{ap_name_base} Fallback Hotspot"
@@ -180,7 +195,25 @@ captive_portal:
return config return config
def wizard_write(path, **kwargs): class WizardWriteKwargs(TypedDict):
"""Keyword arguments for wizard_write function."""
name: str
type: Literal["basic", "empty", "upload"]
# Required for "basic" type
board: NotRequired[str]
platform: NotRequired[str]
ssid: NotRequired[str]
psk: NotRequired[str]
password: NotRequired[str]
ota_password: NotRequired[str]
api_encryption_key: NotRequired[str]
friendly_name: NotRequired[str]
# Required for "upload" type
file_text: NotRequired[str]
def wizard_write(path: str, **kwargs: Unpack[WizardWriteKwargs]) -> bool:
from esphome.components.bk72xx import boards as bk72xx_boards from esphome.components.bk72xx import boards as bk72xx_boards
from esphome.components.esp32 import boards as esp32_boards from esphome.components.esp32 import boards as esp32_boards
from esphome.components.esp8266 import boards as esp8266_boards from esphome.components.esp8266 import boards as esp8266_boards
@@ -237,14 +270,14 @@ def wizard_write(path, **kwargs):
if get_bool_env(ENV_QUICKWIZARD): if get_bool_env(ENV_QUICKWIZARD):
def sleep(time): def sleep(time: float) -> None:
pass pass
else: else:
from time import sleep from time import sleep
def safe_print_step(step, big): def safe_print_step(step: int, big: str) -> None:
safe_print() safe_print()
safe_print() safe_print()
safe_print(f"============= STEP {step} =============") safe_print(f"============= STEP {step} =============")
@@ -253,14 +286,14 @@ def safe_print_step(step, big):
sleep(0.25) sleep(0.25)
def default_input(text, default): def default_input(text: str, default: str) -> str:
safe_print() safe_print()
safe_print(f"Press ENTER for default ({default})") safe_print(f"Press ENTER for default ({default})")
return safe_input(text.format(default)) or default return safe_input(text.format(default)) or default
# From https://stackoverflow.com/a/518232/8924614 # From https://stackoverflow.com/a/518232/8924614
def strip_accents(value): def strip_accents(value: str) -> str:
return "".join( return "".join(
c c
for c in unicodedata.normalize("NFD", str(value)) for c in unicodedata.normalize("NFD", str(value))
@@ -268,7 +301,7 @@ def strip_accents(value):
) )
def wizard(path): def wizard(path: str) -> int:
from esphome.components.bk72xx import boards as bk72xx_boards from esphome.components.bk72xx import boards as bk72xx_boards
from esphome.components.esp32 import boards as esp32_boards from esphome.components.esp32 import boards as esp32_boards
from esphome.components.esp8266 import boards as esp8266_boards from esphome.components.esp8266 import boards as esp8266_boards
@@ -509,6 +542,7 @@ def wizard(path):
ssid=ssid, ssid=ssid,
psk=psk, psk=psk,
password=password, password=password,
type="basic",
): ):
return 1 return 1

View File

@@ -317,10 +317,16 @@ def clean_build():
# Clean PlatformIO cache to resolve CMake compiler detection issues # Clean PlatformIO cache to resolve CMake compiler detection issues
# This helps when toolchain paths change or get corrupted # This helps when toolchain paths change or get corrupted
cache_dir = CORE.platformio_cache_dir try:
if os.path.isdir(cache_dir): from platformio.project.helpers import get_project_cache_dir
_LOGGER.info("Deleting PlatformIO cache %s", cache_dir) except ImportError:
shutil.rmtree(cache_dir) # PlatformIO is not available, skip cache cleaning
pass
else:
cache_dir = get_project_cache_dir()
if cache_dir and cache_dir.strip() and os.path.isdir(cache_dir):
_LOGGER.info("Deleting PlatformIO cache %s", cache_dir)
shutil.rmtree(cache_dir)
GITIGNORE_CONTENT = """# Gitignore settings for ESPHome GITIGNORE_CONTENT = """# Gitignore settings for ESPHome

View File

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "esphome" name = "esphome"
license = {text = "MIT"} license = "MIT"
description = "ESPHome is a system to configure your microcontrollers by simple yet powerful configuration files and control them remotely through Home Automation systems." description = "ESPHome is a system to configure your microcontrollers by simple yet powerful configuration files and control them remotely through Home Automation systems."
readme = "README.md" readme = "README.md"
authors = [ authors = [
@@ -15,7 +15,6 @@ classifiers = [
"Environment :: Console", "Environment :: Console",
"Intended Audience :: Developers", "Intended Audience :: Developers",
"Intended Audience :: End Users/Desktop", "Intended Audience :: End Users/Desktop",
"License :: OSI Approved :: MIT License",
"Programming Language :: C++", "Programming Language :: C++",
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
"Topic :: Home Automation", "Topic :: Home Automation",

View File

@@ -12,7 +12,7 @@ platformio==6.1.18 # When updating platformio, also update /docker/Dockerfile
esptool==5.0.2 esptool==5.0.2
click==8.1.7 click==8.1.7
esphome-dashboard==20250904.0 esphome-dashboard==20250904.0
aioesphomeapi==41.0.0 aioesphomeapi==41.1.0
zeroconf==0.147.2 zeroconf==0.147.2
puremagic==1.30 puremagic==1.30
ruamel.yaml==0.18.15 # dashboard_import ruamel.yaml==0.18.15 # dashboard_import

View File

@@ -7,7 +7,7 @@ pre-commit
# Unit tests # Unit tests
pytest==8.4.2 pytest==8.4.2
pytest-cov==7.0.0 pytest-cov==7.0.0
pytest-mock==3.15.0 pytest-mock==3.15.1
pytest-asyncio==1.2.0 pytest-asyncio==1.2.0
pytest-xdist==3.8.0 pytest-xdist==3.8.0
asyncmock==0.4.2 asyncmock==0.4.2

View File

@@ -1226,6 +1226,18 @@ def test_has_mqtt_logging_no_log_topic() -> None:
setup_core(config={}) setup_core(config={})
assert has_mqtt_logging() is False assert has_mqtt_logging() is False
# Setup MQTT config with CONF_LOG_TOPIC but no CONF_LEVEL (regression test for #10771)
# This simulates the default configuration created by validate_config in the MQTT component
setup_core(
config={
CONF_MQTT: {
CONF_BROKER: "mqtt.local",
CONF_LOG_TOPIC: {CONF_TOPIC: "esphome/debug"},
}
}
)
assert has_mqtt_logging() is True
def test_has_mqtt() -> None: def test_has_mqtt() -> None:
"""Test has_mqtt function.""" """Test has_mqtt function."""

View File

@@ -1,9 +1,12 @@
"""Tests for the wizard.py file.""" """Tests for the wizard.py file."""
import os import os
from pathlib import Path
from typing import Any
from unittest.mock import MagicMock from unittest.mock import MagicMock
import pytest import pytest
from pytest import MonkeyPatch
from esphome.components.bk72xx.boards import BK72XX_BOARD_PINS from esphome.components.bk72xx.boards import BK72XX_BOARD_PINS
from esphome.components.esp32.boards import ESP32_BOARD_PINS from esphome.components.esp32.boards import ESP32_BOARD_PINS
@@ -15,7 +18,7 @@ import esphome.wizard as wz
@pytest.fixture @pytest.fixture
def default_config(): def default_config() -> dict[str, Any]:
return { return {
"type": "basic", "type": "basic",
"name": "test-name", "name": "test-name",
@@ -28,7 +31,7 @@ def default_config():
@pytest.fixture @pytest.fixture
def wizard_answers(): def wizard_answers() -> list[str]:
return [ return [
"test-node", # Name of the node "test-node", # Name of the node
"ESP8266", # platform "ESP8266", # platform
@@ -53,7 +56,9 @@ def test_sanitize_quotes_replaces_with_escaped_char():
assert output_str == '\\"key\\": \\"value\\"' assert output_str == '\\"key\\": \\"value\\"'
def test_config_file_fallback_ap_includes_descriptive_name(default_config): def test_config_file_fallback_ap_includes_descriptive_name(
default_config: dict[str, Any],
):
""" """
The fallback AP should include the node and a descriptive name The fallback AP should include the node and a descriptive name
""" """
@@ -67,7 +72,9 @@ def test_config_file_fallback_ap_includes_descriptive_name(default_config):
assert 'ssid: "Test Node Fallback Hotspot"' in config assert 'ssid: "Test Node Fallback Hotspot"' in config
def test_config_file_fallback_ap_name_less_than_32_chars(default_config): def test_config_file_fallback_ap_name_less_than_32_chars(
default_config: dict[str, Any],
):
""" """
The fallback AP name must be less than 32 chars. The fallback AP name must be less than 32 chars.
Since it is composed of the node name and "Fallback Hotspot" this can be too long and needs truncating Since it is composed of the node name and "Fallback Hotspot" this can be too long and needs truncating
@@ -82,7 +89,7 @@ def test_config_file_fallback_ap_name_less_than_32_chars(default_config):
assert 'ssid: "A Very Long Name For This Node"' in config assert 'ssid: "A Very Long Name For This Node"' in config
def test_config_file_should_include_ota(default_config): def test_config_file_should_include_ota(default_config: dict[str, Any]):
""" """
The Over-The-Air update should be enabled by default The Over-The-Air update should be enabled by default
""" """
@@ -95,7 +102,9 @@ def test_config_file_should_include_ota(default_config):
assert "ota:" in config assert "ota:" in config
def test_config_file_should_include_ota_when_password_set(default_config): def test_config_file_should_include_ota_when_password_set(
default_config: dict[str, Any],
):
""" """
The Over-The-Air update should be enabled when a password is set The Over-The-Air update should be enabled when a password is set
""" """
@@ -109,7 +118,9 @@ def test_config_file_should_include_ota_when_password_set(default_config):
assert "ota:" in config assert "ota:" in config
def test_wizard_write_sets_platform(default_config, tmp_path, monkeypatch): def test_wizard_write_sets_platform(
default_config: dict[str, Any], tmp_path: Path, monkeypatch: MonkeyPatch
):
""" """
If the platform is not explicitly set, use "ESP8266" if the board is one of the ESP8266 boards If the platform is not explicitly set, use "ESP8266" if the board is one of the ESP8266 boards
""" """
@@ -126,7 +137,7 @@ def test_wizard_write_sets_platform(default_config, tmp_path, monkeypatch):
assert "esp8266:" in generated_config assert "esp8266:" in generated_config
def test_wizard_empty_config(tmp_path, monkeypatch): def test_wizard_empty_config(tmp_path: Path, monkeypatch: MonkeyPatch):
""" """
The wizard should be able to create an empty configuration The wizard should be able to create an empty configuration
""" """
@@ -146,7 +157,7 @@ def test_wizard_empty_config(tmp_path, monkeypatch):
assert generated_config == "" assert generated_config == ""
def test_wizard_upload_config(tmp_path, monkeypatch): def test_wizard_upload_config(tmp_path: Path, monkeypatch: MonkeyPatch):
""" """
The wizard should be able to import an base64 encoded configuration The wizard should be able to import an base64 encoded configuration
""" """
@@ -168,7 +179,7 @@ def test_wizard_upload_config(tmp_path, monkeypatch):
def test_wizard_write_defaults_platform_from_board_esp8266( def test_wizard_write_defaults_platform_from_board_esp8266(
default_config, tmp_path, monkeypatch default_config: dict[str, Any], tmp_path: Path, monkeypatch: MonkeyPatch
): ):
""" """
If the platform is not explicitly set, use "ESP8266" if the board is one of the ESP8266 boards If the platform is not explicitly set, use "ESP8266" if the board is one of the ESP8266 boards
@@ -189,7 +200,7 @@ def test_wizard_write_defaults_platform_from_board_esp8266(
def test_wizard_write_defaults_platform_from_board_esp32( def test_wizard_write_defaults_platform_from_board_esp32(
default_config, tmp_path, monkeypatch default_config: dict[str, Any], tmp_path: Path, monkeypatch: MonkeyPatch
): ):
""" """
If the platform is not explicitly set, use "ESP32" if the board is one of the ESP32 boards If the platform is not explicitly set, use "ESP32" if the board is one of the ESP32 boards
@@ -210,7 +221,7 @@ def test_wizard_write_defaults_platform_from_board_esp32(
def test_wizard_write_defaults_platform_from_board_bk72xx( def test_wizard_write_defaults_platform_from_board_bk72xx(
default_config, tmp_path, monkeypatch default_config: dict[str, Any], tmp_path: Path, monkeypatch: MonkeyPatch
): ):
""" """
If the platform is not explicitly set, use "BK72XX" if the board is one of BK72XX boards If the platform is not explicitly set, use "BK72XX" if the board is one of BK72XX boards
@@ -231,7 +242,7 @@ def test_wizard_write_defaults_platform_from_board_bk72xx(
def test_wizard_write_defaults_platform_from_board_ln882x( def test_wizard_write_defaults_platform_from_board_ln882x(
default_config, tmp_path, monkeypatch default_config: dict[str, Any], tmp_path: Path, monkeypatch: MonkeyPatch
): ):
""" """
If the platform is not explicitly set, use "LN882X" if the board is one of LN882X boards If the platform is not explicitly set, use "LN882X" if the board is one of LN882X boards
@@ -252,7 +263,7 @@ def test_wizard_write_defaults_platform_from_board_ln882x(
def test_wizard_write_defaults_platform_from_board_rtl87xx( def test_wizard_write_defaults_platform_from_board_rtl87xx(
default_config, tmp_path, monkeypatch default_config: dict[str, Any], tmp_path: Path, monkeypatch: MonkeyPatch
): ):
""" """
If the platform is not explicitly set, use "RTL87XX" if the board is one of RTL87XX boards If the platform is not explicitly set, use "RTL87XX" if the board is one of RTL87XX boards
@@ -272,7 +283,7 @@ def test_wizard_write_defaults_platform_from_board_rtl87xx(
assert "rtl87xx:" in generated_config assert "rtl87xx:" in generated_config
def test_safe_print_step_prints_step_number_and_description(monkeypatch): def test_safe_print_step_prints_step_number_and_description(monkeypatch: MonkeyPatch):
""" """
The safe_print_step function prints the step number and the passed description The safe_print_step function prints the step number and the passed description
""" """
@@ -296,7 +307,7 @@ def test_safe_print_step_prints_step_number_and_description(monkeypatch):
assert any(f"STEP {step_num}" in arg for arg in all_args) assert any(f"STEP {step_num}" in arg for arg in all_args)
def test_default_input_uses_default_if_no_input_supplied(monkeypatch): def test_default_input_uses_default_if_no_input_supplied(monkeypatch: MonkeyPatch):
""" """
The default_input() function should return the supplied default value if the user doesn't enter anything The default_input() function should return the supplied default value if the user doesn't enter anything
""" """
@@ -312,7 +323,7 @@ def test_default_input_uses_default_if_no_input_supplied(monkeypatch):
assert retval == default_string assert retval == default_string
def test_default_input_uses_user_supplied_value(monkeypatch): def test_default_input_uses_user_supplied_value(monkeypatch: MonkeyPatch):
""" """
The default_input() function should return the value that the user enters The default_input() function should return the value that the user enters
""" """
@@ -376,7 +387,9 @@ def test_wizard_rejects_existing_files(tmpdir):
assert retval == 2 assert retval == 2
def test_wizard_accepts_default_answers_esp8266(tmpdir, monkeypatch, wizard_answers): def test_wizard_accepts_default_answers_esp8266(
tmpdir, monkeypatch: MonkeyPatch, wizard_answers: list[str]
):
""" """
The wizard should accept the given default answers for esp8266 The wizard should accept the given default answers for esp8266
""" """
@@ -396,7 +409,9 @@ def test_wizard_accepts_default_answers_esp8266(tmpdir, monkeypatch, wizard_answ
assert retval == 0 assert retval == 0
def test_wizard_accepts_default_answers_esp32(tmpdir, monkeypatch, wizard_answers): def test_wizard_accepts_default_answers_esp32(
tmpdir, monkeypatch: MonkeyPatch, wizard_answers: list[str]
):
""" """
The wizard should accept the given default answers for esp32 The wizard should accept the given default answers for esp32
""" """
@@ -418,7 +433,9 @@ def test_wizard_accepts_default_answers_esp32(tmpdir, monkeypatch, wizard_answer
assert retval == 0 assert retval == 0
def test_wizard_offers_better_node_name(tmpdir, monkeypatch, wizard_answers): def test_wizard_offers_better_node_name(
tmpdir, monkeypatch: MonkeyPatch, wizard_answers: list[str]
):
""" """
When the node name does not conform, a better alternative is offered When the node name does not conform, a better alternative is offered
* Removes special chars * Removes special chars
@@ -449,7 +466,9 @@ def test_wizard_offers_better_node_name(tmpdir, monkeypatch, wizard_answers):
assert wz.default_input.call_args.args[1] == expected_name assert wz.default_input.call_args.args[1] == expected_name
def test_wizard_requires_correct_platform(tmpdir, monkeypatch, wizard_answers): def test_wizard_requires_correct_platform(
tmpdir, monkeypatch: MonkeyPatch, wizard_answers: list[str]
):
""" """
When the platform is not either esp32 or esp8266, the wizard should reject it When the platform is not either esp32 or esp8266, the wizard should reject it
""" """
@@ -471,7 +490,9 @@ def test_wizard_requires_correct_platform(tmpdir, monkeypatch, wizard_answers):
assert retval == 0 assert retval == 0
def test_wizard_requires_correct_board(tmpdir, monkeypatch, wizard_answers): def test_wizard_requires_correct_board(
tmpdir, monkeypatch: MonkeyPatch, wizard_answers: list[str]
):
""" """
When the board is not a valid esp8266 board, the wizard should reject it When the board is not a valid esp8266 board, the wizard should reject it
""" """
@@ -493,7 +514,9 @@ def test_wizard_requires_correct_board(tmpdir, monkeypatch, wizard_answers):
assert retval == 0 assert retval == 0
def test_wizard_requires_valid_ssid(tmpdir, monkeypatch, wizard_answers): def test_wizard_requires_valid_ssid(
tmpdir, monkeypatch: MonkeyPatch, wizard_answers: list[str]
):
""" """
When the board is not a valid esp8266 board, the wizard should reject it When the board is not a valid esp8266 board, the wizard should reject it
""" """
@@ -515,7 +538,9 @@ def test_wizard_requires_valid_ssid(tmpdir, monkeypatch, wizard_answers):
assert retval == 0 assert retval == 0
def test_wizard_write_protects_existing_config(tmpdir, default_config, monkeypatch): def test_wizard_write_protects_existing_config(
tmpdir, default_config: dict[str, Any], monkeypatch: MonkeyPatch
):
""" """
The wizard_write function should not overwrite existing config files and return False The wizard_write function should not overwrite existing config files and return False
""" """

View File

@@ -369,9 +369,15 @@ def test_clean_build(
assert dependencies_lock.exists() assert dependencies_lock.exists()
assert platformio_cache_dir.exists() assert platformio_cache_dir.exists()
# Call the function # Mock PlatformIO's get_project_cache_dir
with caplog.at_level("INFO"): with patch(
clean_build() "platformio.project.helpers.get_project_cache_dir"
) as mock_get_cache_dir:
mock_get_cache_dir.return_value = str(platformio_cache_dir)
# Call the function
with caplog.at_level("INFO"):
clean_build()
# Verify all were removed # Verify all were removed
assert not pioenvs_dir.exists() assert not pioenvs_dir.exists()
@@ -458,6 +464,86 @@ def test_clean_build_nothing_exists(
assert not dependencies_lock.exists() assert not dependencies_lock.exists()
@patch("esphome.writer.CORE")
def test_clean_build_platformio_not_available(
mock_core: MagicMock,
tmp_path: Path,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test clean_build when PlatformIO is not available."""
# Create directory structure and files
pioenvs_dir = tmp_path / ".pioenvs"
pioenvs_dir.mkdir()
piolibdeps_dir = tmp_path / ".piolibdeps"
piolibdeps_dir.mkdir()
dependencies_lock = tmp_path / "dependencies.lock"
dependencies_lock.write_text("lock file")
# Setup mocks
mock_core.relative_pioenvs_path.return_value = str(pioenvs_dir)
mock_core.relative_piolibdeps_path.return_value = str(piolibdeps_dir)
mock_core.relative_build_path.return_value = str(dependencies_lock)
# Verify all exist before
assert pioenvs_dir.exists()
assert piolibdeps_dir.exists()
assert dependencies_lock.exists()
# Mock import error for platformio
with (
patch.dict("sys.modules", {"platformio.project.helpers": None}),
caplog.at_level("INFO"),
):
# Call the function
clean_build()
# Verify standard paths were removed but no cache cleaning attempted
assert not pioenvs_dir.exists()
assert not piolibdeps_dir.exists()
assert not dependencies_lock.exists()
# Verify no cache logging
assert "PlatformIO cache" not in caplog.text
@patch("esphome.writer.CORE")
def test_clean_build_empty_cache_dir(
mock_core: MagicMock,
tmp_path: Path,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test clean_build when get_project_cache_dir returns empty/whitespace."""
# Create directory structure and files
pioenvs_dir = tmp_path / ".pioenvs"
pioenvs_dir.mkdir()
# Setup mocks
mock_core.relative_pioenvs_path.return_value = str(pioenvs_dir)
mock_core.relative_piolibdeps_path.return_value = str(tmp_path / ".piolibdeps")
mock_core.relative_build_path.return_value = str(tmp_path / "dependencies.lock")
# Verify pioenvs exists before
assert pioenvs_dir.exists()
# Mock PlatformIO's get_project_cache_dir to return whitespace
with patch(
"platformio.project.helpers.get_project_cache_dir"
) as mock_get_cache_dir:
mock_get_cache_dir.return_value = " " # Whitespace only
# Call the function
with caplog.at_level("INFO"):
clean_build()
# Verify pioenvs was removed
assert not pioenvs_dir.exists()
# Verify no cache cleaning was attempted due to empty string
assert "PlatformIO cache" not in caplog.text
@patch("esphome.writer.CORE") @patch("esphome.writer.CORE")
def test_write_gitignore_creates_new_file( def test_write_gitignore_creates_new_file(
mock_core: MagicMock, mock_core: MagicMock,