Compare commits

...
Author SHA1 Message Date
J. Nick Koston 01737d6998 Assert dir() does not import the platform packages 2026-08-13 09:18:33 -05:00
J. Nick Koston 986123c5ee Expose lazy UART tables in dir() and pin the contract with a test 2026-08-13 08:39:24 -05:00
J. Nick Koston 4abfdbbb15 Keep UART selection tables importable for downstream tooling 2026-08-12 22:56:28 -05:00
J. Nick Koston aa6c3a4c3a [logger] Defer platform imports out of module scope 2026-08-12 22:48:10 -05:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Jonathan Swoboda
f337d0acff Bump pylint from 4.0.6 to 4.0.7 (#18320)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-08-13 02:13:29 +00:00
J. Nick KostonandGitHub 9cc05b30d4 [web_server_base] Stop deleting the web server on captive portal teardown (#18324) 2026-08-12 21:09:01 -05:00
Jesse Hills e0b68c4d6d Merge branch 'beta' into dev 2026-08-13 14:06:46 +12:00
J. Nick KostonandGitHub 8e624b4117 [core] Retry framework downloads on transient network errors (#18330) 2026-08-13 12:14:50 +12:00
J. Nick KostonandGitHub 787a909aa4 [core] Don't block logs startup on MQTT IP discovery when addresses are known (#18313) 2026-08-13 12:11:32 +12:00
J. Nick KostonandGitHub 905485b673 [ld2420] Fix out-of-bounds read when device reports unknown command error (#18322) 2026-08-13 11:49:34 +12:00
esphome[bot]andGitHub 99677390e0 Bump bundled esphome-device-builder to 1.9.6 (#18328) 2026-08-12 16:27:14 -05:00
Kevin AhrendtandGitHub f2121130f9 [sendspin] Bump sendspin-cpp to v0.7.2 (#18316) 2026-08-12 12:49:54 -04:00
esphome[bot]andGitHub 1a01c34ec4 Bump aioesphomeapi from 45.10.0 to 45.10.1 (#18318) 2026-08-12 16:33:45 +00:00
J. Nick KostonandGitHub 3c46cc9c35 [usb_uart] Fix uint32_t format specifier warning in pl2303 (#18310) 2026-08-12 10:59:24 -04:00
Jesse Hills e192ec8fee Merge branch 'beta' into dev 2026-08-13 00:29:08 +12:00
Jesse Hills 8a1aa5753d Bump version to 2026.9.0-dev 2026-08-12 22:53:42 +12:00
7 changed files with 144 additions and 68 deletions
+1 -1
View File
@@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome
# could be handy for archiving the generated documentation or if some version
# control system is used.
PROJECT_NUMBER = 2026.8.0b2
PROJECT_NUMBER = 2026.9.0-dev
# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a
+99 -58
View File
@@ -1,38 +1,9 @@
import functools
import re
from esphome import automation
from esphome.automation import LambdaAction, StatelessLambdaAction
import esphome.codegen as cg
from esphome.components.esp32 import (
VARIANT_ESP32,
VARIANT_ESP32C2,
VARIANT_ESP32C3,
VARIANT_ESP32C5,
VARIANT_ESP32C6,
VARIANT_ESP32C61,
VARIANT_ESP32H2,
VARIANT_ESP32H4,
VARIANT_ESP32H21,
VARIANT_ESP32P4,
VARIANT_ESP32S2,
VARIANT_ESP32S3,
VARIANT_ESP32S31,
add_idf_sdkconfig_option,
get_esp32_variant,
require_usb_serial_jtag_secondary,
require_vfs_termios,
)
from esphome.components.libretiny import get_libretiny_component, get_libretiny_family
from esphome.components.libretiny.const import (
COMPONENT_BK72XX,
COMPONENT_LN882X,
COMPONENT_RTL87XX,
)
from esphome.components.zephyr import (
zephyr_add_cdc_acm,
zephyr_add_overlay,
zephyr_add_prj_conf,
)
from esphome.config_helpers import filter_source_files_from_platform
import esphome.config_validation as cv
from esphome.const import (
@@ -108,30 +79,8 @@ CONF_TASK_LOG_BUFFER_SIZE = "task_log_buffer_size"
CONF_WAIT_FOR_CDC = "wait_for_cdc"
CONF_EARLY_MESSAGE = "early_message"
UART_SELECTION_ESP32 = {
VARIANT_ESP32: [UART0, UART1, UART2],
VARIANT_ESP32C2: [UART0, UART1],
VARIANT_ESP32C3: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG],
VARIANT_ESP32C5: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG],
VARIANT_ESP32C6: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG],
VARIANT_ESP32C61: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG],
VARIANT_ESP32H2: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG],
VARIANT_ESP32H4: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG],
VARIANT_ESP32H21: [UART0, UART1, USB_SERIAL_JTAG],
VARIANT_ESP32P4: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG],
VARIANT_ESP32S2: [UART0, UART1, USB_CDC],
VARIANT_ESP32S3: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG],
VARIANT_ESP32S31: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG],
}
UART_SELECTION_ESP8266 = [UART0, UART0_SWAP, UART1]
UART_SELECTION_LIBRETINY = {
COMPONENT_BK72XX: [DEFAULT, UART1, UART2],
COMPONENT_LN882X: [DEFAULT, UART0, UART1, UART2],
COMPONENT_RTL87XX: [DEFAULT, UART0, UART1, UART2],
}
UART_SELECTION_RP2040 = [USB_CDC, UART0, UART1]
UART_SELECTION_NRF52 = [USB_CDC, UART0]
@@ -164,22 +113,99 @@ HARDWARE_UART_TO_SERIAL = {
is_log_level = cv.one_of(*LOG_LEVELS, upper=True)
@functools.cache
def _uart_selection_esp32() -> dict[str, list[str]]:
from esphome.components.esp32 import (
VARIANT_ESP32,
VARIANT_ESP32C2,
VARIANT_ESP32C3,
VARIANT_ESP32C5,
VARIANT_ESP32C6,
VARIANT_ESP32C61,
VARIANT_ESP32H2,
VARIANT_ESP32H4,
VARIANT_ESP32H21,
VARIANT_ESP32P4,
VARIANT_ESP32S2,
VARIANT_ESP32S3,
VARIANT_ESP32S31,
)
return {
VARIANT_ESP32: [UART0, UART1, UART2],
VARIANT_ESP32C2: [UART0, UART1],
VARIANT_ESP32C3: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG],
VARIANT_ESP32C5: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG],
VARIANT_ESP32C6: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG],
VARIANT_ESP32C61: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG],
VARIANT_ESP32H2: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG],
VARIANT_ESP32H4: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG],
VARIANT_ESP32H21: [UART0, UART1, USB_SERIAL_JTAG],
VARIANT_ESP32P4: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG],
VARIANT_ESP32S2: [UART0, UART1, USB_CDC],
VARIANT_ESP32S3: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG],
VARIANT_ESP32S31: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG],
}
@functools.cache
def _uart_selection_libretiny() -> dict[str, list[str]]:
from esphome.components.libretiny.const import (
COMPONENT_BK72XX,
COMPONENT_LN882X,
COMPONENT_RTL87XX,
)
return {
COMPONENT_BK72XX: [DEFAULT, UART1, UART2],
COMPONENT_LN882X: [DEFAULT, UART0, UART1, UART2],
COMPONENT_RTL87XX: [DEFAULT, UART0, UART1, UART2],
}
# These tables are introspected by external tooling (esphome/device-builder);
# built on access so the platform packages stay out of module import.
_LAZY_UART_SELECTION_TABLES = {
"UART_SELECTION_ESP32": _uart_selection_esp32,
"UART_SELECTION_LIBRETINY": _uart_selection_libretiny,
}
def __getattr__(name: str) -> dict[str, list[str]]:
if (table := _LAZY_UART_SELECTION_TABLES.get(name)) is not None:
return table()
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
def __dir__() -> list[str]:
return [*globals(), *_LAZY_UART_SELECTION_TABLES]
def uart_selection(value):
if CORE.is_esp32:
from esphome.components.esp32 import get_esp32_variant
uart_selection_esp32 = _uart_selection_esp32()
variant = get_esp32_variant()
if variant in UART_SELECTION_ESP32:
return cv.one_of(*UART_SELECTION_ESP32[variant], upper=True)(value)
if variant in uart_selection_esp32:
return cv.one_of(*uart_selection_esp32[variant], upper=True)(value)
if CORE.is_esp8266:
return cv.one_of(*UART_SELECTION_ESP8266, upper=True)(value)
if CORE.is_rp2:
return cv.one_of(*UART_SELECTION_RP2040, upper=True)(value)
if CORE.is_libretiny:
from esphome.components.libretiny import (
get_libretiny_component,
get_libretiny_family,
)
uart_selection_libretiny = _uart_selection_libretiny()
family = get_libretiny_family()
if family in UART_SELECTION_LIBRETINY:
return cv.one_of(*UART_SELECTION_LIBRETINY[family], upper=True)(value)
if family in uart_selection_libretiny:
return cv.one_of(*uart_selection_libretiny[family], upper=True)(value)
component = get_libretiny_component()
if component in UART_SELECTION_LIBRETINY:
return cv.one_of(*UART_SELECTION_LIBRETINY[component], upper=True)(value)
if component in uart_selection_libretiny:
return cv.one_of(*uart_selection_libretiny[component], upper=True)(value)
if CORE.is_host:
raise cv.Invalid("Uart selection not valid for host platform")
if CORE.is_nrf52:
@@ -384,6 +410,8 @@ async def _late_logger_init(config: ConfigType) -> None:
level = config[CONF_LEVEL]
baud_rate: int = config[CONF_BAUD_RATE]
if CORE.using_zephyr:
from esphome.components.zephyr import zephyr_add_prj_conf
task_log_buffer_size = config.get(CONF_TASK_LOG_BUFFER_SIZE, 0)
if task_log_buffer_size > 0:
zephyr_add_prj_conf("MPSC_PBUF", True)
@@ -451,6 +479,8 @@ async def _late_logger_init(config: ConfigType) -> None:
cg.add_build_flag("-DUSE_STORE_LOG_STR_IN_FLASH")
if CORE.is_esp32:
from esphome.components.esp32 import add_idf_sdkconfig_option
if config[CONF_HARDWARE_UART] == USB_CDC:
add_idf_sdkconfig_option("CONFIG_ESP_CONSOLE_USB_CDC", True)
cg.add_define("USE_LOGGER_UART_SELECTION_USB_CDC")
@@ -468,6 +498,11 @@ async def _late_logger_init(config: ConfigType) -> None:
and config[CONF_HARDWARE_UART] != USB_SERIAL_JTAG
and has_serial_logging
):
from esphome.components.esp32 import (
require_usb_serial_jtag_secondary,
require_vfs_termios,
)
require_usb_serial_jtag_secondary()
require_vfs_termios()
except cv.Invalid:
@@ -484,6 +519,12 @@ async def _late_logger_init(config: ConfigType) -> None:
cg.add_define("USE_LOGGER_EARLY_MESSAGE")
if CORE.is_nrf52:
from esphome.components.zephyr import (
zephyr_add_cdc_acm,
zephyr_add_overlay,
zephyr_add_prj_conf,
)
# esphome implement own fatal error handler which save PC/LR before reset
zephyr_add_prj_conf("RESET_ON_FATAL_ERROR", False)
zephyr_add_prj_conf("THREAD_LOCAL_STORAGE", True)
@@ -249,7 +249,7 @@ void WebServerOTAComponent::setup() {
return;
}
// AsyncWebServer takes ownership of the handler and will delete it when the server is destroyed
// The handler lives for the life of the process; WebServerBase never destroys its server
base->add_handler(new OTARequestHandler(this)); // NOLINT
}
@@ -112,9 +112,18 @@ class AuthMiddlewareHandler : public MiddlewareHandler {
class WebServerBase final {
public:
// The AsyncWebServer is created once and intentionally never deleted: on Arduino
// platforms ESPAsyncWebServer owns its registered handlers, so destroying it would
// also destroy live components (e.g. the captive portal) out from under us.
// init()/deinit() refcount users and start/stop the listener; handlers are
// registered once at creation and survive listener restarts.
void init() {
if (this->initialized_) {
this->initialized_++;
this->initialized_++;
if (this->server_ != nullptr) {
if (this->initialized_ == 1) {
// Restart the listener after a previous deinit()
this->server_->begin();
}
return;
}
this->server_ = new AsyncWebServer(this->port_);
@@ -126,14 +135,13 @@ class WebServerBase final {
for (auto *handler : this->handlers_)
this->server_->addHandler(handler);
this->initialized_++;
}
void deinit() {
if (this->initialized_ == 0)
return; // unbalanced deinit()
this->initialized_--;
if (this->initialized_ == 0) {
delete this->server_;
this->server_ = nullptr;
this->server_->end();
}
}
AsyncWebServer *get_server() const { return this->server_; }
+1 -1
View File
@@ -4,7 +4,7 @@ from enum import Enum
from esphome.enum import StrEnum
__version__ = "2026.8.0b2"
__version__ = "2026.9.0-dev"
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
VALID_SUBSTITUTIONS_CHARACTERS = (
+1 -1
View File
@@ -1,4 +1,4 @@
pylint==4.0.6
pylint==4.0.7
flake8==7.3.0 # also change in .pre-commit-config.yaml when updating
ruff==0.16.2 # also change in .pre-commit-config.yaml when updating
pyupgrade==3.21.2 # also change in .pre-commit-config.yaml when updating
@@ -0,0 +1,27 @@
"""Tests for the logger component's lazy platform tables."""
import sys
def test_uart_selection_tables_exposed_for_external_tooling() -> None:
"""device-builder introspects these names off the module; keep them
accessible (and discoverable) without importing the platform packages."""
for mod in ("esphome.components.esp32", "esphome.components.libretiny"):
sys.modules.pop(mod, None)
from esphome.components import logger
assert "UART_SELECTION_ESP32" in dir(logger)
assert "UART_SELECTION_LIBRETINY" in dir(logger)
# dir() must not import the platform packages; only table access may
assert "esphome.components.esp32" not in sys.modules
assert "esphome.components.libretiny" not in sys.modules
assert logger.UART_SELECTION_ESP32["ESP32C3"] == [
"UART0",
"UART1",
"USB_CDC",
"USB_SERIAL_JTAG",
]
assert "bk72xx" in logger.UART_SELECTION_LIBRETINY
# Repeated access returns the same cached object
assert logger.UART_SELECTION_ESP32 is logger.UART_SELECTION_ESP32