mirror of
https://github.com/esphome/esphome.git
synced 2026-09-17 10:08:40 +00:00
Merge branch 'dev' into lightweight-callback-manager
This commit is contained in:
@@ -113,10 +113,11 @@ APIError APIFrameHelper::loop() {
|
||||
|
||||
// Common socket write error handling
|
||||
APIError APIFrameHelper::handle_socket_write_error_() {
|
||||
if (errno == EWOULDBLOCK || errno == EAGAIN) {
|
||||
const int err = errno;
|
||||
if (err == EWOULDBLOCK || err == EAGAIN) {
|
||||
return APIError::WOULD_BLOCK;
|
||||
}
|
||||
HELPER_LOG("Socket write failed with errno %d", errno);
|
||||
HELPER_LOG("Socket write failed with errno %d", err);
|
||||
this->state_ = State::FAILED;
|
||||
return APIError::SOCKET_WRITE_FAILED;
|
||||
}
|
||||
@@ -278,11 +279,12 @@ APIError APIFrameHelper::init_common_() {
|
||||
|
||||
APIError APIFrameHelper::handle_socket_read_result_(ssize_t received) {
|
||||
if (received == -1) {
|
||||
if (errno == EWOULDBLOCK || errno == EAGAIN) {
|
||||
const int err = errno;
|
||||
if (err == EWOULDBLOCK || err == EAGAIN) {
|
||||
return APIError::WOULD_BLOCK;
|
||||
}
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Socket read failed with errno %d", errno);
|
||||
HELPER_LOG("Socket read failed with errno %d", err);
|
||||
return APIError::SOCKET_READ_FAILED;
|
||||
} else if (received == 0) {
|
||||
state_ = State::FAILED;
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "esphome/components/socket/socket.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "proto.h"
|
||||
|
||||
namespace esphome::api {
|
||||
|
||||
@@ -37,8 +38,6 @@ static constexpr uint16_t RX_BUF_NULL_TERMINATOR = 1;
|
||||
// Must be >= MAX_INITIAL_PER_BATCH in api_connection.h (enforced by static_assert there)
|
||||
static constexpr size_t MAX_MESSAGES_PER_BATCH = 34;
|
||||
|
||||
class ProtoWriteBuffer;
|
||||
|
||||
// Max client name length (e.g., "Home Assistant 2026.1.0.dev0" = 28 chars)
|
||||
static constexpr size_t CLIENT_INFO_NAME_MAX_LEN = 32;
|
||||
|
||||
@@ -161,7 +160,14 @@ class APIFrameHelper {
|
||||
this->nodelay_counter_ = 0;
|
||||
}
|
||||
}
|
||||
virtual APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) = 0;
|
||||
APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) {
|
||||
// Resize buffer to include footer space if needed (e.g. Noise MAC)
|
||||
if (frame_footer_size_)
|
||||
buffer.get_buffer()->resize(buffer.get_buffer()->size() + frame_footer_size_);
|
||||
MessageInfo msg{type, 0,
|
||||
static_cast<uint16_t>(buffer.get_buffer()->size() - frame_header_padding_ - frame_footer_size_)};
|
||||
return write_protobuf_messages(buffer, std::span<const MessageInfo>(&msg, 1));
|
||||
}
|
||||
// Write multiple protobuf messages in a single operation
|
||||
// messages contains (message_type, offset, length) for each message in the buffer
|
||||
// The buffer contains all messages with appropriate padding before each
|
||||
|
||||
@@ -450,14 +450,6 @@ APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
||||
buffer->type = type;
|
||||
return APIError::OK;
|
||||
}
|
||||
APIError APINoiseFrameHelper::write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) {
|
||||
// Resize to include MAC space (required for Noise encryption)
|
||||
buffer.get_buffer()->resize(buffer.get_buffer()->size() + frame_footer_size_);
|
||||
MessageInfo msg{type, 0,
|
||||
static_cast<uint16_t>(buffer.get_buffer()->size() - frame_header_padding_ - frame_footer_size_)};
|
||||
return write_protobuf_messages(buffer, std::span<const MessageInfo>(&msg, 1));
|
||||
}
|
||||
|
||||
APIError APINoiseFrameHelper::write_protobuf_messages(ProtoWriteBuffer buffer, std::span<const MessageInfo> messages) {
|
||||
APIError aerr = this->check_data_state_();
|
||||
if (aerr != APIError::OK)
|
||||
|
||||
@@ -22,7 +22,6 @@ class APINoiseFrameHelper final : public APIFrameHelper {
|
||||
APIError init() override;
|
||||
APIError loop() override;
|
||||
APIError read_packet(ReadPacketBuffer *buffer) override;
|
||||
APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) override;
|
||||
APIError write_protobuf_messages(ProtoWriteBuffer buffer, std::span<const MessageInfo> messages) override;
|
||||
|
||||
protected:
|
||||
|
||||
@@ -235,11 +235,6 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
||||
buffer->type = this->rx_header_parsed_type_;
|
||||
return APIError::OK;
|
||||
}
|
||||
APIError APIPlaintextFrameHelper::write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) {
|
||||
MessageInfo msg{type, 0, static_cast<uint16_t>(buffer.get_buffer()->size() - frame_header_padding_)};
|
||||
return write_protobuf_messages(buffer, std::span<const MessageInfo>(&msg, 1));
|
||||
}
|
||||
|
||||
APIError APIPlaintextFrameHelper::write_protobuf_messages(ProtoWriteBuffer buffer,
|
||||
std::span<const MessageInfo> messages) {
|
||||
APIError aerr = this->check_data_state_();
|
||||
@@ -257,9 +252,11 @@ APIError APIPlaintextFrameHelper::write_protobuf_messages(ProtoWriteBuffer buffe
|
||||
uint16_t total_write_len = 0;
|
||||
|
||||
for (const auto &msg : messages) {
|
||||
// Calculate varint sizes for header layout
|
||||
uint8_t size_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(msg.payload_size));
|
||||
uint8_t type_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(msg.message_type));
|
||||
// Calculate varint sizes for header layout using inline ternary to avoid varint_slow call overhead
|
||||
uint8_t size_varint_len = msg.payload_size < ProtoSize::VARINT_THRESHOLD_1_BYTE
|
||||
? 1
|
||||
: (msg.payload_size < ProtoSize::VARINT_THRESHOLD_2_BYTE ? 2 : 3);
|
||||
uint8_t type_varint_len = msg.message_type < ProtoSize::VARINT_THRESHOLD_1_BYTE ? 1 : 2;
|
||||
uint8_t total_header_len = 1 + size_varint_len + type_varint_len;
|
||||
|
||||
// Calculate where to start writing the header
|
||||
@@ -281,8 +278,8 @@ APIError APIPlaintextFrameHelper::write_protobuf_messages(ProtoWriteBuffer buffe
|
||||
//
|
||||
// Example 3 (large values): total_header_len = 6, header_offset = 6 - 6 = 0
|
||||
// [0] - 0x00 indicator byte
|
||||
// [1-3] - Payload size varint (3 bytes, for sizes 16384-2097151)
|
||||
// [4-5] - Message type varint (2 bytes, for types 128-32767)
|
||||
// [1-3] - Payload size varint (3 bytes, for sizes 16384-65535)
|
||||
// [4-5] - Message type varint (2 bytes, for types 128-16383)
|
||||
// [6...] - Actual payload data
|
||||
//
|
||||
// The message starts at offset + frame_header_padding_
|
||||
|
||||
@@ -19,7 +19,6 @@ class APIPlaintextFrameHelper final : public APIFrameHelper {
|
||||
APIError init() override;
|
||||
APIError loop() override;
|
||||
APIError read_packet(ReadPacketBuffer *buffer) override;
|
||||
APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) override;
|
||||
APIError write_protobuf_messages(ProtoWriteBuffer buffer, std::span<const MessageInfo> messages) override;
|
||||
|
||||
protected:
|
||||
|
||||
@@ -473,6 +473,12 @@ class ProtoDecodableMessage : public ProtoMessage {
|
||||
|
||||
class ProtoSize {
|
||||
public:
|
||||
// Varint encoding thresholds: values below each threshold fit in N bytes
|
||||
static constexpr uint32_t VARINT_THRESHOLD_1_BYTE = 1 << 7; // 128
|
||||
static constexpr uint32_t VARINT_THRESHOLD_2_BYTE = 1 << 14; // 16384
|
||||
static constexpr uint32_t VARINT_THRESHOLD_3_BYTE = 1 << 21; // 2097152
|
||||
static constexpr uint32_t VARINT_THRESHOLD_4_BYTE = 1 << 28; // 268435456
|
||||
|
||||
/**
|
||||
* @brief Calculates the size in bytes needed to encode a uint32_t value as a varint
|
||||
*
|
||||
@@ -480,7 +486,7 @@ class ProtoSize {
|
||||
* @return The number of bytes needed to encode the value
|
||||
*/
|
||||
static constexpr inline uint32_t ESPHOME_ALWAYS_INLINE varint(uint32_t value) {
|
||||
if (value < 128) [[likely]]
|
||||
if (value < VARINT_THRESHOLD_1_BYTE) [[likely]]
|
||||
return 1; // Fast path: 7 bits, most common case
|
||||
if (__builtin_is_constant_evaluated())
|
||||
return varint_wide(value);
|
||||
@@ -492,11 +498,11 @@ class ProtoSize {
|
||||
static uint32_t varint_slow(uint32_t value) __attribute__((noinline));
|
||||
// Shared cascade for values >= 128 (used by both constexpr and noinline paths)
|
||||
static constexpr inline uint32_t ESPHOME_ALWAYS_INLINE varint_wide(uint32_t value) {
|
||||
if (value < 16384)
|
||||
if (value < VARINT_THRESHOLD_2_BYTE)
|
||||
return 2;
|
||||
if (value < 2097152)
|
||||
if (value < VARINT_THRESHOLD_3_BYTE)
|
||||
return 3;
|
||||
if (value < 268435456)
|
||||
if (value < VARINT_THRESHOLD_4_BYTE)
|
||||
return 4;
|
||||
return 5;
|
||||
}
|
||||
|
||||
@@ -52,11 +52,12 @@ bool AsyncClient::connect(const char *host, uint16_t port) {
|
||||
connect_cb_(connect_arg_, this);
|
||||
return true;
|
||||
}
|
||||
if (errno != EINPROGRESS) {
|
||||
ESP_LOGE(TAG, "Connect failed: %d", errno);
|
||||
const int saved_errno = errno;
|
||||
if (saved_errno != EINPROGRESS) {
|
||||
ESP_LOGE(TAG, "Connect failed: %d", saved_errno);
|
||||
close();
|
||||
if (error_cb_)
|
||||
error_cb_(error_arg_, this, errno);
|
||||
error_cb_(error_arg_, this, saved_errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -79,11 +80,12 @@ size_t AsyncClient::write(const char *data, size_t len) {
|
||||
|
||||
ssize_t sent = socket_->write(data, len);
|
||||
if (sent < 0) {
|
||||
if (errno != EAGAIN && errno != EWOULDBLOCK) {
|
||||
ESP_LOGE(TAG, "Write error: %d", errno);
|
||||
const int err = errno;
|
||||
if (err != EAGAIN && err != EWOULDBLOCK) {
|
||||
ESP_LOGE(TAG, "Write error: %d", err);
|
||||
close();
|
||||
if (error_cb_)
|
||||
error_cb_(error_arg_, this, errno);
|
||||
error_cb_(error_arg_, this, err);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -129,10 +131,11 @@ void AsyncClient::loop() {
|
||||
error_cb_(error_arg_, this, error);
|
||||
}
|
||||
} else if (ret < 0) {
|
||||
ESP_LOGE(TAG, "Select error: %d", errno);
|
||||
const int err = errno;
|
||||
ESP_LOGE(TAG, "Select error: %d", err);
|
||||
close();
|
||||
if (error_cb_)
|
||||
error_cb_(error_arg_, this, errno);
|
||||
error_cb_(error_arg_, this, err);
|
||||
}
|
||||
} else if (connected_) {
|
||||
// For connected sockets, use the Application's select() results
|
||||
@@ -148,11 +151,14 @@ void AsyncClient::loop() {
|
||||
} else if (len > 0) {
|
||||
if (data_cb_)
|
||||
data_cb_(data_arg_, this, buf, len);
|
||||
} else if (errno != EAGAIN && errno != EWOULDBLOCK) {
|
||||
ESP_LOGW(TAG, "Read error: %d", errno);
|
||||
close();
|
||||
if (error_cb_)
|
||||
error_cb_(error_arg_, this, errno);
|
||||
} else {
|
||||
const int err = errno;
|
||||
if (err != EAGAIN && err != EWOULDBLOCK) {
|
||||
ESP_LOGW(TAG, "Read error: %d", err);
|
||||
close();
|
||||
if (error_cb_)
|
||||
error_cb_(error_arg_, this, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,14 +67,14 @@ bool BLENUS::read_array(uint8_t *data, size_t len) {
|
||||
|
||||
// First, use the peek buffer if available
|
||||
if (this->has_peek_) {
|
||||
#ifdef USE_UART_DEBUGGER
|
||||
this->debug_callback_.call(uart::UART_DIRECTION_RX, this->peek_buffer_);
|
||||
#endif
|
||||
data[0] = this->peek_buffer_;
|
||||
this->has_peek_ = false;
|
||||
data++;
|
||||
if (--len == 0) { // Decrement len first, then check it...
|
||||
#ifdef USE_UART_DEBUGGER
|
||||
this->debug_callback_.call(uart::UART_DIRECTION_RX, this->peek_buffer_);
|
||||
#endif
|
||||
return true; // No more to read
|
||||
return true; // No more to read
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -100,8 +100,9 @@ void DNSServer::process_next_request() {
|
||||
&client_addr_len);
|
||||
|
||||
if (len < 0) {
|
||||
if (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) {
|
||||
ESP_LOGE(TAG, "recvfrom failed: %d", errno);
|
||||
const int err = errno;
|
||||
if (err != EAGAIN && err != EWOULDBLOCK && err != EINTR) {
|
||||
ESP_LOGE(TAG, "recvfrom failed: %d", err);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "driver/gpio.h"
|
||||
#include "deep_sleep_component.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <esp_idf_version.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace deep_sleep {
|
||||
@@ -26,7 +27,7 @@ namespace deep_sleep {
|
||||
// - ext0: Single pin wakeup using RTC GPIO (esp_sleep_enable_ext0_wakeup)
|
||||
// - ext1: Multiple pin wakeup (esp_sleep_enable_ext1_wakeup)
|
||||
// - Touch: Touch pad wakeup (esp_sleep_enable_touchpad_wakeup)
|
||||
// - GPIO wakeup: GPIO wakeup for RTC pins (esp_deep_sleep_enable_gpio_wakeup)
|
||||
// - GPIO wakeup: GPIO wakeup for RTC pins
|
||||
|
||||
static const char *const TAG = "deep_sleep";
|
||||
|
||||
@@ -135,8 +136,13 @@ void DeepSleepComponent::deep_sleep_() {
|
||||
}
|
||||
// Internal pullup/pulldown resistors are enabled automatically, when
|
||||
// ESP_SLEEP_GPIO_ENABLE_INTERNAL_RESISTORS is set (by default it is)
|
||||
esp_deep_sleep_enable_gpio_wakeup(1 << this->wakeup_pin_->get_pin(),
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0)
|
||||
esp_sleep_enable_gpio_wakeup_on_hp_periph_powerdown(1ULL << this->wakeup_pin_->get_pin(),
|
||||
static_cast<esp_sleep_gpio_wake_up_mode_t>(level));
|
||||
#else
|
||||
esp_deep_sleep_enable_gpio_wakeup(1ULL << this->wakeup_pin_->get_pin(),
|
||||
static_cast<esp_deepsleep_gpio_wake_up_mode_t>(level));
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -20,12 +20,6 @@ bool random_bytes(uint8_t *data, size_t len) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Mutex::Mutex() { handle_ = xSemaphoreCreateMutex(); }
|
||||
Mutex::~Mutex() {}
|
||||
void Mutex::lock() { xSemaphoreTake(this->handle_, portMAX_DELAY); }
|
||||
bool Mutex::try_lock() { return xSemaphoreTake(this->handle_, 0) == pdTRUE; }
|
||||
void Mutex::unlock() { xSemaphoreGive(this->handle_); }
|
||||
|
||||
// only affects the executing core
|
||||
// so should not be used as a mutex lock, only to get accurate timing
|
||||
IRAM_ATTR InterruptLock::InterruptLock() { portDISABLE_INTERRUPTS(); }
|
||||
|
||||
@@ -12,12 +12,8 @@ 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 doesn't have mutexes, but that shouldn't be an issue as it's single-core and non-preemptive OS.
|
||||
Mutex::Mutex() {}
|
||||
Mutex::~Mutex() {}
|
||||
void Mutex::lock() {}
|
||||
bool Mutex::try_lock() { return true; }
|
||||
void Mutex::unlock() {}
|
||||
// ESP8266 Mutex is defined inline as a no-op in helpers.h when USE_ESP8266 (or USE_RP2040) is set,
|
||||
// independent of the ESPHOME_THREAD_SINGLE thread model define.
|
||||
|
||||
IRAM_ATTR InterruptLock::InterruptLock() { state_ = xt_rsil(15); }
|
||||
IRAM_ATTR InterruptLock::~InterruptLock() { xt_wsr_ps(state_); }
|
||||
|
||||
@@ -332,12 +332,13 @@ void ESPHomeOTAComponent::handle_data_() {
|
||||
size_t requested = remaining < OTA_BUFFER_SIZE ? remaining : OTA_BUFFER_SIZE;
|
||||
ssize_t read = this->client_->read(buf, requested);
|
||||
if (read == -1) {
|
||||
if (this->would_block_(errno)) {
|
||||
const int err = errno;
|
||||
if (this->would_block_(err)) {
|
||||
// read() already waited up to SO_RCVTIMEO for data, just feed WDT
|
||||
App.feed_wdt();
|
||||
continue;
|
||||
}
|
||||
ESP_LOGW(TAG, "Read err %d", errno);
|
||||
ESP_LOGW(TAG, "Read err %d", err);
|
||||
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
|
||||
} else if (read == 0) {
|
||||
ESP_LOGW(TAG, "Remote closed");
|
||||
@@ -426,8 +427,9 @@ bool ESPHomeOTAComponent::readall_(uint8_t *buf, size_t len) {
|
||||
|
||||
ssize_t read = this->client_->read(buf + at, len - at);
|
||||
if (read == -1) {
|
||||
if (!this->would_block_(errno)) {
|
||||
ESP_LOGW(TAG, "Read err %zu bytes, errno %d", len, errno);
|
||||
const int err = errno;
|
||||
if (!this->would_block_(err)) {
|
||||
ESP_LOGW(TAG, "Read err %zu bytes, errno %d", len, err);
|
||||
return false;
|
||||
}
|
||||
} else if (read == 0) {
|
||||
@@ -455,8 +457,9 @@ bool ESPHomeOTAComponent::writeall_(const uint8_t *buf, size_t len) {
|
||||
|
||||
ssize_t written = this->client_->write(buf + at, len - at);
|
||||
if (written == -1) {
|
||||
if (!this->would_block_(errno)) {
|
||||
ESP_LOGW(TAG, "Write err %zu bytes, errno %d", len, errno);
|
||||
const int err = errno;
|
||||
if (!this->would_block_(err)) {
|
||||
ESP_LOGW(TAG, "Write err %zu bytes, errno %d", len, err);
|
||||
return false;
|
||||
}
|
||||
// EWOULDBLOCK: on raw TCP writes never block, delay(1) prevents spinning
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
|
||||
from esphome import automation, pins
|
||||
@@ -35,6 +36,7 @@ from esphome.const import (
|
||||
CONF_VALUE,
|
||||
KEY_CORE,
|
||||
KEY_FRAMEWORK_VERSION,
|
||||
KEY_NATIVE_IDF,
|
||||
Platform,
|
||||
PlatformFramework,
|
||||
)
|
||||
@@ -53,6 +55,9 @@ LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# Key for tracking IP state listener count in CORE.data
|
||||
ETHERNET_IP_STATE_LISTENERS_KEY = "ethernet_ip_state_listeners"
|
||||
# Key for tracking configured ethernet type
|
||||
ETHERNET_TYPE_KEY = "ethernet_type"
|
||||
KEY_ETHERNET = "ethernet"
|
||||
|
||||
|
||||
def request_ethernet_ip_state_listener() -> None:
|
||||
@@ -126,9 +131,32 @@ _PHY_TYPE_TO_DEFINE = {
|
||||
"JL1101": "USE_ETHERNET_JL1101",
|
||||
"KSZ8081": "USE_ETHERNET_KSZ8081",
|
||||
"KSZ8081RNA": "USE_ETHERNET_KSZ8081",
|
||||
"W5500": "USE_ETHERNET_W5500",
|
||||
"DM9051": "USE_ETHERNET_DM9051",
|
||||
"LAN8670": "USE_ETHERNET_LAN8670",
|
||||
}
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class IDFRegistryComponent:
|
||||
"""An ESP-IDF component from the Espressif Component Registry."""
|
||||
|
||||
name: str
|
||||
version: str
|
||||
|
||||
|
||||
# IDF 6.0 moved per-chip PHY/MAC drivers to the Espressif Component Registry.
|
||||
_IDF6_ETHERNET_COMPONENTS: dict[str, IDFRegistryComponent] = {
|
||||
"LAN8720": IDFRegistryComponent("espressif/lan87xx", "1.0.0"),
|
||||
"RTL8201": IDFRegistryComponent("espressif/rtl8201", "1.0.1"),
|
||||
"DP83848": IDFRegistryComponent("espressif/dp83848", "1.0.0"),
|
||||
"IP101": IDFRegistryComponent("espressif/ip101", "1.0.0"),
|
||||
"KSZ8081": IDFRegistryComponent("espressif/ksz80xx", "1.0.0"),
|
||||
"KSZ8081RNA": IDFRegistryComponent("espressif/ksz80xx", "1.0.0"),
|
||||
"W5500": IDFRegistryComponent("espressif/w5500", "1.0.1"),
|
||||
"DM9051": IDFRegistryComponent("espressif/dm9051", "1.0.0"),
|
||||
}
|
||||
|
||||
SPI_ETHERNET_TYPES = ["W5500", "DM9051"]
|
||||
SPI_ETHERNET_DEFAULT_POLLING_INTERVAL = TimePeriodMilliseconds(milliseconds=10)
|
||||
|
||||
@@ -406,6 +434,7 @@ async def to_code(config):
|
||||
|
||||
cg.add(var.set_type(ETHERNET_TYPES[config[CONF_TYPE]]))
|
||||
cg.add(var.set_use_address(config[CONF_USE_ADDRESS]))
|
||||
CORE.data.setdefault(KEY_ETHERNET, {})[ETHERNET_TYPE_KEY] = config[CONF_TYPE]
|
||||
|
||||
if CONF_MANUAL_IP in config:
|
||||
cg.add_define("USE_ETHERNET_MANUAL_IP")
|
||||
@@ -439,6 +468,7 @@ async def _to_code_esp32(var, config):
|
||||
from esphome.components.esp32 import (
|
||||
add_idf_component,
|
||||
add_idf_sdkconfig_option,
|
||||
idf_version,
|
||||
include_builtin_idf_component,
|
||||
)
|
||||
|
||||
@@ -459,7 +489,11 @@ async def _to_code_esp32(var, config):
|
||||
|
||||
cg.add_define("USE_ETHERNET_SPI")
|
||||
add_idf_sdkconfig_option("CONFIG_ETH_USE_SPI_ETHERNET", True)
|
||||
add_idf_sdkconfig_option(f"CONFIG_ETH_SPI_ETHERNET_{config[CONF_TYPE]}", True)
|
||||
# CONFIG_ETH_SPI_ETHERNET_{TYPE} Kconfig options were removed in IDF 6.0
|
||||
if idf_version() < cv.Version(6, 0, 0):
|
||||
add_idf_sdkconfig_option(
|
||||
f"CONFIG_ETH_SPI_ETHERNET_{config[CONF_TYPE]}", True
|
||||
)
|
||||
elif config[CONF_TYPE] == "OPENETH":
|
||||
cg.add_define("USE_ETHERNET_OPENETH")
|
||||
add_idf_sdkconfig_option("CONFIG_ETH_USE_OPENETH", True)
|
||||
@@ -491,6 +525,12 @@ async def _to_code_esp32(var, config):
|
||||
# Add LAN867x 10BASE-T1S PHY support component
|
||||
add_idf_component(name="espressif/lan867x", ref="2.0.0")
|
||||
|
||||
# IDF 6.0 moved per-chip PHY/MAC drivers to the Espressif Component Registry
|
||||
if idf_version() >= cv.Version(6, 0, 0) and (
|
||||
component := _IDF6_ETHERNET_COMPONENTS.get(config[CONF_TYPE])
|
||||
):
|
||||
add_idf_component(name=component.name, ref=component.version)
|
||||
|
||||
|
||||
def _final_validate_rmii_pins(config: ConfigType) -> None:
|
||||
"""Validate that RMII pins are not used by other components."""
|
||||
@@ -565,11 +605,36 @@ async def final_step():
|
||||
cg.add_define("ESPHOME_ETHERNET_IP_STATE_LISTENERS", ip_state_count)
|
||||
|
||||
|
||||
FILTER_SOURCE_FILES = filter_source_files_from_platform(
|
||||
_platform_filter = filter_source_files_from_platform(
|
||||
{
|
||||
"ethernet_component_esp32.cpp": {
|
||||
PlatformFramework.ESP32_IDF,
|
||||
PlatformFramework.ESP32_ARDUINO,
|
||||
},
|
||||
"esp_eth_phy_jl1101.c": {
|
||||
PlatformFramework.ESP32_IDF,
|
||||
PlatformFramework.ESP32_ARDUINO,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def _filter_source_files() -> list[str]:
|
||||
excluded = _platform_filter()
|
||||
eth_data = CORE.data.get(KEY_ETHERNET, {})
|
||||
eth_type = eth_data.get(ETHERNET_TYPE_KEY)
|
||||
# Only compile the custom JL1101 driver when JL1101 is configured
|
||||
# and pioarduino doesn't have it builtin (IDF 5.4.2 to 5.x)
|
||||
if eth_type != "JL1101":
|
||||
excluded.append("esp_eth_phy_jl1101.c")
|
||||
elif CORE.is_esp32 and not CORE.data.get(KEY_NATIVE_IDF, False):
|
||||
from esphome.components.esp32 import idf_version
|
||||
|
||||
# pioarduino has JL1101 builtin on IDF 5.4.2-5.x; exclude custom driver
|
||||
# to avoid shadowing. Native IDF builds always need the custom driver.
|
||||
if cv.Version(5, 4, 2) <= idf_version() < cv.Version(6, 0, 0):
|
||||
excluded.append("esp_eth_phy_jl1101.c")
|
||||
return excluded
|
||||
|
||||
|
||||
FILTER_SOURCE_FILES = _filter_source_files
|
||||
|
||||
@@ -29,7 +29,8 @@
|
||||
#include "esp_rom_sys.h"
|
||||
#include "esp_idf_version.h"
|
||||
|
||||
#if defined(USE_ETHERNET_JL1101) && (ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 4, 2) || !defined(PLATFORMIO))
|
||||
#if defined(USE_ETHERNET_JL1101) && (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0) || \
|
||||
ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 4, 2) || !defined(PLATFORMIO))
|
||||
|
||||
static const char *TAG = "jl1101";
|
||||
#define PHY_CHECK(a, str, goto_tag, ...) \
|
||||
|
||||
@@ -239,7 +239,8 @@ class EthernetComponent : public Component {
|
||||
extern EthernetComponent *global_eth_component;
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#if defined(USE_ETHERNET_JL1101) && (ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 4, 2) || !defined(PLATFORMIO))
|
||||
#if defined(USE_ETHERNET_JL1101) && (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0) || \
|
||||
ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 4, 2) || !defined(PLATFORMIO))
|
||||
extern "C" esp_eth_phy_t *esp_eth_phy_new_jl1101(const eth_phy_config_t *config);
|
||||
#endif
|
||||
#endif // USE_ESP32
|
||||
|
||||
@@ -10,6 +10,36 @@
|
||||
#include <cinttypes>
|
||||
#include "esp_event.h"
|
||||
|
||||
// IDF 6.0 moved per-chip PHY/MAC drivers to the Espressif Component Registry;
|
||||
// they are no longer included via esp_eth.h and need explicit includes.
|
||||
// On IDF 5.x these headers don't exist as standalone files.
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0)
|
||||
#ifdef USE_ETHERNET_LAN8720
|
||||
#include "esp_eth_phy_lan87xx.h"
|
||||
#endif
|
||||
#ifdef USE_ETHERNET_RTL8201
|
||||
#include "esp_eth_phy_rtl8201.h"
|
||||
#endif
|
||||
#ifdef USE_ETHERNET_DP83848
|
||||
#include "esp_eth_phy_dp83848.h"
|
||||
#endif
|
||||
#ifdef USE_ETHERNET_IP101
|
||||
#include "esp_eth_phy_ip101.h"
|
||||
#endif
|
||||
#ifdef USE_ETHERNET_KSZ8081
|
||||
#include "esp_eth_phy_ksz80xx.h"
|
||||
#endif
|
||||
#ifdef USE_ETHERNET_W5500
|
||||
#include "esp_eth_mac_w5500.h"
|
||||
#include "esp_eth_phy_w5500.h"
|
||||
#endif
|
||||
#ifdef USE_ETHERNET_DM9051
|
||||
#include "esp_eth_mac_dm9051.h"
|
||||
#include "esp_eth_phy_dm9051.h"
|
||||
#endif
|
||||
#endif // ESP_IDF_VERSION >= 6.0.0
|
||||
|
||||
// LAN867x header exists on all IDF versions (external component since IDF 5.3)
|
||||
#ifdef USE_ETHERNET_LAN8670
|
||||
#include "esp_eth_phy_lan867x.h"
|
||||
#endif
|
||||
@@ -164,21 +194,21 @@ void EthernetComponent::setup() {
|
||||
.post_cb = nullptr,
|
||||
};
|
||||
|
||||
#if CONFIG_ETH_SPI_ETHERNET_W5500
|
||||
#ifdef USE_ETHERNET_W5500
|
||||
eth_w5500_config_t w5500_config = ETH_W5500_DEFAULT_CONFIG(host, &devcfg);
|
||||
#endif
|
||||
#if CONFIG_ETH_SPI_ETHERNET_DM9051
|
||||
#ifdef USE_ETHERNET_DM9051
|
||||
eth_dm9051_config_t dm9051_config = ETH_DM9051_DEFAULT_CONFIG(host, &devcfg);
|
||||
#endif
|
||||
|
||||
#if CONFIG_ETH_SPI_ETHERNET_W5500
|
||||
#ifdef USE_ETHERNET_W5500
|
||||
w5500_config.int_gpio_num = this->interrupt_pin_;
|
||||
#ifdef USE_ETHERNET_SPI_POLLING_SUPPORT
|
||||
w5500_config.poll_period_ms = this->polling_interval_;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if CONFIG_ETH_SPI_ETHERNET_DM9051
|
||||
#ifdef USE_ETHERNET_DM9051
|
||||
dm9051_config.int_gpio_num = this->interrupt_pin_;
|
||||
#ifdef USE_ETHERNET_SPI_POLLING_SUPPORT
|
||||
dm9051_config.poll_period_ms = this->polling_interval_;
|
||||
@@ -204,7 +234,8 @@ void EthernetComponent::setup() {
|
||||
esp32_emac_config.smi_mdio_gpio_num = this->mdio_pin_;
|
||||
#endif
|
||||
esp32_emac_config.clock_config.rmii.clock_mode = this->clk_mode_;
|
||||
esp32_emac_config.clock_config.rmii.clock_gpio = (emac_rmii_clock_gpio_t) this->clk_pin_;
|
||||
esp32_emac_config.clock_config.rmii.clock_gpio =
|
||||
static_cast<decltype(esp32_emac_config.clock_config.rmii.clock_gpio)>(this->clk_pin_);
|
||||
|
||||
esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&esp32_emac_config, &mac_config);
|
||||
#endif
|
||||
@@ -213,7 +244,11 @@ void EthernetComponent::setup() {
|
||||
#ifdef USE_ETHERNET_OPENETH
|
||||
case ETHERNET_TYPE_OPENETH: {
|
||||
phy_config.autonego_timeout_ms = 1000;
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0)
|
||||
this->phy_ = esp_eth_phy_new_generic(&phy_config);
|
||||
#else
|
||||
this->phy_ = esp_eth_phy_new_dp83848(&phy_config);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
@@ -242,8 +277,10 @@ void EthernetComponent::setup() {
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
#if defined(USE_ETHERNET_JL1101) && (ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 4, 2) || !defined(PLATFORMIO))
|
||||
#ifdef USE_ETHERNET_JL1101
|
||||
case ETHERNET_TYPE_JL1101: {
|
||||
// PlatformIO (pioarduino): builtin esp_eth_phy_new_jl1101() on all IDF versions
|
||||
// Non-PlatformIO: custom ESPHome driver (esp_eth_phy_jl1101.c)
|
||||
this->phy_ = esp_eth_phy_new_jl1101(&phy_config);
|
||||
break;
|
||||
}
|
||||
@@ -263,14 +300,14 @@ void EthernetComponent::setup() {
|
||||
#endif
|
||||
#endif
|
||||
#ifdef USE_ETHERNET_SPI
|
||||
#if CONFIG_ETH_SPI_ETHERNET_W5500
|
||||
#ifdef USE_ETHERNET_W5500
|
||||
case ETHERNET_TYPE_W5500: {
|
||||
mac = esp_eth_mac_new_w5500(&w5500_config, &mac_config);
|
||||
this->phy_ = esp_eth_phy_new_w5500(&phy_config);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
#if CONFIG_ETH_SPI_ETHERNET_DM9051
|
||||
#ifdef USE_ETHERNET_DM9051
|
||||
case ETHERNET_TYPE_DM9051: {
|
||||
mac = esp_eth_mac_new_dm9051(&dm9051_config, &mac_config);
|
||||
this->phy_ = esp_eth_phy_new_dm9051(&phy_config);
|
||||
@@ -354,7 +391,7 @@ void EthernetComponent::dump_config() {
|
||||
eth_type = "IP101";
|
||||
break;
|
||||
#endif
|
||||
#if defined(USE_ETHERNET_JL1101) && (ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 4, 2) || !defined(PLATFORMIO))
|
||||
#ifdef USE_ETHERNET_JL1101
|
||||
case ETHERNET_TYPE_JL1101:
|
||||
eth_type = "JL1101";
|
||||
break;
|
||||
@@ -368,12 +405,12 @@ void EthernetComponent::dump_config() {
|
||||
eth_type = "KSZ8081RNA";
|
||||
break;
|
||||
#endif
|
||||
#if CONFIG_ETH_SPI_ETHERNET_W5500
|
||||
#ifdef USE_ETHERNET_W5500
|
||||
case ETHERNET_TYPE_W5500:
|
||||
eth_type = "W5500";
|
||||
break;
|
||||
#endif
|
||||
#if CONFIG_ETH_SPI_ETHERNET_DM9051
|
||||
#ifdef USE_ETHERNET_DM9051
|
||||
case ETHERNET_TYPE_DM9051:
|
||||
eth_type = "DM9051";
|
||||
break;
|
||||
|
||||
@@ -7,6 +7,7 @@ from esphome.const import (
|
||||
CONF_OUTPUT_ID,
|
||||
CONF_RGB_ORDER,
|
||||
)
|
||||
from esphome.core import CORE
|
||||
|
||||
CODEOWNERS = ["@OttoWinter"]
|
||||
fastled_base_ns = cg.esphome_ns.namespace("fastled_base")
|
||||
@@ -41,5 +42,9 @@ async def new_fastled_light(config):
|
||||
cg.add(var.set_max_refresh_rate(config[CONF_MAX_REFRESH_RATE]))
|
||||
|
||||
cg.add_library("fastled/FastLED", "3.9.16")
|
||||
if CORE.is_esp32:
|
||||
from esphome.components.esp32 import include_builtin_idf_component
|
||||
|
||||
include_builtin_idf_component("esp_lcd")
|
||||
await light.register_light(var, config)
|
||||
return var
|
||||
|
||||
@@ -15,12 +15,6 @@ bool random_bytes(uint8_t *data, size_t len) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Mutex::Mutex() { handle_ = xSemaphoreCreateMutex(); }
|
||||
Mutex::~Mutex() {}
|
||||
void Mutex::lock() { xSemaphoreTake(this->handle_, portMAX_DELAY); }
|
||||
bool Mutex::try_lock() { return xSemaphoreTake(this->handle_, 0) == pdTRUE; }
|
||||
void Mutex::unlock() { xSemaphoreGive(this->handle_); }
|
||||
|
||||
// only affects the executing core
|
||||
// so should not be used as a mutex lock, only to get accurate timing
|
||||
IRAM_ATTR InterruptLock::InterruptLock() { portDISABLE_INTERRUPTS(); }
|
||||
|
||||
@@ -111,7 +111,12 @@ struct LogBuffer {
|
||||
}
|
||||
#endif
|
||||
void write_body(const char *text, uint16_t text_length) {
|
||||
this->write_(text, text_length);
|
||||
const uint16_t available = this->remaining_();
|
||||
const uint16_t copy_len = (text_length < available) ? text_length : available;
|
||||
if (copy_len > 0) {
|
||||
memcpy(this->current_(), text, copy_len);
|
||||
this->pos += copy_len;
|
||||
}
|
||||
this->finalize_();
|
||||
}
|
||||
|
||||
@@ -119,21 +124,23 @@ struct LogBuffer {
|
||||
bool full_() const { return this->pos >= this->size; }
|
||||
uint16_t remaining_() const { return this->size - this->pos; }
|
||||
char *current_() { return this->data + this->pos; }
|
||||
void write_(const char *value, uint16_t length) {
|
||||
const uint16_t available = this->remaining_();
|
||||
const uint16_t copy_len = (length < available) ? length : available;
|
||||
if (copy_len > 0) {
|
||||
memcpy(this->current_(), value, copy_len);
|
||||
this->pos += copy_len;
|
||||
}
|
||||
}
|
||||
void finalize_() {
|
||||
// Write color reset sequence
|
||||
static constexpr uint16_t RESET_COLOR_LEN = sizeof(ESPHOME_LOG_RESET_COLOR) - 1;
|
||||
this->write_(ESPHOME_LOG_RESET_COLOR, RESET_COLOR_LEN);
|
||||
this->write_ansi_reset_();
|
||||
// Null terminate
|
||||
this->data[this->full_() ? this->size - 1 : this->pos] = '\0';
|
||||
}
|
||||
// Write ANSI reset sequence inline ("\033[0m") - avoids write_() call overhead
|
||||
static constexpr uint16_t ANSI_RESET_LEN = 4; // "\033[0m"
|
||||
void write_ansi_reset_() {
|
||||
if (this->remaining_() >= ANSI_RESET_LEN) {
|
||||
char *p = this->current_();
|
||||
*p++ = '\033';
|
||||
*p++ = '[';
|
||||
*p++ = '0';
|
||||
*p++ = 'm';
|
||||
this->pos += ANSI_RESET_LEN;
|
||||
}
|
||||
}
|
||||
void strip_trailing_newlines_() {
|
||||
while (this->pos > 0 && this->data[this->pos - 1] == '\n')
|
||||
this->pos--;
|
||||
|
||||
@@ -233,7 +233,11 @@ class Logger final : public Component {
|
||||
void cdc_loop_();
|
||||
#endif
|
||||
void process_messages_();
|
||||
#if defined(USE_HOST) || defined(USE_ZEPHYR)
|
||||
void write_msg_(const char *msg, uint16_t len);
|
||||
#else
|
||||
inline void write_msg_(const char *msg, uint16_t len); // Defined in platform-specific logger_*.h
|
||||
#endif
|
||||
|
||||
// Format a log message with printf-style arguments and write it to a buffer with header, footer, and null terminator
|
||||
// thread_name: name of the calling thread/task, or nullptr for main task (callers already know which task they're on)
|
||||
@@ -366,7 +370,7 @@ class Logger final : public Component {
|
||||
bool non_main_task_recursion_guard_{false}; // Shared guard for all non-main tasks on LibreTiny
|
||||
#endif
|
||||
#else
|
||||
bool global_recursion_guard_{false}; // Simple global recursion guard for single-task platforms
|
||||
bool global_recursion_guard_{false}; // Simple global recursion guard for single-task platforms
|
||||
#endif
|
||||
|
||||
// Large buffer placed last to keep frequently-accessed member offsets small
|
||||
@@ -498,3 +502,15 @@ class LoggerMessageTrigger final : public Trigger<uint8_t, const char *, const c
|
||||
};
|
||||
|
||||
} // namespace esphome::logger
|
||||
|
||||
// Platform-specific inline implementations of write_msg_()
|
||||
// Must be included after the Logger class definition is complete
|
||||
#if defined(USE_ESP32)
|
||||
#include "logger_esp32.h"
|
||||
#elif defined(USE_ESP8266)
|
||||
#include "logger_esp8266.h"
|
||||
#elif defined(USE_RP2040)
|
||||
#include "logger_rp2040.h"
|
||||
#elif defined(USE_LIBRETINY)
|
||||
#include "logger_libretiny.h"
|
||||
#endif
|
||||
|
||||
@@ -123,23 +123,6 @@ void Logger::pre_setup() {
|
||||
#endif
|
||||
}
|
||||
|
||||
void HOT Logger::write_msg_(const char *msg, uint16_t len) {
|
||||
#if defined(USE_LOGGER_UART_SELECTION_USB_CDC) || defined(USE_LOGGER_UART_SELECTION_USB_SERIAL_JTAG)
|
||||
// USB CDC/JTAG - single write including newline (already in buffer)
|
||||
// Use fwrite to stdout which goes through VFS to USB console
|
||||
//
|
||||
// Note: These defines indicate the user's YAML configuration choice (hardware_uart: USB_CDC/USB_SERIAL_JTAG).
|
||||
// They are ONLY defined when the user explicitly selects USB as the logger output in their config.
|
||||
// This is compile-time selection, not runtime detection - if USB is configured, it's always used.
|
||||
// There is no fallback to regular UART if "USB isn't connected" - that's the user's responsibility
|
||||
// to configure correctly for their hardware. This approach eliminates runtime overhead.
|
||||
fwrite(msg, 1, len, stdout);
|
||||
#else
|
||||
// Regular UART - single write including newline (already in buffer)
|
||||
uart_write_bytes(this->uart_num_, msg, len);
|
||||
#endif
|
||||
}
|
||||
|
||||
const LogString *Logger::get_uart_selection_() {
|
||||
switch (this->uart_) {
|
||||
case UART_SELECTION_UART0:
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#include "esphome/core/helpers.h"
|
||||
#include <driver/uart.h>
|
||||
|
||||
namespace esphome::logger {
|
||||
|
||||
inline void HOT Logger::write_msg_(const char *msg, uint16_t len) {
|
||||
#if defined(USE_LOGGER_UART_SELECTION_USB_CDC) || defined(USE_LOGGER_UART_SELECTION_USB_SERIAL_JTAG)
|
||||
// USB CDC/JTAG - single write including newline (already in buffer)
|
||||
// Use fwrite to stdout which goes through VFS to USB console
|
||||
//
|
||||
// Note: These defines indicate the user's YAML configuration choice (hardware_uart: USB_CDC/USB_SERIAL_JTAG).
|
||||
// They are ONLY defined when the user explicitly selects USB as the logger output in their config.
|
||||
// This is compile-time selection, not runtime detection - if USB is configured, it's always used.
|
||||
// There is no fallback to regular UART if "USB isn't connected" - that's the user's responsibility
|
||||
// to configure correctly for their hardware. This approach eliminates runtime overhead.
|
||||
fwrite(msg, 1, len, stdout);
|
||||
#else
|
||||
// Regular UART - single write including newline (already in buffer)
|
||||
uart_write_bytes(this->uart_num_, msg, len);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace esphome::logger
|
||||
|
||||
#endif
|
||||
@@ -28,11 +28,6 @@ void Logger::pre_setup() {
|
||||
ESP_LOGI(TAG, "Log initialized");
|
||||
}
|
||||
|
||||
void HOT Logger::write_msg_(const char *msg, uint16_t len) {
|
||||
// Single write with newline already in buffer (added by caller)
|
||||
this->hw_serial_->write(msg, len);
|
||||
}
|
||||
|
||||
const LogString *Logger::get_uart_selection_() {
|
||||
#if defined(USE_ESP8266_LOGGER_SERIAL)
|
||||
if (this->uart_ == UART_SELECTION_UART0_SWAP) {
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome::logger {
|
||||
|
||||
// Single write with newline already in buffer (added by caller)
|
||||
inline void HOT Logger::write_msg_(const char *msg, uint16_t len) { this->hw_serial_->write(msg, len); }
|
||||
|
||||
} // namespace esphome::logger
|
||||
|
||||
#endif
|
||||
@@ -49,8 +49,6 @@ void Logger::pre_setup() {
|
||||
ESP_LOGI(TAG, "Log initialized");
|
||||
}
|
||||
|
||||
void HOT Logger::write_msg_(const char *msg, uint16_t len) { this->hw_serial_->write(msg, len); }
|
||||
|
||||
const LogString *Logger::get_uart_selection_() {
|
||||
switch (this->uart_) {
|
||||
case UART_SELECTION_DEFAULT:
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_LIBRETINY
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome::logger {
|
||||
|
||||
// Single write with newline already in buffer (added by caller)
|
||||
inline void HOT Logger::write_msg_(const char *msg, uint16_t len) { this->hw_serial_->write(msg, len); }
|
||||
|
||||
} // namespace esphome::logger
|
||||
|
||||
#endif
|
||||
@@ -34,11 +34,6 @@ void Logger::pre_setup() {
|
||||
#endif
|
||||
}
|
||||
|
||||
void HOT Logger::write_msg_(const char *msg, uint16_t len) {
|
||||
// Single write with newline already in buffer (added by caller)
|
||||
this->hw_serial_->write(msg, len);
|
||||
}
|
||||
|
||||
const LogString *Logger::get_uart_selection_() {
|
||||
switch (this->uart_) {
|
||||
case UART_SELECTION_UART0:
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_RP2040
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome::logger {
|
||||
|
||||
// Single write with newline already in buffer (added by caller)
|
||||
inline void HOT Logger::write_msg_(const char *msg, uint16_t len) { this->hw_serial_->write(msg, len); }
|
||||
|
||||
} // namespace esphome::logger
|
||||
|
||||
#endif
|
||||
@@ -35,12 +35,7 @@ bool random_bytes(uint8_t *data, size_t len) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// RP2040 doesn't have mutexes, but that shouldn't be an issue as it's single-core and non-preemptive OS.
|
||||
Mutex::Mutex() {}
|
||||
Mutex::~Mutex() {}
|
||||
void Mutex::lock() {}
|
||||
bool Mutex::try_lock() { return true; }
|
||||
void Mutex::unlock() {}
|
||||
// RP2040 Mutex is defined inline in helpers.h for RP2040/ESP8266 builds.
|
||||
|
||||
IRAM_ATTR InterruptLock::InterruptLock() { state_ = save_and_disable_interrupts(); }
|
||||
IRAM_ATTR InterruptLock::~InterruptLock() { restore_interrupts(state_); }
|
||||
|
||||
@@ -11,6 +11,7 @@ from esphome.components.image import (
|
||||
)
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_FORMAT, CONF_ID, CONF_RESIZE, CONF_TYPE
|
||||
from esphome.core import CORE
|
||||
|
||||
AUTO_LOAD = ["image"]
|
||||
CODEOWNERS = ["@guillempages", "@clydebarrow", "@kahrendt"]
|
||||
@@ -75,6 +76,13 @@ class JPEGFormat(Format):
|
||||
def actions(self) -> None:
|
||||
cg.add_define("USE_RUNTIME_IMAGE_JPEG")
|
||||
cg.add_library("JPEGDEC", "1.8.4", "https://github.com/bitbank2/JPEGDEC#1.8.4")
|
||||
if CORE.is_esp32:
|
||||
from esphome.components.esp32 import add_idf_component
|
||||
|
||||
# JPEGDEC uses ESP32-S3 SIMD optimizations (guarded by board-level
|
||||
# ARDUINO_ESP32S3_DEV define) that require esp-dsp headers.
|
||||
# On Arduino this overwrites the stub; on IDF it adds the component.
|
||||
add_idf_component(name="espressif/esp-dsp", ref="1.7.1")
|
||||
|
||||
|
||||
class PNGFormat(Format):
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "tinyusb_component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "tinyusb_default_config.h"
|
||||
|
||||
namespace esphome::tinyusb {
|
||||
|
||||
@@ -15,19 +16,19 @@ void TinyUSB::setup() {
|
||||
this->string_descriptor_[SERIAL_NUMBER] = mac_addr_buf;
|
||||
}
|
||||
|
||||
this->tusb_cfg_ = {
|
||||
.port = TINYUSB_PORT_FULL_SPEED_0,
|
||||
.phy = {.skip_setup = false},
|
||||
.descriptor =
|
||||
{
|
||||
.device = &this->usb_descriptor_,
|
||||
.string = this->string_descriptor_,
|
||||
.string_count = SIZE,
|
||||
},
|
||||
// Start from esp_tinyusb defaults to keep required task settings valid across esp_tinyusb updates.
|
||||
this->tusb_cfg_ = TINYUSB_DEFAULT_CONFIG();
|
||||
this->tusb_cfg_.port = TINYUSB_PORT_FULL_SPEED_0;
|
||||
this->tusb_cfg_.phy.skip_setup = false;
|
||||
this->tusb_cfg_.descriptor = {
|
||||
.device = &this->usb_descriptor_,
|
||||
.string = this->string_descriptor_,
|
||||
.string_count = SIZE,
|
||||
};
|
||||
|
||||
esp_err_t result = tinyusb_driver_install(&this->tusb_cfg_);
|
||||
if (result != ESP_OK) {
|
||||
ESP_LOGE(TAG, "tinyusb_driver_install failed: %s", esp_err_to_name(result));
|
||||
this->mark_failed();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,9 @@ from esphome.components.esp32 import (
|
||||
VARIANT_ESP32P4,
|
||||
VARIANT_ESP32S2,
|
||||
VARIANT_ESP32S3,
|
||||
add_idf_component,
|
||||
add_idf_sdkconfig_option,
|
||||
idf_version,
|
||||
only_on_variant,
|
||||
)
|
||||
import esphome.config_validation as cv
|
||||
@@ -64,6 +66,9 @@ async def register_usb_client(config):
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
# IDF 6.0 moved USB host to an external component
|
||||
if idf_version() >= cv.Version(6, 0, 0):
|
||||
add_idf_component(name="espressif/usb", ref="1.3.0")
|
||||
add_idf_sdkconfig_option("CONFIG_USB_HOST_CONTROL_TRANSFER_MAX_SIZE", 1024)
|
||||
if config.get(CONF_ENABLE_HUBS):
|
||||
add_idf_sdkconfig_option("CONFIG_USB_HOST_HUBS_SUPPORTED", True)
|
||||
|
||||
@@ -74,12 +74,13 @@ int nonblocking_send(httpd_handle_t hd, int sockfd, const char *buf, size_t buf_
|
||||
// Use MSG_DONTWAIT to prevent blocking when TCP send buffer is full
|
||||
int ret = send(sockfd, buf, buf_len, flags | MSG_DONTWAIT);
|
||||
if (ret < 0) {
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
||||
const int err = errno;
|
||||
if (err == EAGAIN || err == EWOULDBLOCK) {
|
||||
// Buffer full - retry later
|
||||
return HTTPD_SOCK_ERR_TIMEOUT;
|
||||
}
|
||||
// Real error
|
||||
ESP_LOGD(TAG, "send error: errno %d", errno);
|
||||
ESP_LOGD(TAG, "send error: errno %d", err);
|
||||
return HTTPD_SOCK_ERR_FAIL;
|
||||
}
|
||||
return ret;
|
||||
|
||||
@@ -698,18 +698,22 @@ void Application::yield_with_select_(uint32_t delay_ms) {
|
||||
#endif
|
||||
|
||||
// Process select() result:
|
||||
// ret < 0: error (except EINTR which is normal)
|
||||
// ret > 0: socket(s) have data ready - normal and expected
|
||||
// ret == 0: timeout occurred - normal and expected
|
||||
if (ret >= 0 || errno == EINTR) [[likely]] {
|
||||
if (ret >= 0) [[likely]] {
|
||||
// Yield if zero timeout since select(0) only polls without yielding
|
||||
if (delay_ms == 0) [[unlikely]] {
|
||||
yield();
|
||||
}
|
||||
return;
|
||||
}
|
||||
// ret < 0: error (EINTR is normal, anything else is unexpected)
|
||||
const int err = errno;
|
||||
if (err == EINTR) {
|
||||
return;
|
||||
}
|
||||
// select() error - log and fall through to delay()
|
||||
ESP_LOGW(TAG, "select() failed with errno %d", errno);
|
||||
ESP_LOGW(TAG, "select() failed with errno %d", err);
|
||||
}
|
||||
// No sockets registered or select() failed - use regular delay
|
||||
delay(delay_ms);
|
||||
|
||||
@@ -510,7 +510,8 @@ void PollingComponent::stop_poller() {
|
||||
uint32_t PollingComponent::get_update_interval() const { return this->update_interval_; }
|
||||
void PollingComponent::set_update_interval(uint32_t update_interval) { this->update_interval_ = update_interval; }
|
||||
|
||||
static void __attribute__((noinline, cold)) warn_blocking(Component *component, uint32_t blocking_time) {
|
||||
void __attribute__((noinline, cold))
|
||||
WarnIfComponentBlockingGuard::warn_blocking(Component *component, uint32_t blocking_time) {
|
||||
bool should_warn;
|
||||
if (component != nullptr) {
|
||||
should_warn = component->should_warn_of_blocking(blocking_time);
|
||||
@@ -524,10 +525,8 @@ static void __attribute__((noinline, cold)) warn_blocking(Component *component,
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t WarnIfComponentBlockingGuard::finish() {
|
||||
uint32_t curr_time = millis();
|
||||
uint32_t blocking_time = curr_time - this->started_;
|
||||
#ifdef USE_RUNTIME_STATS
|
||||
void WarnIfComponentBlockingGuard::record_runtime_stats_() {
|
||||
// Use micros() for accurate sub-millisecond timing. millis() has insufficient
|
||||
// resolution — most components complete in microseconds but millis() only has
|
||||
// 1ms granularity, so results were essentially random noise.
|
||||
@@ -535,12 +534,8 @@ uint32_t WarnIfComponentBlockingGuard::finish() {
|
||||
uint32_t duration_us = micros() - this->started_us_;
|
||||
global_runtime_stats->record_component_time(this->component_, duration_us);
|
||||
}
|
||||
#endif
|
||||
if (blocking_time > WARN_IF_BLOCKING_OVER_MS) {
|
||||
warn_blocking(this->component_, blocking_time);
|
||||
}
|
||||
return curr_time;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_SETUP_PRIORITY_OVERRIDE
|
||||
void clear_setup_priority_overrides() {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <string>
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/optional.h"
|
||||
@@ -575,9 +576,7 @@ class PollingComponent : public Component {
|
||||
uint32_t update_interval_;
|
||||
};
|
||||
|
||||
#ifdef USE_RUNTIME_STATS
|
||||
uint32_t micros(); // Forward declare for inline constructor
|
||||
#endif
|
||||
// millis() and micros() are available via hal.h
|
||||
|
||||
class WarnIfComponentBlockingGuard {
|
||||
public:
|
||||
@@ -592,7 +591,18 @@ class WarnIfComponentBlockingGuard {
|
||||
}
|
||||
|
||||
// Finish the timing operation and return the current time
|
||||
uint32_t finish();
|
||||
// Inlined: the fast path is just millis() + subtract + compare
|
||||
inline uint32_t HOT finish() {
|
||||
uint32_t curr_time = millis();
|
||||
uint32_t blocking_time = curr_time - this->started_;
|
||||
#ifdef USE_RUNTIME_STATS
|
||||
this->record_runtime_stats_();
|
||||
#endif
|
||||
if (blocking_time > WARN_IF_BLOCKING_OVER_MS) [[unlikely]] {
|
||||
warn_blocking(this->component_, blocking_time);
|
||||
}
|
||||
return curr_time;
|
||||
}
|
||||
|
||||
~WarnIfComponentBlockingGuard() = default;
|
||||
|
||||
@@ -601,7 +611,12 @@ class WarnIfComponentBlockingGuard {
|
||||
Component *component_;
|
||||
#ifdef USE_RUNTIME_STATS
|
||||
uint32_t started_us_;
|
||||
void record_runtime_stats_();
|
||||
#endif
|
||||
|
||||
private:
|
||||
// Cold path for blocking warning - defined in component.cpp
|
||||
static void __attribute__((noinline, cold)) warn_blocking(Component *component, uint32_t blocking_time);
|
||||
};
|
||||
|
||||
// Function to clear setup priority overrides after all components are set up
|
||||
|
||||
@@ -281,6 +281,8 @@
|
||||
#define USE_ETHERNET_SPI
|
||||
#define USE_ETHERNET_SPI_POLLING_SUPPORT
|
||||
#define USE_ETHERNET_OPENETH
|
||||
#define USE_ETHERNET_W5500
|
||||
#define USE_ETHERNET_DM9051
|
||||
#define CONFIG_ETH_SPI_ETHERNET_W5500 1
|
||||
#define CONFIG_ETH_SPI_ETHERNET_DM9051 1
|
||||
#define CONFIG_ETH_USE_ESP32_EMAC 1
|
||||
|
||||
+21
-6
@@ -1917,19 +1917,34 @@ template<typename T> class Parented {
|
||||
*/
|
||||
class Mutex {
|
||||
public:
|
||||
Mutex();
|
||||
Mutex(const Mutex &) = delete;
|
||||
Mutex &operator=(const Mutex &) = delete;
|
||||
|
||||
#if defined(USE_ESP8266) || defined(USE_RP2040)
|
||||
// Single-threaded platforms: inline no-ops so the compiler eliminates all call overhead.
|
||||
Mutex() = default;
|
||||
~Mutex() = default;
|
||||
void lock() {}
|
||||
bool try_lock() { return true; }
|
||||
void unlock() {}
|
||||
#elif defined(USE_ESP32) || defined(USE_LIBRETINY)
|
||||
// FreeRTOS platforms: inline to avoid out-of-line call overhead.
|
||||
Mutex() { handle_ = xSemaphoreCreateMutex(); }
|
||||
~Mutex() = default;
|
||||
void lock() { xSemaphoreTake(this->handle_, portMAX_DELAY); }
|
||||
bool try_lock() { return xSemaphoreTake(this->handle_, 0) == pdTRUE; }
|
||||
void unlock() { xSemaphoreGive(this->handle_); }
|
||||
|
||||
private:
|
||||
SemaphoreHandle_t handle_;
|
||||
#else
|
||||
Mutex();
|
||||
~Mutex();
|
||||
void lock();
|
||||
bool try_lock();
|
||||
void unlock();
|
||||
|
||||
Mutex &operator=(const Mutex &) = delete;
|
||||
|
||||
private:
|
||||
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
|
||||
SemaphoreHandle_t handle_;
|
||||
#else
|
||||
// d-pointer to store private data on new platforms
|
||||
void *handle_; // NOLINT(clang-diagnostic-unused-private-field)
|
||||
#endif
|
||||
|
||||
@@ -5,6 +5,8 @@ dependencies:
|
||||
version: 2.0.3
|
||||
esphome/micro-opus:
|
||||
version: 0.3.5
|
||||
espressif/esp-dsp:
|
||||
version: "1.7.1"
|
||||
espressif/esp-tflite-micro:
|
||||
version: 1.3.3~1
|
||||
espressif/esp32-camera:
|
||||
@@ -29,6 +31,34 @@ dependencies:
|
||||
version: "2.0.0"
|
||||
rules:
|
||||
- if: "target in [esp32, esp32p4]"
|
||||
espressif/lan87xx:
|
||||
version: "1.0.0"
|
||||
rules:
|
||||
- if: "idf_version >=6.0.0 && target in [esp32, esp32p4]"
|
||||
espressif/rtl8201:
|
||||
version: "1.0.1"
|
||||
rules:
|
||||
- if: "idf_version >=6.0.0 && target in [esp32, esp32p4]"
|
||||
espressif/dp83848:
|
||||
version: "1.0.0"
|
||||
rules:
|
||||
- if: "idf_version >=6.0.0 && target in [esp32, esp32p4]"
|
||||
espressif/ip101:
|
||||
version: "1.0.0"
|
||||
rules:
|
||||
- if: "idf_version >=6.0.0 && target in [esp32, esp32p4]"
|
||||
espressif/ksz80xx:
|
||||
version: "1.0.0"
|
||||
rules:
|
||||
- if: "idf_version >=6.0.0 && target in [esp32, esp32p4]"
|
||||
espressif/w5500:
|
||||
version: "1.0.1"
|
||||
rules:
|
||||
- if: "idf_version >=6.0.0"
|
||||
espressif/dm9051:
|
||||
version: "1.0.0"
|
||||
rules:
|
||||
- if: "idf_version >=6.0.0"
|
||||
espressif/esp_tinyusb:
|
||||
version: "2.1.1"
|
||||
rules:
|
||||
@@ -41,5 +71,9 @@ dependencies:
|
||||
version: "1.0.0"
|
||||
rules:
|
||||
- if: "idf_version >=6.0.0"
|
||||
espressif/usb:
|
||||
version: "1.3.0"
|
||||
rules:
|
||||
- if: "idf_version >=6.0.0 && target in [esp32s2, esp32s3, esp32p4]"
|
||||
esp32async/asynctcp:
|
||||
version: 3.4.91
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ platformio==6.1.19
|
||||
esptool==5.2.0
|
||||
click==8.3.1
|
||||
esphome-dashboard==20260210.0
|
||||
aioesphomeapi==44.5.1
|
||||
aioesphomeapi==44.5.2
|
||||
zeroconf==0.148.0
|
||||
puremagic==1.30
|
||||
ruamel.yaml==0.19.1 # dashboard_import
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<<: !include common.yaml
|
||||
@@ -0,0 +1,19 @@
|
||||
packages:
|
||||
spi: !include ../../test_build_components/common/spi/esp32-s3-ard.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
http_request:
|
||||
|
||||
display:
|
||||
- platform: ili9xxx
|
||||
spi_id: spi_bus
|
||||
id: main_lcd
|
||||
model: ili9342
|
||||
cs_pin: 20
|
||||
dc_pin: 13
|
||||
reset_pin: 21
|
||||
invert_colors: true
|
||||
lambda: |-
|
||||
it.fill(Color(0, 0, 0));
|
||||
it.image(0, 0, id(online_rgba_image));
|
||||
@@ -0,0 +1,19 @@
|
||||
packages:
|
||||
spi: !include ../../test_build_components/common/spi/esp32-s3-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
http_request:
|
||||
|
||||
display:
|
||||
- platform: ili9xxx
|
||||
spi_id: spi_bus
|
||||
id: main_lcd
|
||||
model: ili9342
|
||||
cs_pin: 20
|
||||
dc_pin: 13
|
||||
reset_pin: 21
|
||||
invert_colors: true
|
||||
lambda: |-
|
||||
it.fill(Color(0, 0, 0));
|
||||
it.image(0, 0, id(online_rgba_image));
|
||||
Reference in New Issue
Block a user