mirror of
https://github.com/esphome/esphome.git
synced 2026-02-03 19:19:40 -07:00
Compare commits
10 Commits
compact_st
...
esp32_ard_
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6de59fa246 | ||
|
|
ccf5c1f7e9 | ||
|
|
efecea9450 | ||
|
|
26e4cda610 | ||
|
|
dfcf611a67 | ||
|
|
f4be547d41 | ||
|
|
6e2f7a196f | ||
|
|
e2182b6227 | ||
|
|
77fa58541f | ||
|
|
49840ed4fa |
4
.github/workflows/codeql.yml
vendored
4
.github/workflows/codeql.yml
vendored
@@ -58,7 +58,7 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@b20883b0cd1f46c72ae0ba6d1090936928f9fa30 # v4.32.0
|
||||
uses: github/codeql-action/init@6bc82e05fd0ea64601dd4b465378bbcf57de0314 # v4.32.1
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
build-mode: ${{ matrix.build-mode }}
|
||||
@@ -86,6 +86,6 @@ jobs:
|
||||
exit 1
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@b20883b0cd1f46c72ae0ba6d1090936928f9fa30 # v4.32.0
|
||||
uses: github/codeql-action/analyze@6bc82e05fd0ea64601dd4b465378bbcf57de0314 # v4.32.1
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
|
||||
@@ -89,8 +89,9 @@ async def to_code(config):
|
||||
var.set_state_save_interval(config[CONF_STATE_SAVE_INTERVAL].total_milliseconds)
|
||||
)
|
||||
|
||||
# Although this component does not use SPI, the BSEC library requires the SPI library
|
||||
# Although this component does not use SPI/Wire directly, the BSEC library requires them
|
||||
cg.add_library("SPI", None)
|
||||
cg.add_library("Wire", None)
|
||||
|
||||
cg.add_define("USE_BSEC")
|
||||
cg.add_library("boschsensortec/BSEC Software Library", "1.6.1480")
|
||||
|
||||
@@ -5,6 +5,7 @@ import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import shutil
|
||||
|
||||
from esphome import yaml_util
|
||||
import esphome.codegen as cg
|
||||
@@ -50,6 +51,7 @@ from esphome.writer import clean_cmake_cache
|
||||
|
||||
from .boards import BOARDS, STANDARD_BOARDS
|
||||
from .const import ( # noqa
|
||||
KEY_ARDUINO_LIBRARIES,
|
||||
KEY_BOARD,
|
||||
KEY_COMPONENTS,
|
||||
KEY_ESP32,
|
||||
@@ -124,10 +126,14 @@ COMPILER_OPTIMIZATIONS = {
|
||||
# - "sdmmc": driver -> esp_driver_sdmmc -> sdmmc dependency chain
|
||||
DEFAULT_EXCLUDED_IDF_COMPONENTS = (
|
||||
"cmock", # Unit testing mock framework - ESPHome doesn't use IDF's testing
|
||||
"driver", # Legacy driver shim - only needed by esp32_touch, esp32_can for legacy headers
|
||||
"esp_adc", # ADC driver - only needed by adc component
|
||||
"esp_driver_dac", # DAC driver - only needed by esp32_dac component
|
||||
"esp_driver_i2s", # I2S driver - only needed by i2s_audio component
|
||||
"esp_driver_mcpwm", # MCPWM driver - ESPHome doesn't use motor control PWM
|
||||
"esp_driver_rmt", # RMT driver - only needed by remote_transmitter/receiver, neopixelbus
|
||||
"esp_driver_touch_sens", # Touch sensor driver - only needed by esp32_touch
|
||||
"esp_driver_twai", # TWAI/CAN driver - only needed by esp32_can component
|
||||
"esp_eth", # Ethernet driver - only needed by ethernet component
|
||||
"esp_hid", # HID host/device support - ESPHome doesn't implement HID functionality
|
||||
"esp_http_client", # HTTP client - only needed by http_request component
|
||||
@@ -138,14 +144,178 @@ DEFAULT_EXCLUDED_IDF_COMPONENTS = (
|
||||
"espcoredump", # Core dump support - ESPHome has its own debug component
|
||||
"fatfs", # FAT filesystem - ESPHome doesn't use filesystem storage
|
||||
"mqtt", # ESP-IDF MQTT library - ESPHome has its own MQTT implementation
|
||||
"openthread", # Thread protocol - only needed by openthread component
|
||||
"perfmon", # Xtensa performance monitor - ESPHome has its own debug component
|
||||
"protocomm", # Protocol communication for provisioning - unused by ESPHome
|
||||
"spiffs", # SPIFFS filesystem - ESPHome doesn't use filesystem storage (IDF only)
|
||||
"ulp", # ULP coprocessor - not currently used by any ESPHome component
|
||||
"unity", # Unit testing framework - ESPHome doesn't use IDF's testing
|
||||
"wear_levelling", # Flash wear levelling for fatfs - unused since fatfs unused
|
||||
"wifi_provisioning", # WiFi provisioning - ESPHome uses its own improv implementation
|
||||
)
|
||||
|
||||
# Additional IDF managed components to exclude for Arduino framework builds
|
||||
# These are pulled in by the Arduino framework's idf_component.yml but not used by ESPHome
|
||||
# Note: Component names include the namespace prefix (e.g., "espressif__cbor") because
|
||||
# that's how managed components are registered in the IDF build system
|
||||
# List includes direct dependencies from arduino-esp32/idf_component.yml
|
||||
# plus transitive dependencies from RainMaker/Insights (except espressif/mdns which we need)
|
||||
ARDUINO_EXCLUDED_IDF_COMPONENTS = (
|
||||
"chmorgan__esp-libhelix-mp3", # MP3 decoder - not used
|
||||
"espressif__cbor", # CBOR library - only used by RainMaker/Insights
|
||||
"espressif__esp-dsp", # DSP library - not used
|
||||
"espressif__esp-modbus", # Modbus - ESPHome has its own
|
||||
"espressif__esp-sr", # Speech recognition - not used
|
||||
"espressif__esp-zboss-lib", # Zigbee ZBOSS library - not used
|
||||
"espressif__esp-zigbee-lib", # Zigbee library - not used
|
||||
"espressif__esp_diag_data_store", # Diagnostics - not used
|
||||
"espressif__esp_diagnostics", # Diagnostics - not used
|
||||
"espressif__esp_hosted", # ESP hosted - only for ESP32-P4
|
||||
"espressif__esp_insights", # ESP Insights - not used
|
||||
"espressif__esp_modem", # Modem library - not used
|
||||
"espressif__esp_rainmaker", # RainMaker - not used
|
||||
"espressif__esp_rcp_update", # RCP update - RainMaker transitive dep
|
||||
"espressif__esp_schedule", # Schedule - RainMaker transitive dep
|
||||
"espressif__esp_secure_cert_mgr", # Secure cert - RainMaker transitive dep
|
||||
"espressif__esp_wifi_remote", # WiFi remote - only for ESP32-P4
|
||||
"espressif__json_generator", # JSON generator - RainMaker transitive dep
|
||||
"espressif__json_parser", # JSON parser - RainMaker transitive dep
|
||||
"espressif__lan867x", # Ethernet PHY - ESPHome uses ESP-IDF ethernet directly
|
||||
"espressif__libsodium", # Crypto - ESPHome uses its own noise-c library
|
||||
"espressif__network_provisioning", # Network provisioning - not used
|
||||
"espressif__qrcode", # QR code - not used
|
||||
"espressif__rmaker_common", # RainMaker common - not used
|
||||
"joltwallet__littlefs", # LittleFS - ESPHome doesn't use filesystem
|
||||
)
|
||||
|
||||
# Mapping of Arduino libraries to IDF managed components they require
|
||||
# When an Arduino library is enabled via cg.add_library(), these components
|
||||
# are automatically un-stubbed from ARDUINO_EXCLUDED_IDF_COMPONENTS.
|
||||
#
|
||||
# Note: Some libraries (Matter, LittleFS, ESP_SR, WiFiProv, ArduinoOTA) already have
|
||||
# conditional maybe_add_component() calls in arduino-esp32/CMakeLists.txt that handle
|
||||
# their managed component dependencies. Our mapping is primarily needed for libraries
|
||||
# that don't have such conditionals (Ethernet, PPP, Zigbee, RainMaker, Insights, etc.)
|
||||
# and to ensure the stubs are removed from our idf_component.yml overrides.
|
||||
ARDUINO_LIBRARY_IDF_COMPONENTS: dict[str, tuple[str, ...]] = {
|
||||
"BLE": ("esp_driver_gptimer",),
|
||||
"BluetoothSerial": ("esp_driver_gptimer",),
|
||||
"ESP_HostedOTA": ("espressif__esp_hosted", "espressif__esp_wifi_remote"),
|
||||
"ESP_SR": ("espressif__esp-sr",),
|
||||
"Ethernet": ("espressif__lan867x",),
|
||||
"FFat": ("fatfs",),
|
||||
"Insights": (
|
||||
"espressif__cbor",
|
||||
"espressif__esp_insights",
|
||||
"espressif__esp_diagnostics",
|
||||
"espressif__esp_diag_data_store",
|
||||
"espressif__rmaker_common", # Transitive dep from esp_insights
|
||||
),
|
||||
"LittleFS": ("joltwallet__littlefs",),
|
||||
"Matter": ("espressif__esp_matter",),
|
||||
"PPP": ("espressif__esp_modem",),
|
||||
"RainMaker": (
|
||||
# Direct deps from idf_component.yml
|
||||
"espressif__cbor",
|
||||
"espressif__esp_rainmaker",
|
||||
"espressif__esp_insights",
|
||||
"espressif__esp_diagnostics",
|
||||
"espressif__esp_diag_data_store",
|
||||
"espressif__rmaker_common",
|
||||
"espressif__qrcode",
|
||||
# Transitive deps from esp_rainmaker
|
||||
"espressif__esp_rcp_update",
|
||||
"espressif__esp_schedule",
|
||||
"espressif__esp_secure_cert_mgr",
|
||||
"espressif__json_generator",
|
||||
"espressif__json_parser",
|
||||
"espressif__network_provisioning",
|
||||
),
|
||||
"SD": ("fatfs",),
|
||||
"SD_MMC": ("fatfs",),
|
||||
"SPIFFS": ("spiffs",),
|
||||
"WiFiProv": ("espressif__network_provisioning", "espressif__qrcode"),
|
||||
"Zigbee": ("espressif__esp-zigbee-lib", "espressif__esp-zboss-lib"),
|
||||
}
|
||||
|
||||
# Arduino library to Arduino library dependencies
|
||||
# When enabling one library, also enable its dependencies
|
||||
# Kconfig "select" statements don't work with CONFIG_ARDUINO_SELECTIVE_COMPILATION
|
||||
ARDUINO_LIBRARY_DEPENDENCIES: dict[str, tuple[str, ...]] = {
|
||||
"Ethernet": ("Network",),
|
||||
"WiFi": ("Network",),
|
||||
}
|
||||
|
||||
|
||||
def _idf_component_stub_name(component: str) -> str:
|
||||
"""Get stub directory name from IDF component name.
|
||||
|
||||
Component names are typically namespace__name (e.g., espressif__cbor).
|
||||
Returns just the name part (e.g., cbor). If no namespace is present,
|
||||
returns the original component name.
|
||||
"""
|
||||
_prefix, sep, suffix = component.partition("__")
|
||||
return suffix if sep else component
|
||||
|
||||
|
||||
def _idf_component_dep_name(component: str) -> str:
|
||||
"""Convert IDF component name to dependency format.
|
||||
|
||||
Converts espressif__cbor to espressif/cbor.
|
||||
"""
|
||||
return component.replace("__", "/")
|
||||
|
||||
|
||||
# Arduino libraries to disable by default when using Arduino framework
|
||||
# ESPHome uses ESP-IDF APIs directly; we only need the Arduino core
|
||||
# (HardwareSerial, Print, Stream, GPIO functions which are always compiled)
|
||||
# Components use cg.add_library() which auto-enables any they need
|
||||
# This list must match ARDUINO_ALL_LIBRARIES from arduino-esp32/CMakeLists.txt
|
||||
ARDUINO_DISABLED_LIBRARIES: frozenset[str] = frozenset(
|
||||
{
|
||||
"ArduinoOTA",
|
||||
"AsyncUDP",
|
||||
"BLE",
|
||||
"BluetoothSerial",
|
||||
"DNSServer",
|
||||
"EEPROM",
|
||||
"ESP_HostedOTA",
|
||||
"ESP_I2S",
|
||||
"ESP_NOW",
|
||||
"ESP_SR",
|
||||
"ESPmDNS",
|
||||
"Ethernet",
|
||||
"FFat",
|
||||
"FS",
|
||||
"Hash",
|
||||
"HTTPClient",
|
||||
"HTTPUpdate",
|
||||
"Insights",
|
||||
"LittleFS",
|
||||
"Matter",
|
||||
"NetBIOS",
|
||||
"Network",
|
||||
"NetworkClientSecure",
|
||||
"OpenThread",
|
||||
"PPP",
|
||||
"Preferences",
|
||||
"RainMaker",
|
||||
"SD",
|
||||
"SD_MMC",
|
||||
"SimpleBLE",
|
||||
"SPI",
|
||||
"SPIFFS",
|
||||
"Ticker",
|
||||
"Update",
|
||||
"USB",
|
||||
"WebServer",
|
||||
"WiFi",
|
||||
"WiFiProv",
|
||||
"Wire",
|
||||
"Zigbee",
|
||||
}
|
||||
)
|
||||
|
||||
# ESP32 (original) chip revision options
|
||||
# Setting minimum revision to 3.0 or higher:
|
||||
# - Reduces flash size by excluding workaround code for older chip bugs
|
||||
@@ -237,7 +407,13 @@ def set_core_data(config):
|
||||
CORE.data[KEY_ESP32][KEY_COMPONENTS] = {}
|
||||
# Initialize with default exclusions - components can call include_builtin_idf_component()
|
||||
# to re-enable any they need
|
||||
CORE.data[KEY_ESP32][KEY_EXCLUDE_COMPONENTS] = set(DEFAULT_EXCLUDED_IDF_COMPONENTS)
|
||||
excluded = set(DEFAULT_EXCLUDED_IDF_COMPONENTS)
|
||||
# Add Arduino-specific managed component exclusions when using Arduino framework
|
||||
if conf[CONF_TYPE] == FRAMEWORK_ARDUINO:
|
||||
excluded.update(ARDUINO_EXCLUDED_IDF_COMPONENTS)
|
||||
CORE.data[KEY_ESP32][KEY_EXCLUDE_COMPONENTS] = excluded
|
||||
# Initialize Arduino library tracking - cg.add_library() auto-enables libraries
|
||||
CORE.data[KEY_ESP32][KEY_ARDUINO_LIBRARIES] = set()
|
||||
CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = cv.Version.parse(
|
||||
config[CONF_FRAMEWORK][CONF_VERSION]
|
||||
)
|
||||
@@ -385,6 +561,26 @@ def include_builtin_idf_component(name: str) -> None:
|
||||
CORE.data[KEY_ESP32][KEY_EXCLUDE_COMPONENTS].discard(name)
|
||||
|
||||
|
||||
def _enable_arduino_library(name: str) -> None:
|
||||
"""Enable an Arduino library that is disabled by default.
|
||||
|
||||
This is called automatically by CORE.add_library() when a component adds
|
||||
an Arduino library via cg.add_library(). Components should not call this
|
||||
directly - just use cg.add_library("LibName", None).
|
||||
|
||||
Args:
|
||||
name: The library name (e.g., "Wire", "SPI", "WiFi")
|
||||
"""
|
||||
enabled_libs: set[str] = CORE.data[KEY_ESP32][KEY_ARDUINO_LIBRARIES]
|
||||
enabled_libs.add(name)
|
||||
# Also enable any required Arduino library dependencies
|
||||
for dep_lib in ARDUINO_LIBRARY_DEPENDENCIES.get(name, ()):
|
||||
enabled_libs.add(dep_lib)
|
||||
# Also enable any required IDF components
|
||||
for idf_component in ARDUINO_LIBRARY_IDF_COMPONENTS.get(name, ()):
|
||||
include_builtin_idf_component(idf_component)
|
||||
|
||||
|
||||
def add_extra_script(stage: str, filename: str, path: Path):
|
||||
"""Add an extra script to the project."""
|
||||
key = f"{stage}:{filename}"
|
||||
@@ -1126,6 +1322,27 @@ async def _write_exclude_components() -> None:
|
||||
)
|
||||
|
||||
|
||||
@coroutine_with_priority(CoroPriority.FINAL)
|
||||
async def _write_arduino_libraries_sdkconfig() -> None:
|
||||
"""Write Arduino selective compilation sdkconfig after all components have added libraries.
|
||||
|
||||
This must run at FINAL priority so that all components have had a chance to call
|
||||
cg.add_library() which auto-enables Arduino libraries via _enable_arduino_library().
|
||||
"""
|
||||
if KEY_ESP32 not in CORE.data:
|
||||
return
|
||||
# Enable Arduino selective compilation to disable unused Arduino libraries
|
||||
# ESPHome uses ESP-IDF APIs directly; we only need the Arduino core
|
||||
# (HardwareSerial, Print, Stream, GPIO functions which are always compiled)
|
||||
# cg.add_library() auto-enables needed libraries; users can also add
|
||||
# libraries via esphome: libraries: config which calls cg.add_library()
|
||||
add_idf_sdkconfig_option("CONFIG_ARDUINO_SELECTIVE_COMPILATION", True)
|
||||
enabled_libs = CORE.data[KEY_ESP32].get(KEY_ARDUINO_LIBRARIES, set())
|
||||
for lib in ARDUINO_DISABLED_LIBRARIES:
|
||||
# Enable if explicitly requested, disable otherwise
|
||||
add_idf_sdkconfig_option(f"CONFIG_ARDUINO_SELECTIVE_{lib}", lib in enabled_libs)
|
||||
|
||||
|
||||
@coroutine_with_priority(CoroPriority.FINAL)
|
||||
async def _add_yaml_idf_components(components: list[ConfigType]):
|
||||
"""Add IDF components from YAML config with final priority to override code-added components."""
|
||||
@@ -1544,6 +1761,11 @@ async def to_code(config):
|
||||
# Default exclusions are added in set_core_data() during config validation.
|
||||
CORE.add_job(_write_exclude_components)
|
||||
|
||||
# Write Arduino selective compilation sdkconfig at FINAL priority after all
|
||||
# components have had a chance to call cg.add_library() to enable libraries they need.
|
||||
if conf[CONF_TYPE] == FRAMEWORK_ARDUINO:
|
||||
CORE.add_job(_write_arduino_libraries_sdkconfig)
|
||||
|
||||
|
||||
APP_PARTITION_SIZES = {
|
||||
"2MB": 0x0C0000, # 768 KB
|
||||
@@ -1624,11 +1846,49 @@ def _write_sdkconfig():
|
||||
|
||||
def _write_idf_component_yml():
|
||||
yml_path = CORE.relative_build_path("src/idf_component.yml")
|
||||
dependencies: dict[str, dict] = {}
|
||||
|
||||
# For Arduino builds, override unused managed components from the Arduino framework
|
||||
# by pointing them to empty stub directories using override_path
|
||||
# This prevents the IDF component manager from downloading the real components
|
||||
if CORE.using_arduino:
|
||||
# Determine which IDF components are needed by enabled Arduino libraries
|
||||
enabled_libs = CORE.data[KEY_ESP32].get(KEY_ARDUINO_LIBRARIES, set())
|
||||
required_idf_components = {
|
||||
comp
|
||||
for lib in enabled_libs
|
||||
for comp in ARDUINO_LIBRARY_IDF_COMPONENTS.get(lib, ())
|
||||
}
|
||||
|
||||
# Only stub components that are not required by any enabled Arduino library
|
||||
components_to_stub = (
|
||||
set(ARDUINO_EXCLUDED_IDF_COMPONENTS) - required_idf_components
|
||||
)
|
||||
|
||||
stubs_dir = CORE.relative_build_path("component_stubs")
|
||||
stubs_dir.mkdir(exist_ok=True)
|
||||
for component_name in components_to_stub:
|
||||
# Create stub directory with minimal CMakeLists.txt
|
||||
stub_path = stubs_dir / _idf_component_stub_name(component_name)
|
||||
stub_path.mkdir(exist_ok=True)
|
||||
stub_cmake = stub_path / "CMakeLists.txt"
|
||||
if not stub_cmake.exists():
|
||||
stub_cmake.write_text("idf_component_register()\n")
|
||||
dependencies[_idf_component_dep_name(component_name)] = {
|
||||
"version": "*",
|
||||
"override_path": str(stub_path),
|
||||
}
|
||||
|
||||
# Remove stubs for components that are now required by enabled libraries
|
||||
for component_name in required_idf_components:
|
||||
stub_path = stubs_dir / _idf_component_stub_name(component_name)
|
||||
if stub_path.exists():
|
||||
shutil.rmtree(stub_path)
|
||||
|
||||
if CORE.data[KEY_ESP32][KEY_COMPONENTS]:
|
||||
components: dict = CORE.data[KEY_ESP32][KEY_COMPONENTS]
|
||||
dependencies = {}
|
||||
for name, component in components.items():
|
||||
dependency = {}
|
||||
dependency: dict[str, str] = {}
|
||||
if component[KEY_REF]:
|
||||
dependency["version"] = component[KEY_REF]
|
||||
if component[KEY_REPO]:
|
||||
@@ -1636,9 +1896,8 @@ def _write_idf_component_yml():
|
||||
if component[KEY_PATH]:
|
||||
dependency["path"] = component[KEY_PATH]
|
||||
dependencies[name] = dependency
|
||||
contents = yaml_util.dump({"dependencies": dependencies})
|
||||
else:
|
||||
contents = ""
|
||||
|
||||
contents = yaml_util.dump({"dependencies": dependencies}) if dependencies else ""
|
||||
if write_file_if_changed(yml_path, contents):
|
||||
dependencies_lock = CORE.relative_build_path("dependencies.lock")
|
||||
if dependencies_lock.is_file():
|
||||
|
||||
@@ -7,6 +7,7 @@ KEY_VARIANT = "variant"
|
||||
KEY_SDKCONFIG_OPTIONS = "sdkconfig_options"
|
||||
KEY_COMPONENTS = "components"
|
||||
KEY_EXCLUDE_COMPONENTS = "exclude_components"
|
||||
KEY_ARDUINO_LIBRARIES = "arduino_libraries"
|
||||
KEY_REPO = "repo"
|
||||
KEY_REF = "ref"
|
||||
KEY_REFRESH = "refresh"
|
||||
|
||||
@@ -15,6 +15,7 @@ from esphome.components.esp32 import (
|
||||
VARIANT_ESP32S2,
|
||||
VARIANT_ESP32S3,
|
||||
get_esp32_variant,
|
||||
include_builtin_idf_component,
|
||||
)
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
@@ -121,6 +122,10 @@ def get_default_tx_enqueue_timeout(bit_rate):
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
# Legacy driver component provides driver/twai.h header
|
||||
include_builtin_idf_component("driver")
|
||||
# Also enable esp_driver_twai for future migration to new API
|
||||
include_builtin_idf_component("esp_driver_twai")
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await canbus.register_canbus(var, config)
|
||||
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
from esphome import pins
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import output
|
||||
from esphome.components.esp32 import VARIANT_ESP32, VARIANT_ESP32S2, get_esp32_variant
|
||||
from esphome.components.esp32 import (
|
||||
VARIANT_ESP32,
|
||||
VARIANT_ESP32S2,
|
||||
get_esp32_variant,
|
||||
include_builtin_idf_component,
|
||||
)
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_NUMBER, CONF_PIN
|
||||
|
||||
@@ -38,6 +43,7 @@ CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend(
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
include_builtin_idf_component("esp_driver_dac")
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await output.register_output(var, config)
|
||||
|
||||
@@ -269,6 +269,8 @@ CONFIG_SCHEMA = cv.All(
|
||||
async def to_code(config):
|
||||
# Re-enable ESP-IDF's touch sensor driver (excluded by default to save compile time)
|
||||
include_builtin_idf_component("esp_driver_touch_sens")
|
||||
# Legacy driver component provides driver/touch_sensor.h header
|
||||
include_builtin_idf_component("driver")
|
||||
|
||||
touch = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(touch, config)
|
||||
|
||||
@@ -13,7 +13,7 @@ from esphome.const import (
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_WIFI,
|
||||
)
|
||||
from esphome.core import CORE, HexInt
|
||||
from esphome.core import HexInt
|
||||
from esphome.types import ConfigType
|
||||
|
||||
CODEOWNERS = ["@jesserockz"]
|
||||
@@ -124,9 +124,6 @@ async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
if CORE.using_arduino:
|
||||
cg.add_library("WiFi", None)
|
||||
|
||||
# ESP-NOW uses wake_loop_threadsafe() to wake the main loop from ESP-NOW callbacks
|
||||
# This enables low-latency event processing instead of waiting for select() timeout
|
||||
socket.require_wake_loop_threadsafe()
|
||||
|
||||
@@ -431,9 +431,6 @@ async def to_code(config):
|
||||
# Add LAN867x 10BASE-T1S PHY support component
|
||||
add_idf_component(name="espressif/lan867x", ref="2.0.0")
|
||||
|
||||
if CORE.using_arduino:
|
||||
cg.add_library("WiFi", None)
|
||||
|
||||
if on_connect_config := config.get(CONF_ON_CONNECT):
|
||||
cg.add_define("USE_ETHERNET_CONNECT_TRIGGER")
|
||||
await automation.build_automation(
|
||||
|
||||
@@ -114,6 +114,7 @@ async def to_code(config):
|
||||
cg.add(var.set_external_dac_channels(2 if config[CONF_MODE] == "stereo" else 1))
|
||||
cg.add(var.set_i2s_comm_fmt_lsb(config[CONF_I2S_COMM_FMT] == "lsb"))
|
||||
|
||||
cg.add_library("WiFi", None)
|
||||
cg.add_library("NetworkClientSecure", None)
|
||||
cg.add_library("HTTPClient", None)
|
||||
cg.add_library("esphome/ESP32-audioI2S", "2.3.0")
|
||||
|
||||
@@ -267,26 +267,16 @@ bool ImprovSerialComponent::parse_improv_payload_(improv::ImprovCommand &command
|
||||
for (auto &scan : results) {
|
||||
if (scan.get_is_hidden())
|
||||
continue;
|
||||
const char *ssid_cstr = scan.get_ssid().c_str();
|
||||
// Check if we've already sent this SSID
|
||||
bool duplicate = false;
|
||||
for (const auto &seen : networks) {
|
||||
if (strcmp(seen.c_str(), ssid_cstr) == 0) {
|
||||
duplicate = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (duplicate)
|
||||
const std::string &ssid = scan.get_ssid();
|
||||
if (std::find(networks.begin(), networks.end(), ssid) != networks.end())
|
||||
continue;
|
||||
// Only allocate std::string after confirming it's not a duplicate
|
||||
std::string ssid(ssid_cstr);
|
||||
// Send each ssid separately to avoid overflowing the buffer
|
||||
char rssi_buf[5]; // int8_t: -128 to 127, max 4 chars + null
|
||||
*int8_to_str(rssi_buf, scan.get_rssi()) = '\0';
|
||||
std::vector<uint8_t> data =
|
||||
improv::build_rpc_response(improv::GET_WIFI_NETWORKS, {ssid, rssi_buf, YESNO(scan.get_with_auth())}, false);
|
||||
this->send_response_(data);
|
||||
networks.push_back(std::move(ssid));
|
||||
networks.push_back(ssid);
|
||||
}
|
||||
// Send empty response to signify the end of the list.
|
||||
std::vector<uint8_t> data =
|
||||
|
||||
@@ -128,22 +128,7 @@ void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const ch
|
||||
// Note: USE_STORE_LOG_STR_IN_FLASH is only defined for ESP8266.
|
||||
//
|
||||
// This function handles format strings stored in flash memory (PROGMEM) to save RAM.
|
||||
// The buffer is used in a special way to avoid allocating extra memory:
|
||||
//
|
||||
// Memory layout during execution:
|
||||
// Step 1: Copy format string from flash to buffer
|
||||
// tx_buffer_: [format_string][null][.....................]
|
||||
// tx_buffer_at_: ------------------^
|
||||
// msg_start: saved here -----------^
|
||||
//
|
||||
// Step 2: format_log_to_buffer_with_terminator_ reads format string from beginning
|
||||
// and writes formatted output starting at msg_start position
|
||||
// tx_buffer_: [format_string][null][formatted_message][null]
|
||||
// tx_buffer_at_: -------------------------------------^
|
||||
//
|
||||
// Step 3: Output the formatted message (starting at msg_start)
|
||||
// write_msg_ and callbacks receive: this->tx_buffer_ + msg_start
|
||||
// which points to: [formatted_message][null]
|
||||
// Uses vsnprintf_P to read the format string directly from flash without copying to RAM.
|
||||
//
|
||||
void Logger::log_vprintf_(uint8_t level, const char *tag, int line, const __FlashStringHelper *format,
|
||||
va_list args) { // NOLINT
|
||||
@@ -153,35 +138,25 @@ void Logger::log_vprintf_(uint8_t level, const char *tag, int line, const __Flas
|
||||
RecursionGuard guard(global_recursion_guard_);
|
||||
this->tx_buffer_at_ = 0;
|
||||
|
||||
// Copy format string from progmem
|
||||
auto *format_pgm_p = reinterpret_cast<const uint8_t *>(format);
|
||||
char ch = '.';
|
||||
while (this->tx_buffer_at_ < this->tx_buffer_size_ && ch != '\0') {
|
||||
this->tx_buffer_[this->tx_buffer_at_++] = ch = (char) progmem_read_byte(format_pgm_p++);
|
||||
}
|
||||
// Write header, format body directly from flash, and write footer
|
||||
this->write_header_to_buffer_(level, tag, line, nullptr, this->tx_buffer_, &this->tx_buffer_at_,
|
||||
this->tx_buffer_size_);
|
||||
this->format_body_to_buffer_P_(this->tx_buffer_, &this->tx_buffer_at_, this->tx_buffer_size_,
|
||||
reinterpret_cast<PGM_P>(format), args);
|
||||
this->write_footer_to_buffer_(this->tx_buffer_, &this->tx_buffer_at_, this->tx_buffer_size_);
|
||||
|
||||
// Buffer full from copying format - RAII guard handles cleanup on return
|
||||
if (this->tx_buffer_at_ >= this->tx_buffer_size_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Save the offset before calling format_log_to_buffer_with_terminator_
|
||||
// since it will increment tx_buffer_at_ to the end of the formatted string
|
||||
uint16_t msg_start = this->tx_buffer_at_;
|
||||
this->format_log_to_buffer_with_terminator_(level, tag, line, this->tx_buffer_, args, this->tx_buffer_,
|
||||
&this->tx_buffer_at_, this->tx_buffer_size_);
|
||||
|
||||
uint16_t msg_length =
|
||||
this->tx_buffer_at_ - msg_start; // Don't subtract 1 - tx_buffer_at_ is already at the null terminator position
|
||||
// Ensure null termination
|
||||
uint16_t null_pos = this->tx_buffer_at_ >= this->tx_buffer_size_ ? this->tx_buffer_size_ - 1 : this->tx_buffer_at_;
|
||||
this->tx_buffer_[null_pos] = '\0';
|
||||
|
||||
// Listeners get message first (before console write)
|
||||
#ifdef USE_LOG_LISTENERS
|
||||
for (auto *listener : this->log_listeners_)
|
||||
listener->on_log(level, tag, this->tx_buffer_ + msg_start, msg_length);
|
||||
listener->on_log(level, tag, this->tx_buffer_, this->tx_buffer_at_);
|
||||
#endif
|
||||
|
||||
// Write to console starting at the msg_start
|
||||
this->write_tx_buffer_to_console_(msg_start, &msg_length);
|
||||
// Write to console
|
||||
this->write_tx_buffer_to_console_();
|
||||
}
|
||||
#endif // USE_STORE_LOG_STR_IN_FLASH
|
||||
|
||||
|
||||
@@ -597,31 +597,40 @@ class Logger : public Component {
|
||||
*buffer_at = pos;
|
||||
}
|
||||
|
||||
// Helper to process vsnprintf return value and strip trailing newlines.
|
||||
// Updates buffer_at with the formatted length, handling truncation:
|
||||
// - When vsnprintf truncates (ret >= remaining), it writes (remaining - 1) chars + null terminator
|
||||
// - When it doesn't truncate (ret < remaining), it writes ret chars + null terminator
|
||||
__attribute__((always_inline)) static inline void process_vsnprintf_result(const char *buffer, uint16_t *buffer_at,
|
||||
uint16_t remaining, int ret) {
|
||||
if (ret < 0)
|
||||
return; // Encoding error, do not increment buffer_at
|
||||
*buffer_at += (ret >= remaining) ? (remaining - 1) : static_cast<uint16_t>(ret);
|
||||
// Remove all trailing newlines right after formatting
|
||||
while (*buffer_at > 0 && buffer[*buffer_at - 1] == '\n')
|
||||
(*buffer_at)--;
|
||||
}
|
||||
|
||||
inline void HOT format_body_to_buffer_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size, const char *format,
|
||||
va_list args) {
|
||||
// Get remaining capacity in the buffer
|
||||
// Check remaining capacity in the buffer
|
||||
if (*buffer_at >= buffer_size)
|
||||
return;
|
||||
const uint16_t remaining = buffer_size - *buffer_at;
|
||||
|
||||
const int ret = vsnprintf(buffer + *buffer_at, remaining, format, args);
|
||||
|
||||
if (ret < 0) {
|
||||
return; // Encoding error, do not increment buffer_at
|
||||
}
|
||||
|
||||
// Update buffer_at with the formatted length (handle truncation)
|
||||
// When vsnprintf truncates (ret >= remaining), it writes (remaining - 1) chars + null terminator
|
||||
// When it doesn't truncate (ret < remaining), it writes ret chars + null terminator
|
||||
uint16_t formatted_len = (ret >= remaining) ? (remaining - 1) : ret;
|
||||
*buffer_at += formatted_len;
|
||||
|
||||
// Remove all trailing newlines right after formatting
|
||||
while (*buffer_at > 0 && buffer[*buffer_at - 1] == '\n') {
|
||||
(*buffer_at)--;
|
||||
}
|
||||
process_vsnprintf_result(buffer, buffer_at, remaining, vsnprintf(buffer + *buffer_at, remaining, format, args));
|
||||
}
|
||||
|
||||
#ifdef USE_STORE_LOG_STR_IN_FLASH
|
||||
// ESP8266 variant that reads format string directly from flash using vsnprintf_P
|
||||
inline void HOT format_body_to_buffer_P_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size, PGM_P format,
|
||||
va_list args) {
|
||||
if (*buffer_at >= buffer_size)
|
||||
return;
|
||||
const uint16_t remaining = buffer_size - *buffer_at;
|
||||
process_vsnprintf_result(buffer, buffer_at, remaining, vsnprintf_P(buffer + *buffer_at, remaining, format, args));
|
||||
}
|
||||
#endif
|
||||
|
||||
inline void HOT write_footer_to_buffer_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size) {
|
||||
static constexpr uint16_t RESET_COLOR_LEN = sizeof(ESPHOME_LOG_RESET_COLOR) - 1;
|
||||
this->write_body_to_buffer_(ESPHOME_LOG_RESET_COLOR, RESET_COLOR_LEN, buffer, buffer_at, buffer_size);
|
||||
|
||||
@@ -30,7 +30,7 @@ from esphome.const import (
|
||||
UNIT_PERCENT,
|
||||
UNIT_WATT,
|
||||
)
|
||||
from esphome.core import coroutine
|
||||
from esphome.core import CORE, coroutine
|
||||
|
||||
CODEOWNERS = ["@dudanov"]
|
||||
DEPENDENCIES = ["climate", "uart"]
|
||||
@@ -290,4 +290,7 @@ async def to_code(config):
|
||||
if CONF_HUMIDITY_SETPOINT in config:
|
||||
sens = await sensor.new_sensor(config[CONF_HUMIDITY_SETPOINT])
|
||||
cg.add(var.set_humidity_setpoint_sensor(sens))
|
||||
# MideaUART library requires WiFi (WiFi auto-enables Network via dependency mapping)
|
||||
if CORE.is_esp32:
|
||||
cg.add_library("WiFi", None)
|
||||
cg.add_library("dudanov/MideaUART", "1.1.9")
|
||||
|
||||
@@ -137,8 +137,7 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
@coroutine_with_priority(CoroPriority.NETWORK)
|
||||
async def to_code(config):
|
||||
cg.add_define("USE_NETWORK")
|
||||
if CORE.using_arduino and CORE.is_esp32:
|
||||
cg.add_library("Networking", None)
|
||||
# ESP32 with Arduino uses ESP-IDF network APIs directly, no Arduino Network library needed
|
||||
|
||||
# Apply high performance networking settings
|
||||
# Config can explicitly enable/disable, or default to component-driven behavior
|
||||
|
||||
@@ -4,6 +4,7 @@ from esphome.components.esp32 import (
|
||||
VARIANT_ESP32C6,
|
||||
VARIANT_ESP32H2,
|
||||
add_idf_sdkconfig_option,
|
||||
include_builtin_idf_component,
|
||||
only_on_variant,
|
||||
require_vfs_select,
|
||||
)
|
||||
@@ -172,6 +173,9 @@ FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
# Re-enable openthread IDF component (excluded by default)
|
||||
include_builtin_idf_component("openthread")
|
||||
|
||||
cg.add_define("USE_OPENTHREAD")
|
||||
|
||||
# OpenThread SRP needs access to mDNS services after setup
|
||||
|
||||
@@ -38,11 +38,8 @@ async def to_code(config):
|
||||
cg.add_define("WEB_SERVER_DEFAULT_HEADERS_COUNT", 1)
|
||||
return
|
||||
|
||||
# ESP32 uses IDF web server (early return above), so this is for other Arduino platforms
|
||||
if CORE.using_arduino:
|
||||
if CORE.is_esp32:
|
||||
cg.add_library("WiFi", None)
|
||||
cg.add_library("FS", None)
|
||||
cg.add_library("Update", None)
|
||||
if CORE.is_esp8266:
|
||||
cg.add_library("ESP8266WiFi", None)
|
||||
if CORE.is_libretiny:
|
||||
|
||||
@@ -351,7 +351,7 @@ bool WiFiComponent::needs_scan_results_() const {
|
||||
return this->scan_result_.empty() || !this->scan_result_[0].get_matches();
|
||||
}
|
||||
|
||||
bool WiFiComponent::ssid_was_seen_in_scan_(const CompactString &ssid) const {
|
||||
bool WiFiComponent::ssid_was_seen_in_scan_(const std::string &ssid) const {
|
||||
// Check if this SSID is configured as hidden
|
||||
// If explicitly marked hidden, we should always try hidden mode regardless of scan results
|
||||
for (const auto &conf : this->sta_) {
|
||||
@@ -955,12 +955,9 @@ WiFiAP WiFiComponent::get_sta() const {
|
||||
return config ? *config : WiFiAP{};
|
||||
}
|
||||
void WiFiComponent::save_wifi_sta(const std::string &ssid, const std::string &password) {
|
||||
this->save_wifi_sta(ssid.c_str(), password.c_str());
|
||||
}
|
||||
void WiFiComponent::save_wifi_sta(const char *ssid, const char *password) {
|
||||
SavedWifiSettings save{}; // zero-initialized - all bytes set to \0, guaranteeing null termination
|
||||
strncpy(save.ssid, ssid, sizeof(save.ssid) - 1); // max 32 chars, byte 32 remains \0
|
||||
strncpy(save.password, password, sizeof(save.password) - 1); // max 64 chars, byte 64 remains \0
|
||||
strncpy(save.ssid, ssid.c_str(), sizeof(save.ssid) - 1); // max 32 chars, byte 32 remains \0
|
||||
strncpy(save.password, password.c_str(), sizeof(save.password) - 1); // max 64 chars, byte 64 remains \0
|
||||
this->pref_.save(&save);
|
||||
// ensure it's written immediately
|
||||
global_preferences->sync();
|
||||
@@ -1805,11 +1802,11 @@ void WiFiComponent::log_and_adjust_priority_for_failed_connect_() {
|
||||
}
|
||||
|
||||
// Get SSID for logging (use pointer to avoid copy)
|
||||
const char *ssid = nullptr;
|
||||
const std::string *ssid = nullptr;
|
||||
if (this->retry_phase_ == WiFiRetryPhase::SCAN_CONNECTING && !this->scan_result_.empty()) {
|
||||
ssid = this->scan_result_[0].get_ssid().c_str();
|
||||
ssid = &this->scan_result_[0].get_ssid();
|
||||
} else if (const WiFiAP *config = this->get_selected_sta_()) {
|
||||
ssid = config->get_ssid().c_str();
|
||||
ssid = &config->get_ssid();
|
||||
}
|
||||
|
||||
// Only decrease priority on the last attempt for this phase
|
||||
@@ -1829,8 +1826,8 @@ void WiFiComponent::log_and_adjust_priority_for_failed_connect_() {
|
||||
}
|
||||
char bssid_s[18];
|
||||
format_mac_addr_upper(failed_bssid.value().data(), bssid_s);
|
||||
ESP_LOGD(TAG, "Failed " LOG_SECRET("'%s'") " " LOG_SECRET("(%s)") ", priority %d → %d", ssid != nullptr ? ssid : "",
|
||||
bssid_s, old_priority, new_priority);
|
||||
ESP_LOGD(TAG, "Failed " LOG_SECRET("'%s'") " " LOG_SECRET("(%s)") ", priority %d → %d",
|
||||
ssid != nullptr ? ssid->c_str() : "", bssid_s, old_priority, new_priority);
|
||||
|
||||
// After adjusting priority, check if all priorities are now at minimum
|
||||
// If so, clear the vector to save memory and reset for fresh start
|
||||
@@ -2078,14 +2075,10 @@ void WiFiComponent::save_fast_connect_settings_() {
|
||||
}
|
||||
#endif
|
||||
|
||||
void WiFiAP::set_ssid(const std::string &ssid) { this->ssid_ = CompactString(ssid.c_str(), ssid.size()); }
|
||||
void WiFiAP::set_ssid(const char *ssid) { this->ssid_ = CompactString(ssid, strlen(ssid)); }
|
||||
void WiFiAP::set_ssid(const std::string &ssid) { this->ssid_ = ssid; }
|
||||
void WiFiAP::set_bssid(const bssid_t &bssid) { this->bssid_ = bssid; }
|
||||
void WiFiAP::clear_bssid() { this->bssid_ = {}; }
|
||||
void WiFiAP::set_password(const std::string &password) {
|
||||
this->password_ = CompactString(password.c_str(), password.size());
|
||||
}
|
||||
void WiFiAP::set_password(const char *password) { this->password_ = CompactString(password, strlen(password)); }
|
||||
void WiFiAP::set_password(const std::string &password) { this->password_ = password; }
|
||||
#ifdef USE_WIFI_WPA2_EAP
|
||||
void WiFiAP::set_eap(optional<EAPAuth> eap_auth) { this->eap_ = std::move(eap_auth); }
|
||||
#endif
|
||||
@@ -2095,8 +2088,10 @@ void WiFiAP::clear_channel() { this->channel_ = 0; }
|
||||
void WiFiAP::set_manual_ip(optional<ManualIP> manual_ip) { this->manual_ip_ = manual_ip; }
|
||||
#endif
|
||||
void WiFiAP::set_hidden(bool hidden) { this->hidden_ = hidden; }
|
||||
const std::string &WiFiAP::get_ssid() const { return this->ssid_; }
|
||||
const bssid_t &WiFiAP::get_bssid() const { return this->bssid_; }
|
||||
bool WiFiAP::has_bssid() const { return this->bssid_ != bssid_t{}; }
|
||||
const std::string &WiFiAP::get_password() const { return this->password_; }
|
||||
#ifdef USE_WIFI_WPA2_EAP
|
||||
const optional<EAPAuth> &WiFiAP::get_eap() const { return this->eap_; }
|
||||
#endif
|
||||
@@ -2107,12 +2102,12 @@ const optional<ManualIP> &WiFiAP::get_manual_ip() const { return this->manual_ip
|
||||
#endif
|
||||
bool WiFiAP::get_hidden() const { return this->hidden_; }
|
||||
|
||||
WiFiScanResult::WiFiScanResult(const bssid_t &bssid, const char *ssid, size_t ssid_len, uint8_t channel, int8_t rssi,
|
||||
bool with_auth, bool is_hidden)
|
||||
WiFiScanResult::WiFiScanResult(const bssid_t &bssid, std::string ssid, uint8_t channel, int8_t rssi, bool with_auth,
|
||||
bool is_hidden)
|
||||
: bssid_(bssid),
|
||||
channel_(channel),
|
||||
rssi_(rssi),
|
||||
ssid_(ssid, ssid_len),
|
||||
ssid_(std::move(ssid)),
|
||||
with_auth_(with_auth),
|
||||
is_hidden_(is_hidden) {}
|
||||
bool WiFiScanResult::matches(const WiFiAP &config) const {
|
||||
@@ -2155,6 +2150,7 @@ bool WiFiScanResult::matches(const WiFiAP &config) const {
|
||||
bool WiFiScanResult::get_matches() const { return this->matches_; }
|
||||
void WiFiScanResult::set_matches(bool matches) { this->matches_ = matches; }
|
||||
const bssid_t &WiFiScanResult::get_bssid() const { return this->bssid_; }
|
||||
const std::string &WiFiScanResult::get_ssid() const { return this->ssid_; }
|
||||
uint8_t WiFiScanResult::get_channel() const { return this->channel_; }
|
||||
int8_t WiFiScanResult::get_rssi() const { return this->rssi_; }
|
||||
bool WiFiScanResult::get_with_auth() const { return this->with_auth_; }
|
||||
@@ -2227,7 +2223,7 @@ void WiFiComponent::process_roaming_scan_() {
|
||||
|
||||
for (const auto &result : this->scan_result_) {
|
||||
// Must be same SSID, different BSSID
|
||||
if (result.get_ssid() != current_ssid.c_str() || result.get_bssid() == current_bssid)
|
||||
if (current_ssid != result.get_ssid() || result.get_bssid() == current_bssid)
|
||||
continue;
|
||||
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
|
||||
|
||||
@@ -175,13 +175,9 @@ template<typename T> using wifi_scan_vector_t = FixedVector<T>;
|
||||
class WiFiAP {
|
||||
public:
|
||||
void set_ssid(const std::string &ssid);
|
||||
void set_ssid(const char *ssid);
|
||||
void set_ssid(const CompactString &ssid) { this->ssid_ = ssid; }
|
||||
void set_bssid(const bssid_t &bssid);
|
||||
void clear_bssid();
|
||||
void set_password(const std::string &password);
|
||||
void set_password(const char *password);
|
||||
void set_password(const CompactString &password) { this->password_ = password; }
|
||||
#ifdef USE_WIFI_WPA2_EAP
|
||||
void set_eap(optional<EAPAuth> eap_auth);
|
||||
#endif // USE_WIFI_WPA2_EAP
|
||||
@@ -192,10 +188,10 @@ class WiFiAP {
|
||||
void set_manual_ip(optional<ManualIP> manual_ip);
|
||||
#endif
|
||||
void set_hidden(bool hidden);
|
||||
const CompactString &get_ssid() const { return this->ssid_; }
|
||||
const CompactString &get_password() const { return this->password_; }
|
||||
const std::string &get_ssid() const;
|
||||
const bssid_t &get_bssid() const;
|
||||
bool has_bssid() const;
|
||||
const std::string &get_password() const;
|
||||
#ifdef USE_WIFI_WPA2_EAP
|
||||
const optional<EAPAuth> &get_eap() const;
|
||||
#endif // USE_WIFI_WPA2_EAP
|
||||
@@ -208,8 +204,8 @@ class WiFiAP {
|
||||
bool get_hidden() const;
|
||||
|
||||
protected:
|
||||
CompactString ssid_;
|
||||
CompactString password_;
|
||||
std::string ssid_;
|
||||
std::string password_;
|
||||
#ifdef USE_WIFI_WPA2_EAP
|
||||
optional<EAPAuth> eap_;
|
||||
#endif // USE_WIFI_WPA2_EAP
|
||||
@@ -225,15 +221,14 @@ class WiFiAP {
|
||||
|
||||
class WiFiScanResult {
|
||||
public:
|
||||
WiFiScanResult(const bssid_t &bssid, const char *ssid, size_t ssid_len, uint8_t channel, int8_t rssi, bool with_auth,
|
||||
bool is_hidden);
|
||||
WiFiScanResult(const bssid_t &bssid, std::string ssid, uint8_t channel, int8_t rssi, bool with_auth, bool is_hidden);
|
||||
|
||||
bool matches(const WiFiAP &config) const;
|
||||
|
||||
bool get_matches() const;
|
||||
void set_matches(bool matches);
|
||||
const bssid_t &get_bssid() const;
|
||||
const CompactString &get_ssid() const { return this->ssid_; }
|
||||
const std::string &get_ssid() const;
|
||||
uint8_t get_channel() const;
|
||||
int8_t get_rssi() const;
|
||||
bool get_with_auth() const;
|
||||
@@ -247,7 +242,7 @@ class WiFiScanResult {
|
||||
bssid_t bssid_;
|
||||
uint8_t channel_;
|
||||
int8_t rssi_;
|
||||
CompactString ssid_;
|
||||
std::string ssid_;
|
||||
int8_t priority_{0};
|
||||
bool matches_{false};
|
||||
bool with_auth_;
|
||||
@@ -386,10 +381,6 @@ class WiFiComponent : public Component {
|
||||
void set_passive_scan(bool passive);
|
||||
|
||||
void save_wifi_sta(const std::string &ssid, const std::string &password);
|
||||
void save_wifi_sta(const char *ssid, const char *password);
|
||||
void save_wifi_sta(const CompactString &ssid, const CompactString &password) {
|
||||
this->save_wifi_sta(ssid.c_str(), password.c_str());
|
||||
}
|
||||
|
||||
// ========== INTERNAL METHODS ==========
|
||||
// (In most use cases you won't need these)
|
||||
@@ -550,7 +541,7 @@ class WiFiComponent : public Component {
|
||||
int8_t find_first_non_hidden_index_() const;
|
||||
/// Check if an SSID was seen in the most recent scan results
|
||||
/// Used to skip hidden mode for SSIDs we know are visible
|
||||
bool ssid_was_seen_in_scan_(const CompactString &ssid) const;
|
||||
bool ssid_was_seen_in_scan_(const std::string &ssid) const;
|
||||
/// Check if full scan results are needed (captive portal active, improv, listeners)
|
||||
bool needs_full_scan_results_() const;
|
||||
/// Check if network matches any configured network (for scan result filtering)
|
||||
|
||||
@@ -784,8 +784,8 @@ void WiFiComponent::wifi_scan_done_callback_(void *arg, STATUS status) {
|
||||
const char *ssid_cstr = reinterpret_cast<const char *>(it->ssid);
|
||||
if (needs_full || this->matches_configured_network_(ssid_cstr, it->bssid)) {
|
||||
this->scan_result_.emplace_back(
|
||||
bssid_t{it->bssid[0], it->bssid[1], it->bssid[2], it->bssid[3], it->bssid[4], it->bssid[5]}, ssid_cstr,
|
||||
it->ssid_len, it->channel, it->rssi, it->authmode != AUTH_OPEN, it->is_hidden != 0);
|
||||
bssid_t{it->bssid[0], it->bssid[1], it->bssid[2], it->bssid[3], it->bssid[4], it->bssid[5]},
|
||||
std::string(ssid_cstr, it->ssid_len), it->channel, it->rssi, it->authmode != AUTH_OPEN, it->is_hidden != 0);
|
||||
} else {
|
||||
this->log_discarded_scan_result_(ssid_cstr, it->bssid, it->rssi, it->channel);
|
||||
}
|
||||
|
||||
@@ -870,7 +870,8 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) {
|
||||
if (needs_full || this->matches_configured_network_(ssid_cstr, record.bssid)) {
|
||||
bssid_t bssid;
|
||||
std::copy(record.bssid, record.bssid + 6, bssid.begin());
|
||||
this->scan_result_.emplace_back(bssid, ssid_cstr, strlen(ssid_cstr), record.primary, record.rssi,
|
||||
std::string ssid(ssid_cstr);
|
||||
this->scan_result_.emplace_back(bssid, std::move(ssid), record.primary, record.rssi,
|
||||
record.authmode != WIFI_AUTH_OPEN, ssid_cstr[0] == '\0');
|
||||
} else {
|
||||
this->log_discarded_scan_result_(ssid_cstr, record.bssid, record.rssi, record.primary);
|
||||
|
||||
@@ -694,7 +694,7 @@ void WiFiComponent::wifi_scan_done_callback_() {
|
||||
auto &ap = scan->ap[i];
|
||||
this->scan_result_.emplace_back(bssid_t{ap.bssid.addr[0], ap.bssid.addr[1], ap.bssid.addr[2], ap.bssid.addr[3],
|
||||
ap.bssid.addr[4], ap.bssid.addr[5]},
|
||||
ssid_cstr, strlen(ssid_cstr), ap.channel, ap.rssi, ap.auth != WIFI_AUTH_OPEN,
|
||||
std::string(ssid_cstr), ap.channel, ap.rssi, ap.auth != WIFI_AUTH_OPEN,
|
||||
ssid_cstr[0] == '\0');
|
||||
} else {
|
||||
auto &ap = scan->ap[i];
|
||||
|
||||
@@ -149,8 +149,9 @@ void WiFiComponent::wifi_scan_result(void *env, const cyw43_ev_scan_result_t *re
|
||||
|
||||
bssid_t bssid;
|
||||
std::copy(result->bssid, result->bssid + 6, bssid.begin());
|
||||
WiFiScanResult res(bssid, ssid_cstr, strlen(ssid_cstr), result->channel, result->rssi,
|
||||
result->auth_mode != CYW43_AUTH_OPEN, ssid_cstr[0] == '\0');
|
||||
std::string ssid(ssid_cstr);
|
||||
WiFiScanResult res(bssid, std::move(ssid), result->channel, result->rssi, result->auth_mode != CYW43_AUTH_OPEN,
|
||||
ssid_cstr[0] == '\0');
|
||||
if (std::find(this->scan_result_.begin(), this->scan_result_.end(), res) == this->scan_result_.end()) {
|
||||
this->scan_result_.push_back(res);
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ void ScanResultsWiFiInfo::on_wifi_scan_results(const wifi::wifi_scan_vector_t<wi
|
||||
for (const auto &scan : results) {
|
||||
if (scan.get_is_hidden())
|
||||
continue;
|
||||
const auto &ssid = scan.get_ssid();
|
||||
const std::string &ssid = scan.get_ssid();
|
||||
// Max space: ssid + ": " (2) + "-128" (4) + "dB\n" (3) = ssid + 9
|
||||
if (ptr + ssid.size() + 9 > end)
|
||||
break;
|
||||
|
||||
@@ -3,6 +3,7 @@ from esphome.components.light.effects import register_addressable_effect
|
||||
from esphome.components.light.types import AddressableLightEffect
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_NAME, CONF_PORT
|
||||
from esphome.core import CORE
|
||||
|
||||
wled_ns = cg.esphome_ns.namespace("wled")
|
||||
WLEDLightEffect = wled_ns.class_("WLEDLightEffect", AddressableLightEffect)
|
||||
@@ -27,4 +28,6 @@ async def wled_light_effect_to_code(config, effect_id):
|
||||
cg.add(effect.set_port(config[CONF_PORT]))
|
||||
cg.add(effect.set_sync_group_mask(config[CONF_SYNC_GROUP_MASK]))
|
||||
cg.add(effect.set_blank_on_start(config[CONF_BLANK_ON_START]))
|
||||
if CORE.is_esp32:
|
||||
cg.add_library("WiFi", None)
|
||||
return effect
|
||||
|
||||
@@ -892,6 +892,16 @@ class EsphomeCore:
|
||||
library.name if "/" not in library.name else library.name.split("/")[-1]
|
||||
)
|
||||
|
||||
# Auto-enable Arduino libraries on ESP32 Arduino builds
|
||||
if self.is_esp32 and self.using_arduino:
|
||||
from esphome.components.esp32 import (
|
||||
ARDUINO_DISABLED_LIBRARIES,
|
||||
_enable_arduino_library,
|
||||
)
|
||||
|
||||
if short_name in ARDUINO_DISABLED_LIBRARIES:
|
||||
_enable_arduino_library(short_name)
|
||||
|
||||
if short_name not in self.platformio_libraries:
|
||||
_LOGGER.debug("Adding library: %s", library)
|
||||
self.platformio_libraries[short_name] = library
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <new>
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#include "rom/crc.h"
|
||||
@@ -859,60 +858,4 @@ void IRAM_ATTR HOT delay_microseconds_safe(uint32_t us) {
|
||||
;
|
||||
}
|
||||
|
||||
// CompactString implementation
|
||||
CompactString::CompactString(const char *str, size_t len) {
|
||||
if (len > MAX_LENGTH) {
|
||||
len = MAX_LENGTH; // Clamp to max valid length
|
||||
}
|
||||
|
||||
this->length_ = len;
|
||||
if (len <= INLINE_CAPACITY) {
|
||||
// Store inline with null terminator
|
||||
this->is_heap_ = 0;
|
||||
if (len > 0) {
|
||||
std::memcpy(this->storage_, str, len);
|
||||
}
|
||||
this->storage_[len] = '\0';
|
||||
} else {
|
||||
// Heap allocate with null terminator
|
||||
this->is_heap_ = 1;
|
||||
char *heap_data = new char[len + 1]; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
std::memcpy(heap_data, str, len);
|
||||
heap_data[len] = '\0';
|
||||
this->set_heap_ptr_(heap_data);
|
||||
}
|
||||
}
|
||||
|
||||
CompactString::CompactString(const CompactString &other) : CompactString(other.data(), other.size()) {}
|
||||
|
||||
CompactString &CompactString::operator=(const CompactString &other) {
|
||||
if (this != &other) {
|
||||
this->~CompactString();
|
||||
new (this) CompactString(other);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
CompactString::CompactString(CompactString &&other) noexcept : length_(other.length_), is_heap_(other.is_heap_) {
|
||||
// Copy full storage (includes null terminator for inline, or pointer for heap)
|
||||
std::memcpy(this->storage_, other.storage_, INLINE_CAPACITY + 1);
|
||||
other.length_ = 0;
|
||||
other.is_heap_ = 0;
|
||||
other.storage_[0] = '\0';
|
||||
}
|
||||
|
||||
CompactString &CompactString::operator=(CompactString &&other) noexcept {
|
||||
if (this != &other) {
|
||||
this->~CompactString();
|
||||
new (this) CompactString(std::move(other));
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
CompactString::~CompactString() {
|
||||
if (this->is_heap_) {
|
||||
delete[] this->get_heap_ptr_(); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace esphome
|
||||
|
||||
@@ -1784,58 +1784,4 @@ template<typename T, enable_if_t<std::is_pointer<T *>::value, int> = 0> T &id(T
|
||||
|
||||
///@}
|
||||
|
||||
/// 20-byte string: 18 chars inline + null, heap for longer. Always null-terminated.
|
||||
class CompactString {
|
||||
public:
|
||||
static constexpr uint8_t MAX_LENGTH = 127;
|
||||
static constexpr uint8_t INLINE_CAPACITY = 18; // 18 chars + null terminator fits in 19 bytes
|
||||
static constexpr uint8_t BUFFER_SIZE = MAX_LENGTH + 1; // For external buffer (128 bytes)
|
||||
|
||||
CompactString() : length_(0), is_heap_(0) { this->storage_[0] = '\0'; }
|
||||
CompactString(const char *str, size_t len);
|
||||
CompactString(const CompactString &other);
|
||||
CompactString(CompactString &&other) noexcept;
|
||||
CompactString &operator=(const CompactString &other);
|
||||
CompactString &operator=(CompactString &&other) noexcept;
|
||||
~CompactString();
|
||||
|
||||
const char *data() const { return this->is_heap_ ? this->get_heap_ptr_() : this->storage_; }
|
||||
const char *c_str() const { return this->data(); } // Always null-terminated
|
||||
size_t size() const { return this->length_; }
|
||||
bool empty() const { return this->length_ == 0; }
|
||||
|
||||
// Implicit conversion to std::string for backwards compatibility
|
||||
operator std::string() const { return std::string(this->data(), this->size()); }
|
||||
|
||||
bool operator==(const CompactString &other) const {
|
||||
return this->size() == other.size() && std::memcmp(this->data(), other.data(), this->size()) == 0;
|
||||
}
|
||||
bool operator==(const std::string &other) const {
|
||||
return this->size() == other.size() && std::memcmp(this->data(), other.data(), this->size()) == 0;
|
||||
}
|
||||
bool operator==(const char *other) const {
|
||||
return this->size() == std::strlen(other) && std::memcmp(this->data(), other, this->size()) == 0;
|
||||
}
|
||||
bool operator!=(const CompactString &other) const { return !(*this == other); }
|
||||
bool operator!=(const std::string &other) const { return !(*this == other); }
|
||||
bool operator!=(const char *other) const { return !(*this == other); }
|
||||
|
||||
protected:
|
||||
char *get_heap_ptr_() const {
|
||||
char *ptr;
|
||||
std::memcpy(&ptr, this->storage_, sizeof(ptr));
|
||||
return ptr;
|
||||
}
|
||||
void set_heap_ptr_(char *ptr) { std::memcpy(this->storage_, &ptr, sizeof(ptr)); }
|
||||
|
||||
// Storage for string data. When is_heap_=0, contains the string directly (null-terminated).
|
||||
// When is_heap_=1, first sizeof(char*) bytes contain pointer to heap allocation.
|
||||
char storage_[INLINE_CAPACITY + 1]; // 19 bytes: 18 chars + null terminator
|
||||
uint8_t length_ : 7; // String length (0-127)
|
||||
uint8_t is_heap_ : 1; // 1 if using heap pointer, 0 if using inline storage
|
||||
// Total size: 20 bytes (19 bytes storage + 1 byte bitfields)
|
||||
};
|
||||
|
||||
static_assert(sizeof(CompactString) == 20, "CompactString must be exactly 20 bytes");
|
||||
|
||||
} // namespace esphome
|
||||
|
||||
16
tests/components/i2s_audio/test.esp32-ard.yaml
Normal file
16
tests/components/i2s_audio/test.esp32-ard.yaml
Normal file
@@ -0,0 +1,16 @@
|
||||
substitutions:
|
||||
i2s_bclk_pin: GPIO15
|
||||
i2s_lrclk_pin: GPIO4
|
||||
i2s_mclk_pin: GPIO5
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
wifi:
|
||||
ssid: test
|
||||
password: test1234
|
||||
|
||||
media_player:
|
||||
- platform: i2s_audio
|
||||
name: Test Media Player
|
||||
dac_type: internal
|
||||
mode: stereo
|
||||
@@ -780,3 +780,78 @@ class TestEsphomeCore:
|
||||
target.config = {const.CONF_ESPHOME: {"name": "test"}, "logger": {}}
|
||||
|
||||
assert target.has_networking is False
|
||||
|
||||
def test_add_library__esp32_arduino_enables_disabled_library(self, target):
|
||||
"""Test add_library auto-enables Arduino libraries on ESP32 Arduino builds."""
|
||||
target.data[const.KEY_CORE] = {
|
||||
const.KEY_TARGET_PLATFORM: "esp32",
|
||||
const.KEY_TARGET_FRAMEWORK: "arduino",
|
||||
}
|
||||
|
||||
library = core.Library("WiFi", None)
|
||||
|
||||
with patch("esphome.components.esp32._enable_arduino_library") as mock_enable:
|
||||
target.add_library(library)
|
||||
mock_enable.assert_called_once_with("WiFi")
|
||||
|
||||
assert "WiFi" in target.platformio_libraries
|
||||
|
||||
def test_add_library__esp32_arduino_ignores_non_arduino_library(self, target):
|
||||
"""Test add_library doesn't enable libraries not in ARDUINO_DISABLED_LIBRARIES."""
|
||||
target.data[const.KEY_CORE] = {
|
||||
const.KEY_TARGET_PLATFORM: "esp32",
|
||||
const.KEY_TARGET_FRAMEWORK: "arduino",
|
||||
}
|
||||
|
||||
library = core.Library("SomeOtherLib", "1.0.0")
|
||||
|
||||
with patch("esphome.components.esp32._enable_arduino_library") as mock_enable:
|
||||
target.add_library(library)
|
||||
mock_enable.assert_not_called()
|
||||
|
||||
assert "SomeOtherLib" in target.platformio_libraries
|
||||
|
||||
def test_add_library__esp32_idf_does_not_enable_arduino_library(self, target):
|
||||
"""Test add_library doesn't auto-enable Arduino libraries on ESP32 IDF builds."""
|
||||
target.data[const.KEY_CORE] = {
|
||||
const.KEY_TARGET_PLATFORM: "esp32",
|
||||
const.KEY_TARGET_FRAMEWORK: "esp-idf",
|
||||
}
|
||||
|
||||
library = core.Library("WiFi", None)
|
||||
|
||||
with patch("esphome.components.esp32._enable_arduino_library") as mock_enable:
|
||||
target.add_library(library)
|
||||
mock_enable.assert_not_called()
|
||||
|
||||
assert "WiFi" in target.platformio_libraries
|
||||
|
||||
def test_add_library__esp8266_does_not_enable_arduino_library(self, target):
|
||||
"""Test add_library doesn't auto-enable Arduino libraries on ESP8266."""
|
||||
target.data[const.KEY_CORE] = {
|
||||
const.KEY_TARGET_PLATFORM: "esp8266",
|
||||
const.KEY_TARGET_FRAMEWORK: "arduino",
|
||||
}
|
||||
|
||||
library = core.Library("WiFi", None)
|
||||
|
||||
with patch("esphome.components.esp32._enable_arduino_library") as mock_enable:
|
||||
target.add_library(library)
|
||||
mock_enable.assert_not_called()
|
||||
|
||||
assert "WiFi" in target.platformio_libraries
|
||||
|
||||
def test_add_library__extracts_short_name_from_path(self, target):
|
||||
"""Test add_library extracts short name from library paths like owner/lib."""
|
||||
target.data[const.KEY_CORE] = {
|
||||
const.KEY_TARGET_PLATFORM: "esp32",
|
||||
const.KEY_TARGET_FRAMEWORK: "arduino",
|
||||
}
|
||||
|
||||
library = core.Library("arduino/Wire", None)
|
||||
|
||||
with patch("esphome.components.esp32._enable_arduino_library") as mock_enable:
|
||||
target.add_library(library)
|
||||
mock_enable.assert_called_once_with("Wire")
|
||||
|
||||
assert "Wire" in target.platformio_libraries
|
||||
|
||||
Reference in New Issue
Block a user