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

This commit is contained in:
J. Nick Koston
2026-07-06 20:38:00 -05:00
417 changed files with 2433 additions and 1684 deletions
+1 -1
View File
@@ -32,7 +32,7 @@ runs:
# detects the activated venv via ``VIRTUAL_ENV`` so the venv layout
# downstream jobs rely on is preserved.
if: steps.cache-venv.outputs.cache-hit != 'true'
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
uses: astral-sh/setup-uv@d31148d669074a8d0a63714ba94f3201e7020bc3 # v8.3.0
with:
enable-cache: true
# Pin uv version so the action does not have to fetch the
+1 -1
View File
@@ -29,7 +29,7 @@ jobs:
- name: Set up uv
# ``--system`` (below) installs into the setup-python interpreter;
# no venv is created or restored by this workflow.
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
uses: astral-sh/setup-uv@d31148d669074a8d0a63714ba94f3201e7020bc3 # v8.3.0
with:
enable-cache: true
# Pin uv version so the action does not have to fetch the
+4 -4
View File
@@ -49,7 +49,7 @@ jobs:
# detects the activated venv via ``VIRTUAL_ENV`` so downstream jobs
# that ``. venv/bin/activate`` see an identical layout.
if: steps.cache-venv.outputs.cache-hit != 'true'
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
uses: astral-sh/setup-uv@d31148d669074a8d0a63714ba94f3201e7020bc3 # v8.3.0
with:
enable-cache: true
# Pin uv version so the action does not have to fetch the
@@ -112,7 +112,7 @@ jobs:
script/build_codeowners.py --check
script/build_language_schema.py --check
script/generate-esp32-boards.py --check
script/generate-rp2040-boards.py --check
script/generate-rp2-boards.py --check
script/ci_check_duplicate_test_ids.py
import-time:
@@ -171,7 +171,7 @@ jobs:
# install step (order-of-magnitude faster on cold boots,
# with its own wheel cache). actions/setup-python still
# provides the interpreter.
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
uses: astral-sh/setup-uv@d31148d669074a8d0a63714ba94f3201e7020bc3 # v8.3.0
with:
enable-cache: true
# Pin uv version so the action does not have to fetch the
@@ -372,7 +372,7 @@ jobs:
- name: Set up uv
# Only needed on cache miss to populate the venv.
if: steps.cache-venv.outputs.cache-hit != 'true'
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
uses: astral-sh/setup-uv@d31148d669074a8d0a63714ba94f3201e7020bc3 # v8.3.0
with:
enable-cache: true
# Pin uv version so the action does not have to fetch the
+1 -1
View File
@@ -47,7 +47,7 @@ jobs:
# setup-python interpreter so subsequent ``pre-commit`` /
# ``script/run-in-env.py`` steps find the deps without a
# ``uv run`` prefix.
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
uses: astral-sh/setup-uv@d31148d669074a8d0a63714ba94f3201e7020bc3 # v8.3.0
with:
enable-cache: true
# Pin uv version so the action does not have to fetch the
+5 -4
View File
@@ -427,13 +427,14 @@ This document provides essential context for AI models interacting with this pro
When a PR's only edits to a component are `validate.*.yaml` files (no source changes, no `test.*.yaml` changes, and the component isn't pulled in as a dependency of another changed component), CI skips the compile stage for that component entirely and only runs config validation. This is decided in `script/determine-jobs.py` via `_component_change_is_validate_only` and surfaced as the `validate_only_components` output that the `test-build-components-split` job consumes.
* **Test Grouping with Packages:** Components that use shared bus packages can be grouped together in CI to reduce build count. **Never define buses (uart, i2c, spi, modbus) directly in test YAML files** — always use packages from `test_build_components/common/`:
* **Test Grouping with Packages:** Components that use shared bus packages can be grouped together in CI to reduce build count. **Never define buses (uart, i2c, spi, modbus) directly in test YAML files** — always use packages from `test_build_components/common/`.
All includes in test files must go through dict-style `packages:` so that batch grouping works correctly — the grouping scripts only understand dict-style packages. Never use list-style packages (`packages: [- !include ...]`) or top-level merge keys (`<<: !include common.yaml`). Bus packages are keyed by the bus name; the component's `common.yaml` is keyed by the component name (e.g. `cst328: !include common.yaml`):
```yaml
# test.esp32-idf.yaml — use packages for buses
# test.esp32-idf.yaml — everything included via named packages
packages:
uart: !include ../../test_build_components/common/uart_115200/esp32-idf.yaml
<<: !include common.yaml
my_component: !include common.yaml
```
```yaml
# common.yaml — component config only, NO bus definitions
+1 -1
View File
@@ -426,7 +426,7 @@ esphome/components/rf_bridge/* @jesserockz
esphome/components/rgbct/* @jesserockz
esphome/components/ring_buffer/* @kahrendt
esphome/components/router/speaker/* @kahrendt
esphome/components/rp2040/* @jesserockz
esphome/components/rp2/* @jesserockz
esphome/components/rp2040_ble/* @bdraco
esphome/components/rp2040_pio_led_strip/* @Papa-DMan
esphome/components/rp2040_pwm/* @jesserockz
+1 -1
View File
@@ -22,7 +22,7 @@ RUN \
-r /requirements.txt
# Install the ESPHome Device Builder dashboard.
RUN uv pip install --no-cache-dir esphome-device-builder==1.1.0
RUN uv pip install --no-cache-dir esphome-device-builder==1.2.0
RUN \
platformio settings set enable_telemetry No \
+5 -5
View File
@@ -362,7 +362,7 @@ def choose_upload_log_host(
bootsel_permission_error = False
if (
purpose == Purpose.UPLOADING
and CORE.is_rp2040
and CORE.is_rp2
and (picotool := _find_picotool()) is not None
):
bootsel = detect_rp2040_bootsel(picotool)
@@ -409,7 +409,7 @@ def choose_upload_log_host(
# Show helpful BOOTSEL instructions for RP2040 when no BOOTSEL device is found
if (
purpose == Purpose.UPLOADING
and CORE.is_rp2040
and CORE.is_rp2
and not any(get_port_type(opt[1]) == PortType.BOOTSEL for opt in options)
):
if bootsel_permission_error:
@@ -1008,7 +1008,7 @@ def upload_using_platformio(config: ConfigType, port: str) -> int:
# RP2040 platform-raspberrypi build recipe expects firmware.bin.signed for
# the upload target, but 'nobuild' skips the build phase that creates it.
# Create it here so the upload doesn't fail.
if CORE.is_rp2040:
if CORE.is_rp2:
idedata = toolchain.get_idedata(config)
build_dir = Path(idedata.firmware_elf_path).parent
firmware_bin = build_dir / "firmware.bin"
@@ -1196,7 +1196,7 @@ def upload_program(
if CORE.is_esp32 or CORE.is_esp8266:
file = getattr(args, "file", None)
exit_code = upload_using_esptool(config, host, file, args.upload_speed)
elif CORE.is_rp2040 or CORE.is_libretiny:
elif CORE.is_rp2 or CORE.is_libretiny:
exit_code = upload_using_platformio(config, host)
# else: Unknown target platform, exit_code remains 1
@@ -1675,7 +1675,7 @@ def command_run(args: ArgsProtocol, config: ConfigType) -> int | None:
# After BOOTSEL upload, wait for a new serial port to appear
# so it shows up in the log chooser
if successful_device is None and CORE.is_rp2040:
if successful_device is None and CORE.is_rp2:
_wait_for_serial_port(known_ports=pre_upload_ports)
# If exactly one new serial port appeared, use it directly
serial_ports = get_serial_ports()
+6
View File
@@ -0,0 +1,6 @@
# Importing `esphome.loader` here installs the component-alias
# ``sys.meta_path`` finder before any submodule lookup runs. Without this,
# `from esphome.components import <legacy_alias>` from a fresh interpreter
# can race the finder install and raise ImportError, since the legacy
# alias dir no longer exists on disk.
from esphome import loader as _loader # noqa: F401
+4 -4
View File
@@ -227,12 +227,12 @@ ESP32_VARIANT_ADC2_PIN_TO_CHANNEL = {
def validate_adc_pin(value):
if str(value).upper() == "VCC":
if CORE.is_rp2040:
if CORE.is_rp2:
return pins.internal_gpio_input_pin_schema(29)
return cv.only_on([PLATFORM_ESP8266])("VCC")
if str(value).upper() == "TEMPERATURE":
return cv.only_on_rp2040("TEMPERATURE")
return cv.only_on_rp2("TEMPERATURE")
if CORE.is_esp32:
conf = pins.internal_gpio_input_pin_schema(value)
@@ -261,11 +261,11 @@ def validate_adc_pin(value):
raise cv.Invalid("ESP8266: Only pin A0 (GPIO17) supports ADC")
return conf
if CORE.is_rp2040:
if CORE.is_rp2:
conf = pins.internal_gpio_input_pin_schema(value)
number = conf[CONF_NUMBER]
if number not in (26, 27, 28, 29):
raise cv.Invalid("RP2040: Only pins 26, 27, 28 and 29 support ADC")
raise cv.Invalid("RP2: Only pins 26, 27, 28 and 29 support ADC")
return conf
if CORE.is_libretiny:
+4 -4
View File
@@ -123,9 +123,9 @@ class ADCSensor final : public sensor::Sensor, public PollingComponent, public v
void set_autorange(bool autorange) { this->autorange_ = autorange; }
#endif // USE_ESP32
#ifdef USE_RP2040
#ifdef USE_RP2
void set_is_temperature() { this->is_temperature_ = true; }
#endif // USE_RP2040
#endif // USE_RP2
protected:
uint8_t sample_count_{1};
@@ -152,9 +152,9 @@ class ADCSensor final : public sensor::Sensor, public PollingComponent, public v
static adc_oneshot_unit_handle_t shared_adc_handles[2];
#endif // USE_ESP32
#ifdef USE_RP2040
#ifdef USE_RP2
bool is_temperature_{false};
#endif // USE_RP2040
#endif // USE_RP2
#ifdef USE_ZEPHYR
const struct adc_dt_spec *channel_ = nullptr;
@@ -1,4 +1,4 @@
#ifdef USE_RP2040
#ifdef USE_RP2
#include "adc_sensor.h"
#include "esphome/core/log.h"
@@ -17,7 +17,7 @@
namespace esphome::adc {
static const char *const TAG = "adc.rp2040";
static const char *const TAG = "adc.rp2";
void ADCSensor::setup() {
static bool initialized = false;
@@ -102,4 +102,4 @@ float ADCSensor::sample() {
} // namespace esphome::adc
#endif // USE_RP2040
#endif // USE_RP2
+1 -1
View File
@@ -201,7 +201,7 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform(
PlatformFramework.ESP32_IDF,
},
"adc_sensor_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO},
"adc_sensor_rp2040.cpp": {PlatformFramework.RP2040_ARDUINO},
"adc_sensor_rp2.cpp": {PlatformFramework.RP2_ARDUINO},
"adc_sensor_libretiny.cpp": {
PlatformFramework.BK72XX_ARDUINO,
PlatformFramework.RTL87XX_ARDUINO,
+3 -3
View File
@@ -300,7 +300,7 @@ CONFIG_SCHEMA = cv.All(
CONF_LISTEN_BACKLOG,
esp8266=1, # Limited RAM (~40KB free), LWIP raw sockets
esp32=4, # More RAM (520KB), BSD sockets
rp2040=1, # Limited RAM (264KB), LWIP raw sockets like ESP8266
rp2=1, # Limited RAM (264KB), LWIP raw sockets like ESP8266
bk72xx=4, # Moderate RAM, BSD-style sockets
rtl87xx=4, # Moderate RAM, BSD-style sockets
host=4, # Abundant resources
@@ -311,7 +311,7 @@ CONFIG_SCHEMA = cv.All(
CONF_MAX_CONNECTIONS,
esp8266=4, # ~40KB free RAM, each connection uses ~500-1000 bytes
esp32=5, # 520KB RAM available
rp2040=4, # 264KB RAM but LWIP constraints
rp2=4, # 264KB RAM but LWIP constraints
bk72xx=5, # Moderate RAM
rtl87xx=5, # Moderate RAM
host=8, # Abundant resources
@@ -326,7 +326,7 @@ CONFIG_SCHEMA = cv.All(
CONF_MAX_SEND_QUEUE,
esp8266=4, # Limited RAM, need to fail fast
esp32=8, # More RAM, can buffer more
rp2040=8, # Moderate RAM
rp2=8, # Moderate RAM
bk72xx=8, # Moderate RAM
nrf52=8, # Moderate RAM
rtl87xx=8, # Moderate RAM
+1 -1
View File
@@ -1759,7 +1759,7 @@ bool APIConnection::send_device_info_response_() {
// Manufacturer string - define once, handle ESP8266 PROGMEM separately
#if defined(USE_ESP8266) || defined(USE_ESP32)
#define ESPHOME_MANUFACTURER "Espressif"
#elif defined(USE_RP2040)
#elif defined(USE_RP2)
#define ESPHOME_MANUFACTURER "Raspberry Pi"
#elif defined(USE_BK72XX)
#define ESPHOME_MANUFACTURER "Beken"
+4 -4
View File
@@ -18,8 +18,8 @@
#ifdef USE_ESP32_CRASH_HANDLER
#include "esphome/components/esp32/crash_handler.h"
#endif
#ifdef USE_RP2040_CRASH_HANDLER
#include "esphome/components/rp2040/crash_handler.h"
#ifdef USE_RP2_CRASH_HANDLER
#include "esphome/components/rp2/crash_handler.h"
#endif
#ifdef USE_ESP8266_CRASH_HANDLER
#include "esphome/components/esp8266/crash_handler.h"
@@ -279,8 +279,8 @@ class APIConnection final : public APIServerConnectionBase {
esp32::crash_handler_log();
esp32::crash_handler_clear();
#endif
#ifdef USE_RP2040_CRASH_HANDLER
rp2040::crash_handler_log();
#ifdef USE_RP2_CRASH_HANDLER
rp2::crash_handler_log();
#endif
#ifdef USE_ESP8266_CRASH_HANDLER
esp8266::crash_handler_log();
+3 -3
View File
@@ -13,7 +13,7 @@ def AUTO_LOAD() -> list[str]:
if (
not CORE.is_esp32
and not CORE.is_esp8266
and not CORE.is_rp2040
and not CORE.is_rp2
and not CORE.is_libretiny
):
return ["socket"]
@@ -37,7 +37,7 @@ async def to_code(config):
elif CORE.is_esp8266:
# https://github.com/ESP32Async/ESPAsyncTCP
cg.add_library("ESP32Async/ESPAsyncTCP", "2.0.0")
elif CORE.is_rp2040:
elif CORE.is_rp2:
# https://github.com/ayushsharma82/RPAsyncTCP
# RPAsyncTCP is a drop-in replacement for AsyncTCP_RP2040W with better
# ESPAsyncWebServer compatibility
@@ -47,6 +47,6 @@ async def to_code(config):
def FILTER_SOURCE_FILES() -> list[str]:
# Exclude socket implementation for platforms that use AsyncTCP libraries
if CORE.is_esp32 or CORE.is_esp8266 or CORE.is_rp2040 or CORE.is_libretiny:
if CORE.is_esp32 or CORE.is_esp8266 or CORE.is_rp2 or CORE.is_libretiny:
return ["async_tcp_socket.cpp"]
return []
+1 -1
View File
@@ -7,7 +7,7 @@
#elif defined(USE_ESP8266)
// Use ESPAsyncTCP library for ESP8266 (always Arduino)
#include <ESPAsyncTCP.h>
#elif defined(USE_RP2040)
#elif defined(USE_RP2)
// Use RPAsyncTCP library for RP2040
#include <RPAsyncTCP.h>
#else
@@ -1,6 +1,6 @@
#include "async_tcp_socket.h"
#if !defined(USE_ESP32) && !defined(USE_ESP8266) && !defined(USE_RP2040) && !defined(USE_LIBRETINY) && \
#if !defined(USE_ESP32) && !defined(USE_ESP8266) && !defined(USE_RP2) && !defined(USE_LIBRETINY) && \
(defined(USE_SOCKET_IMPL_LWIP_SOCKETS) || defined(USE_SOCKET_IMPL_BSD_SOCKETS))
#include "esphome/components/network/util.h"
@@ -2,7 +2,7 @@
#include "esphome/core/defines.h"
#if !defined(USE_ESP32) && !defined(USE_ESP8266) && !defined(USE_RP2040) && !defined(USE_LIBRETINY) && \
#if !defined(USE_ESP32) && !defined(USE_ESP8266) && !defined(USE_RP2) && !defined(USE_LIBRETINY) && \
(defined(USE_SOCKET_IMPL_LWIP_SOCKETS) || defined(USE_SOCKET_IMPL_BSD_SOCKETS))
#include "esphome/components/socket/socket.h"
@@ -379,9 +379,17 @@ void BluetoothProxy::bluetooth_set_connection_params(const api::BluetoothSetConn
}
void BluetoothProxy::subscribe_api_connection(api::APIConnection *api_connection, uint32_t flags) {
if (this->api_connection_ != nullptr) {
ESP_LOGE(TAG, "Only one API subscription is allowed at a time");
return;
if (this->api_connection_ != nullptr && this->api_connection_ != api_connection) {
// A previous subscriber still holds the slot. This is almost always a stale
// connection from a client that dropped without a clean disconnect and has
// not yet hit the keepalive timeout; rejecting the new subscriber would
// silently starve it of advertisements until it reconnects, so the newest
// subscriber wins instead.
char old_peername[socket::SOCKADDR_STR_LEN];
char new_peername[socket::SOCKADDR_STR_LEN];
ESP_LOGW(TAG, "Subscription from %s (%s) replaces %s (%s)", api_connection->get_name(),
api_connection->get_peername_to(new_peername), this->api_connection_->get_name(),
this->api_connection_->get_peername_to(old_peername));
}
this->api_connection_ = api_connection;
this->parent_->recalculate_advertisement_parser_types();
@@ -13,7 +13,7 @@ from esphome.const import (
PLATFORM_ESP32,
PLATFORM_ESP8266,
PLATFORM_LN882X,
PLATFORM_RP2040,
PLATFORM_RP2,
PLATFORM_RTL87XX,
PlatformFramework,
)
@@ -54,7 +54,7 @@ CONFIG_SCHEMA = cv.All(
PLATFORM_ESP8266,
PLATFORM_BK72XX,
PLATFORM_LN882X,
PLATFORM_RP2040,
PLATFORM_RP2,
PLATFORM_RTL87XX,
]
),
@@ -105,7 +105,7 @@ async def to_code(config):
if config[CONF_COMPRESSION] == "gzip":
cg.add_define("USE_CAPTIVE_PORTAL_GZIP")
if CORE.using_arduino and (CORE.is_esp8266 or CORE.is_libretiny or CORE.is_rp2040):
if CORE.using_arduino and (CORE.is_esp8266 or CORE.is_libretiny or CORE.is_rp2):
cg.add_library("DNSServer", None)
+1
View File
@@ -16,6 +16,7 @@ CONF_COLOR_DEPTH = "color_depth"
CONF_CRC_ENABLE = "crc_enable"
CONF_DATA_BITS = "data_bits"
CONF_DRAW_ROUNDING = "draw_rounding"
CONF_ENABLE_OTA_DOWNGRADE_PROTECTION = "enable_ota_downgrade_protection"
CONF_ENABLED = "enabled"
CONF_GYROSCOPE_ODR = "gyroscope_odr"
CONF_GYROSCOPE_RANGE = "gyroscope_range"
+1 -1
View File
@@ -70,7 +70,7 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform(
},
"debug_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO},
"debug_host.cpp": {PlatformFramework.HOST_NATIVE},
"debug_rp2040.cpp": {PlatformFramework.RP2040_ARDUINO},
"debug_rp2.cpp": {PlatformFramework.RP2_ARDUINO},
"debug_libretiny.cpp": {
PlatformFramework.BK72XX_ARDUINO,
PlatformFramework.RTL87XX_ARDUINO,
@@ -1,5 +1,5 @@
#include "debug_component.h"
#ifdef USE_RP2040
#ifdef USE_RP2
#include "esphome/core/defines.h"
#include "esphome/core/log.h"
#include <Arduino.h>
@@ -9,8 +9,8 @@
#else
#include <hardware/structs/vreg_and_chip_reset.h>
#endif
#ifdef USE_RP2040_CRASH_HANDLER
#include "esphome/components/rp2040/crash_handler.h"
#ifdef USE_RP2_CRASH_HANDLER
#include "esphome/components/rp2/crash_handler.h"
#endif
namespace esphome::debug {
@@ -41,8 +41,8 @@ const char *DebugComponent::get_reset_reason_(std::span<char, RESET_REASON_BUFFE
if (watchdog_caused_reboot()) {
bool handled = false;
#ifdef USE_RP2040_CRASH_HANDLER
if (rp2040::crash_handler_has_data()) {
#ifdef USE_RP2_CRASH_HANDLER
if (rp2::crash_handler_has_data()) {
pos = buf_append_str(buf, size, pos, "Crash (HardFault)|");
handled = true;
}
+67
View File
@@ -11,6 +11,7 @@ from typing import Any
from esphome import yaml_util
import esphome.codegen as cg
from esphome.components.const import CONF_ENABLE_OTA_DOWNGRADE_PROTECTION
import esphome.config_validation as cv
from esphome.const import (
CONF_ADVANCED,
@@ -29,6 +30,7 @@ from esphome.const import (
CONF_PATH,
CONF_PLATFORM_VERSION,
CONF_PLATFORMIO_OPTIONS,
CONF_PROJECT,
CONF_REF,
CONF_SAFE_MODE,
CONF_SIZE,
@@ -1098,6 +1100,50 @@ def _detect_variant(value):
return value
def _ota_downgrade_protection_errors(
project_version: str | None, signed_ota_enabled: bool
) -> list[cv.Invalid]:
"""Validate prerequisites for OTA downgrade protection.
Called only when the feature is enabled. Returns a ``cv.Invalid`` for each
unmet requirement: a dotted-numeric project version (the firmware version
compared on-device) and signed OTA (so the embedded version cannot be
forged).
"""
path = [CONF_FRAMEWORK, CONF_ADVANCED, CONF_ENABLE_OTA_DOWNGRADE_PROTECTION]
errs: list[cv.Invalid] = []
if not project_version:
errs.append(
cv.Invalid(
f"'{CONF_ENABLE_OTA_DOWNGRADE_PROTECTION}' requires a "
f"'{CONF_PROJECT}' with a '{CONF_VERSION}' to be set in the "
f"'{CONF_ESPHOME}' section; this version is the firmware version "
"compared during OTA.",
path=path,
)
)
elif not re.fullmatch(r"\d+(\.\d+)*", project_version):
# The on-device comparison parses dotted-numeric versions only.
errs.append(
cv.Invalid(
f"'{CONF_ENABLE_OTA_DOWNGRADE_PROTECTION}' requires the "
f"'{CONF_PROJECT}' '{CONF_VERSION}' to be dotted-numeric (such "
f"as '1.2.3'), got '{project_version}'.",
path=path,
)
)
if not signed_ota_enabled:
errs.append(
cv.Invalid(
f"'{CONF_ENABLE_OTA_DOWNGRADE_PROTECTION}' requires "
f"'{CONF_SIGNED_OTA_VERIFICATION}' to be enabled; without signed "
"OTA the embedded version cannot be trusted.",
path=path,
)
)
return errs
def final_validate(config):
# Imported locally to avoid circular import issues
from esphome.components.psram import DOMAIN as PSRAM_DOMAIN
@@ -1303,6 +1349,14 @@ def final_validate(config):
"Binaries will NOT be signed automatically during build. "
"You must sign them externally before flashing."
)
if advanced[CONF_ENABLE_OTA_DOWNGRADE_PROTECTION]:
project = full_config[CONF_ESPHOME].get(CONF_PROJECT)
errs.extend(
_ota_downgrade_protection_errors(
project[CONF_VERSION] if project else None,
bool(advanced.get(CONF_SIGNED_OTA_VERIFICATION)),
)
)
if errs:
raise cv.MultipleInvalid(errs)
@@ -1540,6 +1594,9 @@ FRAMEWORK_SCHEMA = cv.Schema(
min=8192, max=32768
),
cv.Optional(CONF_ENABLE_OTA_ROLLBACK, default=True): cv.boolean,
cv.Optional(
CONF_ENABLE_OTA_DOWNGRADE_PROTECTION, default=False
): cv.boolean,
cv.Optional(CONF_SIGNED_OTA_VERIFICATION): cv.All(
cv.Schema(
{
@@ -2358,6 +2415,16 @@ async def to_code(config):
add_idf_sdkconfig_option("CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE", True)
cg.add_define("USE_OTA_ROLLBACK")
# Enable software OTA downgrade protection. Embed the project version into
# the image's esp_app_desc_t so the OTA backend can compare it against the
# running version (final_validate guarantees a dotted-numeric project
# version and that signed OTA is enabled).
if advanced[CONF_ENABLE_OTA_DOWNGRADE_PROTECTION]:
project_version = CORE.config[CONF_ESPHOME][CONF_PROJECT][CONF_VERSION]
add_idf_sdkconfig_option("CONFIG_APP_PROJECT_VER_FROM_CONFIG", True)
add_idf_sdkconfig_option("CONFIG_APP_PROJECT_VER", project_version)
cg.add_define("USE_OTA_DOWNGRADE_PROTECTION")
# Enable signed app verification without hardware secure boot
if signed_ota := advanced.get(CONF_SIGNED_OTA_VERIFICATION):
add_idf_sdkconfig_option("CONFIG_SECURE_SIGNED_APPS_NO_SECURE_BOOT", True)
+6
View File
@@ -341,6 +341,12 @@ async def to_code(config):
for symbol in ("vprintf", "printf", "fprintf"):
cg.add_build_flag(f"-Wl,--wrap={symbol}")
# Wrap the lwIP2 glue's do-nothing dhcp_cleanup()/dhcp_release() stubs so the
# linker can drop their "STUB: ..." message strings from DRAM.
# See lwip_glue_stubs.cpp for implementation.
for symbol in ("dhcp_cleanup", "dhcp_release"):
cg.add_build_flag(f"-Wl,--wrap={symbol}")
# Wrap Arduino's millis() so all callers (including Arduino libraries and ISR
# handlers) use our fast accumulator instead of the expensive 4x 64-bit multiply
# implementation in the Arduino ESP8266 core.
+1 -1
View File
@@ -12,7 +12,7 @@ namespace esphome {
uint32_t random_uint32() { return os_random(); }
bool random_bytes(uint8_t *data, size_t len) { return os_get_random(data, len) == 0; }
// ESP8266 Mutex is defined inline as a no-op in helpers.h when USE_ESP8266 (or USE_RP2040) is set,
// ESP8266 Mutex is defined inline as a no-op in helpers.h when USE_ESP8266 (or USE_RP2) is set,
// independent of the ESPHOME_THREAD_SINGLE thread model define.
IRAM_ATTR InterruptLock::InterruptLock() { state_ = xt_rsil(15); }
@@ -0,0 +1,35 @@
/*
* Linker wrap stubs for the lwIP2 glue's dead DHCP entry points.
*
* The ESP8266 SDK blobs call dhcp_cleanup() and dhcp_release() when the
* station leaves an access point (cnx_sta_leave, wifi_station_dhcpc_stop).
* In the prebuilt lwIP2 glue (liblwip2-*.a, glue-esp/lwip-esp.c) these are
* stubs whose only effect is printing "STUB: dhcp_cleanup" and
* "STUB: dhcp_release"; the real DHCP teardown happens through lwIP2's
* renamed dhcp_cleanup_LWIP2()/dhcp_release_LWIP2() functions.
*
* On ESP8266 .rodata lives in DRAM, so those message strings waste scarce
* RAM. Wrapping the stubs with silent equivalents lets the linker garbage
* collect the glue stub bodies together with their strings.
*
* Saves 38 bytes of RAM and removes the "STUB:" log noise on Wi-Fi
* disconnect. Behavior is otherwise unchanged.
*/
#if defined(USE_ESP8266)
namespace esphome::esp8266 {}
// NOLINTBEGIN(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp,readability-identifier-naming)
extern "C" {
// The callers are closed-source SDK blobs; the netif argument is unused.
void __wrap_dhcp_cleanup(void * /*netif*/) {}
// The glue stub returns ERR_ABRT (-8; lwIP 1.4 err_t is a signed char).
signed char __wrap_dhcp_release(void * /*netif*/) { return -8; }
} // extern "C"
// NOLINTEND(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp,readability-identifier-naming)
#endif // USE_ESP8266
+1 -1
View File
@@ -126,7 +126,7 @@ CONFIG_SCHEMA = cv.All(
CONF_PORT,
esp8266=8266,
esp32=3232,
rp2040=2040,
rp2=2040,
bk72xx=8892,
ln882x=8820,
rtl87xx=8892,
@@ -8,7 +8,7 @@
#include "esphome/components/ota/ota_backend.h"
#include "esphome/components/ota/ota_backend_esp8266.h"
#include "esphome/components/ota/ota_backend_arduino_libretiny.h"
#include "esphome/components/ota/ota_backend_arduino_rp2040.h"
#include "esphome/components/ota/ota_backend_arduino_rp2.h"
#include "esphome/components/ota/ota_backend_esp_idf.h"
#include "esphome/core/application.h"
#include "esphome/core/hal.h"
+29 -3
View File
@@ -41,7 +41,7 @@ DeletePeerAction = espnow_ns.class_("DeletePeerAction", automation.Action)
ESPNowHandlerTrigger = automation.Trigger.template(
ESPNowRecvInfoConstRef,
cg.uint8.operator("const").operator("ptr"),
cg.uint8,
cg.uint16,
)
OnUnknownPeerTrigger = espnow_ns.class_(
@@ -56,6 +56,20 @@ OnBroadcastTrigger = espnow_ns.class_(
CONF_AUTO_ADD_PEER = "auto_add_peer"
CONF_MAX_PAYLOAD_SIZE = "max_payload_size"
# Payload limits of ESP-NOW v1 and v2 frames. The radio negotiates the
# protocol version per peer on its own; the option only sizes this device's
# packet buffers, whose static RAM cost is proportional to it (~8 KB at 250
# bytes, ~44 KB at 1470).
ESPNOW_PAYLOAD_V1 = 250
ESPNOW_PAYLOAD_V2 = 1470
# Config-time cap for action payloads. The per-device limit is the
# ``max_payload_size`` option, which the action schema cannot see; send()
# enforces it at runtime.
MAX_ESPNOW_PACKET_SIZE = ESPNOW_PAYLOAD_V2
CONF_PEERS = "peers"
CONF_ON_SENT = "on_sent"
CONF_ON_UNKNOWN_PEER = "on_unknown_peer"
@@ -63,7 +77,15 @@ CONF_ON_BROADCAST = "on_broadcast"
CONF_CONTINUE_ON_ERROR = "continue_on_error"
CONF_WAIT_FOR_SENT = "wait_for_sent"
MAX_ESPNOW_PACKET_SIZE = 250 # Maximum size of the payload in bytes
def _validate_max_payload_size(value: int) -> int:
if value > ESPNOW_PAYLOAD_V1:
return cv.require_framework_version(
esp_idf=cv.Version(5, 4, 0),
esp32_arduino=cv.Version(3, 2, 0),
extra_message="ESP-NOW v2 frames need an ESP-NOW v2 capable framework",
)(value)
return value
def validate_channel(value):
@@ -78,6 +100,9 @@ CONFIG_SCHEMA = cv.All(
cv.GenerateID(): cv.declare_id(ESPNowComponent),
cv.OnlyWithout(CONF_CHANNEL, CONF_WIFI): validate_channel,
cv.Optional(CONF_ENABLE_ON_BOOT, default=True): cv.boolean,
cv.Optional(CONF_MAX_PAYLOAD_SIZE, default=ESPNOW_PAYLOAD_V1): cv.All(
cv.int_range(min=1, max=ESPNOW_PAYLOAD_V2), _validate_max_payload_size
),
cv.Optional(CONF_AUTO_ADD_PEER, default=False): cv.boolean,
cv.Optional(CONF_PEERS): cv.ensure_list(cv.mac_address),
cv.Optional(CONF_ON_UNKNOWN_PEER): automation.validate_automation(
@@ -113,7 +138,7 @@ async def _trigger_to_code(config):
[
(ESPNowRecvInfoConstRef, "info"),
(cg.uint8.operator("const").operator("ptr"), "data"),
(cg.uint8, "size"),
(cg.uint16, "size"),
],
config,
)
@@ -125,6 +150,7 @@ async def to_code(config):
await cg.register_component(var, config)
cg.add_define("USE_ESPNOW")
cg.add_define("USE_ESPNOW_MAX_PAYLOAD_SIZE", config[CONF_MAX_PAYLOAD_SIZE])
if wifi_channel := config.get(CONF_CHANNEL):
cg.add(var.set_wifi_channel(wifi_channel))
+6 -6
View File
@@ -119,7 +119,7 @@ template<typename... Ts> class SetChannelAction final : public Action<Ts...>, pu
}
};
class OnReceiveTrigger final : public Trigger<const ESPNowRecvInfo &, const uint8_t *, uint8_t>,
class OnReceiveTrigger final : public Trigger<const ESPNowRecvInfo &, const uint8_t *, uint16_t>,
public ESPNowReceivedPacketHandler {
public:
explicit OnReceiveTrigger(std::array<uint8_t, ESP_NOW_ETH_ALEN> address) : has_address_(true) {
@@ -128,7 +128,7 @@ class OnReceiveTrigger final : public Trigger<const ESPNowRecvInfo &, const uint
explicit OnReceiveTrigger() {}
bool on_receive(const ESPNowRecvInfo &info, const uint8_t *data, uint8_t size) override {
bool on_receive(const ESPNowRecvInfo &info, const uint8_t *data, uint16_t size) override {
bool match = !this->has_address_ || (memcmp(this->address_, info.src_addr, ESP_NOW_ETH_ALEN) == 0);
if (!match)
return false;
@@ -141,15 +141,15 @@ class OnReceiveTrigger final : public Trigger<const ESPNowRecvInfo &, const uint
bool has_address_{false};
uint8_t address_[ESP_NOW_ETH_ALEN]{};
};
class OnUnknownPeerTrigger final : public Trigger<const ESPNowRecvInfo &, const uint8_t *, uint8_t>,
class OnUnknownPeerTrigger final : public Trigger<const ESPNowRecvInfo &, const uint8_t *, uint16_t>,
public ESPNowUnknownPeerHandler {
public:
bool on_unknown_peer(const ESPNowRecvInfo &info, const uint8_t *data, uint8_t size) override {
bool on_unknown_peer(const ESPNowRecvInfo &info, const uint8_t *data, uint16_t size) override {
this->trigger(info, data, size);
return false; // Return false to continue processing other internal handlers
}
};
class OnBroadcastTrigger final : public Trigger<const ESPNowRecvInfo &, const uint8_t *, uint8_t>,
class OnBroadcastTrigger final : public Trigger<const ESPNowRecvInfo &, const uint8_t *, uint16_t>,
public ESPNowBroadcastHandler {
public:
explicit OnBroadcastTrigger(std::array<uint8_t, ESP_NOW_ETH_ALEN> address) : has_address_(true) {
@@ -157,7 +157,7 @@ class OnBroadcastTrigger final : public Trigger<const ESPNowRecvInfo &, const ui
}
explicit OnBroadcastTrigger() {}
bool on_broadcast(const ESPNowRecvInfo &info, const uint8_t *data, uint8_t size) override {
bool on_broadcast(const ESPNowRecvInfo &info, const uint8_t *data, uint16_t size) override {
bool match = !this->has_address_ || (memcmp(this->address_, info.src_addr, ESP_NOW_ETH_ALEN) == 0);
if (!match)
return false;
@@ -4,6 +4,7 @@
#include "espnow_err.h"
#include <algorithm>
#include <cinttypes>
#include "esphome/core/application.h"
@@ -96,9 +97,9 @@ void on_send_report(const uint8_t *mac_addr, esp_now_send_status_t status)
void on_data_received(const esp_now_recv_info_t *info, const uint8_t *data, int size) {
// Drop oversized frames before copying. ESP-NOW v2 peers (IDF >= 5.4 builds a
// v2 stack with no opt-out) can send up to ESP_NOW_MAX_DATA_LEN_V2 (1470 B),
// but our receive buffer is ESP_NOW_MAX_DATA_LEN (250 B); copying a larger
// frame would overflow packet_.receive.data.
if (size < 0 || size > ESP_NOW_MAX_DATA_LEN) {
// but the receive buffer only fits v2 frames with ``max_payload_size``; copying a
// larger frame would overflow packet_.receive.data.
if (size < 0 || size > ESPNOW_MAX_DATA_LEN) {
global_esp_now->receive_packet_queue_.increment_dropped_count();
return;
}
@@ -285,11 +286,14 @@ void ESPNowComponent::loop() {
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char src_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
char dst_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
// Cap the hex dump at a v1 frame: a full v2 frame would need a
// ~4.4 KB stack buffer.
char hex_buf[format_hex_pretty_size(ESP_NOW_MAX_DATA_LEN)];
format_mac_addr_upper(info.src_addr, src_buf);
format_mac_addr_upper(info.des_addr, dst_buf);
ESP_LOGV(TAG, "<<< [%s -> %s] %s", src_buf, dst_buf,
format_hex_pretty_to(hex_buf, packet->packet_.receive.data, packet->packet_.receive.size));
format_hex_pretty_to(hex_buf, packet->packet_.receive.data,
std::min<uint16_t>(packet->packet_.receive.size, ESP_NOW_MAX_DATA_LEN)));
#endif
if (memcmp(info.des_addr, ESPNOW_BROADCAST_ADDR, ESP_NOW_ETH_ALEN) == 0) {
for (auto *handler : this->broadcast_handlers_) {
@@ -362,7 +366,7 @@ esp_err_t ESPNowComponent::send(const uint8_t *peer_address, const uint8_t *payl
return ESP_ERR_ESPNOW_PEER_NOT_SET;
} else if (memcmp(peer_address, this->own_address_, ESP_NOW_ETH_ALEN) == 0) {
return ESP_ERR_ESPNOW_OWN_ADDRESS;
} else if (size > ESP_NOW_MAX_DATA_LEN) {
} else if (size > ESPNOW_MAX_DATA_LEN) {
return ESP_ERR_ESPNOW_DATA_SIZE;
} else if (!esp_now_is_peer_exist(peer_address)) {
if (memcmp(peer_address, ESPNOW_BROADCAST_ADDR, ESP_NOW_ETH_ALEN) == 0 || this->auto_add_peer_) {
+3 -3
View File
@@ -62,7 +62,7 @@ class ESPNowUnknownPeerHandler {
/// @param data Pointer to the received data payload
/// @param size Size of the received data in bytes
/// @return true if the packet was handled, false otherwise
virtual bool on_unknown_peer(const ESPNowRecvInfo &info, const uint8_t *data, uint8_t size) = 0;
virtual bool on_unknown_peer(const ESPNowRecvInfo &info, const uint8_t *data, uint16_t size) = 0;
};
/// Handler interface for receiving ESPNow packets
@@ -74,7 +74,7 @@ class ESPNowReceivedPacketHandler {
/// @param data Pointer to the received data payload
/// @param size Size of the received data in bytes
/// @return true if the packet was handled, false otherwise
virtual bool on_receive(const ESPNowRecvInfo &info, const uint8_t *data, uint8_t size) = 0;
virtual bool on_receive(const ESPNowRecvInfo &info, const uint8_t *data, uint16_t size) = 0;
};
/// Handler interface for receiving ESPNow broadcast packets
/// Components should inherit from this class to handle incoming ESPNow data
@@ -85,7 +85,7 @@ class ESPNowBroadcastHandler {
/// @param data Pointer to the received data payload
/// @param size Size of the received data in bytes
/// @return true if the packet was handled, false otherwise
virtual bool on_broadcast(const ESPNowRecvInfo &info, const uint8_t *data, uint8_t size) = 0;
virtual bool on_broadcast(const ESPNowRecvInfo &info, const uint8_t *data, uint16_t size) = 0;
};
class ESPNowComponent final : public Component {
+26 -9
View File
@@ -19,6 +19,23 @@ namespace esphome::espnow {
static const uint8_t ESPNOW_BROADCAST_ADDR[ESP_NOW_ETH_ALEN] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
static const uint8_t ESPNOW_MULTICAST_ADDR[ESP_NOW_ETH_ALEN] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE};
// Maximum payload this component sends and receives, from the
// ``max_payload_size`` option. The radio stack speaks ESP-NOW v2 regardless
// (negotiated per peer); payloads beyond the v1 limit (250 bytes) are opt-in
// because the packet pools are statically sized from this, so their RAM cost
// is proportional (~8 KB at 250 bytes, ~44 KB at the v2 limit of 1470).
#ifndef USE_ESPNOW_MAX_PAYLOAD_SIZE
#define USE_ESPNOW_MAX_PAYLOAD_SIZE ESP_NOW_MAX_DATA_LEN
#endif
static constexpr uint16_t ESPNOW_MAX_DATA_LEN = USE_ESPNOW_MAX_PAYLOAD_SIZE;
#ifdef ESP_NOW_MAX_DATA_LEN_V2
static_assert(ESPNOW_MAX_DATA_LEN <= ESP_NOW_MAX_DATA_LEN_V2,
"espnow max_payload_size cannot exceed the ESP-NOW v2 frame limit");
#else
static_assert(ESPNOW_MAX_DATA_LEN <= ESP_NOW_MAX_DATA_LEN,
"espnow max_payload_size beyond 250 bytes requires an ESP-IDF with ESP-NOW v2 support (5.4+)");
#endif
struct WifiPacketRxControl {
int8_t rssi; // Received Signal Strength Indicator (RSSI) of packet, unit: dBm
uint32_t timestamp; // Timestamp in microseconds when the packet was received, precise only if modem sleep or
@@ -78,10 +95,10 @@ class ESPNowPacket {
union {
// NOLINTNEXTLINE(readability-identifier-naming)
struct received_data {
ESPNowRecvInfo info; // Information about the received packet
uint8_t data[ESP_NOW_MAX_DATA_LEN]; // Data received in the packet
uint8_t size; // Size of the received data
WifiPacketRxControl rx_ctrl; // Status of the received packet
ESPNowRecvInfo info; // Information about the received packet
uint8_t data[ESPNOW_MAX_DATA_LEN]; // Data received in the packet
uint16_t size; // Size of the received data
WifiPacketRxControl rx_ctrl; // Status of the received packet
} receive;
// NOLINTNEXTLINE(readability-identifier-naming)
@@ -144,15 +161,15 @@ class ESPNowSendPacket {
this->callback_ = nullptr; // Reset callback
}
uint8_t address_[ESP_NOW_ETH_ALEN]{0}; // MAC address of the peer to send the packet to
uint8_t data_[ESP_NOW_MAX_DATA_LEN]{0}; // Data to send
uint8_t size_{0}; // Size of the data to send, must be <= ESP_NOW_MAX_DATA_LEN
send_callback_t callback_{nullptr}; // Callback to call when the send operation is complete
uint8_t address_[ESP_NOW_ETH_ALEN]{0}; // MAC address of the peer to send the packet to
uint8_t data_[ESPNOW_MAX_DATA_LEN]{0}; // Data to send
uint16_t size_{0}; // Size of the data to send, must be <= ESPNOW_MAX_DATA_LEN
send_callback_t callback_{nullptr}; // Callback to call when the send operation is complete
private:
void init_data_(const uint8_t *peer_address, const uint8_t *payload, size_t size) {
memcpy(this->address_, peer_address, ESP_NOW_ETH_ALEN);
if (size > ESP_NOW_MAX_DATA_LEN) {
if (size > ESPNOW_MAX_DATA_LEN) {
this->size_ = 0;
return;
}
@@ -42,8 +42,8 @@ void ESPNowTransport::send_packet(const std::vector<uint8_t> &buf) const {
return;
}
if (buf.size() > ESP_NOW_MAX_DATA_LEN) {
ESP_LOGE(TAG, "Packet too large: %zu bytes (max %d)", buf.size(), ESP_NOW_MAX_DATA_LEN);
if (buf.size() > ESPNOW_MAX_DATA_LEN) {
ESP_LOGE(TAG, "Packet too large: %zu bytes (max %u)", buf.size(), (unsigned) ESPNOW_MAX_DATA_LEN);
return;
}
@@ -55,8 +55,8 @@ void ESPNowTransport::send_packet(const std::vector<uint8_t> &buf) const {
});
}
bool ESPNowTransport::on_receive(const ESPNowRecvInfo &info, const uint8_t *data, uint8_t size) {
ESP_LOGV(TAG, "Received packet of size %u from %02X:%02X:%02X:%02X:%02X:%02X", size, info.src_addr[0],
bool ESPNowTransport::on_receive(const ESPNowRecvInfo &info, const uint8_t *data, uint16_t size) {
ESP_LOGV(TAG, "Received packet of size %u from %02X:%02X:%02X:%02X:%02X:%02X", (unsigned) size, info.src_addr[0],
info.src_addr[1], info.src_addr[2], info.src_addr[3], info.src_addr[4], info.src_addr[5]);
if (data == nullptr || size == 0) {
@@ -70,9 +70,9 @@ bool ESPNowTransport::on_receive(const ESPNowRecvInfo &info, const uint8_t *data
return false; // Allow other handlers to run
}
bool ESPNowTransport::on_broadcast(const ESPNowRecvInfo &info, const uint8_t *data, uint8_t size) {
ESP_LOGV(TAG, "Received broadcast packet of size %u from %02X:%02X:%02X:%02X:%02X:%02X", size, info.src_addr[0],
info.src_addr[1], info.src_addr[2], info.src_addr[3], info.src_addr[4], info.src_addr[5]);
bool ESPNowTransport::on_broadcast(const ESPNowRecvInfo &info, const uint8_t *data, uint16_t size) {
ESP_LOGV(TAG, "Received broadcast packet of size %u from %02X:%02X:%02X:%02X:%02X:%02X", (unsigned) size,
info.src_addr[0], info.src_addr[1], info.src_addr[2], info.src_addr[3], info.src_addr[4], info.src_addr[5]);
if (data == nullptr || size == 0) {
ESP_LOGW(TAG, "Received empty or null broadcast packet");
@@ -24,12 +24,12 @@ class ESPNowTransport final : public packet_transport::PacketTransport,
}
// ESPNow handler interface
bool on_receive(const ESPNowRecvInfo &info, const uint8_t *data, uint8_t size) override;
bool on_broadcast(const ESPNowRecvInfo &info, const uint8_t *data, uint8_t size) override;
bool on_receive(const ESPNowRecvInfo &info, const uint8_t *data, uint16_t size) override;
bool on_broadcast(const ESPNowRecvInfo &info, const uint8_t *data, uint16_t size) override;
protected:
void send_packet(const std::vector<uint8_t> &buf) const override;
size_t get_max_packet_size() override { return ESP_NOW_MAX_DATA_LEN; }
size_t get_max_packet_size() override { return ESPNOW_MAX_DATA_LEN; }
bool should_send() override;
peer_address_t peer_address_{{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}};
+15 -13
View File
@@ -179,9 +179,11 @@ _ALWAYS_EXTERNAL_IDF_COMPONENTS = {"LAN8670", "ENC28J60"}
# ESP32-only SPI ethernet types (W5100 is RP2040-only, no ESP-IDF driver)
SPI_ETHERNET_TYPES = {"W5500", "DM9051", "ENC28J60"}
# RP2040-supported ethernet types (SPI and PIO QSPI)
RP2040_ETHERNET_TYPES = {"W5100", "W5500", "W6100", "W6300", "ENC28J60"}
_RP2040_SPI_LIBRARIES = {
# RP2-supported ethernet types (SPI and PIO QSPI). Applies to the whole
# RP2 family (RP2040 and RP2350); the chip-specific W5100 caveat in the
# comment above is about ESP-IDF driver coverage, not the RP2 platform.
RP2_ETHERNET_TYPES = {"W5100", "W5500", "W6100", "W6300", "ENC28J60"}
_RP2_SPI_LIBRARIES = {
"W5100": "lwIP_w5100",
"W5500": "lwIP_w5500",
"ENC28J60": "lwIP_enc28j60",
@@ -361,10 +363,10 @@ def _validate(config):
f"{config[CONF_TYPE]} PHY requires RMII interface and is only supported "
f"on ESP32 classic and ESP32-P4, not {variant}"
)
elif CORE.is_rp2040 and config[CONF_TYPE] not in RP2040_ETHERNET_TYPES:
elif CORE.is_rp2 and config[CONF_TYPE] not in RP2_ETHERNET_TYPES:
raise cv.Invalid(
f"Only {', '.join(sorted(RP2040_ETHERNET_TYPES))} are supported on RP2040, "
f"not {config[CONF_TYPE]}"
f"Only {', '.join(sorted(RP2_ETHERNET_TYPES))} are supported on the RP2 "
f"platform, not {config[CONF_TYPE]}"
)
return config
@@ -459,7 +461,7 @@ SPI_SCHEMA = cv.All(
}
),
),
cv.only_on([Platform.ESP32, Platform.RP2040]),
cv.only_on([Platform.ESP32, Platform.RP2]),
_validate_spi_interface,
)
@@ -473,13 +475,13 @@ CONFIG_SCHEMA = cv.All(
"JL1101": RMII_SCHEMA,
"KSZ8081": RMII_SCHEMA,
"KSZ8081RNA": RMII_SCHEMA,
"W5100": cv.All(SPI_SCHEMA, cv.only_on([Platform.RP2040])),
"W5100": cv.All(SPI_SCHEMA, cv.only_on([Platform.RP2])),
"W5500": SPI_SCHEMA,
"OPENETH": cv.All(BASE_SCHEMA, cv.only_on([Platform.ESP32])),
"DM9051": SPI_SCHEMA,
"ENC28J60": SPI_SCHEMA,
"W6100": cv.All(SPI_SCHEMA, cv.only_on([Platform.RP2040])),
"W6300": cv.All(SPI_SCHEMA, cv.only_on([Platform.RP2040])),
"W6100": cv.All(SPI_SCHEMA, cv.only_on([Platform.RP2])),
"W6300": cv.All(SPI_SCHEMA, cv.only_on([Platform.RP2])),
"LAN8670": RMII_SCHEMA,
"GENERIC": GENERIC_SCHEMA,
"YT8531": GENERIC_SCHEMA,
@@ -537,7 +539,7 @@ async def to_code(config):
if CORE.is_esp32:
await _to_code_esp32(var, config)
elif CORE.is_rp2040:
elif CORE.is_rp2:
await _to_code_rp2040(var, config)
cg.add(var.set_type(ETHERNET_TYPES[config[CONF_TYPE]]))
@@ -670,7 +672,7 @@ async def _to_code_rp2040(var: cg.Pvariable, config: ConfigType) -> None:
cg.add(var.set_reset_pin(config[CONF_RESET_PIN]))
cg.add_define("USE_ETHERNET_SPI")
cg.add_library(_RP2040_SPI_LIBRARIES[config[CONF_TYPE]], None)
cg.add_library(_RP2_SPI_LIBRARIES[config[CONF_TYPE]], None)
def _final_validate_rmii_pins(config: ConfigType) -> None:
@@ -752,7 +754,7 @@ _platform_filter = filter_source_files_from_platform(
PlatformFramework.ESP32_IDF,
PlatformFramework.ESP32_ARDUINO,
},
"ethernet_component_rp2040.cpp": {PlatformFramework.RP2040_ARDUINO},
"ethernet_component_rp2.cpp": {PlatformFramework.RP2_ARDUINO},
"esp_eth_phy_jl1101.c": {
PlatformFramework.ESP32_IDF,
PlatformFramework.ESP32_ARDUINO,
@@ -25,7 +25,7 @@ extern "C" eth_esp32_emac_config_t eth_esp32_emac_default_config(void);
#endif
#endif // USE_ESP32
#ifdef USE_RP2040
#ifdef USE_RP2
#if defined(USE_ETHERNET_W5500)
#include <W5500lwIP.h>
#elif defined(USE_ETHERNET_W5100)
@@ -182,14 +182,14 @@ class EthernetComponent final : public Component {
#endif // USE_ETHERNET_SPI
#endif // USE_ESP32
#ifdef USE_RP2040
#ifdef USE_RP2
void set_clk_pin(uint8_t clk_pin);
void set_miso_pin(uint8_t miso_pin);
void set_mosi_pin(uint8_t mosi_pin);
void set_cs_pin(uint8_t cs_pin);
void set_interrupt_pin(int8_t interrupt_pin);
void set_reset_pin(int8_t reset_pin);
#endif // USE_RP2040
#endif // USE_RP2
#ifdef USE_ETHERNET_IP_STATE_LISTENERS
void add_ip_state_listener(EthernetIPStateListener *listener) { this->ip_state_listeners_.push_back(listener); }
@@ -272,7 +272,7 @@ class EthernetComponent final : public Component {
esp_eth_phy_t *phy_{nullptr};
#endif // USE_ESP32
#ifdef USE_RP2040
#ifdef USE_RP2
static constexpr uint32_t LINK_CHECK_INTERVAL = 500; // ms between link/IP polls
#if defined(USE_ETHERNET_W5100)
static constexpr uint32_t RESET_DELAY_MS = 150; // W5100S PLL lock time
@@ -301,7 +301,7 @@ class EthernetComponent final : public Component {
uint8_t cs_pin_;
int8_t interrupt_pin_{-1};
int8_t reset_pin_{-1};
#endif // USE_RP2040
#endif // USE_RP2
// Common members
#ifdef USE_ETHERNET_MANUAL_IP
@@ -1,12 +1,12 @@
#include "ethernet_component.h"
#if defined(USE_ETHERNET) && defined(USE_RP2040)
#if defined(USE_ETHERNET) && defined(USE_RP2)
#include "esphome/core/application.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include "esphome/components/rp2040/gpio.h"
#include "esphome/components/rp2/gpio.h"
#include <SPI.h>
#include <lwip/dns.h>
@@ -29,7 +29,7 @@ void EthernetComponent::setup() {
// Toggle reset pin if configured
if (this->reset_pin_ >= 0) {
rp2040::RP2040GPIOPin reset_pin;
rp2::RP2GPIOPin reset_pin;
reset_pin.set_pin(this->reset_pin_);
reset_pin.set_flags(gpio::FLAG_OUTPUT);
reset_pin.setup();
@@ -380,4 +380,4 @@ void EthernetComponent::disable() {
} // namespace esphome::ethernet
#endif // USE_ETHERNET && USE_RP2040
#endif // USE_ETHERNET && USE_RP2
@@ -7,7 +7,7 @@
#include <cinttypes>
#if !defined(USE_RP2040) && !defined(USE_HOST)
#if !defined(USE_RP2) && !defined(USE_HOST)
namespace esphome::factory_reset {
@@ -73,4 +73,4 @@ void FactoryResetComponent::setup() {
} // namespace esphome::factory_reset
#endif // !defined(USE_RP2040) && !defined(USE_HOST)
#endif // !defined(USE_RP2) && !defined(USE_HOST)
@@ -3,7 +3,7 @@
#include "esphome/core/component.h"
#include "esphome/core/automation.h"
#include "esphome/core/preferences.h"
#if !defined(USE_RP2040) && !defined(USE_HOST)
#if !defined(USE_RP2) && !defined(USE_HOST)
#ifdef USE_ESP32
#include <esp_system.h>
@@ -32,4 +32,4 @@ class FactoryResetComponent final : public Component {
} // namespace esphome::factory_reset
#endif // !defined(USE_RP2040) && !defined(USE_HOST)
#endif // !defined(USE_RP2) && !defined(USE_HOST)
@@ -47,7 +47,7 @@ CONFIG_SCHEMA = (
host=True,
ln882x=False,
nrf52=True,
rp2040=True,
rp2=True,
rtl87xx=False,
): cv.boolean,
cv.Optional(CONF_INTERRUPT_TYPE, default="ANY"): cv.enum(
@@ -1,6 +1,6 @@
#include <cstring>
#include "hmac_sha256.h"
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_HOST)
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2) || defined(USE_LIBRETINY) || defined(USE_HOST)
#include "esphome/core/helpers.h"
namespace esphome::hmac_sha256 {
+1 -1
View File
@@ -1,7 +1,7 @@
#pragma once
#include "esphome/core/defines.h"
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_HOST)
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2) || defined(USE_LIBRETINY) || defined(USE_HOST)
#include <string>
+6 -6
View File
@@ -73,7 +73,7 @@ def validate_url(value):
def validate_ssl_verification(config):
error_message = ""
if CORE.is_rp2040 and config[CONF_VERIFY_SSL]:
if CORE.is_rp2 and config[CONF_VERIFY_SSL]:
error_message = "ESPHome does not support certificate verification on RP2040"
if (
@@ -96,7 +96,7 @@ def _declare_request_class(value):
return cv.declare_id(HttpRequestHost)(value)
if CORE.is_esp32:
return cv.declare_id(HttpRequestIDF)(value)
if CORE.is_esp8266 or CORE.is_rp2040:
if CORE.is_esp8266 or CORE.is_rp2:
return cv.declare_id(HttpRequestArduino)(value)
return NotImplementedError
@@ -118,7 +118,7 @@ CONFIG_SCHEMA = cv.All(
),
cv.Optional(CONF_VERIFY_SSL, default=True): cv.boolean,
cv.Optional(CONF_WATCHDOG_TIMEOUT): cv.All(
cv.Any(cv.only_on_esp32, cv.only_on_rp2040),
cv.Any(cv.only_on_esp32, cv.only_on_rp2),
cv.positive_not_null_time_period,
cv.positive_time_period_milliseconds,
),
@@ -144,7 +144,7 @@ CONFIG_SCHEMA = cv.All(
esp8266_arduino=cv.Version(2, 5, 1),
esp32_arduino=cv.Version(0, 0, 0),
esp_idf=cv.Version(0, 0, 0),
rp2040_arduino=cv.Version(0, 0, 0),
rp2_arduino=cv.Version(0, 0, 0),
host=cv.Version(0, 0, 0),
),
validate_ssl_verification,
@@ -204,7 +204,7 @@ async def to_code(config):
)
if CORE.is_esp8266:
cg.add_library("ESP8266HTTPClient", None)
if CORE.is_rp2040 and CORE.using_arduino:
if CORE.is_rp2 and CORE.using_arduino:
cg.add_library("HTTPClient", None)
if CORE.is_host:
if IS_MACOS:
@@ -368,7 +368,7 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform(
"http_request_host.cpp": {PlatformFramework.HOST_NATIVE},
"http_request_arduino.cpp": {
PlatformFramework.ESP8266_ARDUINO,
PlatformFramework.RP2040_ARDUINO,
PlatformFramework.RP2_ARDUINO,
PlatformFramework.BK72XX_ARDUINO,
PlatformFramework.RTL87XX_ARDUINO,
PlatformFramework.LN882X_ARDUINO,
@@ -72,7 +72,7 @@ std::shared_ptr<HttpContainer> HttpRequestArduino::perform(const std::string &ur
bool status = container->client_.begin(*stream_ptr, url.c_str());
#elif defined(USE_RP2040)
#elif defined(USE_RP2)
if (secure) {
container->client_.setInsecure();
}
@@ -4,7 +4,7 @@
#if defined(USE_ARDUINO) && !defined(USE_ESP32)
#if defined(USE_RP2040)
#if defined(USE_RP2)
#include <HTTPClient.h>
#include <WiFiClient.h>
#endif
@@ -36,7 +36,7 @@ CONFIG_SCHEMA = cv.All(
esp8266_arduino=cv.Version(2, 5, 1),
esp32_arduino=cv.Version(0, 0, 0),
esp_idf=cv.Version(0, 0, 0),
rp2040_arduino=cv.Version(0, 0, 0),
rp2_arduino=cv.Version(0, 0, 0),
),
)
+8 -8
View File
@@ -49,7 +49,7 @@ from esphome.const import (
PLATFORM_ESP8266,
PLATFORM_HOST,
PLATFORM_NRF52,
PLATFORM_RP2040,
PLATFORM_RP2,
PlatformFramework,
)
from esphome.core import CORE, CoroPriority, coroutine_with_priority
@@ -130,7 +130,7 @@ def validate_config(config):
return cv.require_framework_version(
esp_idf=cv.Version(5, 4, 2), esp32_arduino=cv.Version(3, 2, 1)
)(config)
if CORE.is_rp2040:
if CORE.is_rp2:
sda_controller = _rp2040_i2c_controller(config[CONF_SDA])
scl_controller = _rp2040_i2c_controller(config[CONF_SCL])
if sda_controller != scl_controller:
@@ -171,7 +171,7 @@ CONFIG_SCHEMA = cv.All(
CONF_SDA,
esp32="SDA",
esp8266="SDA",
rp2040="SDA",
rp2="SDA",
nrf52="SDA",
): pins.internal_gpio_pin_number,
cv.SplitDefault(CONF_SDA_PULLUP_ENABLED, esp32=True): cv.All(
@@ -181,7 +181,7 @@ CONFIG_SCHEMA = cv.All(
CONF_SCL,
esp32="SCL",
esp8266="SCL",
rp2040="SCL",
rp2="SCL",
nrf52="SCL",
): pins.internal_gpio_pin_number,
cv.SplitDefault(CONF_SCL_PULLUP_ENABLED, esp32=True): cv.All(
@@ -191,7 +191,7 @@ CONFIG_SCHEMA = cv.All(
CONF_FREQUENCY,
esp32="50kHz",
esp8266="50kHz",
rp2040="50kHz",
rp2="50kHz",
nrf52="100kHz",
host="50kHz",
): cv.All(
@@ -219,7 +219,7 @@ CONFIG_SCHEMA = cv.All(
[
PLATFORM_ESP32,
PLATFORM_ESP8266,
PLATFORM_RP2040,
PLATFORM_RP2,
PLATFORM_NRF52,
PLATFORM_HOST,
]
@@ -233,7 +233,7 @@ def _final_validate(config):
full_config = fv.full_config.get()[CONF_I2C]
if CORE.using_zephyr and len(full_config) > 1:
raise cv.Invalid("Second i2c is not implemented on Zephyr yet")
if CORE.is_rp2040:
if CORE.is_rp2:
if len(full_config) > 2:
raise cv.Invalid(
"The maximum number of I2C interfaces for RP2040/RP2350 is 2"
@@ -443,7 +443,7 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform(
{
"i2c_bus_arduino.cpp": {
PlatformFramework.ESP8266_ARDUINO,
PlatformFramework.RP2040_ARDUINO,
PlatformFramework.RP2_ARDUINO,
PlatformFramework.BK72XX_ARDUINO,
PlatformFramework.RTL87XX_ARDUINO,
PlatformFramework.LN882X_ARDUINO,
+4 -4
View File
@@ -19,7 +19,7 @@ void ArduinoI2CBus::setup() {
#if defined(USE_ESP8266)
wire_ = new TwoWire(); // NOLINT(cppcoreguidelines-owning-memory)
#elif defined(USE_RP2040)
#elif defined(USE_RP2)
// Select Wire instance based on pin assignment, not definition order.
// I2C controller = (gpio / 2) % 2: even pairs (0-1,4-5,...) → I2C0, odd pairs (2-3,6-7,...) → I2C1
// RP2040 datasheet Table 2 (section 1.4.3): https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf
@@ -41,7 +41,7 @@ void ArduinoI2CBus::setup() {
}
void ArduinoI2CBus::set_pins_and_clock_() {
#ifdef USE_RP2040
#ifdef USE_RP2
wire_->setSDA(this->sda_pin_);
wire_->setSCL(this->scl_pin_);
wire_->begin();
@@ -52,7 +52,7 @@ void ArduinoI2CBus::set_pins_and_clock_() {
#if defined(USE_ESP8266)
// https://github.com/esp8266/Arduino/blob/master/libraries/Wire/Wire.h
wire_->setClockStretchLimit(timeout_); // unit: us
#elif defined(USE_RP2040)
#elif defined(USE_RP2)
// https://github.com/earlephilhower/ArduinoCore-API/blob/e37df85425e0ac020bfad226d927f9b00d2e0fb7/api/Stream.h
wire_->setTimeout(timeout_ / 1000); // unit: ms
#endif
@@ -70,7 +70,7 @@ void ArduinoI2CBus::dump_config() {
if (timeout_ > 0) {
#if defined(USE_ESP8266)
ESP_LOGCONFIG(TAG, " Timeout: %u us", this->timeout_);
#elif defined(USE_RP2040)
#elif defined(USE_RP2)
ESP_LOGCONFIG(TAG, " Timeout: %u ms", this->timeout_ / 1000);
#endif
}
@@ -1,4 +1,4 @@
#ifdef USE_RP2040
#ifdef USE_RP2
#include "esphome/core/log.h"
#include "internal_temperature.h"
@@ -7,7 +7,7 @@
namespace esphome::internal_temperature {
static const char *const TAG = "internal_temperature.rp2040";
static const char *const TAG = "internal_temperature.rp2";
void InternalTemperatureSensor::update() {
float temperature = NAN;
@@ -28,4 +28,4 @@ void InternalTemperatureSensor::update() {
} // namespace esphome::internal_temperature
#endif // USE_RP2040
#endif // USE_RP2
@@ -10,7 +10,7 @@ from esphome.const import (
PLATFORM_ESP32,
PLATFORM_LN882X,
PLATFORM_NRF52,
PLATFORM_RP2040,
PLATFORM_RP2,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
PlatformFramework,
@@ -34,7 +34,7 @@ CONFIG_SCHEMA = cv.All(
cv.only_on(
[
PLATFORM_ESP32,
PLATFORM_RP2040,
PLATFORM_RP2,
PLATFORM_BK72XX,
PLATFORM_NRF52,
PLATFORM_LN882X,
@@ -58,7 +58,7 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform(
PlatformFramework.ESP32_ARDUINO,
PlatformFramework.ESP32_IDF,
},
"internal_temperature_rp2040.cpp": {PlatformFramework.RP2040_ARDUINO},
"internal_temperature_rp2.cpp": {PlatformFramework.RP2_ARDUINO},
"internal_temperature_bk72xx.cpp": {
PlatformFramework.BK72XX_ARDUINO,
},
@@ -5,7 +5,7 @@
namespace esphome::libretiny {
static const char *const TAG = "lt.gpio";
static const char *const TAG = "libretiny.gpio";
static int IRAM_ATTR flags_to_mode(gpio::Flags flags) {
if (flags == gpio::FLAG_INPUT) {
@@ -6,7 +6,7 @@
namespace esphome::libretiny {
static const char *const TAG = "lt.component";
static const char *const TAG = "libretiny";
void LTComponent::dump_config() {
ESP_LOGCONFIG(TAG,
+7 -11
View File
@@ -54,7 +54,7 @@ from esphome.const import (
PLATFORM_ESP8266,
PLATFORM_LN882X,
PLATFORM_NRF52,
PLATFORM_RP2040,
PLATFORM_RP2,
PLATFORM_RTL87XX,
PlatformFramework,
)
@@ -154,7 +154,7 @@ HARDWARE_UART_TO_SERIAL = {
UART2: cg.global_ns.Serial2,
DEFAULT: cg.global_ns.Serial,
},
PLATFORM_RP2040: {
PLATFORM_RP2: {
UART0: cg.global_ns.Serial1,
UART1: cg.global_ns.Serial2,
USB_CDC: cg.global_ns.Serial,
@@ -171,7 +171,7 @@ def uart_selection(value):
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_rp2040:
if CORE.is_rp2:
return cv.one_of(*UART_SELECTION_RP2040, upper=True)(value)
if CORE.is_libretiny:
family = get_libretiny_family()
@@ -282,7 +282,7 @@ CONFIG_SCHEMA = cv.All(
esp32_s2=USB_CDC,
esp32_s3=USB_SERIAL_JTAG,
esp32_s31=USB_SERIAL_JTAG,
rp2040=USB_CDC,
rp2=USB_CDC,
bk72xx=DEFAULT,
ln882x=DEFAULT,
rtl87xx=DEFAULT,
@@ -292,7 +292,7 @@ CONFIG_SCHEMA = cv.All(
[
PLATFORM_ESP8266,
PLATFORM_ESP32,
PLATFORM_RP2040,
PLATFORM_RP2,
PLATFORM_BK72XX,
PLATFORM_LN882X,
PLATFORM_RTL87XX,
@@ -417,11 +417,7 @@ async def _late_logger_init(config: ConfigType) -> None:
cg.add_define("USE_ESP8266_LOGGER_SERIAL1")
enable_serial1()
if (
(CORE.is_esp8266 or CORE.is_rp2040)
and has_serial_logging
and is_at_least_verbose
):
if (CORE.is_esp8266 or CORE.is_rp2) and has_serial_logging and is_at_least_verbose:
debug_serial_port = HARDWARE_UART_TO_SERIAL[CORE.target_platform][
config.get(CONF_HARDWARE_UART)
]
@@ -605,7 +601,7 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform(
},
"logger_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO},
"logger_host.cpp": {PlatformFramework.HOST_NATIVE},
"logger_rp2040.cpp": {PlatformFramework.RP2040_ARDUINO},
"logger_rp2.cpp": {PlatformFramework.RP2_ARDUINO},
"logger_libretiny.cpp": {
PlatformFramework.BK72XX_ARDUINO,
PlatformFramework.RTL87XX_ARDUINO,
+1 -1
View File
@@ -206,7 +206,7 @@ void Logger::set_baud_rate(uint32_t baud_rate) { this->baud_rate_ = baud_rate; }
void Logger::set_log_level(const char *tag, uint8_t log_level) { this->log_levels_[tag] = log_level; }
#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_RP2) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR)
UARTSelection Logger::get_uart() const { return this->uart_; }
#endif
+8 -8
View File
@@ -23,10 +23,10 @@
#if defined(USE_ESP8266)
#include <HardwareSerial.h>
#endif // USE_ESP8266
#ifdef USE_RP2040
#ifdef USE_RP2
#include <HardwareSerial.h>
#include <SerialUSB.h>
#endif // USE_RP2040
#endif // USE_RP2
#endif // USE_ARDUINO
#ifdef USE_ESP32
@@ -96,7 +96,7 @@ struct CStrCompare {
// macOS allows up to 64 bytes, Linux up to 16
static constexpr size_t THREAD_NAME_BUF_SIZE = 64;
#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_RP2) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR)
/** Enum for logging UART selection
*
* Advanced configuration (pin selection, etc) is not supported.
@@ -122,7 +122,7 @@ enum UARTSelection : uint8_t {
UART_SELECTION_UART0_SWAP,
#endif // USE_ESP8266
};
#endif // USE_ESP32 || USE_ESP8266 || USE_RP2040 || USE_LIBRETINY || USE_ZEPHYR
#endif // USE_ESP32 || USE_ESP8266 || USE_RP2 || USE_LIBRETINY || USE_ZEPHYR
/**
* @brief Logger component for all ESPHome logging.
@@ -160,7 +160,7 @@ class Logger final : public Component {
#ifdef USE_HOST
void create_pthread_key() { pthread_key_create(&log_recursion_key_, nullptr); }
#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_RP2) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR)
void set_uart_selection(UARTSelection uart_selection) { uart_ = uart_selection; }
/// Get the UART used by the logger.
UARTSelection get_uart() const;
@@ -351,7 +351,7 @@ class Logger final : public Component {
#endif
// Group smaller types together at the end
uint8_t current_level_{ESPHOME_LOG_LEVEL_VERY_VERBOSE};
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_ZEPHYR)
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2) || defined(USE_ZEPHYR)
UARTSelection uart_{UART_SELECTION_UART0};
#endif
#ifdef USE_LIBRETINY
@@ -505,8 +505,8 @@ class LoggerMessageTrigger final : public Trigger<uint8_t, const char *, const c
#include "logger_esp32.h"
#elif defined(USE_ESP8266)
#include "logger_esp8266.h"
#elif defined(USE_RP2040)
#include "logger_rp2040.h"
#elif defined(USE_RP2)
#include "logger_rp2.h"
#elif defined(USE_LIBRETINY)
#include "logger_libretiny.h"
#endif
@@ -1,8 +1,8 @@
#ifdef USE_RP2040
#ifdef USE_RP2
#include "logger.h"
#include "esphome/core/defines.h"
#ifdef USE_RP2040_CRASH_HANDLER
#include "esphome/components/rp2040/crash_handler.h"
#ifdef USE_RP2_CRASH_HANDLER
#include "esphome/components/rp2/crash_handler.h"
#endif
#include "esphome/core/log.h"
@@ -29,8 +29,8 @@ void Logger::pre_setup() {
}
global_logger = this;
ESP_LOGI(TAG, "Log initialized");
#ifdef USE_RP2040_CRASH_HANDLER
rp2040::crash_handler_log();
#ifdef USE_RP2_CRASH_HANDLER
rp2::crash_handler_log();
#endif
}
@@ -50,4 +50,4 @@ const LogString *Logger::get_uart_selection_() {
}
} // namespace esphome::logger
#endif // USE_RP2040
#endif // USE_RP2
@@ -1,6 +1,6 @@
#pragma once
#ifdef USE_RP2040
#ifdef USE_RP2
#include "esphome/core/helpers.h"
namespace esphome::logger {
+1 -1
View File
@@ -929,7 +929,7 @@ void lv_mem_init() {}
void lv_mem_deinit() {}
#if defined(USE_HOST) || defined(USE_RP2040) || defined(USE_ESP8266)
#if defined(USE_HOST) || defined(USE_RP2) || defined(USE_ESP8266)
void *lv_malloc_core(size_t size) {
auto *ptr = malloc(size); // NOLINT
if (ptr == nullptr) {
+4 -4
View File
@@ -5,7 +5,7 @@
namespace esphome::md5 {
#if defined(USE_ARDUINO) && !defined(USE_RP2040) && !defined(USE_ESP32)
#if defined(USE_ARDUINO) && !defined(USE_RP2) && !defined(USE_ESP32)
void MD5Digest::init() {
memset(this->digest_, 0, 16);
MD5Init(&this->ctx_);
@@ -14,7 +14,7 @@ void MD5Digest::init() {
void MD5Digest::add(const uint8_t *data, size_t len) { MD5Update(&this->ctx_, data, len); }
void MD5Digest::calculate() { MD5Final(this->digest_, &this->ctx_); }
#endif // USE_ARDUINO && !USE_RP2040
#endif // USE_ARDUINO && !USE_RP2
#ifdef USE_ESP32
void MD5Digest::init() {
@@ -27,7 +27,7 @@ void MD5Digest::add(const uint8_t *data, size_t len) { esp_rom_md5_update(&this-
void MD5Digest::calculate() { esp_rom_md5_final(this->digest_, &this->ctx_); }
#endif // USE_ESP32
#ifdef USE_RP2040
#ifdef USE_RP2
void MD5Digest::init() {
memset(this->digest_, 0, 16);
br_md5_init(&this->ctx_);
@@ -36,7 +36,7 @@ void MD5Digest::init() {
void MD5Digest::add(const uint8_t *data, size_t len) { br_md5_update(&this->ctx_, data, len); }
void MD5Digest::calculate() { br_md5_out(&this->ctx_, this->digest_); }
#endif // USE_RP2040
#endif // USE_RP2
#ifdef USE_HOST
MD5Digest::~MD5Digest() {
+1 -1
View File
@@ -19,7 +19,7 @@
#define MD5_CTX_TYPE md5_context_t
#endif
#ifdef USE_RP2040
#ifdef USE_RP2
#include <MD5Builder.h>
#define MD5_CTX_TYPE br_md5_context
#endif
+6 -6
View File
@@ -70,11 +70,11 @@ def _require_network_interface(config: ConfigType) -> ConfigType:
window. Reject at config time rather than silently producing a component
that never initializes.
"""
if config.get(CONF_DISABLED) or not (CORE.is_esp8266 or CORE.is_rp2040):
if config.get(CONF_DISABLED) or not (CORE.is_esp8266 or CORE.is_rp2):
return config
full_config = fv.full_config.get()
has_wifi = "wifi" in full_config
has_ethernet = CORE.is_rp2040 and "ethernet" in full_config
has_ethernet = CORE.is_rp2 and "ethernet" in full_config
if not (has_wifi or has_ethernet):
options = "'wifi'" if CORE.is_esp8266 else "'wifi' or 'ethernet'"
raise cv.Invalid(
@@ -192,18 +192,18 @@ async def to_code(config):
if CORE.using_arduino:
if CORE.is_esp8266:
cg.add_library("ESP8266mDNS", None)
elif CORE.is_rp2040:
elif CORE.is_rp2:
cg.add_library("LEAmDNS", None)
# Subscribe to the network IP state listener(s) so MDNS.update() is only
# scheduled during the probe+announce phase. Same on_ip_state() override
# serves both WiFi and Ethernet (signatures match).
if CORE.is_esp8266 or CORE.is_rp2040:
if CORE.is_esp8266 or CORE.is_rp2:
if "wifi" in CORE.config:
from esphome.components import wifi
wifi.request_wifi_ip_state_listener()
if CORE.is_rp2040 and "ethernet" in CORE.config:
if CORE.is_rp2 and "ethernet" in CORE.config:
from esphome.components import ethernet
ethernet.request_ethernet_ip_state_listener()
@@ -274,7 +274,7 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform(
},
"mdns_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO},
"mdns_host.cpp": {PlatformFramework.HOST_NATIVE},
"mdns_rp2040.cpp": {PlatformFramework.RP2040_ARDUINO},
"mdns_rp2.cpp": {PlatformFramework.RP2_ARDUINO},
"mdns_libretiny.cpp": {
PlatformFramework.BK72XX_ARDUINO,
PlatformFramework.RTL87XX_ARDUINO,
+4 -4
View File
@@ -100,7 +100,7 @@ void MDNSComponent::compile_records_(StaticVector<MDNSService, MDNS_SERVICE_COUN
if (!friendly_name_empty) {
txt_count++; // friendly_name
}
#if defined(USE_ESP8266) || defined(USE_ESP32) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_NRF52)
#if defined(USE_ESP8266) || defined(USE_ESP32) || defined(USE_RP2) || defined(USE_LIBRETINY) || defined(USE_NRF52)
txt_count++; // platform
#endif
#if defined(USE_WIFI) || defined(USE_ETHERNET) || defined(USE_OPENTHREAD)
@@ -136,9 +136,9 @@ void MDNSComponent::compile_records_(StaticVector<MDNSService, MDNS_SERVICE_COUN
#elif defined(USE_ESP32)
MDNS_STATIC_CONST_CHAR(PLATFORM_ESP32, "ESP32");
txt_records.push_back({MDNS_STR(TXT_PLATFORM), MDNS_STR(PLATFORM_ESP32)});
#elif defined(USE_RP2040)
MDNS_STATIC_CONST_CHAR(PLATFORM_RP2040, "RP2040");
txt_records.push_back({MDNS_STR(TXT_PLATFORM), MDNS_STR(PLATFORM_RP2040)});
#elif defined(USE_RP2)
MDNS_STATIC_CONST_CHAR(PLATFORM_RP2, "RP2");
txt_records.push_back({MDNS_STR(TXT_PLATFORM), MDNS_STR(PLATFORM_RP2)});
#elif defined(USE_LIBRETINY)
txt_records.push_back({MDNS_STR(TXT_PLATFORM), MDNS_STR(lt_cpu_get_model_name())});
#elif defined(USE_NRF52)
+2 -2
View File
@@ -7,7 +7,7 @@
#include "esphome/core/helpers.h"
// On ESP8266 and RP2040 the scheduler-backed MDNS.update() polling window is armed by
// IP state listener events on whichever network interface is configured.
#if (defined(USE_ESP8266) || defined(USE_RP2040)) && \
#if (defined(USE_ESP8266) || defined(USE_RP2)) && \
((defined(USE_WIFI) && defined(USE_WIFI_IP_STATE_LISTENERS)) || \
(defined(USE_ETHERNET) && defined(USE_ETHERNET_IP_STATE_LISTENERS)))
#include "esphome/components/network/ip_address.h"
@@ -145,7 +145,7 @@ class MDNSComponent final : public Component
#ifdef USE_MDNS_STORE_SERVICES
StaticVector<MDNSService, MDNS_SERVICE_COUNT> services_{};
#endif
#if defined(USE_RP2040) && defined(USE_MDNS_EVENT_DRIVEN_POLLING)
#if defined(USE_RP2) && defined(USE_MDNS_EVENT_DRIVEN_POLLING)
// RP2040 defers MDNS.begin() until the first IP-up event; this tracks that.
bool initialized_{false};
#endif
@@ -1,5 +1,5 @@
#include "esphome/core/defines.h"
#if defined(USE_RP2040) && defined(USE_MDNS)
#if defined(USE_RP2) && defined(USE_MDNS)
#include "esphome/components/network/ip_address.h"
#include "esphome/components/network/util.h"
@@ -17,7 +17,7 @@
namespace esphome::mdns {
static void register_rp2040(MDNSComponent *, StaticVector<MDNSService, MDNS_SERVICE_COUNT> &services) {
static void register_rp2(MDNSComponent *, StaticVector<MDNSService, MDNS_SERVICE_COUNT> &services) {
MDNS.begin(App.get_name().c_str());
for (const auto &service : services) {
@@ -82,7 +82,7 @@ void MDNSComponent::on_ip_state(const network::IPAddresses &ips, const network::
return;
}
if (!this->initialized_) {
this->setup_buffers_and_register_(register_rp2040);
this->setup_buffers_and_register_(register_rp2);
this->initialized_ = true;
} else {
MDNS.notifyAPChange();
@@ -7,22 +7,22 @@
namespace esphome::micro_wake_word {
template<typename... Ts> class StartAction : public Action<Ts...>, public Parented<MicroWakeWord> {
template<typename... Ts> class StartAction final : public Action<Ts...>, public Parented<MicroWakeWord> {
public:
void play(const Ts &...x) override { this->parent_->start(); }
};
template<typename... Ts> class StopAction : public Action<Ts...>, public Parented<MicroWakeWord> {
template<typename... Ts> class StopAction final : public Action<Ts...>, public Parented<MicroWakeWord> {
public:
void play(const Ts &...x) override { this->parent_->stop(); }
};
template<typename... Ts> class IsRunningCondition : public Condition<Ts...>, public Parented<MicroWakeWord> {
template<typename... Ts> class IsRunningCondition final : public Condition<Ts...>, public Parented<MicroWakeWord> {
public:
bool check(const Ts &...x) override { return this->parent_->is_running(); }
};
template<typename... Ts> class EnableModelAction : public Action<Ts...> {
template<typename... Ts> class EnableModelAction final : public Action<Ts...> {
public:
explicit EnableModelAction(WakeWordModel *wake_word_model) : wake_word_model_(wake_word_model) {}
void play(const Ts &...x) override { this->wake_word_model_->enable(); }
@@ -31,7 +31,7 @@ template<typename... Ts> class EnableModelAction : public Action<Ts...> {
WakeWordModel *wake_word_model_;
};
template<typename... Ts> class DisableModelAction : public Action<Ts...> {
template<typename... Ts> class DisableModelAction final : public Action<Ts...> {
public:
explicit DisableModelAction(WakeWordModel *wake_word_model) : wake_word_model_(wake_word_model) {}
void play(const Ts &...x) override { this->wake_word_model_->disable(); }
@@ -40,7 +40,7 @@ template<typename... Ts> class DisableModelAction : public Action<Ts...> {
WakeWordModel *wake_word_model_;
};
template<typename... Ts> class ModelIsEnabledCondition : public Condition<Ts...> {
template<typename... Ts> class ModelIsEnabledCondition final : public Condition<Ts...> {
public:
explicit ModelIsEnabledCondition(WakeWordModel *wake_word_model) : wake_word_model_(wake_word_model) {}
bool check(const Ts &...x) override { return this->wake_word_model_->is_enabled(); }
@@ -31,10 +31,10 @@ enum State {
STOPPED,
};
class MicroWakeWord : public Component
class MicroWakeWord final : public Component
#ifdef USE_OTA_STATE_LISTENER
,
public ota::OTAGlobalStateListener
public ota::OTAGlobalStateListener
#endif
{
public:
+7 -7
View File
@@ -7,34 +7,34 @@
namespace esphome::microphone {
template<typename... Ts> class CaptureAction : public Action<Ts...>, public Parented<Microphone> {
template<typename... Ts> class CaptureAction final : public Action<Ts...>, public Parented<Microphone> {
void play(const Ts &...x) override { this->parent_->start(); }
};
template<typename... Ts> class StopCaptureAction : public Action<Ts...>, public Parented<Microphone> {
template<typename... Ts> class StopCaptureAction final : public Action<Ts...>, public Parented<Microphone> {
void play(const Ts &...x) override { this->parent_->stop(); }
};
template<typename... Ts> class MuteAction : public Action<Ts...>, public Parented<Microphone> {
template<typename... Ts> class MuteAction final : public Action<Ts...>, public Parented<Microphone> {
void play(const Ts &...x) override { this->parent_->set_mute_state(true); }
};
template<typename... Ts> class UnmuteAction : public Action<Ts...>, public Parented<Microphone> {
template<typename... Ts> class UnmuteAction final : public Action<Ts...>, public Parented<Microphone> {
void play(const Ts &...x) override { this->parent_->set_mute_state(false); }
};
class DataTrigger : public Trigger<const std::vector<uint8_t> &> {
class DataTrigger final : public Trigger<const std::vector<uint8_t> &> {
public:
explicit DataTrigger(Microphone *mic) {
mic->add_data_callback([this](const std::vector<uint8_t> &data) { this->trigger(data); });
}
};
template<typename... Ts> class IsCapturingCondition : public Condition<Ts...>, public Parented<Microphone> {
template<typename... Ts> class IsCapturingCondition final : public Condition<Ts...>, public Parented<Microphone> {
public:
bool check(const Ts &...x) override { return this->parent_->is_running(); }
};
template<typename... Ts> class IsMutedCondition : public Condition<Ts...>, public Parented<Microphone> {
template<typename... Ts> class IsMutedCondition final : public Condition<Ts...>, public Parented<Microphone> {
public:
bool check(const Ts &...x) override { return this->parent_->get_mute_state(); }
};
@@ -13,7 +13,7 @@ namespace esphome::microphone {
static const int32_t MAX_GAIN_FACTOR = 64;
class MicrophoneSource {
class MicrophoneSource final {
/*
* @brief Helper class that handles converting raw microphone data to a requested format.
* Components requesting microphone audio should register a callback through this class instead of registering a
+1 -1
View File
@@ -7,7 +7,7 @@
namespace esphome::mics_4514 {
class MICS4514Component : public PollingComponent, public i2c::I2CDevice {
class MICS4514Component final : public PollingComponent, public i2c::I2CDevice {
SUB_SENSOR(carbon_monoxide)
SUB_SENSOR(nitrogen_dioxide)
SUB_SENSOR(methane)
+1 -1
View File
@@ -21,7 +21,7 @@ using climate::ClimateModeMask;
using climate::ClimateSwingModeMask;
using climate::ClimatePresetMask;
class AirConditioner : public ApplianceBase<dudanov::midea::ac::AirConditioner>, public climate::Climate {
class AirConditioner final : public ApplianceBase<dudanov::midea::ac::AirConditioner>, public climate::Climate {
public:
void dump_config() override;
void set_outdoor_temperature_sensor(Sensor *sensor) { this->outdoor_sensor_ = sensor; }
+1 -1
View File
@@ -11,7 +11,7 @@ const uint8_t MIDEA_TEMPC_MAX = 30; // Celsius
const uint8_t MIDEA_TEMPF_MIN = 62; // Fahrenheit
const uint8_t MIDEA_TEMPF_MAX = 86; // Fahrenheit
class MideaIR : public climate_ir::ClimateIR {
class MideaIR final : public climate_ir::ClimateIR {
public:
MideaIR()
: climate_ir::ClimateIR(
+1 -1
View File
@@ -35,7 +35,7 @@ const uint8_t MADCTL_MV = 0x20; // row/column swap
const uint8_t MADCTL_XFLIP = 0x02; // Mirror the display horizontally
const uint8_t MADCTL_YFLIP = 0x01; // Mirror the display vertically
class MipiDsi : public display::Display {
class MipiDsi final : public display::Display {
public:
MipiDsi(size_t width, size_t height, display::ColorBitness color_depth, uint8_t pixel_mode)
: width_(width), height_(height), color_depth_(color_depth), pixel_mode_(pixel_mode) {}
+3 -3
View File
@@ -98,9 +98,9 @@ class MipiRgb : public display::Display {
};
#ifdef USE_SPI
class MipiRgbSpi : public MipiRgb,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING,
spi::DATA_RATE_1MHZ> {
class MipiRgbSpi final : public MipiRgb,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_1MHZ> {
public:
MipiRgbSpi(int width, int height) : MipiRgb(width, height) {}
-72
View File
@@ -10,7 +10,6 @@ from esphome.components.mipi import (
GMCTR,
GMCTRN1,
GMCTRP1,
IDMOFF,
IFCTR,
IFMODE,
INVCTR,
@@ -23,7 +22,6 @@ from esphome.components.mipi import (
PWCTR5,
PWSET,
PWSETN,
SETEXTC,
VMCTR,
VMCTR1,
VMCTR2,
@@ -32,60 +30,6 @@ from esphome.components.mipi import (
)
from esphome.components.spi import TYPE_OCTAL
DriverChip(
"M5CORE",
width=320,
height=240,
cs_pin=14,
dc_pin=27,
reset_pin=33,
initsequence=(
(SETEXTC, 0xFF, 0x93, 0x42),
(PWCTR1, 0x12, 0x12),
(PWCTR2, 0x03),
(VMCTR1, 0xF2),
(IFMODE, 0xE0),
(0xF6, 0x01, 0x00, 0x00),
(
GMCTRP1,
0x00,
0x0C,
0x11,
0x04,
0x11,
0x08,
0x37,
0x89,
0x4C,
0x06,
0x0C,
0x0A,
0x2E,
0x34,
0x0F,
),
(
GMCTRN1,
0x00,
0x0B,
0x11,
0x05,
0x13,
0x09,
0x33,
0x67,
0x48,
0x07,
0x0E,
0x0B,
0x2E,
0x33,
0x0F,
),
(DFUNCTR, 0x08, 0x82, 0x1D, 0x04),
(IDMOFF,),
),
)
ILI9341 = DriverChip(
"ILI9341",
mirror_x=True,
@@ -174,22 +118,6 @@ ILI9342 = DriverChip(
),
)
# M5Stack Core2 uses ILI9341 chip - mirror_x disabled for correct orientation
ILI9341.extend(
"M5CORE2",
# Reset native dimensions due to axis swap.
native_width=320,
native_height=240,
width=320,
height=240,
mirror_x=False,
cs_pin=5,
dc_pin=15,
invert_colors=True,
pixel_mode="18bit",
data_rate="40MHz",
)
DriverChip(
"ILI9481",
mirror_x=True,
@@ -0,0 +1,71 @@
from esphome.components.mipi import (
DFUNCTR,
GMCTRN1,
GMCTRP1,
IDMOFF,
IFMODE,
PWCTR1,
PWCTR2,
SETEXTC,
VMCTR1,
DriverChip,
)
from .ili import ILI9341, ST7789V
# fmt: off
DriverChip(
"M5CORE",
width=320,
height=240,
cs_pin=14,
dc_pin=27,
reset_pin=33,
initsequence=(
(SETEXTC, 0xFF, 0x93, 0x42),
(PWCTR1, 0x12, 0x12),
(PWCTR2, 0x03),
(VMCTR1, 0xF2),
(IFMODE, 0xE0),
(0xF6, 0x01, 0x00, 0x00),
(GMCTRP1, 0x00, 0x0C, 0x11, 0x04, 0x11, 0x08, 0x37, 0x89, 0x4C, 0x06, 0x0C, 0x0A, 0x2E, 0x34, 0x0F,),
(GMCTRN1, 0x00, 0x0B, 0x11, 0x05, 0x13, 0x09, 0x33, 0x67, 0x48, 0x07, 0x0E, 0x0B, 0x2E, 0x33, 0x0F,),
(DFUNCTR, 0x08, 0x82, 0x1D, 0x04),
(IDMOFF,),
),
)
# M5Stack Core2 uses ILI9341 chip - mirror_x disabled for correct orientation
ILI9341.extend(
"M5CORE2",
# Reset native dimensions due to axis swap.
native_width=320,
native_height=240,
width=320,
height=240,
mirror_x=False,
cs_pin=5,
dc_pin=15,
invert_colors=True,
pixel_mode="18bit",
data_rate="40MHz",
)
GC9107 = ST7789V.extend(
"GC9107",
width=128,
height=128,
offset_width=2,
offset_height=1,
pad_width=2,
pad_height=1,
)
GC9107.extend(
"M5STACK-ATOMS3R-GC9107",
data_rate="40MHz",
invert_colors=True,
reset_pin=48,
dc_pin=42,
cs_pin=14,
)
+1 -1
View File
@@ -38,7 +38,7 @@ enum VerticalDirection {
VERTICAL_DIRECTION_DOWN = 0x28,
};
class MitsubishiClimate : public climate_ir::ClimateIR {
class MitsubishiClimate final : public climate_ir::ClimateIR {
public:
MitsubishiClimate()
: climate_ir::ClimateIR(MITSUBISHI_TEMP_MIN, MITSUBISHI_TEMP_MAX, 1.0f, true, true,
@@ -6,7 +6,7 @@
#ifdef USE_ESP32
namespace esphome::mixer_speaker {
template<typename... Ts> class DuckingApplyAction : public Action<Ts...>, public Parented<SourceSpeaker> {
template<typename... Ts> class DuckingApplyAction final : public Action<Ts...>, public Parented<SourceSpeaker> {
TEMPLATABLE_VALUE(uint8_t, decibel_reduction);
TEMPLATABLE_VALUE(uint32_t, duration);
void play(const Ts &...x) override {
@@ -44,7 +44,7 @@ namespace esphome::mixer_speaker {
class MixerSpeaker;
class SourceSpeaker : public speaker::Speaker, public Component {
class SourceSpeaker final : public speaker::Speaker, public Component {
public:
void dump_config() override;
void setup() override;
@@ -118,7 +118,7 @@ class SourceSpeaker : public speaker::Speaker, public Component {
uint32_t stopping_start_ms_{0};
};
class MixerSpeaker : public Component {
class MixerSpeaker final : public Component {
public:
void dump_config() override;
void setup() override;
@@ -20,7 +20,7 @@ enum MLX90393Setting {
MLX90393_LAST,
};
class MLX90393Cls : public PollingComponent, public i2c::I2CDevice, public MLX90393Hal {
class MLX90393Cls final : public PollingComponent, public i2c::I2CDevice, public MLX90393Hal {
public:
void setup() override;
void dump_config() override;
+1 -1
View File
@@ -6,7 +6,7 @@
namespace esphome::mlx90614 {
class MLX90614Component : public PollingComponent, public i2c::I2CDevice {
class MLX90614Component final : public PollingComponent, public i2c::I2CDevice {
public:
void setup() override;
void dump_config() override;
+1 -1
View File
@@ -12,7 +12,7 @@ enum MMC5603Datarate {
MMC5603_DATARATE_255_0_HZ,
};
class MMC5603Component : public PollingComponent, public i2c::I2CDevice {
class MMC5603Component final : public PollingComponent, public i2c::I2CDevice {
public:
void setup() override;
void dump_config() override;
+1 -1
View File
@@ -6,7 +6,7 @@
namespace esphome::mmc5983 {
class MMC5983Component : public PollingComponent, public i2c::I2CDevice {
class MMC5983Component final : public PollingComponent, public i2c::I2CDevice {
public:
void update() override;
void setup() override;
+1 -1
View File
@@ -29,7 +29,7 @@ MODBUS_WRITE_REGISTER_TYPE = {
MODBUS_REGISTER_TYPE = {
**MODBUS_WRITE_REGISTER_TYPE,
"discrete_input": ModbusRegisterType.DISCRETE_INPUT,
"read": ModbusRegisterType.READ,
"read": ModbusRegisterType.INPUT_REGISTER,
}
SensorValueType_ns = modbus_helpers_ns.namespace("SensorValueType")
+17 -16
View File
@@ -51,7 +51,7 @@ void ModbusClientHub::loop() {
// If we're past the send_wait_time timeout and response buffer doesn't have the start of the expected response
if (this->waiting_for_response_.has_value()) {
ModbusDeviceCommand &wfr = this->waiting_for_response_.value();
uint8_t expected_address = wfr.frame.data.get()[0];
uint8_t expected_address = wfr.frame.data.data()[0];
if (this->last_receive_check_ - this->last_send_ > this->last_send_tx_offset_ + this->send_wait_time_ &&
(this->rx_buffer_.empty() || this->rx_buffer_[0] != expected_address)) {
ESP_LOGW(TAG, "Stop waiting for response from %" PRIu8 " %" PRIu32 "ms after last send", expected_address,
@@ -270,8 +270,8 @@ void ModbusClientHub::process_modbus_server_frame(uint8_t address, uint8_t funct
// Check if the response matches the expected address and function code
ModbusDeviceCommand &wfr = this->waiting_for_response_.value();
uint8_t expected_address = wfr.frame.data.get()[0];
uint8_t expected_function_code = wfr.frame.data.get()[1];
uint8_t expected_address = wfr.frame.data.data()[0];
uint8_t expected_function_code = wfr.frame.data.data()[1];
if (expected_address != address || expected_function_code != (function_code & FUNCTION_CODE_MASK)) {
ESP_LOGW(TAG,
"Received incorrect frame address %" PRIu8 " <> %" PRIu8 " or function code 0x%X <> 0x%X, %" PRIu32
@@ -360,7 +360,7 @@ void ModbusServerHub::process_modbus_client_frame_(uint8_t address, uint8_t func
return;
}
ServerResponseStatus status;
ResponseStatus status;
uint8_t response_buffer[modbus::MAX_RAW_SIZE];
const uint8_t *response_data = response_buffer;
uint16_t response_len = 0;
@@ -381,9 +381,9 @@ void ModbusServerHub::process_modbus_client_frame_(uint8_t address, uint8_t func
}
RegisterValues registers;
if (static_cast<ModbusFunctionCode>(function_code) == ModbusFunctionCode::READ_HOLDING_REGISTERS) {
status = device->on_modbus_read_holding_registers(start_address, number_of_registers, registers);
status = device->on_read_holding_registers(start_address, number_of_registers, registers);
} else {
status = device->on_modbus_read_input_registers(start_address, number_of_registers, registers);
status = device->on_read_input_registers(start_address, number_of_registers, registers);
}
// A handler that returns an exception leaves registers partially filled, so check the exception
@@ -436,7 +436,7 @@ void ModbusServerHub::process_modbus_client_frame_(uint8_t address, uint8_t func
for (uint16_t i = 0; i < number_of_registers; i++) {
registers.push_back(helpers::get_data<uint16_t>(data, values_offset + i * 2));
}
status = device->on_modbus_write_registers(start_address, registers);
status = device->on_write_registers(start_address, registers);
response_data = data; // echo the request header per Modbus 6.6, 6.12
response_len = 4;
break;
@@ -458,7 +458,7 @@ bool Modbus::send_frame_(const ModbusFrame &frame) {
ESP_LOGE(TAG, "Attempted to send while transmission blocked");
return false;
}
if (frame.size > MAX_FRAME_SIZE) {
if (frame.size() > MAX_FRAME_SIZE) {
ESP_LOGE(TAG, "Attempted to send frame larger than max frame size of %" PRIu16 " bytes", MAX_FRAME_SIZE);
return false;
}
@@ -470,13 +470,13 @@ bool Modbus::send_frame_(const ModbusFrame &frame) {
if (this->flow_control_pin_ != nullptr) {
this->flow_control_pin_->digital_write(true);
this->write_array(frame.data.get(), frame.size);
this->write_array(frame.data.data(), frame.size());
this->flush();
this->flow_control_pin_->digital_write(false);
this->last_send_tx_offset_ = 0;
} else {
this->write_array(frame.data.get(), frame.size);
this->last_send_tx_offset_ = frame.size * MODBUS_BITS_PER_CHAR * MS_PER_SEC / this->parent_->get_baud_rate() + 1;
this->write_array(frame.data.data(), frame.size());
this->last_send_tx_offset_ = frame.size() * MODBUS_BITS_PER_CHAR * MS_PER_SEC / this->parent_->get_baud_rate() + 1;
}
uint32_t now = millis();
@@ -484,7 +484,7 @@ bool Modbus::send_frame_(const ModbusFrame &frame) {
char hex_buf[format_hex_pretty_size(MODBUS_MAX_LOG_BYTES)];
#endif
ESP_LOGV(TAG, "Write: %s %" PRIu32 "ms after last send, %" PRIu32 "ms after last receive",
format_hex_pretty_to(hex_buf, frame.data.get(), frame.size), now - this->last_send_,
format_hex_pretty_to(hex_buf, frame.data.data(), frame.size()), now - this->last_send_,
now - this->last_modbus_byte_);
this->last_send_ = now;
return true;
@@ -590,12 +590,13 @@ void ModbusClientHub::queue_raw_(uint8_t address, const uint8_t *pdu, uint16_t p
void ModbusClientHub::clear_tx_queue_for_address(uint8_t address, bool clear_sent) {
// Remove any pending commands for this address from the tx buffer
auto &tx_buffer = this->tx_buffer_;
tx_buffer.erase(std::remove_if(tx_buffer.begin(), tx_buffer.end(),
[address](const ModbusDeviceCommand &cmd) { return cmd.frame.data[0] == address; }),
tx_buffer.end());
tx_buffer.erase(
std::remove_if(tx_buffer.begin(), tx_buffer.end(),
[address](const ModbusDeviceCommand &cmd) { return cmd.frame.data.data()[0] == address; }),
tx_buffer.end());
if (clear_sent && this->waiting_for_response_.has_value() && this->waiting_for_response_.value().device) {
if (this->waiting_for_response_.value().frame.data[0] == address) {
if (this->waiting_for_response_.value().frame.data.data()[0] == address) {
ESP_LOGV(TAG, "Clearing waiting for response for address %" PRIu8, address);
// Invalidate the waiting device so it won't process a response.
this->waiting_for_response_.value().device = nullptr;
+31 -22
View File
@@ -18,19 +18,27 @@ namespace esphome::modbus {
static constexpr uint16_t MODBUS_TX_BUFFER_SIZE = 15;
static constexpr uint16_t MODBUS_TX_MAX_DELAY_MS = 5;
struct ModbusFrame {
// Frame with exact-size allocation to avoid std::vector overhead
std::unique_ptr<uint8_t[]> data;
uint16_t size; // Modbus RTU max is 256 bytes
// Typical frames -- reads and single-register/coil writes -- are exactly 8 bytes
// (address + 5-byte PDU + 2-byte CRC) and fit inline with no heap allocation.
static constexpr uint16_t MODBUS_FRAME_INLINE_SIZE = 8;
ModbusFrame(uint8_t address, const uint8_t *pdu, uint16_t pdu_len)
: data(std::make_unique<uint8_t[]>(pdu_len + 3)), size(pdu_len + 3) {
data[0] = address;
memcpy(data.get() + 1, pdu, pdu_len);
auto crc = crc16(data.get(), pdu_len + 1);
data[pdu_len + 1] = crc >> 0;
data[pdu_len + 2] = crc >> 8;
struct ModbusFrame {
// Frame held in a small-buffer-optimized buffer. Typical frames fit inline; only larger
// multi-register or custom frames spill to a single heap allocation. This keeps the common,
// high-frequency tx traffic off the heap entirely, avoiding per-frame alloc/free churn.
// The buffer tracks its own length, so no separate size field is needed.
SmallInlineBuffer<MODBUS_FRAME_INLINE_SIZE> data; // Modbus RTU max is 256 bytes
ModbusFrame(uint8_t address, const uint8_t *pdu, uint16_t pdu_len) {
uint8_t *buf = this->data.init(pdu_len + 3);
buf[0] = address;
memcpy(buf + 1, pdu, pdu_len);
auto crc = crc16(buf, pdu_len + 1);
buf[pdu_len + 1] = crc >> 0;
buf[pdu_len + 2] = crc >> 8;
}
uint16_t size() const { return static_cast<uint16_t>(this->data.size()); }
};
class Modbus : public uart::UARTDevice, public Component {
@@ -201,8 +209,9 @@ class ModbusClientDevice {
using ModbusDevice ESPDEPRECATED("Use ModbusClientDevice instead. Removed in 2026.12.0",
"2026.6.0") = ModbusClientDevice;
// Result of a server register handler: std::nullopt means success, otherwise the Modbus exception code to return.
using ServerResponseStatus = std::optional<ModbusExceptionCode>;
// Transaction status: std::nullopt on success, otherwise the Modbus exception code. Server handlers return it;
// (future) client response callbacks receive it. Named without a side prefix so both directions share it.
using ResponseStatus = std::optional<ModbusExceptionCode>;
// Register values exchanged with server handlers, in host byte order. Sized at the larger of the two protocol
// maxima (read = 125 / 0x7D, write = 123 / 0x7B); the per-direction count limit is enforced by the hub, not by
// the capacity of this type.
@@ -219,19 +228,19 @@ class ModbusServerDevice {
ModbusServerDevice &operator=(ModbusServerDevice &&) = delete;
void set_address(uint8_t address) { this->address_ = address; }
uint8_t get_address() const { return this->address_; }
virtual ServerResponseStatus on_modbus_read_registers(uint16_t start_address, uint16_t number_of_registers,
RegisterValues &registers) {
virtual ResponseStatus on_read_registers(uint16_t start_address, uint16_t number_of_registers,
RegisterValues &registers) {
return ModbusExceptionCode::ILLEGAL_FUNCTION;
};
virtual ServerResponseStatus on_modbus_read_input_registers(uint16_t start_address, uint16_t number_of_registers,
RegisterValues &registers) {
return this->on_modbus_read_registers(start_address, number_of_registers, registers);
virtual ResponseStatus on_read_input_registers(uint16_t start_address, uint16_t number_of_registers,
RegisterValues &registers) {
return this->on_read_registers(start_address, number_of_registers, registers);
};
virtual ServerResponseStatus on_modbus_read_holding_registers(uint16_t start_address, uint16_t number_of_registers,
RegisterValues &registers) {
return this->on_modbus_read_registers(start_address, number_of_registers, registers);
virtual ResponseStatus on_read_holding_registers(uint16_t start_address, uint16_t number_of_registers,
RegisterValues &registers) {
return this->on_read_registers(start_address, number_of_registers, registers);
};
virtual ServerResponseStatus on_modbus_write_registers(uint16_t start_address, const RegisterValues &registers) {
virtual ResponseStatus on_write_registers(uint16_t start_address, const RegisterValues &registers) {
return ModbusExceptionCode::ILLEGAL_FUNCTION;
};
@@ -1,6 +1,7 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
namespace esphome::modbus {
@@ -48,7 +49,11 @@ enum class ModbusRegisterType : uint8_t {
COIL = 0x01,
DISCRETE_INPUT = 0x02,
HOLDING = 0x03,
READ = 0x04,
// Named INPUT_REGISTER (not INPUT) because Arduino cores define INPUT as a macro.
INPUT_REGISTER = 0x04,
// Remove before 2027.2.0
READ ESPDEPRECATED("Use ModbusRegisterType::INPUT_REGISTER instead. Removed in 2027.2.0", "2026.7.0") =
INPUT_REGISTER,
};
// 7 MODBUS Exception Responses:
+2 -2
View File
@@ -90,7 +90,7 @@ inline ModbusFunctionCode modbus_register_read_function(ModbusRegisterType reg_t
return ModbusFunctionCode::READ_DISCRETE_INPUTS;
case ModbusRegisterType::HOLDING:
return ModbusFunctionCode::READ_HOLDING_REGISTERS;
case ModbusRegisterType::READ:
case ModbusRegisterType::INPUT_REGISTER:
return ModbusFunctionCode::READ_INPUT_REGISTERS;
default:
return ModbusFunctionCode::INVALID;
@@ -104,7 +104,7 @@ inline ModbusFunctionCode modbus_register_write_function(ModbusRegisterType reg_
case ModbusRegisterType::HOLDING:
return multiple ? ModbusFunctionCode::WRITE_MULTIPLE_REGISTERS : ModbusFunctionCode::WRITE_SINGLE_REGISTER;
// These register types can't be written (per spec)
case ModbusRegisterType::READ:
case ModbusRegisterType::INPUT_REGISTER:
case ModbusRegisterType::DISCRETE_INPUT:
default:
return ModbusFunctionCode::INVALID;
@@ -220,7 +220,7 @@ def function_code_to_register(function_code):
"read_coils": ModbusRegisterType.COIL,
"read_discrete_inputs": ModbusRegisterType.DISCRETE_INPUT,
"read_holding_registers": ModbusRegisterType.HOLDING,
"read_input_registers": ModbusRegisterType.READ,
"read_input_registers": ModbusRegisterType.INPUT_REGISTER,
"write_single_coil": ModbusRegisterType.COIL,
"write_single_register": ModbusRegisterType.HOLDING,
"write_multiple_coils": ModbusRegisterType.COIL,
@@ -8,7 +8,7 @@
namespace esphome::modbus_controller {
class ModbusBinarySensor : public Component, public binary_sensor::BinarySensor, public SensorItem {
class ModbusBinarySensor final : public Component, public binary_sensor::BinarySensor, public SensorItem {
public:
ModbusBinarySensor(ModbusRegisterType register_type, uint16_t start_address, uint8_t offset, uint32_t bitmask,
uint16_t skip_updates, bool force_new_range) {
@@ -279,7 +279,7 @@ class ModbusCommandItem {
* Responses for the commands are dispatched to the modbus sensor items.
*/
class ModbusController : public PollingComponent, public modbus::ModbusClientDevice {
class ModbusController final : public PollingComponent, public modbus::ModbusClientDevice {
public:
void dump_config() override;
void loop() override;
@@ -10,7 +10,7 @@ namespace esphome::modbus_controller {
using value_to_data_t = std::function<float>(float);
class ModbusNumber : public number::Number, public Component, public SensorItem {
class ModbusNumber final : public number::Number, public Component, public SensorItem {
public:
ModbusNumber(ModbusRegisterType register_type, uint16_t start_address, uint8_t offset, uint32_t bitmask,
SensorValueType value_type, int register_count, uint16_t skip_updates, bool force_new_range) {
@@ -8,7 +8,7 @@
namespace esphome::modbus_controller {
class ModbusFloatOutput : public output::FloatOutput, public Component, public SensorItem {
class ModbusFloatOutput final : public output::FloatOutput, public Component, public SensorItem {
public:
ModbusFloatOutput(uint16_t start_address, uint8_t offset, SensorValueType value_type, int register_count) {
this->register_type = ModbusRegisterType::HOLDING;
@@ -41,7 +41,7 @@ class ModbusFloatOutput : public output::FloatOutput, public Component, public S
bool use_write_multiple_{false};
};
class ModbusBinaryOutput : public output::BinaryOutput, public Component, public SensorItem {
class ModbusBinaryOutput final : public output::BinaryOutput, public Component, public SensorItem {
public:
ModbusBinaryOutput(uint16_t start_address, uint8_t offset) {
this->register_type = ModbusRegisterType::COIL;
@@ -9,7 +9,7 @@
namespace esphome::modbus_controller {
class ModbusSelect : public Component, public select::Select, public SensorItem {
class ModbusSelect final : public Component, public select::Select, public SensorItem {
public:
ModbusSelect(SensorValueType sensor_value_type, uint16_t start_address, uint8_t register_count, uint16_t skip_updates,
bool force_new_range, std::vector<int64_t> mapping) {
@@ -8,7 +8,7 @@
namespace esphome::modbus_controller {
class ModbusSensor : public Component, public sensor::Sensor, public SensorItem {
class ModbusSensor final : public Component, public sensor::Sensor, public SensorItem {
public:
ModbusSensor(ModbusRegisterType register_type, uint16_t start_address, uint8_t offset, uint32_t bitmask,
SensorValueType value_type, int register_count, uint16_t skip_updates, bool force_new_range) {
@@ -8,7 +8,7 @@
namespace esphome::modbus_controller {
class ModbusSwitch : public Component, public switch_::Switch, public SensorItem {
class ModbusSwitch final : public Component, public switch_::Switch, public SensorItem {
public:
ModbusSwitch(ModbusRegisterType register_type, uint16_t start_address, uint8_t offset, uint32_t bitmask,
uint16_t skip_updates, bool force_new_range) {
@@ -10,7 +10,7 @@ namespace esphome::modbus_controller {
enum class RawEncoding { NONE = 0, HEXBYTES = 1, COMMA = 2, ANSI = 3 };
class ModbusTextSensor : public Component, public text_sensor::TextSensor, public SensorItem {
class ModbusTextSensor final : public Component, public text_sensor::TextSensor, public SensorItem {
public:
ModbusTextSensor(ModbusRegisterType register_type, uint16_t start_address, uint8_t offset, uint8_t register_count,
uint16_t response_bytes, RawEncoding encode, uint16_t skip_updates, bool force_new_range) {

Some files were not shown because too many files have changed in this diff Show More