mirror of
https://github.com/esphome/esphome.git
synced 2026-09-02 02:56:01 +00:00
Merge branch 'dev' into socket-lwip-raw-udp
This commit is contained in:
@@ -27,7 +27,7 @@ jobs:
|
||||
|
||||
- name: Generate a token
|
||||
id: generate-token
|
||||
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2
|
||||
uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v2
|
||||
with:
|
||||
app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }}
|
||||
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
|
||||
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
echo "You have modified clang-tidy configuration but have not updated the hash." | tee -a $GITHUB_STEP_SUMMARY
|
||||
echo "Please run 'script/clang_tidy_hash.py --update' and commit the changes." | tee -a $GITHUB_STEP_SUMMARY
|
||||
|
||||
- if: failure()
|
||||
- if: failure() && github.event.pull_request.head.repo.full_name == github.repository
|
||||
name: Request changes
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
with:
|
||||
@@ -53,7 +53,7 @@ jobs:
|
||||
body: 'You have modified clang-tidy configuration but have not updated the hash.\nPlease run `script/clang_tidy_hash.py --update` and commit the changes.'
|
||||
})
|
||||
|
||||
- if: success()
|
||||
- if: success() && github.event.pull_request.head.repo.full_name == github.repository
|
||||
name: Dismiss review
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
with:
|
||||
|
||||
@@ -58,7 +58,7 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6
|
||||
uses: github/codeql-action/init@b1bff81932f5cdfc8695c7752dcee935dcd061c8 # v4.33.0
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
build-mode: ${{ matrix.build-mode }}
|
||||
@@ -86,6 +86,6 @@ jobs:
|
||||
exit 1
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6
|
||||
uses: github/codeql-action/analyze@b1bff81932f5cdfc8695c7752dcee935dcd061c8 # v4.33.0
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
|
||||
@@ -221,7 +221,7 @@ jobs:
|
||||
steps:
|
||||
- name: Generate a token
|
||||
id: generate-token
|
||||
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1
|
||||
uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0
|
||||
with:
|
||||
app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }}
|
||||
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
|
||||
@@ -256,7 +256,7 @@ jobs:
|
||||
steps:
|
||||
- name: Generate a token
|
||||
id: generate-token
|
||||
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1
|
||||
uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0
|
||||
with:
|
||||
app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }}
|
||||
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
|
||||
@@ -287,7 +287,7 @@ jobs:
|
||||
steps:
|
||||
- name: Generate a token
|
||||
id: generate-token
|
||||
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1
|
||||
uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0
|
||||
with:
|
||||
app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }}
|
||||
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
|
||||
|
||||
@@ -35,7 +35,7 @@ class Am43 : public esphome::ble_client::BLEClientNode, public PollingComponent
|
||||
uint8_t current_sensor_;
|
||||
// The AM43 often gets into a state where it spams loads of battery update
|
||||
// notifications. Here we will limit to no more than every 10s.
|
||||
uint8_t last_battery_update_;
|
||||
uint32_t last_battery_update_;
|
||||
};
|
||||
|
||||
} // namespace am43
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -36,11 +36,11 @@ struct SavedNoisePsk {
|
||||
} PACKED; // NOLINT
|
||||
#endif
|
||||
|
||||
class APIServer : public Component,
|
||||
public Controller
|
||||
class APIServer final : public Component,
|
||||
public Controller
|
||||
#ifdef USE_CAMERA
|
||||
,
|
||||
public camera::CameraListener
|
||||
public camera::CameraListener
|
||||
#endif
|
||||
{
|
||||
public:
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ enum AS3935RegisterMasks {
|
||||
INT_MASK = 0xF0,
|
||||
THRESH_MASK = 0x0F,
|
||||
R_SPIKE_MASK = 0xF0,
|
||||
ENERGY_MASK = 0xF0,
|
||||
ENERGY_MASK = 0xE0,
|
||||
CAP_MASK = 0xF0,
|
||||
LIGHT_MASK = 0xCF,
|
||||
DISTURB_MASK = 0xDF,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ class TimeoutFilter : public Filter, public Component {
|
||||
TemplatableValue<uint32_t> timeout_delay_{};
|
||||
};
|
||||
|
||||
class DelayedOnOffFilter : public Filter, public Component {
|
||||
class DelayedOnOffFilter final : public Filter, public Component {
|
||||
public:
|
||||
optional<bool> new_value(bool value) override;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -136,6 +136,9 @@ bool DallasTemperatureSensor::check_scratch_pad_() {
|
||||
float DallasTemperatureSensor::get_temp_c_() {
|
||||
int16_t temp = (this->scratch_pad_[1] << 8) | this->scratch_pad_[0];
|
||||
if ((this->address_ & 0xff) == DALLAS_MODEL_DS18S20) {
|
||||
if (this->scratch_pad_[7] == 0) {
|
||||
return NAN;
|
||||
}
|
||||
return (temp >> 1) + (this->scratch_pad_[7] - this->scratch_pad_[6]) / float(this->scratch_pad_[7]) - 0.25;
|
||||
}
|
||||
switch (this->resolution_) {
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -612,10 +612,12 @@ def _format_framework_espidf_version(
|
||||
ext = "tar.xz"
|
||||
else:
|
||||
ext = "zip"
|
||||
# Build version string with dot-separated extra (e.g., "5.5.3.1" not "5.5.3-1")
|
||||
# Build version string with extra separator based on type:
|
||||
# numeric extra uses dot (e.g., "5.5.3.1"), string extra uses dash (e.g., "6.0.0-rc1")
|
||||
ver_str = f"{ver.major}.{ver.minor}.{ver.patch}"
|
||||
if ver.extra:
|
||||
ver_str += f".{ver.extra}"
|
||||
sep = "." if str(ver.extra).isdigit() else "-"
|
||||
ver_str += f"{sep}{ver.extra}"
|
||||
if release:
|
||||
return f"pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v{ver_str}.{release}/esp-idf-v{ver_str}.{ext}"
|
||||
return f"pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v{ver_str}/esp-idf-v{ver_str}.{ext}"
|
||||
|
||||
@@ -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,24 +1,10 @@
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
|
||||
from esphome import automation, pins
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.esp32 import (
|
||||
VARIANT_ESP32,
|
||||
VARIANT_ESP32C3,
|
||||
VARIANT_ESP32C5,
|
||||
VARIANT_ESP32C6,
|
||||
VARIANT_ESP32C61,
|
||||
VARIANT_ESP32P4,
|
||||
VARIANT_ESP32S2,
|
||||
VARIANT_ESP32S3,
|
||||
add_idf_component,
|
||||
add_idf_sdkconfig_option,
|
||||
get_esp32_variant,
|
||||
idf_version,
|
||||
include_builtin_idf_component,
|
||||
)
|
||||
from esphome.components.network import ip_address_literal
|
||||
from esphome.components.spi import CONF_INTERFACE_INDEX, get_spi_interface
|
||||
from esphome.config_helpers import filter_source_files_from_platform
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ADDRESS,
|
||||
@@ -50,6 +36,9 @@ from esphome.const import (
|
||||
CONF_VALUE,
|
||||
KEY_CORE,
|
||||
KEY_FRAMEWORK_VERSION,
|
||||
KEY_NATIVE_IDF,
|
||||
Platform,
|
||||
PlatformFramework,
|
||||
)
|
||||
from esphome.core import (
|
||||
CORE,
|
||||
@@ -61,12 +50,14 @@ import esphome.final_validate as fv
|
||||
from esphome.types import ConfigType
|
||||
|
||||
CONFLICTS_WITH = ["wifi"]
|
||||
DEPENDENCIES = ["esp32"]
|
||||
AUTO_LOAD = ["network"]
|
||||
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:
|
||||
@@ -140,10 +131,35 @@ _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"]
|
||||
# RP2040-supported SPI ethernet types
|
||||
RP2040_SPI_ETHERNET_TYPES = ["W5500"]
|
||||
SPI_ETHERNET_DEFAULT_POLLING_INTERVAL = TimePeriodMilliseconds(milliseconds=10)
|
||||
|
||||
emac_rmii_clock_mode_t = cg.global_ns.enum("emac_rmii_clock_mode_t")
|
||||
@@ -174,9 +190,16 @@ EthernetComponent = ethernet_ns.class_("EthernetComponent", cg.Component)
|
||||
ManualIP = ethernet_ns.struct("ManualIP")
|
||||
|
||||
|
||||
def _is_framework_spi_polling_mode_supported():
|
||||
# SPI Ethernet without IRQ feature is added in
|
||||
# esp-idf >= (5.3+ ,5.2.1+, 5.1.4)
|
||||
def _is_framework_spi_polling_mode_supported() -> bool:
|
||||
"""Check if ESP-IDF framework supports SPI polling mode (ESP32 only).
|
||||
|
||||
SPI Ethernet without IRQ feature is added in
|
||||
esp-idf >= (5.3+, 5.2.1+, 5.1.4)
|
||||
"""
|
||||
if not CORE.is_esp32:
|
||||
return False
|
||||
from esphome.components.esp32 import idf_version
|
||||
|
||||
ver = idf_version()
|
||||
if ver >= cv.Version(5, 3, 0):
|
||||
return True
|
||||
@@ -195,52 +218,68 @@ def _validate(config):
|
||||
use_address = CORE.name + config[CONF_DOMAIN]
|
||||
config[CONF_USE_ADDRESS] = use_address
|
||||
|
||||
if config[CONF_TYPE] in SPI_ETHERNET_TYPES:
|
||||
if _is_framework_spi_polling_mode_supported():
|
||||
if CONF_POLLING_INTERVAL in config and CONF_INTERRUPT_PIN in config:
|
||||
raise cv.Invalid(
|
||||
f"Cannot specify more than one of {CONF_INTERRUPT_PIN}, {CONF_POLLING_INTERVAL}"
|
||||
)
|
||||
if CONF_POLLING_INTERVAL not in config and CONF_INTERRUPT_PIN not in config:
|
||||
config[CONF_POLLING_INTERVAL] = SPI_ETHERNET_DEFAULT_POLLING_INTERVAL
|
||||
else:
|
||||
if CONF_POLLING_INTERVAL in config:
|
||||
raise cv.Invalid(
|
||||
"In this version of the framework "
|
||||
f"({CORE.target_framework} {CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION]}), "
|
||||
f"'{CONF_POLLING_INTERVAL}' is not supported."
|
||||
)
|
||||
if CONF_INTERRUPT_PIN not in config:
|
||||
raise cv.Invalid(
|
||||
"In this version of the framework "
|
||||
f"({CORE.target_framework} {CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION]}), "
|
||||
f"'{CONF_INTERRUPT_PIN}' is a required option for [ethernet]."
|
||||
)
|
||||
elif config[CONF_TYPE] != "OPENETH":
|
||||
if CONF_CLK_MODE in config:
|
||||
mode, pin = CLK_MODES_DEPRECATED[config[CONF_CLK_MODE]]
|
||||
LOGGER.warning(
|
||||
"[ethernet] The 'clk_mode' option is deprecated. "
|
||||
"Please replace 'clk_mode: %s' with:\n"
|
||||
" clk:\n"
|
||||
" mode: %s\n"
|
||||
" pin: %s\n"
|
||||
"Removal scheduled for 2026.7.0.",
|
||||
config[CONF_CLK_MODE],
|
||||
mode,
|
||||
pin,
|
||||
)
|
||||
config[CONF_CLK] = CLK_SCHEMA({CONF_MODE: mode, CONF_PIN: pin})
|
||||
del config[CONF_CLK_MODE]
|
||||
elif CONF_CLK not in config:
|
||||
raise cv.Invalid("'clk' is a required option for [ethernet].")
|
||||
variant = get_esp32_variant()
|
||||
if variant not in (VARIANT_ESP32, VARIANT_ESP32P4):
|
||||
raise cv.Invalid(
|
||||
f"{config[CONF_TYPE]} PHY requires RMII interface and is only supported "
|
||||
f"on ESP32 classic and ESP32-P4, not {variant}"
|
||||
if CORE.is_esp32:
|
||||
if config[CONF_TYPE] in SPI_ETHERNET_TYPES:
|
||||
if _is_framework_spi_polling_mode_supported():
|
||||
if CONF_POLLING_INTERVAL in config and CONF_INTERRUPT_PIN in config:
|
||||
raise cv.Invalid(
|
||||
f"Cannot specify more than one of {CONF_INTERRUPT_PIN}, {CONF_POLLING_INTERVAL}"
|
||||
)
|
||||
if (
|
||||
CONF_POLLING_INTERVAL not in config
|
||||
and CONF_INTERRUPT_PIN not in config
|
||||
):
|
||||
config[CONF_POLLING_INTERVAL] = (
|
||||
SPI_ETHERNET_DEFAULT_POLLING_INTERVAL
|
||||
)
|
||||
else:
|
||||
if CONF_POLLING_INTERVAL in config:
|
||||
raise cv.Invalid(
|
||||
"In this version of the framework "
|
||||
f"({CORE.target_framework} {CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION]}), "
|
||||
f"'{CONF_POLLING_INTERVAL}' is not supported."
|
||||
)
|
||||
if CONF_INTERRUPT_PIN not in config:
|
||||
raise cv.Invalid(
|
||||
"In this version of the framework "
|
||||
f"({CORE.target_framework} {CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION]}), "
|
||||
f"'{CONF_INTERRUPT_PIN}' is a required option for [ethernet]."
|
||||
)
|
||||
elif config[CONF_TYPE] != "OPENETH":
|
||||
from esphome.components.esp32 import (
|
||||
VARIANT_ESP32,
|
||||
VARIANT_ESP32P4,
|
||||
get_esp32_variant,
|
||||
)
|
||||
|
||||
if CONF_CLK_MODE in config:
|
||||
mode, pin = CLK_MODES_DEPRECATED[config[CONF_CLK_MODE]]
|
||||
LOGGER.warning(
|
||||
"[ethernet] The 'clk_mode' option is deprecated. "
|
||||
"Please replace 'clk_mode: %s' with:\n"
|
||||
" clk:\n"
|
||||
" mode: %s\n"
|
||||
" pin: %s\n"
|
||||
"Removal scheduled for 2026.7.0.",
|
||||
config[CONF_CLK_MODE],
|
||||
mode,
|
||||
pin,
|
||||
)
|
||||
config[CONF_CLK] = CLK_SCHEMA({CONF_MODE: mode, CONF_PIN: pin})
|
||||
del config[CONF_CLK_MODE]
|
||||
elif CONF_CLK not in config:
|
||||
raise cv.Invalid("'clk' is a required option for [ethernet].")
|
||||
variant = get_esp32_variant()
|
||||
if variant not in (VARIANT_ESP32, VARIANT_ESP32P4):
|
||||
raise cv.Invalid(
|
||||
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_SPI_ETHERNET_TYPES:
|
||||
raise cv.Invalid(
|
||||
f"Only {', '.join(RP2040_SPI_ETHERNET_TYPES)} are supported on RP2040, "
|
||||
f"not {config[CONF_TYPE]}"
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
@@ -269,41 +308,50 @@ CLK_SCHEMA = cv.Schema(
|
||||
cv.Required(CONF_PIN): pins.internal_gpio_pin_number,
|
||||
}
|
||||
)
|
||||
RMII_SCHEMA = BASE_SCHEMA.extend(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_MDC_PIN): pins.internal_gpio_output_pin_number,
|
||||
cv.Required(CONF_MDIO_PIN): pins.internal_gpio_output_pin_number,
|
||||
cv.Optional(CONF_CLK_MODE): cv.enum(
|
||||
CLK_MODES_DEPRECATED, upper=True, space="_"
|
||||
),
|
||||
cv.Optional(CONF_CLK): CLK_SCHEMA,
|
||||
cv.Optional(CONF_PHY_ADDR, default=0): cv.int_range(min=0, max=31),
|
||||
cv.Optional(CONF_POWER_PIN): pins.internal_gpio_output_pin_number,
|
||||
cv.Optional(CONF_PHY_REGISTERS): cv.ensure_list(PHY_REGISTER_SCHEMA),
|
||||
}
|
||||
)
|
||||
RMII_SCHEMA = cv.All(
|
||||
BASE_SCHEMA.extend(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_MDC_PIN): pins.internal_gpio_output_pin_number,
|
||||
cv.Required(CONF_MDIO_PIN): pins.internal_gpio_output_pin_number,
|
||||
cv.Optional(CONF_CLK_MODE): cv.enum(
|
||||
CLK_MODES_DEPRECATED, upper=True, space="_"
|
||||
),
|
||||
cv.Optional(CONF_CLK): CLK_SCHEMA,
|
||||
cv.Optional(CONF_PHY_ADDR, default=0): cv.int_range(min=0, max=31),
|
||||
cv.Optional(CONF_POWER_PIN): pins.internal_gpio_output_pin_number,
|
||||
cv.Optional(CONF_PHY_REGISTERS): cv.ensure_list(PHY_REGISTER_SCHEMA),
|
||||
}
|
||||
)
|
||||
),
|
||||
cv.only_on([Platform.ESP32]),
|
||||
)
|
||||
|
||||
SPI_SCHEMA = BASE_SCHEMA.extend(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_CLK_PIN): pins.internal_gpio_output_pin_number,
|
||||
cv.Required(CONF_MISO_PIN): pins.internal_gpio_input_pin_number,
|
||||
cv.Required(CONF_MOSI_PIN): pins.internal_gpio_output_pin_number,
|
||||
cv.Required(CONF_CS_PIN): pins.internal_gpio_output_pin_number,
|
||||
cv.Optional(CONF_INTERRUPT_PIN): pins.internal_gpio_input_pin_number,
|
||||
cv.Optional(CONF_RESET_PIN): pins.internal_gpio_output_pin_number,
|
||||
cv.Optional(CONF_CLOCK_SPEED, default="26.67MHz"): cv.All(
|
||||
cv.frequency, cv.int_range(int(8e6), int(80e6))
|
||||
),
|
||||
# Set default value (SPI_ETHERNET_DEFAULT_POLLING_INTERVAL) at _validate()
|
||||
cv.Optional(CONF_POLLING_INTERVAL): cv.All(
|
||||
cv.positive_time_period_milliseconds,
|
||||
cv.Range(min=TimePeriodMilliseconds(milliseconds=1)),
|
||||
),
|
||||
}
|
||||
SPI_SCHEMA = cv.All(
|
||||
BASE_SCHEMA.extend(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_CLK_PIN): pins.internal_gpio_output_pin_number,
|
||||
cv.Required(CONF_MISO_PIN): pins.internal_gpio_input_pin_number,
|
||||
cv.Required(CONF_MOSI_PIN): pins.internal_gpio_output_pin_number,
|
||||
cv.Required(CONF_CS_PIN): pins.internal_gpio_output_pin_number,
|
||||
cv.Optional(CONF_INTERRUPT_PIN): pins.internal_gpio_input_pin_number,
|
||||
cv.Optional(CONF_RESET_PIN): pins.internal_gpio_output_pin_number,
|
||||
cv.SplitDefault(CONF_CLOCK_SPEED, esp32="26.67MHz"): cv.All(
|
||||
cv.only_on_esp32,
|
||||
cv.frequency,
|
||||
cv.int_range(int(8e6), int(80e6)),
|
||||
),
|
||||
# Set default value (SPI_ETHERNET_DEFAULT_POLLING_INTERVAL) at _validate()
|
||||
cv.Optional(CONF_POLLING_INTERVAL): cv.All(
|
||||
cv.only_on_esp32,
|
||||
cv.positive_time_period_milliseconds,
|
||||
cv.Range(min=TimePeriodMilliseconds(milliseconds=1)),
|
||||
),
|
||||
}
|
||||
),
|
||||
),
|
||||
cv.only_on([Platform.ESP32, Platform.RP2040]),
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
@@ -317,7 +365,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
"KSZ8081": RMII_SCHEMA,
|
||||
"KSZ8081RNA": RMII_SCHEMA,
|
||||
"W5500": SPI_SCHEMA,
|
||||
"OPENETH": BASE_SCHEMA,
|
||||
"OPENETH": cv.All(BASE_SCHEMA, cv.only_on([Platform.ESP32])),
|
||||
"DM9051": SPI_SCHEMA,
|
||||
"LAN8670": RMII_SCHEMA,
|
||||
},
|
||||
@@ -328,8 +376,21 @@ CONFIG_SCHEMA = cv.All(
|
||||
|
||||
|
||||
def _final_validate_spi(config):
|
||||
if not CORE.is_esp32:
|
||||
return # SPI interface validation is ESP32-only
|
||||
if config[CONF_TYPE] not in SPI_ETHERNET_TYPES:
|
||||
return
|
||||
from esphome.components.esp32 import (
|
||||
VARIANT_ESP32C3,
|
||||
VARIANT_ESP32C5,
|
||||
VARIANT_ESP32C6,
|
||||
VARIANT_ESP32C61,
|
||||
VARIANT_ESP32S2,
|
||||
VARIANT_ESP32S3,
|
||||
get_esp32_variant,
|
||||
)
|
||||
from esphome.components.spi import CONF_INTERFACE_INDEX, get_spi_interface
|
||||
|
||||
if spi_configs := fv.full_config.get().get(CONF_SPI):
|
||||
variant = get_esp32_variant()
|
||||
if variant in (
|
||||
@@ -378,6 +439,51 @@ async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
if CORE.is_esp32:
|
||||
await _to_code_esp32(var, config)
|
||||
elif CORE.is_rp2040:
|
||||
await _to_code_rp2040(var, 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")
|
||||
cg.add(var.set_manual_ip(manual_ip(config[CONF_MANUAL_IP])))
|
||||
|
||||
# Add compile-time define for PHY types with specific code
|
||||
if phy_define := _PHY_TYPE_TO_DEFINE.get(config[CONF_TYPE]):
|
||||
cg.add_define(phy_define)
|
||||
|
||||
if mac_address := config.get(CONF_MAC_ADDRESS):
|
||||
cg.add(var.set_fixed_mac(mac_address.parts))
|
||||
|
||||
cg.add_define("USE_ETHERNET")
|
||||
|
||||
if on_connect_config := config.get(CONF_ON_CONNECT):
|
||||
cg.add_define("USE_ETHERNET_CONNECT_TRIGGER")
|
||||
await automation.build_automation(
|
||||
var.get_connect_trigger(), [], on_connect_config
|
||||
)
|
||||
|
||||
if on_disconnect_config := config.get(CONF_ON_DISCONNECT):
|
||||
cg.add_define("USE_ETHERNET_DISCONNECT_TRIGGER")
|
||||
await automation.build_automation(
|
||||
var.get_disconnect_trigger(), [], on_disconnect_config
|
||||
)
|
||||
|
||||
CORE.add_job(final_step)
|
||||
|
||||
|
||||
async def _to_code_esp32(var: cg.Pvariable, config: ConfigType) -> None:
|
||||
from esphome.components.esp32 import (
|
||||
add_idf_component,
|
||||
add_idf_sdkconfig_option,
|
||||
idf_version,
|
||||
include_builtin_idf_component,
|
||||
)
|
||||
|
||||
if config[CONF_TYPE] in SPI_ETHERNET_TYPES:
|
||||
cg.add(var.set_clk_pin(config[CONF_CLK_PIN]))
|
||||
cg.add(var.set_miso_pin(config[CONF_MISO_PIN]))
|
||||
@@ -395,7 +501,11 @@ async def to_code(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)
|
||||
@@ -415,22 +525,6 @@ async def to_code(config):
|
||||
)
|
||||
cg.add(var.add_phy_register(reg))
|
||||
|
||||
cg.add(var.set_type(ETHERNET_TYPES[config[CONF_TYPE]]))
|
||||
cg.add(var.set_use_address(config[CONF_USE_ADDRESS]))
|
||||
|
||||
if CONF_MANUAL_IP in config:
|
||||
cg.add_define("USE_ETHERNET_MANUAL_IP")
|
||||
cg.add(var.set_manual_ip(manual_ip(config[CONF_MANUAL_IP])))
|
||||
|
||||
# Add compile-time define for PHY types with specific code
|
||||
if phy_define := _PHY_TYPE_TO_DEFINE.get(config[CONF_TYPE]):
|
||||
cg.add_define(phy_define)
|
||||
|
||||
if mac_address := config.get(CONF_MAC_ADDRESS):
|
||||
cg.add(var.set_fixed_mac(mac_address.parts))
|
||||
|
||||
cg.add_define("USE_ETHERNET")
|
||||
|
||||
# Disable WiFi when using Ethernet to save memory
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_WIFI_ENABLED", False)
|
||||
# Also disable WiFi/BT coexistence since WiFi is disabled
|
||||
@@ -443,27 +537,41 @@ async def to_code(config):
|
||||
# Add LAN867x 10BASE-T1S PHY support component
|
||||
add_idf_component(name="espressif/lan867x", ref="2.0.0")
|
||||
|
||||
if on_connect_config := config.get(CONF_ON_CONNECT):
|
||||
cg.add_define("USE_ETHERNET_CONNECT_TRIGGER")
|
||||
await automation.build_automation(
|
||||
var.get_connect_trigger(), [], on_connect_config
|
||||
)
|
||||
# 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)
|
||||
|
||||
if on_disconnect_config := config.get(CONF_ON_DISCONNECT):
|
||||
cg.add_define("USE_ETHERNET_DISCONNECT_TRIGGER")
|
||||
await automation.build_automation(
|
||||
var.get_disconnect_trigger(), [], on_disconnect_config
|
||||
)
|
||||
|
||||
CORE.add_job(final_step)
|
||||
async def _to_code_rp2040(var: cg.Pvariable, config: ConfigType) -> None:
|
||||
cg.add(var.set_clk_pin(config[CONF_CLK_PIN]))
|
||||
cg.add(var.set_miso_pin(config[CONF_MISO_PIN]))
|
||||
cg.add(var.set_mosi_pin(config[CONF_MOSI_PIN]))
|
||||
cg.add(var.set_cs_pin(config[CONF_CS_PIN]))
|
||||
if CONF_INTERRUPT_PIN in config:
|
||||
cg.add(var.set_interrupt_pin(config[CONF_INTERRUPT_PIN]))
|
||||
if CONF_RESET_PIN in config:
|
||||
cg.add(var.set_reset_pin(config[CONF_RESET_PIN]))
|
||||
|
||||
cg.add_define("USE_ETHERNET_SPI")
|
||||
cg.add_library("lwIP_w5500", None)
|
||||
|
||||
|
||||
def _final_validate_rmii_pins(config: ConfigType) -> None:
|
||||
"""Validate that RMII pins are not used by other components."""
|
||||
if not CORE.is_esp32:
|
||||
return # RMII validation is ESP32-only
|
||||
# Only validate for RMII-based PHYs on ESP32/ESP32P4
|
||||
if config[CONF_TYPE] in SPI_ETHERNET_TYPES or config[CONF_TYPE] == "OPENETH":
|
||||
return # SPI and OPENETH don't use RMII
|
||||
|
||||
from esphome.components.esp32 import (
|
||||
VARIANT_ESP32,
|
||||
VARIANT_ESP32P4,
|
||||
get_esp32_variant,
|
||||
)
|
||||
|
||||
variant = get_esp32_variant()
|
||||
if variant == VARIANT_ESP32:
|
||||
rmii_pins = ESP32_RMII_FIXED_PINS
|
||||
@@ -521,3 +629,39 @@ async def final_step():
|
||||
if ip_state_count := CORE.data.get(ETHERNET_IP_STATE_LISTENERS_KEY, 0):
|
||||
cg.add_define("USE_ETHERNET_IP_STATE_LISTENERS")
|
||||
cg.add_define("ESPHOME_ETHERNET_IP_STATE_LISTENERS", ip_state_count)
|
||||
|
||||
|
||||
_platform_filter = filter_source_files_from_platform(
|
||||
{
|
||||
"ethernet_component_esp32.cpp": {
|
||||
PlatformFramework.ESP32_IDF,
|
||||
PlatformFramework.ESP32_ARDUINO,
|
||||
},
|
||||
"ethernet_component_rp2040.cpp": {PlatformFramework.RP2040_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, ...) \
|
||||
|
||||
@@ -1,547 +1,28 @@
|
||||
#include "ethernet_component.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#ifdef USE_ETHERNET
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/util.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include <lwip/dns.h>
|
||||
#include <cinttypes>
|
||||
#include "esp_event.h"
|
||||
|
||||
#ifdef USE_ETHERNET_LAN8670
|
||||
#include "esp_eth_phy_lan867x.h"
|
||||
#endif
|
||||
|
||||
#ifdef USE_ETHERNET_SPI
|
||||
#include <driver/gpio.h>
|
||||
#include <driver/spi_master.h>
|
||||
#endif
|
||||
|
||||
namespace esphome::ethernet {
|
||||
|
||||
static const char *const TAG = "ethernet";
|
||||
|
||||
// PHY register size for hex logging
|
||||
static constexpr size_t PHY_REG_SIZE = 2;
|
||||
|
||||
EthernetComponent *global_eth_component; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
void EthernetComponent::log_error_and_mark_failed_(esp_err_t err, const char *message) {
|
||||
ESP_LOGE(TAG, "%s: (%d) %s", message, err, esp_err_to_name(err));
|
||||
this->mark_failed();
|
||||
}
|
||||
|
||||
#define ESPHL_ERROR_CHECK(err, message) \
|
||||
if ((err) != ESP_OK) { \
|
||||
this->log_error_and_mark_failed_(err, message); \
|
||||
return; \
|
||||
}
|
||||
|
||||
#define ESPHL_ERROR_CHECK_RET(err, message, ret) \
|
||||
if ((err) != ESP_OK) { \
|
||||
this->log_error_and_mark_failed_(err, message); \
|
||||
return ret; \
|
||||
}
|
||||
|
||||
EthernetComponent::EthernetComponent() { global_eth_component = this; }
|
||||
|
||||
void EthernetComponent::setup() {
|
||||
if (esp_reset_reason() != ESP_RST_DEEPSLEEP) {
|
||||
// Delay here to allow power to stabilise before Ethernet is initialized.
|
||||
delay(300); // NOLINT
|
||||
}
|
||||
|
||||
esp_err_t err;
|
||||
|
||||
#ifdef USE_ETHERNET_SPI
|
||||
// Install GPIO ISR handler to be able to service SPI Eth modules interrupts
|
||||
gpio_install_isr_service(0);
|
||||
|
||||
spi_bus_config_t buscfg = {
|
||||
.mosi_io_num = this->mosi_pin_,
|
||||
.miso_io_num = this->miso_pin_,
|
||||
.sclk_io_num = this->clk_pin_,
|
||||
.quadwp_io_num = -1,
|
||||
.quadhd_io_num = -1,
|
||||
.data4_io_num = -1,
|
||||
.data5_io_num = -1,
|
||||
.data6_io_num = -1,
|
||||
.data7_io_num = -1,
|
||||
.max_transfer_sz = 0,
|
||||
.flags = 0,
|
||||
.intr_flags = 0,
|
||||
};
|
||||
|
||||
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C5) || defined(USE_ESP32_VARIANT_ESP32C6) || \
|
||||
defined(USE_ESP32_VARIANT_ESP32C61) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
|
||||
auto host = SPI2_HOST;
|
||||
#else
|
||||
auto host = SPI3_HOST;
|
||||
#endif
|
||||
|
||||
err = spi_bus_initialize(host, &buscfg, SPI_DMA_CH_AUTO);
|
||||
ESPHL_ERROR_CHECK(err, "SPI bus initialize error");
|
||||
#endif
|
||||
|
||||
err = esp_netif_init();
|
||||
ESPHL_ERROR_CHECK(err, "ETH netif init error");
|
||||
err = esp_event_loop_create_default();
|
||||
ESPHL_ERROR_CHECK(err, "ETH event loop error");
|
||||
|
||||
esp_netif_config_t cfg = ESP_NETIF_DEFAULT_ETH();
|
||||
this->eth_netif_ = esp_netif_new(&cfg);
|
||||
|
||||
// Init MAC and PHY configs to default
|
||||
eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG();
|
||||
eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG();
|
||||
|
||||
#ifdef USE_ETHERNET_SPI // Configure SPI interface and Ethernet driver for specific SPI module
|
||||
spi_device_interface_config_t devcfg = {
|
||||
.command_bits = 0,
|
||||
.address_bits = 0,
|
||||
.dummy_bits = 0,
|
||||
.mode = 0,
|
||||
.duty_cycle_pos = 0,
|
||||
.cs_ena_pretrans = 0,
|
||||
.cs_ena_posttrans = 0,
|
||||
.clock_speed_hz = this->clock_speed_,
|
||||
.input_delay_ns = 0,
|
||||
.spics_io_num = this->cs_pin_,
|
||||
.flags = 0,
|
||||
.queue_size = 20,
|
||||
.pre_cb = nullptr,
|
||||
.post_cb = nullptr,
|
||||
};
|
||||
|
||||
#if CONFIG_ETH_SPI_ETHERNET_W5500
|
||||
eth_w5500_config_t w5500_config = ETH_W5500_DEFAULT_CONFIG(host, &devcfg);
|
||||
#endif
|
||||
#if CONFIG_ETH_SPI_ETHERNET_DM9051
|
||||
eth_dm9051_config_t dm9051_config = ETH_DM9051_DEFAULT_CONFIG(host, &devcfg);
|
||||
#endif
|
||||
|
||||
#if CONFIG_ETH_SPI_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
|
||||
dm9051_config.int_gpio_num = this->interrupt_pin_;
|
||||
#ifdef USE_ETHERNET_SPI_POLLING_SUPPORT
|
||||
dm9051_config.poll_period_ms = this->polling_interval_;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
phy_config.phy_addr = this->phy_addr_spi_;
|
||||
phy_config.reset_gpio_num = this->reset_pin_;
|
||||
|
||||
esp_eth_mac_t *mac = nullptr;
|
||||
#elif defined(USE_ETHERNET_OPENETH)
|
||||
esp_eth_mac_t *mac = esp_eth_mac_new_openeth(&mac_config);
|
||||
#else
|
||||
phy_config.phy_addr = this->phy_addr_;
|
||||
phy_config.reset_gpio_num = this->power_pin_;
|
||||
|
||||
eth_esp32_emac_config_t esp32_emac_config = eth_esp32_emac_default_config();
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)
|
||||
esp32_emac_config.smi_gpio.mdc_num = this->mdc_pin_;
|
||||
esp32_emac_config.smi_gpio.mdio_num = this->mdio_pin_;
|
||||
#else
|
||||
esp32_emac_config.smi_mdc_gpio_num = this->mdc_pin_;
|
||||
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_;
|
||||
|
||||
esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&esp32_emac_config, &mac_config);
|
||||
#endif
|
||||
|
||||
switch (this->type_) {
|
||||
#ifdef USE_ETHERNET_OPENETH
|
||||
case ETHERNET_TYPE_OPENETH: {
|
||||
phy_config.autonego_timeout_ms = 1000;
|
||||
this->phy_ = esp_eth_phy_new_dp83848(&phy_config);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
#if CONFIG_ETH_USE_ESP32_EMAC
|
||||
#ifdef USE_ETHERNET_LAN8720
|
||||
case ETHERNET_TYPE_LAN8720: {
|
||||
this->phy_ = esp_eth_phy_new_lan87xx(&phy_config);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_ETHERNET_RTL8201
|
||||
case ETHERNET_TYPE_RTL8201: {
|
||||
this->phy_ = esp_eth_phy_new_rtl8201(&phy_config);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_ETHERNET_DP83848
|
||||
case ETHERNET_TYPE_DP83848: {
|
||||
this->phy_ = esp_eth_phy_new_dp83848(&phy_config);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_ETHERNET_IP101
|
||||
case ETHERNET_TYPE_IP101: {
|
||||
this->phy_ = esp_eth_phy_new_ip101(&phy_config);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
#if defined(USE_ETHERNET_JL1101) && (ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 4, 2) || !defined(PLATFORMIO))
|
||||
case ETHERNET_TYPE_JL1101: {
|
||||
this->phy_ = esp_eth_phy_new_jl1101(&phy_config);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_ETHERNET_KSZ8081
|
||||
case ETHERNET_TYPE_KSZ8081:
|
||||
case ETHERNET_TYPE_KSZ8081RNA: {
|
||||
this->phy_ = esp_eth_phy_new_ksz80xx(&phy_config);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_ETHERNET_LAN8670
|
||||
case ETHERNET_TYPE_LAN8670: {
|
||||
this->phy_ = esp_eth_phy_new_lan867x(&phy_config);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
#ifdef USE_ETHERNET_SPI
|
||||
#if CONFIG_ETH_SPI_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
|
||||
case ETHERNET_TYPE_DM9051: {
|
||||
mac = esp_eth_mac_new_dm9051(&dm9051_config, &mac_config);
|
||||
this->phy_ = esp_eth_phy_new_dm9051(&phy_config);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
default: {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
esp_eth_config_t eth_config = ETH_DEFAULT_CONFIG(mac, this->phy_);
|
||||
this->eth_handle_ = nullptr;
|
||||
err = esp_eth_driver_install(ð_config, &this->eth_handle_);
|
||||
ESPHL_ERROR_CHECK(err, "ETH driver install error");
|
||||
|
||||
#ifndef USE_ETHERNET_SPI
|
||||
#ifdef USE_ETHERNET_KSZ8081
|
||||
if (this->type_ == ETHERNET_TYPE_KSZ8081RNA && this->clk_mode_ == EMAC_CLK_OUT) {
|
||||
// KSZ8081RNA default is incorrect. It expects a 25MHz clock instead of the 50MHz we provide.
|
||||
this->ksz8081_set_clock_reference_(mac);
|
||||
}
|
||||
#endif // USE_ETHERNET_KSZ8081
|
||||
|
||||
for (const auto &phy_register : this->phy_registers_) {
|
||||
this->write_phy_register_(mac, phy_register);
|
||||
}
|
||||
#endif
|
||||
|
||||
// use ESP internal eth mac
|
||||
uint8_t mac_addr[6];
|
||||
if (this->fixed_mac_.has_value()) {
|
||||
memcpy(mac_addr, this->fixed_mac_->data(), 6);
|
||||
} else {
|
||||
esp_read_mac(mac_addr, ESP_MAC_ETH);
|
||||
}
|
||||
err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_S_MAC_ADDR, mac_addr);
|
||||
ESPHL_ERROR_CHECK(err, "set mac address error");
|
||||
|
||||
/* attach Ethernet driver to TCP/IP stack */
|
||||
err = esp_netif_attach(this->eth_netif_, esp_eth_new_netif_glue(this->eth_handle_));
|
||||
ESPHL_ERROR_CHECK(err, "ETH netif attach error");
|
||||
|
||||
// Register user defined event handers
|
||||
err = esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, &EthernetComponent::eth_event_handler, nullptr);
|
||||
ESPHL_ERROR_CHECK(err, "ETH event handler register error");
|
||||
err = esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, &EthernetComponent::got_ip_event_handler, nullptr);
|
||||
ESPHL_ERROR_CHECK(err, "GOT IP event handler register error");
|
||||
#if USE_NETWORK_IPV6
|
||||
err = esp_event_handler_register(IP_EVENT, IP_EVENT_GOT_IP6, &EthernetComponent::got_ip6_event_handler, nullptr);
|
||||
ESPHL_ERROR_CHECK(err, "GOT IPv6 event handler register error");
|
||||
#endif /* USE_NETWORK_IPV6 */
|
||||
|
||||
/* start Ethernet driver state machine */
|
||||
err = esp_eth_start(this->eth_handle_);
|
||||
ESPHL_ERROR_CHECK(err, "ETH start error");
|
||||
}
|
||||
|
||||
void EthernetComponent::loop() {
|
||||
const uint32_t now = App.get_loop_component_start_time();
|
||||
|
||||
switch (this->state_) {
|
||||
case EthernetComponentState::STOPPED:
|
||||
if (this->started_) {
|
||||
ESP_LOGI(TAG, "Starting connection");
|
||||
this->state_ = EthernetComponentState::CONNECTING;
|
||||
this->start_connect_();
|
||||
}
|
||||
break;
|
||||
case EthernetComponentState::CONNECTING:
|
||||
if (!this->started_) {
|
||||
ESP_LOGI(TAG, "Stopped connection");
|
||||
this->state_ = EthernetComponentState::STOPPED;
|
||||
} else if (this->connected_) {
|
||||
// connection established
|
||||
ESP_LOGI(TAG, "Connected");
|
||||
this->state_ = EthernetComponentState::CONNECTED;
|
||||
|
||||
this->dump_connect_params_();
|
||||
this->status_clear_warning();
|
||||
#ifdef USE_ETHERNET_CONNECT_TRIGGER
|
||||
this->connect_trigger_.trigger();
|
||||
#endif
|
||||
} else if (now - this->connect_begin_ > 15000) {
|
||||
ESP_LOGW(TAG, "Connecting failed; reconnecting");
|
||||
this->start_connect_();
|
||||
}
|
||||
break;
|
||||
case EthernetComponentState::CONNECTED:
|
||||
if (!this->started_) {
|
||||
ESP_LOGI(TAG, "Stopped connection");
|
||||
this->state_ = EthernetComponentState::STOPPED;
|
||||
#ifdef USE_ETHERNET_DISCONNECT_TRIGGER
|
||||
this->disconnect_trigger_.trigger();
|
||||
#endif
|
||||
} else if (!this->connected_) {
|
||||
ESP_LOGW(TAG, "Connection lost; reconnecting");
|
||||
this->state_ = EthernetComponentState::CONNECTING;
|
||||
this->start_connect_();
|
||||
#ifdef USE_ETHERNET_DISCONNECT_TRIGGER
|
||||
this->disconnect_trigger_.trigger();
|
||||
#endif
|
||||
} else {
|
||||
this->finish_connect_();
|
||||
// When connected and stable, disable the loop to save CPU cycles
|
||||
this->disable_loop();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void EthernetComponent::dump_config() {
|
||||
const char *eth_type;
|
||||
switch (this->type_) {
|
||||
#ifdef USE_ETHERNET_LAN8720
|
||||
case ETHERNET_TYPE_LAN8720:
|
||||
eth_type = "LAN8720";
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_ETHERNET_RTL8201
|
||||
case ETHERNET_TYPE_RTL8201:
|
||||
eth_type = "RTL8201";
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_ETHERNET_DP83848
|
||||
case ETHERNET_TYPE_DP83848:
|
||||
eth_type = "DP83848";
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_ETHERNET_IP101
|
||||
case ETHERNET_TYPE_IP101:
|
||||
eth_type = "IP101";
|
||||
break;
|
||||
#endif
|
||||
#if defined(USE_ETHERNET_JL1101) && (ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 4, 2) || !defined(PLATFORMIO))
|
||||
case ETHERNET_TYPE_JL1101:
|
||||
eth_type = "JL1101";
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_ETHERNET_KSZ8081
|
||||
case ETHERNET_TYPE_KSZ8081:
|
||||
eth_type = "KSZ8081";
|
||||
break;
|
||||
|
||||
case ETHERNET_TYPE_KSZ8081RNA:
|
||||
eth_type = "KSZ8081RNA";
|
||||
break;
|
||||
#endif
|
||||
#if CONFIG_ETH_SPI_ETHERNET_W5500
|
||||
case ETHERNET_TYPE_W5500:
|
||||
eth_type = "W5500";
|
||||
break;
|
||||
#endif
|
||||
#if CONFIG_ETH_SPI_ETHERNET_DM9051
|
||||
case ETHERNET_TYPE_DM9051:
|
||||
eth_type = "DM9051";
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_ETHERNET_OPENETH
|
||||
case ETHERNET_TYPE_OPENETH:
|
||||
eth_type = "OPENETH";
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_ETHERNET_LAN8670
|
||||
case ETHERNET_TYPE_LAN8670:
|
||||
eth_type = "LAN8670";
|
||||
break;
|
||||
#endif
|
||||
|
||||
default:
|
||||
eth_type = "Unknown";
|
||||
break;
|
||||
}
|
||||
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"Ethernet:\n"
|
||||
" Connected: %s",
|
||||
YESNO(this->is_connected()));
|
||||
this->dump_connect_params_();
|
||||
#ifdef USE_ETHERNET_SPI
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" CLK Pin: %u\n"
|
||||
" MISO Pin: %u\n"
|
||||
" MOSI Pin: %u\n"
|
||||
" CS Pin: %u",
|
||||
this->clk_pin_, this->miso_pin_, this->mosi_pin_, this->cs_pin_);
|
||||
#ifdef USE_ETHERNET_SPI_POLLING_SUPPORT
|
||||
if (this->polling_interval_ != 0) {
|
||||
ESP_LOGCONFIG(TAG, " Polling Interval: %lu ms", this->polling_interval_);
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
ESP_LOGCONFIG(TAG, " IRQ Pin: %d", this->interrupt_pin_);
|
||||
}
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" Reset Pin: %d\n"
|
||||
" Clock Speed: %d MHz",
|
||||
this->reset_pin_, this->clock_speed_ / 1000000);
|
||||
#else
|
||||
if (this->power_pin_ != -1) {
|
||||
ESP_LOGCONFIG(TAG, " Power Pin: %u", this->power_pin_);
|
||||
}
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" CLK Pin: %u\n"
|
||||
" MDC Pin: %u\n"
|
||||
" MDIO Pin: %u\n"
|
||||
" PHY addr: %u",
|
||||
this->clk_pin_, this->mdc_pin_, this->mdio_pin_, this->phy_addr_);
|
||||
#endif
|
||||
ESP_LOGCONFIG(TAG, " Type: %s", eth_type);
|
||||
}
|
||||
|
||||
float EthernetComponent::get_setup_priority() const { return setup_priority::WIFI; }
|
||||
|
||||
network::IPAddresses EthernetComponent::get_ip_addresses() {
|
||||
network::IPAddresses addresses;
|
||||
esp_netif_ip_info_t ip;
|
||||
esp_err_t err = esp_netif_get_ip_info(this->eth_netif_, &ip);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGV(TAG, "esp_netif_get_ip_info failed: %s", esp_err_to_name(err));
|
||||
// TODO: do something smarter
|
||||
// return false;
|
||||
} else {
|
||||
addresses[0] = network::IPAddress(&ip.ip);
|
||||
}
|
||||
#if USE_NETWORK_IPV6
|
||||
struct esp_ip6_addr if_ip6s[CONFIG_LWIP_IPV6_NUM_ADDRESSES];
|
||||
uint8_t count = 0;
|
||||
count = esp_netif_get_all_ip6(this->eth_netif_, if_ip6s);
|
||||
assert(count <= CONFIG_LWIP_IPV6_NUM_ADDRESSES);
|
||||
assert(count < addresses.size());
|
||||
for (int i = 0; i < count; i++) {
|
||||
addresses[i + 1] = network::IPAddress(&if_ip6s[i]);
|
||||
}
|
||||
#endif /* USE_NETWORK_IPV6 */
|
||||
void EthernetComponent::set_type(EthernetType type) { this->type_ = type; }
|
||||
|
||||
return addresses;
|
||||
}
|
||||
|
||||
network::IPAddress EthernetComponent::get_dns_address(uint8_t num) {
|
||||
LwIPLock lock;
|
||||
const ip_addr_t *dns_ip = dns_getserver(num);
|
||||
return dns_ip;
|
||||
}
|
||||
|
||||
void EthernetComponent::eth_event_handler(void *arg, esp_event_base_t event_base, int32_t event, void *event_data) {
|
||||
const char *event_name;
|
||||
|
||||
switch (event) {
|
||||
case ETHERNET_EVENT_START:
|
||||
event_name = "ETH started";
|
||||
global_eth_component->started_ = true;
|
||||
global_eth_component->enable_loop_soon_any_context();
|
||||
break;
|
||||
case ETHERNET_EVENT_STOP:
|
||||
event_name = "ETH stopped";
|
||||
global_eth_component->started_ = false;
|
||||
global_eth_component->connected_ = false;
|
||||
global_eth_component->enable_loop_soon_any_context(); // Enable loop when connection state changes
|
||||
break;
|
||||
case ETHERNET_EVENT_CONNECTED:
|
||||
event_name = "ETH connected";
|
||||
// For static IP configurations, GOT_IP event may not fire, so notify IP listeners here
|
||||
#if defined(USE_ETHERNET_IP_STATE_LISTENERS) && defined(USE_ETHERNET_MANUAL_IP)
|
||||
if (global_eth_component->manual_ip_.has_value()) {
|
||||
global_eth_component->notify_ip_state_listeners_();
|
||||
}
|
||||
#ifdef USE_ETHERNET_MANUAL_IP
|
||||
void EthernetComponent::set_manual_ip(const ManualIP &manual_ip) { this->manual_ip_ = manual_ip; }
|
||||
#endif
|
||||
break;
|
||||
case ETHERNET_EVENT_DISCONNECTED:
|
||||
event_name = "ETH disconnected";
|
||||
global_eth_component->connected_ = false;
|
||||
global_eth_component->enable_loop_soon_any_context(); // Enable loop when connection state changes
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, "[Ethernet event] %s (num=%" PRId32 ")", event_name, event);
|
||||
}
|
||||
// set_use_address() is guaranteed to be called during component setup by Python code generation,
|
||||
// so use_address_ will always be valid when get_use_address() is called - no fallback needed.
|
||||
const char *EthernetComponent::get_use_address() const { return this->use_address_; }
|
||||
|
||||
void EthernetComponent::got_ip_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id,
|
||||
void *event_data) {
|
||||
ip_event_got_ip_t *event = (ip_event_got_ip_t *) event_data;
|
||||
const esp_netif_ip_info_t *ip_info = &event->ip_info;
|
||||
ESP_LOGV(TAG, "[Ethernet event] ETH Got IP " IPSTR, IP2STR(&ip_info->ip));
|
||||
global_eth_component->got_ipv4_address_ = true;
|
||||
#if USE_NETWORK_IPV6 && (USE_NETWORK_MIN_IPV6_ADDR_COUNT > 0)
|
||||
global_eth_component->connected_ = global_eth_component->ipv6_count_ >= USE_NETWORK_MIN_IPV6_ADDR_COUNT;
|
||||
global_eth_component->enable_loop_soon_any_context(); // Enable loop when connection state changes
|
||||
#else
|
||||
global_eth_component->connected_ = true;
|
||||
global_eth_component->enable_loop_soon_any_context(); // Enable loop when connection state changes
|
||||
#endif /* USE_NETWORK_IPV6 */
|
||||
#ifdef USE_ETHERNET_IP_STATE_LISTENERS
|
||||
global_eth_component->notify_ip_state_listeners_();
|
||||
#endif
|
||||
}
|
||||
|
||||
#if USE_NETWORK_IPV6
|
||||
void EthernetComponent::got_ip6_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id,
|
||||
void *event_data) {
|
||||
ip_event_got_ip6_t *event = (ip_event_got_ip6_t *) event_data;
|
||||
ESP_LOGV(TAG, "[Ethernet event] ETH Got IPv6: " IPV6STR, IPV62STR(event->ip6_info.ip));
|
||||
global_eth_component->ipv6_count_ += 1;
|
||||
#if (USE_NETWORK_MIN_IPV6_ADDR_COUNT > 0)
|
||||
global_eth_component->connected_ =
|
||||
global_eth_component->got_ipv4_address_ && (global_eth_component->ipv6_count_ >= USE_NETWORK_MIN_IPV6_ADDR_COUNT);
|
||||
global_eth_component->enable_loop_soon_any_context(); // Enable loop when connection state changes
|
||||
#else
|
||||
global_eth_component->connected_ = global_eth_component->got_ipv4_address_;
|
||||
global_eth_component->enable_loop_soon_any_context(); // Enable loop when connection state changes
|
||||
#endif
|
||||
#ifdef USE_ETHERNET_IP_STATE_LISTENERS
|
||||
global_eth_component->notify_ip_state_listeners_();
|
||||
#endif
|
||||
}
|
||||
#endif /* USE_NETWORK_IPV6 */
|
||||
void EthernetComponent::set_use_address(const char *use_address) { this->use_address_ = use_address; }
|
||||
|
||||
#ifdef USE_ETHERNET_IP_STATE_LISTENERS
|
||||
void EthernetComponent::notify_ip_state_listeners_() {
|
||||
@@ -554,315 +35,6 @@ void EthernetComponent::notify_ip_state_listeners_() {
|
||||
}
|
||||
#endif // USE_ETHERNET_IP_STATE_LISTENERS
|
||||
|
||||
void EthernetComponent::finish_connect_() {
|
||||
#if USE_NETWORK_IPV6
|
||||
// Retry IPv6 link-local setup if it failed during initial connect
|
||||
// This handles the case where min_ipv6_addr_count is NOT set (or is 0),
|
||||
// allowing us to reach CONNECTED state with just IPv4.
|
||||
// If IPv6 setup failed in start_connect_() because the interface wasn't ready:
|
||||
// - Bootup timing issues (#10281)
|
||||
// - Cable unplugged/network interruption (#10705)
|
||||
// We can now retry since we're in CONNECTED state and the interface is definitely up.
|
||||
if (!this->ipv6_setup_done_) {
|
||||
esp_err_t err = esp_netif_create_ip6_linklocal(this->eth_netif_);
|
||||
if (err == ESP_OK) {
|
||||
ESP_LOGD(TAG, "IPv6 link-local address created (retry succeeded)");
|
||||
}
|
||||
// Always set the flag to prevent continuous retries
|
||||
// If IPv6 setup fails here with the interface up and stable, it's
|
||||
// likely a persistent issue (IPv6 disabled at router, hardware
|
||||
// limitation, etc.) that won't be resolved by further retries.
|
||||
// The device continues to work with IPv4.
|
||||
this->ipv6_setup_done_ = true;
|
||||
}
|
||||
#endif /* USE_NETWORK_IPV6 */
|
||||
}
|
||||
|
||||
void EthernetComponent::start_connect_() {
|
||||
global_eth_component->got_ipv4_address_ = false;
|
||||
#if USE_NETWORK_IPV6
|
||||
global_eth_component->ipv6_count_ = 0;
|
||||
this->ipv6_setup_done_ = false;
|
||||
#endif /* USE_NETWORK_IPV6 */
|
||||
this->connect_begin_ = millis();
|
||||
this->status_set_warning(LOG_STR("waiting for IP configuration"));
|
||||
|
||||
esp_err_t err;
|
||||
err = esp_netif_set_hostname(this->eth_netif_, App.get_name().c_str());
|
||||
if (err != ERR_OK) {
|
||||
ESP_LOGW(TAG, "esp_netif_set_hostname failed: %s", esp_err_to_name(err));
|
||||
}
|
||||
|
||||
esp_netif_ip_info_t info;
|
||||
#ifdef USE_ETHERNET_MANUAL_IP
|
||||
if (this->manual_ip_.has_value()) {
|
||||
info.ip = this->manual_ip_->static_ip;
|
||||
info.gw = this->manual_ip_->gateway;
|
||||
info.netmask = this->manual_ip_->subnet;
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
info.ip.addr = 0;
|
||||
info.gw.addr = 0;
|
||||
info.netmask.addr = 0;
|
||||
}
|
||||
|
||||
esp_netif_dhcp_status_t status = ESP_NETIF_DHCP_INIT;
|
||||
|
||||
err = esp_netif_dhcpc_get_status(this->eth_netif_, &status);
|
||||
ESPHL_ERROR_CHECK(err, "DHCPC Get Status Failed!");
|
||||
|
||||
ESP_LOGV(TAG, "DHCP Client Status: %d", status);
|
||||
|
||||
err = esp_netif_dhcpc_stop(this->eth_netif_);
|
||||
if (err != ESP_ERR_ESP_NETIF_DHCP_ALREADY_STOPPED) {
|
||||
ESPHL_ERROR_CHECK(err, "DHCPC stop error");
|
||||
}
|
||||
|
||||
err = esp_netif_set_ip_info(this->eth_netif_, &info);
|
||||
ESPHL_ERROR_CHECK(err, "DHCPC set IP info error");
|
||||
|
||||
#ifdef USE_ETHERNET_MANUAL_IP
|
||||
if (this->manual_ip_.has_value()) {
|
||||
LwIPLock lock;
|
||||
if (this->manual_ip_->dns1.is_set()) {
|
||||
ip_addr_t d;
|
||||
d = this->manual_ip_->dns1;
|
||||
dns_setserver(0, &d);
|
||||
}
|
||||
if (this->manual_ip_->dns2.is_set()) {
|
||||
ip_addr_t d;
|
||||
d = this->manual_ip_->dns2;
|
||||
dns_setserver(1, &d);
|
||||
}
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
err = esp_netif_dhcpc_start(this->eth_netif_);
|
||||
if (err != ESP_ERR_ESP_NETIF_DHCP_ALREADY_STARTED) {
|
||||
ESPHL_ERROR_CHECK(err, "DHCPC start error");
|
||||
}
|
||||
}
|
||||
#if USE_NETWORK_IPV6
|
||||
// Attempt to create IPv6 link-local address
|
||||
// We MUST attempt this here, not just in finish_connect_(), because with
|
||||
// min_ipv6_addr_count set, the component won't reach CONNECTED state without IPv6.
|
||||
// However, this may fail with ESP_FAIL if the interface is not up yet:
|
||||
// - At bootup when link isn't ready (#10281)
|
||||
// - After disconnection/cable unplugged (#10705)
|
||||
// We'll retry in finish_connect_() if it fails here.
|
||||
err = esp_netif_create_ip6_linklocal(this->eth_netif_);
|
||||
if (err != ESP_OK) {
|
||||
if (err == ESP_ERR_ESP_NETIF_INVALID_PARAMS) {
|
||||
// This is a programming error, not a transient failure
|
||||
ESPHL_ERROR_CHECK(err, "esp_netif_create_ip6_linklocal invalid parameters");
|
||||
} else {
|
||||
// ESP_FAIL means the interface isn't up yet
|
||||
// This is expected and non-fatal, happens in multiple scenarios:
|
||||
// - During reconnection after network interruptions (#10705)
|
||||
// - At bootup when the link isn't ready yet (#10281)
|
||||
// We'll retry once we reach CONNECTED state and the interface is up
|
||||
ESP_LOGW(TAG, "esp_netif_create_ip6_linklocal failed: %s", esp_err_to_name(err));
|
||||
// Don't mark component as failed - this is a transient error
|
||||
}
|
||||
}
|
||||
#endif /* USE_NETWORK_IPV6 */
|
||||
|
||||
this->connect_begin_ = millis();
|
||||
this->status_set_warning();
|
||||
}
|
||||
|
||||
void EthernetComponent::dump_connect_params_() {
|
||||
esp_netif_ip_info_t ip;
|
||||
esp_netif_get_ip_info(this->eth_netif_, &ip);
|
||||
const ip_addr_t *dns_ip1;
|
||||
const ip_addr_t *dns_ip2;
|
||||
{
|
||||
LwIPLock lock;
|
||||
dns_ip1 = dns_getserver(0);
|
||||
dns_ip2 = dns_getserver(1);
|
||||
}
|
||||
|
||||
// Use stack buffers for IP address formatting to avoid heap allocations
|
||||
char ip_buf[network::IP_ADDRESS_BUFFER_SIZE];
|
||||
char subnet_buf[network::IP_ADDRESS_BUFFER_SIZE];
|
||||
char gateway_buf[network::IP_ADDRESS_BUFFER_SIZE];
|
||||
char dns1_buf[network::IP_ADDRESS_BUFFER_SIZE];
|
||||
char dns2_buf[network::IP_ADDRESS_BUFFER_SIZE];
|
||||
char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" IP Address: %s\n"
|
||||
" Hostname: '%s'\n"
|
||||
" Subnet: %s\n"
|
||||
" Gateway: %s\n"
|
||||
" DNS1: %s\n"
|
||||
" DNS2: %s\n"
|
||||
" MAC Address: %s\n"
|
||||
" Is Full Duplex: %s\n"
|
||||
" Link Speed: %u",
|
||||
network::IPAddress(&ip.ip).str_to(ip_buf), App.get_name().c_str(),
|
||||
network::IPAddress(&ip.netmask).str_to(subnet_buf), network::IPAddress(&ip.gw).str_to(gateway_buf),
|
||||
network::IPAddress(dns_ip1).str_to(dns1_buf), network::IPAddress(dns_ip2).str_to(dns2_buf),
|
||||
this->get_eth_mac_address_pretty_into_buffer(mac_buf),
|
||||
YESNO(this->get_duplex_mode() == ETH_DUPLEX_FULL), this->get_link_speed() == ETH_SPEED_100M ? 100 : 10);
|
||||
|
||||
#if USE_NETWORK_IPV6
|
||||
struct esp_ip6_addr if_ip6s[CONFIG_LWIP_IPV6_NUM_ADDRESSES];
|
||||
uint8_t count = 0;
|
||||
count = esp_netif_get_all_ip6(this->eth_netif_, if_ip6s);
|
||||
assert(count <= CONFIG_LWIP_IPV6_NUM_ADDRESSES);
|
||||
for (int i = 0; i < count; i++) {
|
||||
ESP_LOGCONFIG(TAG, " IPv6: " IPV6STR, IPV62STR(if_ip6s[i]));
|
||||
}
|
||||
#endif /* USE_NETWORK_IPV6 */
|
||||
}
|
||||
|
||||
#ifdef USE_ETHERNET_SPI
|
||||
void EthernetComponent::set_clk_pin(uint8_t clk_pin) { this->clk_pin_ = clk_pin; }
|
||||
void EthernetComponent::set_miso_pin(uint8_t miso_pin) { this->miso_pin_ = miso_pin; }
|
||||
void EthernetComponent::set_mosi_pin(uint8_t mosi_pin) { this->mosi_pin_ = mosi_pin; }
|
||||
void EthernetComponent::set_cs_pin(uint8_t cs_pin) { this->cs_pin_ = cs_pin; }
|
||||
void EthernetComponent::set_interrupt_pin(uint8_t interrupt_pin) { this->interrupt_pin_ = interrupt_pin; }
|
||||
void EthernetComponent::set_reset_pin(uint8_t reset_pin) { this->reset_pin_ = reset_pin; }
|
||||
void EthernetComponent::set_clock_speed(int clock_speed) { this->clock_speed_ = clock_speed; }
|
||||
#ifdef USE_ETHERNET_SPI_POLLING_SUPPORT
|
||||
void EthernetComponent::set_polling_interval(uint32_t polling_interval) { this->polling_interval_ = polling_interval; }
|
||||
#endif
|
||||
#else
|
||||
void EthernetComponent::set_phy_addr(uint8_t phy_addr) { this->phy_addr_ = phy_addr; }
|
||||
void EthernetComponent::set_power_pin(int power_pin) { this->power_pin_ = power_pin; }
|
||||
void EthernetComponent::set_mdc_pin(uint8_t mdc_pin) { this->mdc_pin_ = mdc_pin; }
|
||||
void EthernetComponent::set_mdio_pin(uint8_t mdio_pin) { this->mdio_pin_ = mdio_pin; }
|
||||
void EthernetComponent::set_clk_pin(uint8_t clk_pin) { this->clk_pin_ = clk_pin; }
|
||||
void EthernetComponent::set_clk_mode(emac_rmii_clock_mode_t clk_mode) { this->clk_mode_ = clk_mode; }
|
||||
void EthernetComponent::add_phy_register(PHYRegister register_value) { this->phy_registers_.push_back(register_value); }
|
||||
#endif
|
||||
void EthernetComponent::set_type(EthernetType type) { this->type_ = type; }
|
||||
#ifdef USE_ETHERNET_MANUAL_IP
|
||||
void EthernetComponent::set_manual_ip(const ManualIP &manual_ip) { this->manual_ip_ = manual_ip; }
|
||||
#endif
|
||||
|
||||
// set_use_address() is guaranteed to be called during component setup by Python code generation,
|
||||
// so use_address_ will always be valid when get_use_address() is called - no fallback needed.
|
||||
const char *EthernetComponent::get_use_address() const { return this->use_address_; }
|
||||
|
||||
void EthernetComponent::set_use_address(const char *use_address) { this->use_address_ = use_address; }
|
||||
|
||||
void EthernetComponent::get_eth_mac_address_raw(uint8_t *mac) {
|
||||
esp_err_t err;
|
||||
err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_G_MAC_ADDR, mac);
|
||||
ESPHL_ERROR_CHECK(err, "ETH_CMD_G_MAC error");
|
||||
}
|
||||
|
||||
std::string EthernetComponent::get_eth_mac_address_pretty() {
|
||||
char buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
|
||||
return std::string(this->get_eth_mac_address_pretty_into_buffer(buf));
|
||||
}
|
||||
|
||||
const char *EthernetComponent::get_eth_mac_address_pretty_into_buffer(
|
||||
std::span<char, MAC_ADDRESS_PRETTY_BUFFER_SIZE> buf) {
|
||||
uint8_t mac[6];
|
||||
get_eth_mac_address_raw(mac);
|
||||
format_mac_addr_upper(mac, buf.data());
|
||||
return buf.data();
|
||||
}
|
||||
|
||||
eth_duplex_t EthernetComponent::get_duplex_mode() {
|
||||
esp_err_t err;
|
||||
eth_duplex_t duplex_mode;
|
||||
err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_G_DUPLEX_MODE, &duplex_mode);
|
||||
ESPHL_ERROR_CHECK_RET(err, "ETH_CMD_G_DUPLEX_MODE error", ETH_DUPLEX_HALF);
|
||||
return duplex_mode;
|
||||
}
|
||||
|
||||
eth_speed_t EthernetComponent::get_link_speed() {
|
||||
esp_err_t err;
|
||||
eth_speed_t speed;
|
||||
err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_G_SPEED, &speed);
|
||||
ESPHL_ERROR_CHECK_RET(err, "ETH_CMD_G_SPEED error", ETH_SPEED_10M);
|
||||
return speed;
|
||||
}
|
||||
|
||||
bool EthernetComponent::powerdown() {
|
||||
ESP_LOGI(TAG, "Powering down ethernet PHY");
|
||||
if (this->phy_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Ethernet PHY not assigned");
|
||||
return false;
|
||||
}
|
||||
this->connected_ = false;
|
||||
this->started_ = false;
|
||||
// No need to enable_loop() here as this is only called during shutdown/reboot
|
||||
if (this->phy_->pwrctl(this->phy_, false) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Error powering down ethernet PHY");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifndef USE_ETHERNET_SPI
|
||||
|
||||
#ifdef USE_ETHERNET_KSZ8081
|
||||
constexpr uint8_t KSZ80XX_PC2R_REG_ADDR = 0x1F;
|
||||
|
||||
void EthernetComponent::ksz8081_set_clock_reference_(esp_eth_mac_t *mac) {
|
||||
esp_err_t err;
|
||||
|
||||
uint32_t phy_control_2;
|
||||
err = mac->read_phy_reg(mac, this->phy_addr_, KSZ80XX_PC2R_REG_ADDR, &(phy_control_2));
|
||||
ESPHL_ERROR_CHECK(err, "Read PHY Control 2 failed");
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
|
||||
char hex_buf[format_hex_pretty_size(PHY_REG_SIZE)];
|
||||
#endif
|
||||
ESP_LOGVV(TAG, "KSZ8081 PHY Control 2: %s", format_hex_pretty_to(hex_buf, (uint8_t *) &phy_control_2, PHY_REG_SIZE));
|
||||
|
||||
/*
|
||||
* Bit 7 is `RMII Reference Clock Select`. Default is `0`.
|
||||
* KSZ8081RNA:
|
||||
* 0 - clock input to XI (Pin 8) is 25 MHz for RMII - 25 MHz clock mode.
|
||||
* 1 - clock input to XI (Pin 8) is 50 MHz for RMII - 50 MHz clock mode.
|
||||
* KSZ8081RND:
|
||||
* 0 - clock input to XI (Pin 8) is 50 MHz for RMII - 50 MHz clock mode.
|
||||
* 1 - clock input to XI (Pin 8) is 25 MHz (driven clock only, not a crystal) for RMII - 25 MHz clock mode.
|
||||
*/
|
||||
if ((phy_control_2 & (1 << 7)) != (1 << 7)) {
|
||||
phy_control_2 |= 1 << 7;
|
||||
err = mac->write_phy_reg(mac, this->phy_addr_, KSZ80XX_PC2R_REG_ADDR, phy_control_2);
|
||||
ESPHL_ERROR_CHECK(err, "Write PHY Control 2 failed");
|
||||
err = mac->read_phy_reg(mac, this->phy_addr_, KSZ80XX_PC2R_REG_ADDR, &(phy_control_2));
|
||||
ESPHL_ERROR_CHECK(err, "Read PHY Control 2 failed");
|
||||
ESP_LOGVV(TAG, "KSZ8081 PHY Control 2: %s",
|
||||
format_hex_pretty_to(hex_buf, (uint8_t *) &phy_control_2, PHY_REG_SIZE));
|
||||
}
|
||||
}
|
||||
#endif // USE_ETHERNET_KSZ8081
|
||||
|
||||
void EthernetComponent::write_phy_register_(esp_eth_mac_t *mac, PHYRegister register_data) {
|
||||
esp_err_t err;
|
||||
|
||||
#ifdef USE_ETHERNET_RTL8201
|
||||
constexpr uint8_t eth_phy_psr_reg_addr = 0x1F;
|
||||
if (this->type_ == ETHERNET_TYPE_RTL8201 && register_data.page) {
|
||||
ESP_LOGD(TAG, "Select PHY Register Page: 0x%02" PRIX32, register_data.page);
|
||||
err = mac->write_phy_reg(mac, this->phy_addr_, eth_phy_psr_reg_addr, register_data.page);
|
||||
ESPHL_ERROR_CHECK(err, "Select PHY Register page failed");
|
||||
}
|
||||
#endif
|
||||
|
||||
ESP_LOGD(TAG, "Writing PHY reg 0x%02" PRIX32 " = 0x%04" PRIX32, register_data.address, register_data.value);
|
||||
err = mac->write_phy_reg(mac, this->phy_addr_, register_data.address, register_data.value);
|
||||
ESPHL_ERROR_CHECK(err, "Writing PHY Register failed");
|
||||
|
||||
#ifdef USE_ETHERNET_RTL8201
|
||||
if (this->type_ == ETHERNET_TYPE_RTL8201 && register_data.page) {
|
||||
ESP_LOGD(TAG, "Select PHY Register Page 0x00");
|
||||
err = mac->write_phy_reg(mac, this->phy_addr_, eth_phy_psr_reg_addr, 0x0);
|
||||
ESPHL_ERROR_CHECK(err, "Select PHY Register Page 0 failed");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace esphome::ethernet
|
||||
|
||||
#endif // USE_ESP32
|
||||
#endif // USE_ETHERNET
|
||||
|
||||
@@ -7,8 +7,9 @@
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/components/network/ip_address.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#ifdef USE_ETHERNET
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#include "esp_eth.h"
|
||||
#include "esp_eth_mac.h"
|
||||
#include "esp_eth_mac_esp.h"
|
||||
@@ -19,6 +20,11 @@
|
||||
#if CONFIG_ETH_USE_ESP32_EMAC
|
||||
extern "C" eth_esp32_emac_config_t eth_esp32_emac_default_config(void);
|
||||
#endif
|
||||
#endif // USE_ESP32
|
||||
|
||||
#ifdef USE_RP2040
|
||||
#include <W5500lwIP.h>
|
||||
#endif
|
||||
|
||||
namespace esphome::ethernet {
|
||||
|
||||
@@ -73,6 +79,12 @@ enum class EthernetComponentState : uint8_t {
|
||||
CONNECTED,
|
||||
};
|
||||
|
||||
// Platform-neutral duplex/speed types
|
||||
#ifndef USE_ESP32
|
||||
enum eth_duplex_t { ETH_DUPLEX_HALF, ETH_DUPLEX_FULL };
|
||||
enum eth_speed_t { ETH_SPEED_10M, ETH_SPEED_100M };
|
||||
#endif
|
||||
|
||||
class EthernetComponent : public Component {
|
||||
public:
|
||||
EthernetComponent();
|
||||
@@ -83,6 +95,28 @@ class EthernetComponent : public Component {
|
||||
void on_powerdown() override { powerdown(); }
|
||||
bool is_connected() { return this->state_ == EthernetComponentState::CONNECTED; }
|
||||
|
||||
void set_type(EthernetType type);
|
||||
#ifdef USE_ETHERNET_MANUAL_IP
|
||||
void set_manual_ip(const ManualIP &manual_ip);
|
||||
#endif
|
||||
void set_fixed_mac(const std::array<uint8_t, 6> &mac) { this->fixed_mac_ = mac; }
|
||||
|
||||
network::IPAddresses get_ip_addresses();
|
||||
network::IPAddress get_dns_address(uint8_t num);
|
||||
const char *get_use_address() const;
|
||||
void set_use_address(const char *use_address);
|
||||
void get_eth_mac_address_raw(uint8_t *mac);
|
||||
// Remove before 2026.9.0
|
||||
ESPDEPRECATED("Use get_eth_mac_address_pretty_into_buffer() instead. Removed in 2026.9.0", "2026.3.0")
|
||||
std::string get_eth_mac_address_pretty();
|
||||
const char *get_eth_mac_address_pretty_into_buffer(std::span<char, MAC_ADDRESS_PRETTY_BUFFER_SIZE> buf);
|
||||
eth_duplex_t get_duplex_mode();
|
||||
eth_speed_t get_link_speed();
|
||||
bool powerdown();
|
||||
|
||||
#ifdef USE_ESP32
|
||||
esp_eth_handle_t get_eth_handle() const { return this->eth_handle_; }
|
||||
|
||||
#ifdef USE_ETHERNET_SPI
|
||||
void set_clk_pin(uint8_t clk_pin);
|
||||
void set_miso_pin(uint8_t miso_pin);
|
||||
@@ -102,26 +136,17 @@ class EthernetComponent : public Component {
|
||||
void set_clk_pin(uint8_t clk_pin);
|
||||
void set_clk_mode(emac_rmii_clock_mode_t clk_mode);
|
||||
void add_phy_register(PHYRegister register_value);
|
||||
#endif
|
||||
void set_type(EthernetType type);
|
||||
#ifdef USE_ETHERNET_MANUAL_IP
|
||||
void set_manual_ip(const ManualIP &manual_ip);
|
||||
#endif
|
||||
void set_fixed_mac(const std::array<uint8_t, 6> &mac) { this->fixed_mac_ = mac; }
|
||||
#endif // USE_ETHERNET_SPI
|
||||
#endif // USE_ESP32
|
||||
|
||||
network::IPAddresses get_ip_addresses();
|
||||
network::IPAddress get_dns_address(uint8_t num);
|
||||
const char *get_use_address() const;
|
||||
void set_use_address(const char *use_address);
|
||||
void get_eth_mac_address_raw(uint8_t *mac);
|
||||
// Remove before 2026.9.0
|
||||
ESPDEPRECATED("Use get_eth_mac_address_pretty_into_buffer() instead. Removed in 2026.9.0", "2026.3.0")
|
||||
std::string get_eth_mac_address_pretty();
|
||||
const char *get_eth_mac_address_pretty_into_buffer(std::span<char, MAC_ADDRESS_PRETTY_BUFFER_SIZE> buf);
|
||||
eth_duplex_t get_duplex_mode();
|
||||
eth_speed_t get_link_speed();
|
||||
esp_eth_handle_t get_eth_handle() const { return this->eth_handle_; }
|
||||
bool powerdown();
|
||||
#ifdef USE_RP2040
|
||||
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
|
||||
|
||||
#ifdef USE_ETHERNET_IP_STATE_LISTENERS
|
||||
void add_ip_state_listener(EthernetIPStateListener *listener) { this->ip_state_listeners_.push_back(listener); }
|
||||
@@ -133,19 +158,22 @@ class EthernetComponent : public Component {
|
||||
#ifdef USE_ETHERNET_DISCONNECT_TRIGGER
|
||||
Trigger<> *get_disconnect_trigger() { return &this->disconnect_trigger_; }
|
||||
#endif
|
||||
|
||||
protected:
|
||||
void start_connect_();
|
||||
void finish_connect_();
|
||||
void dump_connect_params_();
|
||||
|
||||
#ifdef USE_ETHERNET_IP_STATE_LISTENERS
|
||||
void notify_ip_state_listeners_();
|
||||
#endif
|
||||
|
||||
#ifdef USE_ESP32
|
||||
static void eth_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data);
|
||||
static void got_ip_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data);
|
||||
#if LWIP_IPV6
|
||||
static void got_ip6_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data);
|
||||
#endif /* LWIP_IPV6 */
|
||||
#ifdef USE_ETHERNET_IP_STATE_LISTENERS
|
||||
void notify_ip_state_listeners_();
|
||||
#endif
|
||||
|
||||
void start_connect_();
|
||||
void finish_connect_();
|
||||
void dump_connect_params_();
|
||||
void log_error_and_mark_failed_(esp_err_t err, const char *message);
|
||||
#ifdef USE_ETHERNET_KSZ8081
|
||||
/// @brief Set `RMII Reference Clock Select` bit for KSZ8081.
|
||||
@@ -177,7 +205,27 @@ class EthernetComponent : public Component {
|
||||
uint8_t phy_addr_{0};
|
||||
uint8_t mdc_pin_{23};
|
||||
uint8_t mdio_pin_{18};
|
||||
#endif
|
||||
#endif // USE_ETHERNET_SPI
|
||||
|
||||
// ESP32 pointers
|
||||
esp_netif_t *eth_netif_{nullptr};
|
||||
esp_eth_handle_t eth_handle_;
|
||||
esp_eth_phy_t *phy_{nullptr};
|
||||
#endif // USE_ESP32
|
||||
|
||||
#ifdef USE_RP2040
|
||||
static constexpr uint32_t LINK_CHECK_INTERVAL = 500; // ms between link/IP polls
|
||||
Wiznet5500lwIP *eth_{nullptr};
|
||||
uint32_t last_link_check_{0};
|
||||
uint8_t clk_pin_;
|
||||
uint8_t miso_pin_;
|
||||
uint8_t mosi_pin_;
|
||||
uint8_t cs_pin_;
|
||||
int8_t interrupt_pin_{-1};
|
||||
int8_t reset_pin_{-1};
|
||||
#endif // USE_RP2040
|
||||
|
||||
// Common members
|
||||
#ifdef USE_ETHERNET_MANUAL_IP
|
||||
optional<ManualIP> manual_ip_{};
|
||||
#endif
|
||||
@@ -194,10 +242,6 @@ class EthernetComponent : public Component {
|
||||
bool ipv6_setup_done_{false};
|
||||
#endif /* LWIP_IPV6 */
|
||||
|
||||
// Pointers at the end (naturally aligned)
|
||||
esp_netif_t *eth_netif_{nullptr};
|
||||
esp_eth_handle_t eth_handle_;
|
||||
esp_eth_phy_t *phy_{nullptr};
|
||||
optional<std::array<uint8_t, 6>> fixed_mac_;
|
||||
|
||||
#ifdef USE_ETHERNET_IP_STATE_LISTENERS
|
||||
@@ -219,10 +263,13 @@ class EthernetComponent : public Component {
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
extern EthernetComponent *global_eth_component;
|
||||
|
||||
#if defined(USE_ETHERNET_JL1101) && (ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 4, 2) || !defined(PLATFORMIO))
|
||||
#ifdef USE_ESP32
|
||||
#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
|
||||
|
||||
} // namespace esphome::ethernet
|
||||
|
||||
#endif // USE_ESP32
|
||||
#endif // USE_ETHERNET
|
||||
|
||||
@@ -0,0 +1,878 @@
|
||||
#include "ethernet_component.h"
|
||||
|
||||
#if defined(USE_ETHERNET) && defined(USE_ESP32)
|
||||
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include <lwip/dns.h>
|
||||
#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
|
||||
|
||||
#ifdef USE_ETHERNET_SPI
|
||||
#include <driver/gpio.h>
|
||||
#include <driver/spi_master.h>
|
||||
#endif
|
||||
|
||||
namespace esphome::ethernet {
|
||||
|
||||
static const char *const TAG = "ethernet";
|
||||
|
||||
// PHY register size for hex logging
|
||||
static constexpr size_t PHY_REG_SIZE = 2;
|
||||
|
||||
void EthernetComponent::log_error_and_mark_failed_(esp_err_t err, const char *message) {
|
||||
ESP_LOGE(TAG, "%s: (%d) %s", message, err, esp_err_to_name(err));
|
||||
this->mark_failed();
|
||||
}
|
||||
|
||||
#define ESPHL_ERROR_CHECK(err, message) \
|
||||
if ((err) != ESP_OK) { \
|
||||
this->log_error_and_mark_failed_(err, message); \
|
||||
return; \
|
||||
}
|
||||
|
||||
#define ESPHL_ERROR_CHECK_RET(err, message, ret) \
|
||||
if ((err) != ESP_OK) { \
|
||||
this->log_error_and_mark_failed_(err, message); \
|
||||
return ret; \
|
||||
}
|
||||
|
||||
void EthernetComponent::loop() {
|
||||
const uint32_t now = App.get_loop_component_start_time();
|
||||
|
||||
switch (this->state_) {
|
||||
case EthernetComponentState::STOPPED:
|
||||
if (this->started_) {
|
||||
ESP_LOGI(TAG, "Starting connection");
|
||||
this->state_ = EthernetComponentState::CONNECTING;
|
||||
this->start_connect_();
|
||||
}
|
||||
break;
|
||||
case EthernetComponentState::CONNECTING:
|
||||
if (!this->started_) {
|
||||
ESP_LOGI(TAG, "Stopped connection");
|
||||
this->state_ = EthernetComponentState::STOPPED;
|
||||
} else if (this->connected_) {
|
||||
// connection established
|
||||
ESP_LOGI(TAG, "Connected");
|
||||
this->state_ = EthernetComponentState::CONNECTED;
|
||||
|
||||
this->dump_connect_params_();
|
||||
this->status_clear_warning();
|
||||
#ifdef USE_ETHERNET_CONNECT_TRIGGER
|
||||
this->connect_trigger_.trigger();
|
||||
#endif
|
||||
} else if (now - this->connect_begin_ > 15000) {
|
||||
ESP_LOGW(TAG, "Connecting failed; reconnecting");
|
||||
this->start_connect_();
|
||||
}
|
||||
break;
|
||||
case EthernetComponentState::CONNECTED:
|
||||
if (!this->started_) {
|
||||
ESP_LOGI(TAG, "Stopped connection");
|
||||
this->state_ = EthernetComponentState::STOPPED;
|
||||
#ifdef USE_ETHERNET_DISCONNECT_TRIGGER
|
||||
this->disconnect_trigger_.trigger();
|
||||
#endif
|
||||
} else if (!this->connected_) {
|
||||
ESP_LOGW(TAG, "Connection lost; reconnecting");
|
||||
this->state_ = EthernetComponentState::CONNECTING;
|
||||
this->start_connect_();
|
||||
#ifdef USE_ETHERNET_DISCONNECT_TRIGGER
|
||||
this->disconnect_trigger_.trigger();
|
||||
#endif
|
||||
} else {
|
||||
this->finish_connect_();
|
||||
// When connected and stable, disable the loop to save CPU cycles
|
||||
this->disable_loop();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void EthernetComponent::setup() {
|
||||
if (esp_reset_reason() != ESP_RST_DEEPSLEEP) {
|
||||
// Delay here to allow power to stabilise before Ethernet is initialized.
|
||||
delay(300); // NOLINT
|
||||
}
|
||||
|
||||
esp_err_t err;
|
||||
|
||||
#ifdef USE_ETHERNET_SPI
|
||||
// Install GPIO ISR handler to be able to service SPI Eth modules interrupts
|
||||
gpio_install_isr_service(0);
|
||||
|
||||
spi_bus_config_t buscfg = {
|
||||
.mosi_io_num = this->mosi_pin_,
|
||||
.miso_io_num = this->miso_pin_,
|
||||
.sclk_io_num = this->clk_pin_,
|
||||
.quadwp_io_num = -1,
|
||||
.quadhd_io_num = -1,
|
||||
.data4_io_num = -1,
|
||||
.data5_io_num = -1,
|
||||
.data6_io_num = -1,
|
||||
.data7_io_num = -1,
|
||||
.max_transfer_sz = 0,
|
||||
.flags = 0,
|
||||
.intr_flags = 0,
|
||||
};
|
||||
|
||||
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C5) || defined(USE_ESP32_VARIANT_ESP32C6) || \
|
||||
defined(USE_ESP32_VARIANT_ESP32C61) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
|
||||
auto host = SPI2_HOST;
|
||||
#else
|
||||
auto host = SPI3_HOST;
|
||||
#endif
|
||||
|
||||
err = spi_bus_initialize(host, &buscfg, SPI_DMA_CH_AUTO);
|
||||
ESPHL_ERROR_CHECK(err, "SPI bus initialize error");
|
||||
#endif
|
||||
|
||||
err = esp_netif_init();
|
||||
ESPHL_ERROR_CHECK(err, "ETH netif init error");
|
||||
err = esp_event_loop_create_default();
|
||||
ESPHL_ERROR_CHECK(err, "ETH event loop error");
|
||||
|
||||
esp_netif_config_t cfg = ESP_NETIF_DEFAULT_ETH();
|
||||
this->eth_netif_ = esp_netif_new(&cfg);
|
||||
|
||||
// Init MAC and PHY configs to default
|
||||
eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG();
|
||||
eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG();
|
||||
|
||||
#ifdef USE_ETHERNET_SPI // Configure SPI interface and Ethernet driver for specific SPI module
|
||||
spi_device_interface_config_t devcfg = {
|
||||
.command_bits = 0,
|
||||
.address_bits = 0,
|
||||
.dummy_bits = 0,
|
||||
.mode = 0,
|
||||
.duty_cycle_pos = 0,
|
||||
.cs_ena_pretrans = 0,
|
||||
.cs_ena_posttrans = 0,
|
||||
.clock_speed_hz = this->clock_speed_,
|
||||
.input_delay_ns = 0,
|
||||
.spics_io_num = this->cs_pin_,
|
||||
.flags = 0,
|
||||
.queue_size = 20,
|
||||
.pre_cb = nullptr,
|
||||
.post_cb = nullptr,
|
||||
};
|
||||
|
||||
#ifdef USE_ETHERNET_W5500
|
||||
eth_w5500_config_t w5500_config = ETH_W5500_DEFAULT_CONFIG(host, &devcfg);
|
||||
#endif
|
||||
#ifdef USE_ETHERNET_DM9051
|
||||
eth_dm9051_config_t dm9051_config = ETH_DM9051_DEFAULT_CONFIG(host, &devcfg);
|
||||
#endif
|
||||
|
||||
#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
|
||||
|
||||
#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_;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
phy_config.phy_addr = this->phy_addr_spi_;
|
||||
phy_config.reset_gpio_num = this->reset_pin_;
|
||||
|
||||
esp_eth_mac_t *mac = nullptr;
|
||||
#elif defined(USE_ETHERNET_OPENETH)
|
||||
esp_eth_mac_t *mac = esp_eth_mac_new_openeth(&mac_config);
|
||||
#else
|
||||
phy_config.phy_addr = this->phy_addr_;
|
||||
phy_config.reset_gpio_num = this->power_pin_;
|
||||
|
||||
eth_esp32_emac_config_t esp32_emac_config = eth_esp32_emac_default_config();
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)
|
||||
esp32_emac_config.smi_gpio.mdc_num = this->mdc_pin_;
|
||||
esp32_emac_config.smi_gpio.mdio_num = this->mdio_pin_;
|
||||
#else
|
||||
esp32_emac_config.smi_mdc_gpio_num = this->mdc_pin_;
|
||||
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 =
|
||||
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
|
||||
|
||||
switch (this->type_) {
|
||||
#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
|
||||
#if CONFIG_ETH_USE_ESP32_EMAC
|
||||
#ifdef USE_ETHERNET_LAN8720
|
||||
case ETHERNET_TYPE_LAN8720: {
|
||||
this->phy_ = esp_eth_phy_new_lan87xx(&phy_config);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_ETHERNET_RTL8201
|
||||
case ETHERNET_TYPE_RTL8201: {
|
||||
this->phy_ = esp_eth_phy_new_rtl8201(&phy_config);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_ETHERNET_DP83848
|
||||
case ETHERNET_TYPE_DP83848: {
|
||||
this->phy_ = esp_eth_phy_new_dp83848(&phy_config);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_ETHERNET_IP101
|
||||
case ETHERNET_TYPE_IP101: {
|
||||
this->phy_ = esp_eth_phy_new_ip101(&phy_config);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
#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;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_ETHERNET_KSZ8081
|
||||
case ETHERNET_TYPE_KSZ8081:
|
||||
case ETHERNET_TYPE_KSZ8081RNA: {
|
||||
this->phy_ = esp_eth_phy_new_ksz80xx(&phy_config);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_ETHERNET_LAN8670
|
||||
case ETHERNET_TYPE_LAN8670: {
|
||||
this->phy_ = esp_eth_phy_new_lan867x(&phy_config);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
#ifdef USE_ETHERNET_SPI
|
||||
#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
|
||||
#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);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
default: {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
esp_eth_config_t eth_config = ETH_DEFAULT_CONFIG(mac, this->phy_);
|
||||
this->eth_handle_ = nullptr;
|
||||
err = esp_eth_driver_install(ð_config, &this->eth_handle_);
|
||||
ESPHL_ERROR_CHECK(err, "ETH driver install error");
|
||||
|
||||
#ifndef USE_ETHERNET_SPI
|
||||
#ifdef USE_ETHERNET_KSZ8081
|
||||
if (this->type_ == ETHERNET_TYPE_KSZ8081RNA && this->clk_mode_ == EMAC_CLK_OUT) {
|
||||
// KSZ8081RNA default is incorrect. It expects a 25MHz clock instead of the 50MHz we provide.
|
||||
this->ksz8081_set_clock_reference_(mac);
|
||||
}
|
||||
#endif // USE_ETHERNET_KSZ8081
|
||||
|
||||
for (const auto &phy_register : this->phy_registers_) {
|
||||
this->write_phy_register_(mac, phy_register);
|
||||
}
|
||||
#endif
|
||||
|
||||
// use ESP internal eth mac
|
||||
uint8_t mac_addr[6];
|
||||
if (this->fixed_mac_.has_value()) {
|
||||
memcpy(mac_addr, this->fixed_mac_->data(), 6);
|
||||
} else {
|
||||
esp_read_mac(mac_addr, ESP_MAC_ETH);
|
||||
}
|
||||
err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_S_MAC_ADDR, mac_addr);
|
||||
ESPHL_ERROR_CHECK(err, "set mac address error");
|
||||
|
||||
/* attach Ethernet driver to TCP/IP stack */
|
||||
err = esp_netif_attach(this->eth_netif_, esp_eth_new_netif_glue(this->eth_handle_));
|
||||
ESPHL_ERROR_CHECK(err, "ETH netif attach error");
|
||||
|
||||
// Register user defined event handers
|
||||
err = esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, &EthernetComponent::eth_event_handler, nullptr);
|
||||
ESPHL_ERROR_CHECK(err, "ETH event handler register error");
|
||||
err = esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, &EthernetComponent::got_ip_event_handler, nullptr);
|
||||
ESPHL_ERROR_CHECK(err, "GOT IP event handler register error");
|
||||
#if USE_NETWORK_IPV6
|
||||
err = esp_event_handler_register(IP_EVENT, IP_EVENT_GOT_IP6, &EthernetComponent::got_ip6_event_handler, nullptr);
|
||||
ESPHL_ERROR_CHECK(err, "GOT IPv6 event handler register error");
|
||||
#endif /* USE_NETWORK_IPV6 */
|
||||
|
||||
/* start Ethernet driver state machine */
|
||||
err = esp_eth_start(this->eth_handle_);
|
||||
ESPHL_ERROR_CHECK(err, "ETH start error");
|
||||
}
|
||||
|
||||
void EthernetComponent::dump_config() {
|
||||
const char *eth_type;
|
||||
switch (this->type_) {
|
||||
#ifdef USE_ETHERNET_LAN8720
|
||||
case ETHERNET_TYPE_LAN8720:
|
||||
eth_type = "LAN8720";
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_ETHERNET_RTL8201
|
||||
case ETHERNET_TYPE_RTL8201:
|
||||
eth_type = "RTL8201";
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_ETHERNET_DP83848
|
||||
case ETHERNET_TYPE_DP83848:
|
||||
eth_type = "DP83848";
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_ETHERNET_IP101
|
||||
case ETHERNET_TYPE_IP101:
|
||||
eth_type = "IP101";
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_ETHERNET_JL1101
|
||||
case ETHERNET_TYPE_JL1101:
|
||||
eth_type = "JL1101";
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_ETHERNET_KSZ8081
|
||||
case ETHERNET_TYPE_KSZ8081:
|
||||
eth_type = "KSZ8081";
|
||||
break;
|
||||
|
||||
case ETHERNET_TYPE_KSZ8081RNA:
|
||||
eth_type = "KSZ8081RNA";
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_ETHERNET_W5500
|
||||
case ETHERNET_TYPE_W5500:
|
||||
eth_type = "W5500";
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_ETHERNET_DM9051
|
||||
case ETHERNET_TYPE_DM9051:
|
||||
eth_type = "DM9051";
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_ETHERNET_OPENETH
|
||||
case ETHERNET_TYPE_OPENETH:
|
||||
eth_type = "OPENETH";
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_ETHERNET_LAN8670
|
||||
case ETHERNET_TYPE_LAN8670:
|
||||
eth_type = "LAN8670";
|
||||
break;
|
||||
#endif
|
||||
|
||||
default:
|
||||
eth_type = "Unknown";
|
||||
break;
|
||||
}
|
||||
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"Ethernet:\n"
|
||||
" Connected: %s",
|
||||
YESNO(this->is_connected()));
|
||||
this->dump_connect_params_();
|
||||
#ifdef USE_ETHERNET_SPI
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" CLK Pin: %u\n"
|
||||
" MISO Pin: %u\n"
|
||||
" MOSI Pin: %u\n"
|
||||
" CS Pin: %u",
|
||||
this->clk_pin_, this->miso_pin_, this->mosi_pin_, this->cs_pin_);
|
||||
#ifdef USE_ETHERNET_SPI_POLLING_SUPPORT
|
||||
if (this->polling_interval_ != 0) {
|
||||
ESP_LOGCONFIG(TAG, " Polling Interval: %" PRIu32 " ms", this->polling_interval_);
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
ESP_LOGCONFIG(TAG, " IRQ Pin: %d", this->interrupt_pin_);
|
||||
}
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" Reset Pin: %d\n"
|
||||
" Clock Speed: %d MHz",
|
||||
this->reset_pin_, this->clock_speed_ / 1000000);
|
||||
#else
|
||||
if (this->power_pin_ != -1) {
|
||||
ESP_LOGCONFIG(TAG, " Power Pin: %u", this->power_pin_);
|
||||
}
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" CLK Pin: %u\n"
|
||||
" MDC Pin: %u\n"
|
||||
" MDIO Pin: %u\n"
|
||||
" PHY addr: %u",
|
||||
this->clk_pin_, this->mdc_pin_, this->mdio_pin_, this->phy_addr_);
|
||||
#endif
|
||||
ESP_LOGCONFIG(TAG, " Type: %s", eth_type);
|
||||
}
|
||||
|
||||
network::IPAddresses EthernetComponent::get_ip_addresses() {
|
||||
network::IPAddresses addresses;
|
||||
esp_netif_ip_info_t ip;
|
||||
esp_err_t err = esp_netif_get_ip_info(this->eth_netif_, &ip);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGV(TAG, "esp_netif_get_ip_info failed: %s", esp_err_to_name(err));
|
||||
// TODO: do something smarter
|
||||
// return false;
|
||||
} else {
|
||||
addresses[0] = network::IPAddress(&ip.ip);
|
||||
}
|
||||
#if USE_NETWORK_IPV6
|
||||
struct esp_ip6_addr if_ip6s[CONFIG_LWIP_IPV6_NUM_ADDRESSES];
|
||||
uint8_t count = 0;
|
||||
count = esp_netif_get_all_ip6(this->eth_netif_, if_ip6s);
|
||||
assert(count <= CONFIG_LWIP_IPV6_NUM_ADDRESSES);
|
||||
assert(count < addresses.size());
|
||||
for (int i = 0; i < count; i++) {
|
||||
addresses[i + 1] = network::IPAddress(&if_ip6s[i]);
|
||||
}
|
||||
#endif /* USE_NETWORK_IPV6 */
|
||||
|
||||
return addresses;
|
||||
}
|
||||
|
||||
network::IPAddress EthernetComponent::get_dns_address(uint8_t num) {
|
||||
LwIPLock lock;
|
||||
const ip_addr_t *dns_ip = dns_getserver(num);
|
||||
return dns_ip;
|
||||
}
|
||||
|
||||
void EthernetComponent::eth_event_handler(void *arg, esp_event_base_t event_base, int32_t event, void *event_data) {
|
||||
const char *event_name;
|
||||
|
||||
switch (event) {
|
||||
case ETHERNET_EVENT_START:
|
||||
event_name = "ETH started";
|
||||
global_eth_component->started_ = true;
|
||||
global_eth_component->enable_loop_soon_any_context();
|
||||
break;
|
||||
case ETHERNET_EVENT_STOP:
|
||||
event_name = "ETH stopped";
|
||||
global_eth_component->started_ = false;
|
||||
global_eth_component->connected_ = false;
|
||||
global_eth_component->enable_loop_soon_any_context(); // Enable loop when connection state changes
|
||||
break;
|
||||
case ETHERNET_EVENT_CONNECTED:
|
||||
event_name = "ETH connected";
|
||||
// For static IP configurations, GOT_IP event may not fire, so notify IP listeners here
|
||||
#if defined(USE_ETHERNET_IP_STATE_LISTENERS) && defined(USE_ETHERNET_MANUAL_IP)
|
||||
if (global_eth_component->manual_ip_.has_value()) {
|
||||
global_eth_component->notify_ip_state_listeners_();
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
case ETHERNET_EVENT_DISCONNECTED:
|
||||
event_name = "ETH disconnected";
|
||||
global_eth_component->connected_ = false;
|
||||
global_eth_component->enable_loop_soon_any_context(); // Enable loop when connection state changes
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, "[Ethernet event] %s (num=%" PRId32 ")", event_name, event);
|
||||
}
|
||||
|
||||
void EthernetComponent::got_ip_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id,
|
||||
void *event_data) {
|
||||
ip_event_got_ip_t *event = (ip_event_got_ip_t *) event_data;
|
||||
const esp_netif_ip_info_t *ip_info = &event->ip_info;
|
||||
ESP_LOGV(TAG, "[Ethernet event] ETH Got IP " IPSTR, IP2STR(&ip_info->ip));
|
||||
global_eth_component->got_ipv4_address_ = true;
|
||||
#if USE_NETWORK_IPV6 && (USE_NETWORK_MIN_IPV6_ADDR_COUNT > 0)
|
||||
global_eth_component->connected_ = global_eth_component->ipv6_count_ >= USE_NETWORK_MIN_IPV6_ADDR_COUNT;
|
||||
global_eth_component->enable_loop_soon_any_context(); // Enable loop when connection state changes
|
||||
#else
|
||||
global_eth_component->connected_ = true;
|
||||
global_eth_component->enable_loop_soon_any_context(); // Enable loop when connection state changes
|
||||
#endif /* USE_NETWORK_IPV6 */
|
||||
#ifdef USE_ETHERNET_IP_STATE_LISTENERS
|
||||
global_eth_component->notify_ip_state_listeners_();
|
||||
#endif
|
||||
}
|
||||
|
||||
#if USE_NETWORK_IPV6
|
||||
void EthernetComponent::got_ip6_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id,
|
||||
void *event_data) {
|
||||
ip_event_got_ip6_t *event = (ip_event_got_ip6_t *) event_data;
|
||||
ESP_LOGV(TAG, "[Ethernet event] ETH Got IPv6: " IPV6STR, IPV62STR(event->ip6_info.ip));
|
||||
global_eth_component->ipv6_count_ += 1;
|
||||
#if (USE_NETWORK_MIN_IPV6_ADDR_COUNT > 0)
|
||||
global_eth_component->connected_ =
|
||||
global_eth_component->got_ipv4_address_ && (global_eth_component->ipv6_count_ >= USE_NETWORK_MIN_IPV6_ADDR_COUNT);
|
||||
global_eth_component->enable_loop_soon_any_context(); // Enable loop when connection state changes
|
||||
#else
|
||||
global_eth_component->connected_ = global_eth_component->got_ipv4_address_;
|
||||
global_eth_component->enable_loop_soon_any_context(); // Enable loop when connection state changes
|
||||
#endif
|
||||
#ifdef USE_ETHERNET_IP_STATE_LISTENERS
|
||||
global_eth_component->notify_ip_state_listeners_();
|
||||
#endif
|
||||
}
|
||||
#endif /* USE_NETWORK_IPV6 */
|
||||
|
||||
void EthernetComponent::finish_connect_() {
|
||||
#if USE_NETWORK_IPV6
|
||||
// Retry IPv6 link-local setup if it failed during initial connect
|
||||
// This handles the case where min_ipv6_addr_count is NOT set (or is 0),
|
||||
// allowing us to reach CONNECTED state with just IPv4.
|
||||
// If IPv6 setup failed in start_connect_() because the interface wasn't ready:
|
||||
// - Bootup timing issues (#10281)
|
||||
// - Cable unplugged/network interruption (#10705)
|
||||
// We can now retry since we're in CONNECTED state and the interface is definitely up.
|
||||
if (!this->ipv6_setup_done_) {
|
||||
esp_err_t err = esp_netif_create_ip6_linklocal(this->eth_netif_);
|
||||
if (err == ESP_OK) {
|
||||
ESP_LOGD(TAG, "IPv6 link-local address created (retry succeeded)");
|
||||
}
|
||||
// Always set the flag to prevent continuous retries
|
||||
// If IPv6 setup fails here with the interface up and stable, it's
|
||||
// likely a persistent issue (IPv6 disabled at router, hardware
|
||||
// limitation, etc.) that won't be resolved by further retries.
|
||||
// The device continues to work with IPv4.
|
||||
this->ipv6_setup_done_ = true;
|
||||
}
|
||||
#endif /* USE_NETWORK_IPV6 */
|
||||
}
|
||||
|
||||
void EthernetComponent::start_connect_() {
|
||||
global_eth_component->got_ipv4_address_ = false;
|
||||
#if USE_NETWORK_IPV6
|
||||
global_eth_component->ipv6_count_ = 0;
|
||||
this->ipv6_setup_done_ = false;
|
||||
#endif /* USE_NETWORK_IPV6 */
|
||||
this->connect_begin_ = millis();
|
||||
this->status_set_warning(LOG_STR("waiting for IP configuration"));
|
||||
|
||||
esp_err_t err;
|
||||
err = esp_netif_set_hostname(this->eth_netif_, App.get_name().c_str());
|
||||
if (err != ERR_OK) {
|
||||
ESP_LOGW(TAG, "esp_netif_set_hostname failed: %s", esp_err_to_name(err));
|
||||
}
|
||||
|
||||
esp_netif_ip_info_t info;
|
||||
#ifdef USE_ETHERNET_MANUAL_IP
|
||||
if (this->manual_ip_.has_value()) {
|
||||
info.ip = this->manual_ip_->static_ip;
|
||||
info.gw = this->manual_ip_->gateway;
|
||||
info.netmask = this->manual_ip_->subnet;
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
info.ip.addr = 0;
|
||||
info.gw.addr = 0;
|
||||
info.netmask.addr = 0;
|
||||
}
|
||||
|
||||
esp_netif_dhcp_status_t status = ESP_NETIF_DHCP_INIT;
|
||||
|
||||
err = esp_netif_dhcpc_get_status(this->eth_netif_, &status);
|
||||
ESPHL_ERROR_CHECK(err, "DHCPC Get Status Failed!");
|
||||
|
||||
ESP_LOGV(TAG, "DHCP Client Status: %d", status);
|
||||
|
||||
err = esp_netif_dhcpc_stop(this->eth_netif_);
|
||||
if (err != ESP_ERR_ESP_NETIF_DHCP_ALREADY_STOPPED) {
|
||||
ESPHL_ERROR_CHECK(err, "DHCPC stop error");
|
||||
}
|
||||
|
||||
err = esp_netif_set_ip_info(this->eth_netif_, &info);
|
||||
ESPHL_ERROR_CHECK(err, "DHCPC set IP info error");
|
||||
|
||||
#ifdef USE_ETHERNET_MANUAL_IP
|
||||
if (this->manual_ip_.has_value()) {
|
||||
LwIPLock lock;
|
||||
if (this->manual_ip_->dns1.is_set()) {
|
||||
ip_addr_t d;
|
||||
d = this->manual_ip_->dns1;
|
||||
dns_setserver(0, &d);
|
||||
}
|
||||
if (this->manual_ip_->dns2.is_set()) {
|
||||
ip_addr_t d;
|
||||
d = this->manual_ip_->dns2;
|
||||
dns_setserver(1, &d);
|
||||
}
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
err = esp_netif_dhcpc_start(this->eth_netif_);
|
||||
if (err != ESP_ERR_ESP_NETIF_DHCP_ALREADY_STARTED) {
|
||||
ESPHL_ERROR_CHECK(err, "DHCPC start error");
|
||||
}
|
||||
}
|
||||
#if USE_NETWORK_IPV6
|
||||
// Attempt to create IPv6 link-local address
|
||||
// We MUST attempt this here, not just in finish_connect_(), because with
|
||||
// min_ipv6_addr_count set, the component won't reach CONNECTED state without IPv6.
|
||||
// However, this may fail with ESP_FAIL if the interface is not up yet:
|
||||
// - At bootup when link isn't ready (#10281)
|
||||
// - After disconnection/cable unplugged (#10705)
|
||||
// We'll retry in finish_connect_() if it fails here.
|
||||
err = esp_netif_create_ip6_linklocal(this->eth_netif_);
|
||||
if (err != ESP_OK) {
|
||||
if (err == ESP_ERR_ESP_NETIF_INVALID_PARAMS) {
|
||||
// This is a programming error, not a transient failure
|
||||
ESPHL_ERROR_CHECK(err, "esp_netif_create_ip6_linklocal invalid parameters");
|
||||
} else {
|
||||
// ESP_FAIL means the interface isn't up yet
|
||||
// This is expected and non-fatal, happens in multiple scenarios:
|
||||
// - During reconnection after network interruptions (#10705)
|
||||
// - At bootup when the link isn't ready yet (#10281)
|
||||
// We'll retry once we reach CONNECTED state and the interface is up
|
||||
ESP_LOGW(TAG, "esp_netif_create_ip6_linklocal failed: %s", esp_err_to_name(err));
|
||||
// Don't mark component as failed - this is a transient error
|
||||
}
|
||||
}
|
||||
#endif /* USE_NETWORK_IPV6 */
|
||||
|
||||
this->connect_begin_ = millis();
|
||||
this->status_set_warning();
|
||||
}
|
||||
|
||||
void EthernetComponent::dump_connect_params_() {
|
||||
esp_netif_ip_info_t ip;
|
||||
esp_netif_get_ip_info(this->eth_netif_, &ip);
|
||||
const ip_addr_t *dns_ip1;
|
||||
const ip_addr_t *dns_ip2;
|
||||
{
|
||||
LwIPLock lock;
|
||||
dns_ip1 = dns_getserver(0);
|
||||
dns_ip2 = dns_getserver(1);
|
||||
}
|
||||
|
||||
// Use stack buffers for IP address formatting to avoid heap allocations
|
||||
char ip_buf[network::IP_ADDRESS_BUFFER_SIZE];
|
||||
char subnet_buf[network::IP_ADDRESS_BUFFER_SIZE];
|
||||
char gateway_buf[network::IP_ADDRESS_BUFFER_SIZE];
|
||||
char dns1_buf[network::IP_ADDRESS_BUFFER_SIZE];
|
||||
char dns2_buf[network::IP_ADDRESS_BUFFER_SIZE];
|
||||
char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" IP Address: %s\n"
|
||||
" Hostname: '%s'\n"
|
||||
" Subnet: %s\n"
|
||||
" Gateway: %s\n"
|
||||
" DNS1: %s\n"
|
||||
" DNS2: %s\n"
|
||||
" MAC Address: %s\n"
|
||||
" Is Full Duplex: %s\n"
|
||||
" Link Speed: %u",
|
||||
network::IPAddress(&ip.ip).str_to(ip_buf), App.get_name().c_str(),
|
||||
network::IPAddress(&ip.netmask).str_to(subnet_buf), network::IPAddress(&ip.gw).str_to(gateway_buf),
|
||||
network::IPAddress(dns_ip1).str_to(dns1_buf), network::IPAddress(dns_ip2).str_to(dns2_buf),
|
||||
this->get_eth_mac_address_pretty_into_buffer(mac_buf),
|
||||
YESNO(this->get_duplex_mode() == ETH_DUPLEX_FULL), this->get_link_speed() == ETH_SPEED_100M ? 100 : 10);
|
||||
|
||||
#if USE_NETWORK_IPV6
|
||||
struct esp_ip6_addr if_ip6s[CONFIG_LWIP_IPV6_NUM_ADDRESSES];
|
||||
uint8_t count = 0;
|
||||
count = esp_netif_get_all_ip6(this->eth_netif_, if_ip6s);
|
||||
assert(count <= CONFIG_LWIP_IPV6_NUM_ADDRESSES);
|
||||
for (int i = 0; i < count; i++) {
|
||||
ESP_LOGCONFIG(TAG, " IPv6: " IPV6STR, IPV62STR(if_ip6s[i]));
|
||||
}
|
||||
#endif /* USE_NETWORK_IPV6 */
|
||||
}
|
||||
|
||||
#ifdef USE_ETHERNET_SPI
|
||||
void EthernetComponent::set_clk_pin(uint8_t clk_pin) { this->clk_pin_ = clk_pin; }
|
||||
void EthernetComponent::set_miso_pin(uint8_t miso_pin) { this->miso_pin_ = miso_pin; }
|
||||
void EthernetComponent::set_mosi_pin(uint8_t mosi_pin) { this->mosi_pin_ = mosi_pin; }
|
||||
void EthernetComponent::set_cs_pin(uint8_t cs_pin) { this->cs_pin_ = cs_pin; }
|
||||
void EthernetComponent::set_interrupt_pin(uint8_t interrupt_pin) { this->interrupt_pin_ = interrupt_pin; }
|
||||
void EthernetComponent::set_reset_pin(uint8_t reset_pin) { this->reset_pin_ = reset_pin; }
|
||||
void EthernetComponent::set_clock_speed(int clock_speed) { this->clock_speed_ = clock_speed; }
|
||||
#ifdef USE_ETHERNET_SPI_POLLING_SUPPORT
|
||||
void EthernetComponent::set_polling_interval(uint32_t polling_interval) { this->polling_interval_ = polling_interval; }
|
||||
#endif
|
||||
#else
|
||||
void EthernetComponent::set_phy_addr(uint8_t phy_addr) { this->phy_addr_ = phy_addr; }
|
||||
void EthernetComponent::set_power_pin(int power_pin) { this->power_pin_ = power_pin; }
|
||||
void EthernetComponent::set_mdc_pin(uint8_t mdc_pin) { this->mdc_pin_ = mdc_pin; }
|
||||
void EthernetComponent::set_mdio_pin(uint8_t mdio_pin) { this->mdio_pin_ = mdio_pin; }
|
||||
void EthernetComponent::set_clk_pin(uint8_t clk_pin) { this->clk_pin_ = clk_pin; }
|
||||
void EthernetComponent::set_clk_mode(emac_rmii_clock_mode_t clk_mode) { this->clk_mode_ = clk_mode; }
|
||||
void EthernetComponent::add_phy_register(PHYRegister register_value) { this->phy_registers_.push_back(register_value); }
|
||||
#endif
|
||||
|
||||
void EthernetComponent::get_eth_mac_address_raw(uint8_t *mac) {
|
||||
esp_err_t err;
|
||||
err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_G_MAC_ADDR, mac);
|
||||
ESPHL_ERROR_CHECK(err, "ETH_CMD_G_MAC error");
|
||||
}
|
||||
|
||||
std::string EthernetComponent::get_eth_mac_address_pretty() {
|
||||
char buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
|
||||
return std::string(this->get_eth_mac_address_pretty_into_buffer(buf));
|
||||
}
|
||||
|
||||
const char *EthernetComponent::get_eth_mac_address_pretty_into_buffer(
|
||||
std::span<char, MAC_ADDRESS_PRETTY_BUFFER_SIZE> buf) {
|
||||
uint8_t mac[6];
|
||||
get_eth_mac_address_raw(mac);
|
||||
format_mac_addr_upper(mac, buf.data());
|
||||
return buf.data();
|
||||
}
|
||||
|
||||
eth_duplex_t EthernetComponent::get_duplex_mode() {
|
||||
esp_err_t err;
|
||||
eth_duplex_t duplex_mode;
|
||||
err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_G_DUPLEX_MODE, &duplex_mode);
|
||||
ESPHL_ERROR_CHECK_RET(err, "ETH_CMD_G_DUPLEX_MODE error", ETH_DUPLEX_HALF);
|
||||
return duplex_mode;
|
||||
}
|
||||
|
||||
eth_speed_t EthernetComponent::get_link_speed() {
|
||||
esp_err_t err;
|
||||
eth_speed_t speed;
|
||||
err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_G_SPEED, &speed);
|
||||
ESPHL_ERROR_CHECK_RET(err, "ETH_CMD_G_SPEED error", ETH_SPEED_10M);
|
||||
return speed;
|
||||
}
|
||||
|
||||
bool EthernetComponent::powerdown() {
|
||||
ESP_LOGI(TAG, "Powering down ethernet PHY");
|
||||
if (this->phy_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Ethernet PHY not assigned");
|
||||
return false;
|
||||
}
|
||||
this->connected_ = false;
|
||||
this->started_ = false;
|
||||
// No need to enable_loop() here as this is only called during shutdown/reboot
|
||||
if (this->phy_->pwrctl(this->phy_, false) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Error powering down ethernet PHY");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifndef USE_ETHERNET_SPI
|
||||
|
||||
#ifdef USE_ETHERNET_KSZ8081
|
||||
constexpr uint8_t KSZ80XX_PC2R_REG_ADDR = 0x1F;
|
||||
|
||||
void EthernetComponent::ksz8081_set_clock_reference_(esp_eth_mac_t *mac) {
|
||||
esp_err_t err;
|
||||
|
||||
uint32_t phy_control_2;
|
||||
err = mac->read_phy_reg(mac, this->phy_addr_, KSZ80XX_PC2R_REG_ADDR, &(phy_control_2));
|
||||
ESPHL_ERROR_CHECK(err, "Read PHY Control 2 failed");
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
|
||||
char hex_buf[format_hex_pretty_size(PHY_REG_SIZE)];
|
||||
#endif
|
||||
ESP_LOGVV(TAG, "KSZ8081 PHY Control 2: %s", format_hex_pretty_to(hex_buf, (uint8_t *) &phy_control_2, PHY_REG_SIZE));
|
||||
|
||||
/*
|
||||
* Bit 7 is `RMII Reference Clock Select`. Default is `0`.
|
||||
* KSZ8081RNA:
|
||||
* 0 - clock input to XI (Pin 8) is 25 MHz for RMII - 25 MHz clock mode.
|
||||
* 1 - clock input to XI (Pin 8) is 50 MHz for RMII - 50 MHz clock mode.
|
||||
* KSZ8081RND:
|
||||
* 0 - clock input to XI (Pin 8) is 50 MHz for RMII - 50 MHz clock mode.
|
||||
* 1 - clock input to XI (Pin 8) is 25 MHz (driven clock only, not a crystal) for RMII - 25 MHz clock mode.
|
||||
*/
|
||||
if ((phy_control_2 & (1 << 7)) != (1 << 7)) {
|
||||
phy_control_2 |= 1 << 7;
|
||||
err = mac->write_phy_reg(mac, this->phy_addr_, KSZ80XX_PC2R_REG_ADDR, phy_control_2);
|
||||
ESPHL_ERROR_CHECK(err, "Write PHY Control 2 failed");
|
||||
err = mac->read_phy_reg(mac, this->phy_addr_, KSZ80XX_PC2R_REG_ADDR, &(phy_control_2));
|
||||
ESPHL_ERROR_CHECK(err, "Read PHY Control 2 failed");
|
||||
ESP_LOGVV(TAG, "KSZ8081 PHY Control 2: %s",
|
||||
format_hex_pretty_to(hex_buf, (uint8_t *) &phy_control_2, PHY_REG_SIZE));
|
||||
}
|
||||
}
|
||||
#endif // USE_ETHERNET_KSZ8081
|
||||
|
||||
void EthernetComponent::write_phy_register_(esp_eth_mac_t *mac, PHYRegister register_data) {
|
||||
esp_err_t err;
|
||||
|
||||
#ifdef USE_ETHERNET_RTL8201
|
||||
constexpr uint8_t eth_phy_psr_reg_addr = 0x1F;
|
||||
if (this->type_ == ETHERNET_TYPE_RTL8201 && register_data.page) {
|
||||
ESP_LOGD(TAG, "Select PHY Register Page: 0x%02" PRIX32, register_data.page);
|
||||
err = mac->write_phy_reg(mac, this->phy_addr_, eth_phy_psr_reg_addr, register_data.page);
|
||||
ESPHL_ERROR_CHECK(err, "Select PHY Register page failed");
|
||||
}
|
||||
#endif
|
||||
|
||||
ESP_LOGD(TAG, "Writing PHY reg 0x%02" PRIX32 " = 0x%04" PRIX32, register_data.address, register_data.value);
|
||||
err = mac->write_phy_reg(mac, this->phy_addr_, register_data.address, register_data.value);
|
||||
ESPHL_ERROR_CHECK(err, "Writing PHY Register failed");
|
||||
|
||||
#ifdef USE_ETHERNET_RTL8201
|
||||
if (this->type_ == ETHERNET_TYPE_RTL8201 && register_data.page) {
|
||||
ESP_LOGD(TAG, "Select PHY Register Page 0x00");
|
||||
err = mac->write_phy_reg(mac, this->phy_addr_, eth_phy_psr_reg_addr, 0x0);
|
||||
ESPHL_ERROR_CHECK(err, "Select PHY Register Page 0 failed");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace esphome::ethernet
|
||||
|
||||
#endif // USE_ETHERNET && USE_ESP32
|
||||
@@ -0,0 +1,315 @@
|
||||
#include "ethernet_component.h"
|
||||
|
||||
#if defined(USE_ETHERNET) && defined(USE_RP2040)
|
||||
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include "esphome/components/rp2040/gpio.h"
|
||||
|
||||
#include <SPI.h>
|
||||
#include <lwip/dns.h>
|
||||
#include <lwip/netif.h>
|
||||
|
||||
namespace esphome::ethernet {
|
||||
|
||||
static const char *const TAG = "ethernet";
|
||||
|
||||
void EthernetComponent::setup() {
|
||||
// Configure SPI pins
|
||||
SPI.setRX(this->miso_pin_);
|
||||
SPI.setTX(this->mosi_pin_);
|
||||
SPI.setSCK(this->clk_pin_);
|
||||
|
||||
// Toggle reset pin if configured
|
||||
if (this->reset_pin_ >= 0) {
|
||||
rp2040::RP2040GPIOPin reset_pin;
|
||||
reset_pin.set_pin(this->reset_pin_);
|
||||
reset_pin.set_flags(gpio::FLAG_OUTPUT);
|
||||
reset_pin.setup();
|
||||
reset_pin.digital_write(false);
|
||||
delay(1); // NOLINT
|
||||
reset_pin.digital_write(true);
|
||||
delay(10); // NOLINT - wait for W5500 to initialize after reset
|
||||
}
|
||||
|
||||
// Create the W5500 device instance
|
||||
this->eth_ = new Wiznet5500lwIP(this->cs_pin_, SPI, this->interrupt_pin_); // NOLINT
|
||||
|
||||
// Set hostname before begin() so the LWIP netif gets it
|
||||
this->eth_->hostname(App.get_name().c_str());
|
||||
|
||||
// Configure static IP if set (must be done before begin())
|
||||
#ifdef USE_ETHERNET_MANUAL_IP
|
||||
if (this->manual_ip_.has_value()) {
|
||||
IPAddress ip(this->manual_ip_->static_ip);
|
||||
IPAddress gateway(this->manual_ip_->gateway);
|
||||
IPAddress subnet(this->manual_ip_->subnet);
|
||||
IPAddress dns1(this->manual_ip_->dns1);
|
||||
IPAddress dns2(this->manual_ip_->dns2);
|
||||
this->eth_->config(ip, gateway, subnet, dns1, dns2);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Begin with fixed MAC or auto-generated
|
||||
bool success;
|
||||
if (this->fixed_mac_.has_value()) {
|
||||
success = this->eth_->begin(this->fixed_mac_->data());
|
||||
} else {
|
||||
success = this->eth_->begin();
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
ESP_LOGE(TAG, "Failed to initialize W5500 Ethernet");
|
||||
delete this->eth_; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
this->eth_ = nullptr;
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
// Make this the default interface for routing
|
||||
this->eth_->setDefault(true);
|
||||
|
||||
// The arduino-pico LwipIntfDev automatically handles packet processing
|
||||
// via __addEthernetPacketHandler when no interrupt pin is used,
|
||||
// or via GPIO interrupt when one is provided.
|
||||
|
||||
// Don't set started_ here — let the link polling in loop() set it
|
||||
// when the W5500 link is actually up. Setting it prematurely causes
|
||||
// a "Starting → Stopped → Starting" log sequence because the W5500
|
||||
// needs time after begin() before the PHY link is ready.
|
||||
}
|
||||
|
||||
void EthernetComponent::loop() {
|
||||
// On RP2040, we need to poll connection state since there are no events.
|
||||
const uint32_t now = App.get_loop_component_start_time();
|
||||
|
||||
// Throttle link/IP polling to avoid excessive SPI transactions from linkStatus()
|
||||
// which reads the W5500 PHY register via SPI on every call.
|
||||
// connected() reads netif->ip_addr without LwIPLock, but this is a single
|
||||
// 32-bit aligned read (atomic on ARM) — worst case is a one-iteration-stale
|
||||
// value, which is benign for polling.
|
||||
if (this->eth_ != nullptr && now - this->last_link_check_ >= LINK_CHECK_INTERVAL) {
|
||||
this->last_link_check_ = now;
|
||||
bool link_up = this->eth_->linkStatus() == LinkON;
|
||||
bool has_ip = this->eth_->connected();
|
||||
|
||||
if (!link_up) {
|
||||
if (this->started_) {
|
||||
this->started_ = false;
|
||||
this->connected_ = false;
|
||||
}
|
||||
} else {
|
||||
if (!this->started_) {
|
||||
this->started_ = true;
|
||||
}
|
||||
bool was_connected = this->connected_;
|
||||
this->connected_ = has_ip;
|
||||
if (this->connected_ && !was_connected) {
|
||||
#ifdef USE_ETHERNET_IP_STATE_LISTENERS
|
||||
this->notify_ip_state_listeners_();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// State machine
|
||||
switch (this->state_) {
|
||||
case EthernetComponentState::STOPPED:
|
||||
if (this->started_) {
|
||||
ESP_LOGI(TAG, "Starting connection");
|
||||
this->state_ = EthernetComponentState::CONNECTING;
|
||||
this->start_connect_();
|
||||
}
|
||||
break;
|
||||
case EthernetComponentState::CONNECTING:
|
||||
if (!this->started_) {
|
||||
ESP_LOGI(TAG, "Stopped connection");
|
||||
this->state_ = EthernetComponentState::STOPPED;
|
||||
} else if (this->connected_) {
|
||||
// connection established
|
||||
ESP_LOGI(TAG, "Connected");
|
||||
this->state_ = EthernetComponentState::CONNECTED;
|
||||
|
||||
this->dump_connect_params_();
|
||||
this->status_clear_warning();
|
||||
#ifdef USE_ETHERNET_CONNECT_TRIGGER
|
||||
this->connect_trigger_.trigger();
|
||||
#endif
|
||||
} else if (now - this->connect_begin_ > 15000) {
|
||||
ESP_LOGW(TAG, "Connecting failed; reconnecting");
|
||||
this->start_connect_();
|
||||
}
|
||||
break;
|
||||
case EthernetComponentState::CONNECTED:
|
||||
if (!this->started_) {
|
||||
ESP_LOGI(TAG, "Stopped connection");
|
||||
this->state_ = EthernetComponentState::STOPPED;
|
||||
#ifdef USE_ETHERNET_DISCONNECT_TRIGGER
|
||||
this->disconnect_trigger_.trigger();
|
||||
#endif
|
||||
} else if (!this->connected_) {
|
||||
ESP_LOGW(TAG, "Connection lost; reconnecting");
|
||||
this->state_ = EthernetComponentState::CONNECTING;
|
||||
this->start_connect_();
|
||||
#ifdef USE_ETHERNET_DISCONNECT_TRIGGER
|
||||
this->disconnect_trigger_.trigger();
|
||||
#endif
|
||||
} else {
|
||||
this->finish_connect_();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void EthernetComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"Ethernet:\n"
|
||||
" Type: W5500\n"
|
||||
" Connected: %s\n"
|
||||
" CLK Pin: %u\n"
|
||||
" MISO Pin: %u\n"
|
||||
" MOSI Pin: %u\n"
|
||||
" CS Pin: %u\n"
|
||||
" IRQ Pin: %d\n"
|
||||
" Reset Pin: %d",
|
||||
YESNO(this->is_connected()), this->clk_pin_, this->miso_pin_, this->mosi_pin_, this->cs_pin_,
|
||||
this->interrupt_pin_, this->reset_pin_);
|
||||
this->dump_connect_params_();
|
||||
}
|
||||
|
||||
network::IPAddresses EthernetComponent::get_ip_addresses() {
|
||||
network::IPAddresses addresses;
|
||||
if (this->eth_ != nullptr) {
|
||||
LwIPLock lock;
|
||||
addresses[0] = network::IPAddress(this->eth_->localIP());
|
||||
}
|
||||
return addresses;
|
||||
}
|
||||
|
||||
network::IPAddress EthernetComponent::get_dns_address(uint8_t num) {
|
||||
LwIPLock lock;
|
||||
const ip_addr_t *dns_ip = dns_getserver(num);
|
||||
return dns_ip;
|
||||
}
|
||||
|
||||
void EthernetComponent::get_eth_mac_address_raw(uint8_t *mac) {
|
||||
if (this->eth_ != nullptr) {
|
||||
this->eth_->macAddress(mac);
|
||||
} else {
|
||||
memset(mac, 0, 6);
|
||||
}
|
||||
}
|
||||
|
||||
std::string EthernetComponent::get_eth_mac_address_pretty() {
|
||||
char buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
|
||||
return std::string(this->get_eth_mac_address_pretty_into_buffer(buf));
|
||||
}
|
||||
|
||||
const char *EthernetComponent::get_eth_mac_address_pretty_into_buffer(
|
||||
std::span<char, MAC_ADDRESS_PRETTY_BUFFER_SIZE> buf) {
|
||||
uint8_t mac[6];
|
||||
get_eth_mac_address_raw(mac);
|
||||
format_mac_addr_upper(mac, buf.data());
|
||||
return buf.data();
|
||||
}
|
||||
|
||||
eth_duplex_t EthernetComponent::get_duplex_mode() {
|
||||
// W5500 is always full duplex
|
||||
return ETH_DUPLEX_FULL;
|
||||
}
|
||||
|
||||
eth_speed_t EthernetComponent::get_link_speed() {
|
||||
// W5500 is always 100Mbps
|
||||
return ETH_SPEED_100M;
|
||||
}
|
||||
|
||||
bool EthernetComponent::powerdown() {
|
||||
ESP_LOGI(TAG, "Powering down ethernet");
|
||||
if (this->eth_ != nullptr) {
|
||||
this->eth_->end();
|
||||
}
|
||||
this->connected_ = false;
|
||||
this->started_ = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void EthernetComponent::start_connect_() {
|
||||
this->got_ipv4_address_ = false;
|
||||
this->connect_begin_ = millis();
|
||||
this->status_set_warning(LOG_STR("waiting for IP configuration"));
|
||||
|
||||
// Hostname is already set in setup() via LwipIntf::setHostname()
|
||||
|
||||
#ifdef USE_ETHERNET_MANUAL_IP
|
||||
if (this->manual_ip_.has_value()) {
|
||||
// Static IP was already configured before begin() in setup()
|
||||
// Set DNS servers
|
||||
LwIPLock lock;
|
||||
if (this->manual_ip_->dns1.is_set()) {
|
||||
ip_addr_t d;
|
||||
d = this->manual_ip_->dns1;
|
||||
dns_setserver(0, &d);
|
||||
}
|
||||
if (this->manual_ip_->dns2.is_set()) {
|
||||
ip_addr_t d;
|
||||
d = this->manual_ip_->dns2;
|
||||
dns_setserver(1, &d);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void EthernetComponent::finish_connect_() {
|
||||
// No additional work needed on RP2040 for now
|
||||
// IPv6 link-local could be added here in the future
|
||||
}
|
||||
|
||||
void EthernetComponent::dump_connect_params_() {
|
||||
if (this->eth_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
char ip_buf[network::IP_ADDRESS_BUFFER_SIZE];
|
||||
char subnet_buf[network::IP_ADDRESS_BUFFER_SIZE];
|
||||
char gateway_buf[network::IP_ADDRESS_BUFFER_SIZE];
|
||||
char dns1_buf[network::IP_ADDRESS_BUFFER_SIZE];
|
||||
char dns2_buf[network::IP_ADDRESS_BUFFER_SIZE];
|
||||
char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
|
||||
|
||||
// Copy all lwIP state under the lock to avoid races with IRQ callbacks
|
||||
ip_addr_t ip_addr, netmask, gw, dns1_addr, dns2_addr;
|
||||
{
|
||||
LwIPLock lock;
|
||||
auto *netif = this->eth_->getNetIf();
|
||||
ip_addr = netif->ip_addr;
|
||||
netmask = netif->netmask;
|
||||
gw = netif->gw;
|
||||
dns1_addr = *dns_getserver(0);
|
||||
dns2_addr = *dns_getserver(1);
|
||||
}
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" IP Address: %s\n"
|
||||
" Hostname: '%s'\n"
|
||||
" Subnet: %s\n"
|
||||
" Gateway: %s\n"
|
||||
" DNS1: %s\n"
|
||||
" DNS2: %s\n"
|
||||
" MAC Address: %s",
|
||||
network::IPAddress(&ip_addr).str_to(ip_buf), App.get_name().c_str(),
|
||||
network::IPAddress(&netmask).str_to(subnet_buf), network::IPAddress(&gw).str_to(gateway_buf),
|
||||
network::IPAddress(&dns1_addr).str_to(dns1_buf), network::IPAddress(&dns2_addr).str_to(dns2_buf),
|
||||
this->get_eth_mac_address_pretty_into_buffer(mac_buf));
|
||||
}
|
||||
|
||||
void EthernetComponent::set_clk_pin(uint8_t clk_pin) { this->clk_pin_ = clk_pin; }
|
||||
void EthernetComponent::set_miso_pin(uint8_t miso_pin) { this->miso_pin_ = miso_pin; }
|
||||
void EthernetComponent::set_mosi_pin(uint8_t mosi_pin) { this->mosi_pin_ = mosi_pin; }
|
||||
void EthernetComponent::set_cs_pin(uint8_t cs_pin) { this->cs_pin_ = cs_pin; }
|
||||
void EthernetComponent::set_interrupt_pin(int8_t interrupt_pin) { this->interrupt_pin_ = interrupt_pin; }
|
||||
void EthernetComponent::set_reset_pin(int8_t reset_pin) { this->reset_pin_ = reset_pin; }
|
||||
|
||||
} // namespace esphome::ethernet
|
||||
|
||||
#endif // USE_ETHERNET && USE_RP2040
|
||||
@@ -1,3 +1,5 @@
|
||||
#include "esphome/core/defines.h"
|
||||
#ifdef USE_ESP32
|
||||
#include "esp_eth_mac_esp.h"
|
||||
|
||||
// ETH_ESP32_EMAC_DEFAULT_CONFIG() uses out-of-order designated initializers
|
||||
@@ -8,3 +10,4 @@ eth_esp32_emac_config_t eth_esp32_emac_default_config(void) {
|
||||
return (eth_esp32_emac_config_t) ETH_ESP32_EMAC_DEFAULT_CONFIG();
|
||||
}
|
||||
#endif
|
||||
#endif // USE_ESP32
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include "ethernet_info_text_sensor.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#ifdef USE_ETHERNET
|
||||
|
||||
namespace esphome::ethernet_info {
|
||||
|
||||
@@ -49,4 +49,4 @@ void MACAddressEthernetInfo::dump_config() { LOG_TEXT_SENSOR("", "EthernetInfo M
|
||||
|
||||
} // namespace esphome::ethernet_info
|
||||
|
||||
#endif // USE_ESP32
|
||||
#endif // USE_ETHERNET
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include "esphome/components/text_sensor/text_sensor.h"
|
||||
#include "esphome/components/ethernet/ethernet_component.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#ifdef USE_ETHERNET
|
||||
|
||||
namespace esphome::ethernet_info {
|
||||
|
||||
@@ -50,4 +50,4 @@ class MACAddressEthernetInfo final : public Component, public text_sensor::TextS
|
||||
|
||||
} // namespace esphome::ethernet_info
|
||||
|
||||
#endif // USE_ESP32
|
||||
#endif // USE_ETHERNET
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -39,7 +39,7 @@ class GPIOBinarySensorStore {
|
||||
Component *component_{nullptr}; // Pointer to the component for enable_loop_soon_any_context()
|
||||
};
|
||||
|
||||
class GPIOBinarySensor : public binary_sensor::BinarySensor, public Component {
|
||||
class GPIOBinarySensor final : public binary_sensor::BinarySensor, public Component {
|
||||
public:
|
||||
// No destructor needed: ESPHome components are created at boot and live forever.
|
||||
// Interrupts are only detached on reboot when memory is cleared anyway.
|
||||
|
||||
@@ -131,7 +131,7 @@ uint8_t IRAM_ATTR GPIOOneWireBus::read8() {
|
||||
uint64_t IRAM_ATTR GPIOOneWireBus::read64() {
|
||||
InterruptLock lock;
|
||||
uint64_t ret = 0;
|
||||
for (uint8_t i = 0; i < 8; i++) {
|
||||
for (uint8_t i = 0; i < 64; i++) {
|
||||
ret |= (uint64_t(this->read_bit_()) << i);
|
||||
}
|
||||
return ret;
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
namespace esphome {
|
||||
namespace gpio {
|
||||
|
||||
class GPIOSwitch : public switch_::Switch, public Component {
|
||||
class GPIOSwitch final : public switch_::Switch, public Component {
|
||||
public:
|
||||
void set_pin(GPIOPin *pin) { pin_ = pin; }
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
namespace esphome {
|
||||
namespace homeassistant {
|
||||
|
||||
class HomeassistantTime : public time::RealTimeClock {
|
||||
class HomeassistantTime final : public time::RealTimeClock {
|
||||
public:
|
||||
void setup() override;
|
||||
void update() override;
|
||||
|
||||
@@ -587,7 +587,7 @@ def _build_config_struct(
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
add_idf_component(
|
||||
name="esphome/esp-hub75",
|
||||
ref="0.3.2",
|
||||
ref="0.3.4",
|
||||
)
|
||||
|
||||
# Set compile-time configuration via build flags (so external library sees them)
|
||||
|
||||
@@ -185,7 +185,7 @@ ErrorCode IDFI2CBus::write_readv(uint8_t address, const uint8_t *write_buffer, s
|
||||
jobs[num_jobs++].command = I2C_MASTER_CMD_STOP;
|
||||
ESP_LOGV(TAG, "Sending %zu jobs", num_jobs);
|
||||
esp_err_t err = i2c_master_execute_defined_operations(this->dev_, jobs, num_jobs, 100);
|
||||
if (err == ESP_ERR_INVALID_STATE) {
|
||||
if (err == ESP_ERR_INVALID_STATE || err == ESP_ERR_INVALID_RESPONSE) {
|
||||
ESP_LOGV(TAG, "TX to %02X failed: not acked", address);
|
||||
return ERROR_NOT_ACKNOWLEDGED;
|
||||
} else if (err == ESP_ERR_TIMEOUT) {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
from esphome import pins
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.esp32 import (
|
||||
@@ -26,6 +28,9 @@ CODEOWNERS = ["@jesserockz"]
|
||||
DEPENDENCIES = ["esp32"]
|
||||
MULTI_CONF = True
|
||||
|
||||
CONF_PDM = "pdm"
|
||||
CONF_ADC_TYPE = "adc_type"
|
||||
|
||||
CONF_I2S_DOUT_PIN = "i2s_dout_pin"
|
||||
CONF_I2S_DIN_PIN = "i2s_din_pin"
|
||||
CONF_I2S_MCLK_PIN = "i2s_mclk_pin"
|
||||
@@ -254,7 +259,65 @@ CONFIG_SCHEMA = cv.All(
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class I2SAudioData:
|
||||
"""I2S audio component state stored in CORE.data."""
|
||||
|
||||
port_map: dict[str, int] = field(default_factory=dict)
|
||||
|
||||
|
||||
def _get_data() -> I2SAudioData:
|
||||
if CONF_I2S_AUDIO not in CORE.data:
|
||||
CORE.data[CONF_I2S_AUDIO] = I2SAudioData()
|
||||
return CORE.data[CONF_I2S_AUDIO]
|
||||
|
||||
|
||||
def _assign_ports() -> None:
|
||||
"""Assign I2S port numbers, prioritizing instances with microphone children.
|
||||
|
||||
Microphones (especially PDM) require port 0 on most ESP32 variants.
|
||||
This runs once and stores the mapping in CORE.data.
|
||||
"""
|
||||
data = _get_data()
|
||||
if data.port_map:
|
||||
return
|
||||
|
||||
full_config = fv.full_config.get()
|
||||
i2s_configs = full_config[CONF_I2S_AUDIO]
|
||||
|
||||
# Find i2s_audio instances with microphones that require port 0
|
||||
# (PDM and internal ADC only work on I2S port 0)
|
||||
port0_parent_id = None
|
||||
for mic_config in full_config.get("microphone", []):
|
||||
if CONF_I2S_AUDIO_ID not in mic_config:
|
||||
continue
|
||||
if mic_config.get(CONF_PDM) or mic_config.get(CONF_ADC_TYPE) == "internal":
|
||||
if port0_parent_id is not None:
|
||||
raise cv.Invalid(
|
||||
"Only one PDM/ADC microphone is supported (requires I2S port 0)"
|
||||
)
|
||||
port0_parent_id = str(mic_config[CONF_I2S_AUDIO_ID])
|
||||
|
||||
# Assign ports: port 0 parent first (if any), rest get sequential
|
||||
next_port = 0
|
||||
if port0_parent_id is not None:
|
||||
data.port_map[port0_parent_id] = next_port
|
||||
next_port += 1
|
||||
for config in i2s_configs:
|
||||
config_id = str(config[CONF_ID])
|
||||
if config_id != port0_parent_id:
|
||||
data.port_map[config_id] = next_port
|
||||
next_port += 1
|
||||
|
||||
|
||||
def _final_validate(_):
|
||||
from esphome.components.esp32 import idf_version
|
||||
|
||||
if use_legacy() and idf_version() >= cv.Version(6, 0, 0):
|
||||
raise cv.Invalid(
|
||||
"The legacy I2S driver is not available in ESP-IDF 6.0+. "
|
||||
"Set 'use_legacy: false' in i2s_audio configuration."
|
||||
)
|
||||
i2s_audio_configs = fv.full_config.get()[CONF_I2S_AUDIO]
|
||||
variant = get_esp32_variant()
|
||||
if variant not in I2S_PORTS:
|
||||
@@ -263,6 +326,7 @@ def _final_validate(_):
|
||||
raise cv.Invalid(
|
||||
f"Only {I2S_PORTS[variant]} I2S audio ports are supported on {variant}"
|
||||
)
|
||||
_assign_ports()
|
||||
|
||||
|
||||
def use_legacy():
|
||||
@@ -276,6 +340,12 @@ async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
# Assign I2S port from _final_validate computed mapping
|
||||
data = _get_data()
|
||||
if (port := data.port_map.get(str(config[CONF_ID]))) is None:
|
||||
raise ValueError(f"No I2S port assigned for {config[CONF_ID]}")
|
||||
cg.add(var.set_port(port))
|
||||
|
||||
# Re-enable ESP-IDF's I2S driver (excluded by default to save compile time)
|
||||
include_builtin_idf_component("esp_driver_i2s")
|
||||
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
#include "i2s_audio.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace i2s_audio {
|
||||
|
||||
static const char *const TAG = "i2s_audio";
|
||||
|
||||
void I2SAudioComponent::setup() {
|
||||
static i2s_port_t next_port_num = I2S_NUM_0;
|
||||
if (next_port_num >= SOC_I2S_NUM) {
|
||||
ESP_LOGE(TAG, "Too many components");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
this->port_ = next_port_num;
|
||||
next_port_num = (i2s_port_t) (next_port_num + 1);
|
||||
}
|
||||
|
||||
} // namespace i2s_audio
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include <esp_idf_version.h>
|
||||
#ifdef USE_I2S_LEGACY
|
||||
#include <driver/i2s.h>
|
||||
#else
|
||||
@@ -56,8 +57,6 @@ class I2SAudioOut : public I2SAudioBase {};
|
||||
|
||||
class I2SAudioComponent : public Component {
|
||||
public:
|
||||
void setup() override;
|
||||
|
||||
#ifdef USE_I2S_LEGACY
|
||||
i2s_pin_config_t get_pin_config() const {
|
||||
return {
|
||||
@@ -86,13 +85,17 @@ class I2SAudioComponent : public Component {
|
||||
void set_mclk_pin(int pin) { this->mclk_pin_ = pin; }
|
||||
void set_bclk_pin(int pin) { this->bclk_pin_ = pin; }
|
||||
void set_lrclk_pin(int pin) { this->lrclk_pin_ = pin; }
|
||||
void set_port(int port) { this->port_ = port; }
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0)
|
||||
int get_port() const { return this->port_; }
|
||||
#else
|
||||
i2s_port_t get_port() const { return static_cast<i2s_port_t>(this->port_); }
|
||||
#endif
|
||||
|
||||
void lock() { this->lock_.lock(); }
|
||||
bool try_lock() { return this->lock_.try_lock(); }
|
||||
void unlock() { this->lock_.unlock(); }
|
||||
|
||||
i2s_port_t get_port() const { return this->port_; }
|
||||
|
||||
protected:
|
||||
Mutex lock_;
|
||||
|
||||
@@ -106,7 +109,7 @@ class I2SAudioComponent : public Component {
|
||||
int bclk_pin_{I2S_GPIO_UNUSED};
|
||||
#endif
|
||||
int lrclk_pin_;
|
||||
i2s_port_t port_{};
|
||||
int port_{};
|
||||
};
|
||||
|
||||
} // namespace i2s_audio
|
||||
|
||||
@@ -13,9 +13,11 @@ from esphome.const import (
|
||||
)
|
||||
|
||||
from .. import (
|
||||
CONF_ADC_TYPE,
|
||||
CONF_I2S_DIN_PIN,
|
||||
CONF_LEFT,
|
||||
CONF_MONO,
|
||||
CONF_PDM,
|
||||
CONF_RIGHT,
|
||||
I2SAudioIn,
|
||||
i2s_audio_component_schema,
|
||||
@@ -29,9 +31,7 @@ CODEOWNERS = ["@jesserockz"]
|
||||
DEPENDENCIES = ["i2s_audio"]
|
||||
|
||||
CONF_ADC_PIN = "adc_pin"
|
||||
CONF_ADC_TYPE = "adc_type"
|
||||
CONF_CORRECT_DC_OFFSET = "correct_dc_offset"
|
||||
CONF_PDM = "pdm"
|
||||
|
||||
I2SAudioMicrophone = i2s_audio_ns.class_(
|
||||
"I2SAudioMicrophone", I2SAudioIn, microphone.Microphone, cg.Component
|
||||
|
||||
@@ -37,27 +37,6 @@ enum MicrophoneEventGroupBits : uint32_t {
|
||||
};
|
||||
|
||||
void I2SAudioMicrophone::setup() {
|
||||
#ifdef USE_I2S_LEGACY
|
||||
#if SOC_I2S_SUPPORTS_ADC
|
||||
if (this->adc_) {
|
||||
if (this->parent_->get_port() != I2S_NUM_0) {
|
||||
ESP_LOGE(TAG, "Internal ADC only works on I2S0");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
} else
|
||||
#endif
|
||||
#endif
|
||||
{
|
||||
if (this->pdm_) {
|
||||
if (this->parent_->get_port() != I2S_NUM_0) {
|
||||
ESP_LOGE(TAG, "PDM only works on I2S0");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this->active_listeners_semaphore_ = xSemaphoreCreateCounting(MAX_LISTENERS, MAX_LISTENERS);
|
||||
if (this->active_listeners_semaphore_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Creating semaphore failed");
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include <driver/gpio.h>
|
||||
#include <driver/ledc.h>
|
||||
#include <cinttypes>
|
||||
#include <esp_idf_version.h>
|
||||
@@ -189,7 +190,7 @@ void LEDCOutput::setup() {
|
||||
this->phase_angle_, hpoint);
|
||||
|
||||
ledc_channel_config_t chan_conf{};
|
||||
chan_conf.gpio_num = this->pin_->get_pin();
|
||||
chan_conf.gpio_num = static_cast<gpio_num_t>(this->pin_->get_pin());
|
||||
chan_conf.speed_mode = speed_mode;
|
||||
chan_conf.channel = chan_num;
|
||||
chan_conf.intr_type = LEDC_INTR_DISABLE;
|
||||
|
||||
@@ -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(); }
|
||||
|
||||
@@ -42,7 +42,7 @@ void LilygoT547Touchscreen::setup() {
|
||||
this->x_raw_max_ = this->display_->get_native_width();
|
||||
}
|
||||
if (this->y_raw_max_ == this->y_raw_min_) {
|
||||
this->x_raw_max_ = this->display_->get_native_height();
|
||||
this->y_raw_max_ = this->display_->get_native_height();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -64,6 +64,10 @@ void LilygoT547Touchscreen::update_touches() {
|
||||
}
|
||||
|
||||
point = buffer[5] & 0xF;
|
||||
if (point > 2) {
|
||||
ESP_LOGW(TAG, "Invalid touch point count: %d", point);
|
||||
point = 2;
|
||||
}
|
||||
|
||||
if (point == 1) {
|
||||
err = this->write_register(TOUCH_REGISTER, READ_TOUCH, 1);
|
||||
|
||||
@@ -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
|
||||
@@ -32,7 +32,7 @@
|
||||
namespace esphome {
|
||||
namespace md5 {
|
||||
|
||||
class MD5Digest : public HashBase {
|
||||
class MD5Digest final : public HashBase {
|
||||
public:
|
||||
MD5Digest() = default;
|
||||
~MD5Digest() override;
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esp_lcd_panel_rgb.h"
|
||||
#include <driver/gpio.h>
|
||||
#include <esp_lcd_panel_rgb.h>
|
||||
#include <span>
|
||||
|
||||
namespace esphome {
|
||||
@@ -153,18 +154,18 @@ void MipiRgb::common_setup_() {
|
||||
config.clk_src = LCD_CLK_SRC_PLL160M;
|
||||
size_t data_pin_count = sizeof(this->data_pins_) / sizeof(this->data_pins_[0]);
|
||||
for (size_t i = 0; i != data_pin_count; i++) {
|
||||
config.data_gpio_nums[i] = this->data_pins_[i]->get_pin();
|
||||
config.data_gpio_nums[i] = static_cast<gpio_num_t>(this->data_pins_[i]->get_pin());
|
||||
}
|
||||
config.data_width = data_pin_count;
|
||||
config.disp_gpio_num = -1;
|
||||
config.hsync_gpio_num = this->hsync_pin_->get_pin();
|
||||
config.vsync_gpio_num = this->vsync_pin_->get_pin();
|
||||
config.disp_gpio_num = GPIO_NUM_NC;
|
||||
config.hsync_gpio_num = static_cast<gpio_num_t>(this->hsync_pin_->get_pin());
|
||||
config.vsync_gpio_num = static_cast<gpio_num_t>(this->vsync_pin_->get_pin());
|
||||
if (this->de_pin_) {
|
||||
config.de_gpio_num = this->de_pin_->get_pin();
|
||||
config.de_gpio_num = static_cast<gpio_num_t>(this->de_pin_->get_pin());
|
||||
} else {
|
||||
config.de_gpio_num = -1;
|
||||
config.de_gpio_num = GPIO_NUM_NC;
|
||||
}
|
||||
config.pclk_gpio_num = this->pclk_pin_->get_pin();
|
||||
config.pclk_gpio_num = static_cast<gpio_num_t>(this->pclk_pin_->get_pin());
|
||||
esp_err_t err = esp_lcd_new_rgb_panel(&config, &this->handle_);
|
||||
if (err == ESP_OK)
|
||||
err = esp_lcd_panel_reset(this->handle_);
|
||||
|
||||
@@ -3,7 +3,9 @@ from esphome.automation import Condition
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import logger, socket
|
||||
from esphome.components.esp32 import (
|
||||
add_idf_component,
|
||||
add_idf_sdkconfig_option,
|
||||
idf_version,
|
||||
include_builtin_idf_component,
|
||||
)
|
||||
from esphome.config_helpers import filter_source_files_from_platform
|
||||
@@ -351,7 +353,11 @@ async def to_code(config):
|
||||
if CORE.is_esp32:
|
||||
socket.require_wake_loop_threadsafe()
|
||||
# Re-enable ESP-IDF's mqtt component (excluded by default to save compile time)
|
||||
include_builtin_idf_component("mqtt")
|
||||
# IDF 6.0 moved esp-mqtt to an external component
|
||||
if idf_version() >= cv.Version(6, 0, 0):
|
||||
add_idf_component(name="espressif/mqtt", ref="1.0.0")
|
||||
else:
|
||||
include_builtin_idf_component("mqtt")
|
||||
|
||||
cg.add_define("USE_MQTT")
|
||||
cg.add_global(mqtt_ns.using)
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
namespace esphome {
|
||||
namespace preferences {
|
||||
|
||||
class IntervalSyncer : public Component {
|
||||
class IntervalSyncer final : public Component {
|
||||
public:
|
||||
void set_write_interval(uint32_t write_interval) { this->write_interval_ = write_interval; }
|
||||
void setup() override {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef HAS_PCNT
|
||||
#include <driver/gpio.h>
|
||||
#include <esp_private/esp_clk.h>
|
||||
#include <hal/pcnt_ll.h>
|
||||
#endif
|
||||
@@ -76,8 +77,8 @@ bool HwPulseCounterStorage::pulse_counter_setup(InternalGPIOPin *pin) {
|
||||
}
|
||||
|
||||
pcnt_chan_config_t chan_config = {
|
||||
.edge_gpio_num = this->pin->get_pin(),
|
||||
.level_gpio_num = -1,
|
||||
.edge_gpio_num = static_cast<gpio_num_t>(this->pin->get_pin()),
|
||||
.level_gpio_num = GPIO_NUM_NC,
|
||||
};
|
||||
error = pcnt_new_channel(this->pcnt_unit, &chan_config, &this->pcnt_channel);
|
||||
if (error != ESP_OK) {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
namespace esphome {
|
||||
namespace restart {
|
||||
|
||||
class RestartButton : public button::Button, public Component {
|
||||
class RestartButton final : public button::Button, public Component {
|
||||
public:
|
||||
void dump_config() override;
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
#if defined(USE_WIFI)
|
||||
#include <WiFi.h>
|
||||
#include <pico/cyw43_arch.h> // For cyw43_arch_lwip_begin/end (LwIPLock)
|
||||
#elif defined(USE_ETHERNET)
|
||||
#include <LwipEthernet.h> // For ethernet_arch_lwip_begin/end (LwIPLock)
|
||||
#endif
|
||||
#include <hardware/structs/rosc.h>
|
||||
#include <hardware/sync.h>
|
||||
@@ -35,28 +37,32 @@ 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_); }
|
||||
|
||||
// On RP2040 (Pico W), arduino-pico sets PICO_CYW43_ARCH_THREADSAFE_BACKGROUND=1.
|
||||
// This means lwip callbacks run from a low-priority user IRQ context, not the
|
||||
// On RP2040, lwip callbacks run from a low-priority user IRQ context, not the
|
||||
// main loop (see low_priority_irq_handler() in pico-sdk
|
||||
// async_context_threadsafe_background.c). cyw43_arch_lwip_begin/end acquires the
|
||||
// async_context recursive mutex to prevent IRQ callbacks from firing during
|
||||
// critical sections. See esphome#10681.
|
||||
// async_context_threadsafe_background.c). This applies to both WiFi (CYW43) and
|
||||
// Ethernet (W5500) — both use async_context_threadsafe_background.
|
||||
//
|
||||
// When CYW43 is not available (non-WiFi RP2040 boards), this is a no-op since
|
||||
// Without locking, recv_fn() from IRQ context races with read_locked_() on the
|
||||
// main loop, corrupting the shared rx_buf_ pbuf chain (use-after-free, pbuf_cat
|
||||
// assertion failures). See esphome#10681.
|
||||
//
|
||||
// WiFi uses cyw43_arch_lwip_begin/end; Ethernet uses ethernet_arch_lwip_begin/end.
|
||||
// Both acquire the async_context recursive mutex to prevent IRQ callbacks from
|
||||
// firing during critical sections.
|
||||
//
|
||||
// When neither WiFi nor Ethernet is configured, this is a no-op since
|
||||
// there's no network stack and no lwip callbacks to race with.
|
||||
#if defined(USE_WIFI)
|
||||
LwIPLock::LwIPLock() { cyw43_arch_lwip_begin(); }
|
||||
LwIPLock::~LwIPLock() { cyw43_arch_lwip_end(); }
|
||||
#elif defined(USE_ETHERNET)
|
||||
LwIPLock::LwIPLock() { ethernet_arch_lwip_begin(); }
|
||||
LwIPLock::~LwIPLock() { ethernet_arch_lwip_end(); }
|
||||
#else
|
||||
LwIPLock::LwIPLock() {}
|
||||
LwIPLock::~LwIPLock() {}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "rpi_dpi_rgb.h"
|
||||
#include "esphome/core/gpio.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <driver/gpio.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace rpi_dpi_rgb {
|
||||
@@ -25,14 +26,14 @@ void RpiDpiRgb::setup() {
|
||||
config.clk_src = LCD_CLK_SRC_PLL160M;
|
||||
size_t data_pin_count = sizeof(this->data_pins_) / sizeof(this->data_pins_[0]);
|
||||
for (size_t i = 0; i != data_pin_count; i++) {
|
||||
config.data_gpio_nums[i] = this->data_pins_[i]->get_pin();
|
||||
config.data_gpio_nums[i] = static_cast<gpio_num_t>(this->data_pins_[i]->get_pin());
|
||||
}
|
||||
config.data_width = data_pin_count;
|
||||
config.disp_gpio_num = -1;
|
||||
config.hsync_gpio_num = this->hsync_pin_->get_pin();
|
||||
config.vsync_gpio_num = this->vsync_pin_->get_pin();
|
||||
config.de_gpio_num = this->de_pin_->get_pin();
|
||||
config.pclk_gpio_num = this->pclk_pin_->get_pin();
|
||||
config.disp_gpio_num = GPIO_NUM_NC;
|
||||
config.hsync_gpio_num = static_cast<gpio_num_t>(this->hsync_pin_->get_pin());
|
||||
config.vsync_gpio_num = static_cast<gpio_num_t>(this->vsync_pin_->get_pin());
|
||||
config.de_gpio_num = static_cast<gpio_num_t>(this->de_pin_->get_pin());
|
||||
config.pclk_gpio_num = static_cast<gpio_num_t>(this->pclk_pin_->get_pin());
|
||||
esp_err_t err = esp_lcd_new_rgb_panel(&config, &this->handle_);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "lcd_new_rgb_panel failed: %s", esp_err_to_name(err));
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -68,7 +68,7 @@ void RX8130Component::dump_config() {
|
||||
void RX8130Component::read_time() {
|
||||
uint8_t date[7];
|
||||
if (this->read_register(RX8130_REG_SEC, date, 7) != i2c::ERROR_OK) {
|
||||
this->status_set_warning(ESP_LOG_MSG_COMM_FAIL);
|
||||
this->status_set_warning(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
|
||||
return;
|
||||
}
|
||||
ESPTime rtc_time{
|
||||
@@ -109,7 +109,7 @@ void RX8130Component::write_time() {
|
||||
buff[6] = dec2bcd(now.year % 100);
|
||||
this->stop_(true);
|
||||
if (this->write_register(RX8130_REG_SEC, buff, 7) != i2c::ERROR_OK) {
|
||||
this->status_set_warning(ESP_LOG_MSG_COMM_FAIL);
|
||||
this->status_set_warning(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Wrote UTC time: %04d-%02d-%02d %02d:%02d:%02d", now.year, now.month, now.day_of_month, now.hour,
|
||||
now.minute, now.second);
|
||||
@@ -120,7 +120,7 @@ void RX8130Component::write_time() {
|
||||
void RX8130Component::stop_(bool stop) {
|
||||
const uint8_t data = stop ? RX8130_BIT_CTRL_STOP : RX8130_CLEAR_FLAGS;
|
||||
if (this->write_register(RX8130_REG_CTRL0, &data, 1) != i2c::ERROR_OK) {
|
||||
this->status_set_warning(ESP_LOG_MSG_COMM_FAIL);
|
||||
this->status_set_warning(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
namespace esphome {
|
||||
namespace safe_mode {
|
||||
|
||||
class SafeModeButton : public button::Button, public Component {
|
||||
class SafeModeButton final : public button::Button, public Component {
|
||||
public:
|
||||
void dump_config() override;
|
||||
void set_safe_mode(SafeModeComponent *safe_mode_component);
|
||||
|
||||
@@ -297,19 +297,17 @@ void MR24HPC1Component::r24_split_data_frame_(uint8_t value) {
|
||||
this->sg_recv_data_state_ = FRAME_DATA_LEN_H;
|
||||
break;
|
||||
case FRAME_DATA_LEN_H:
|
||||
if (value <= 4) {
|
||||
this->sg_data_len_ = value * 256;
|
||||
if (value == 0) {
|
||||
this->sg_frame_buf_[4] = value;
|
||||
this->sg_recv_data_state_ = FRAME_DATA_LEN_L;
|
||||
} else {
|
||||
this->sg_data_len_ = 0;
|
||||
this->sg_recv_data_state_ = FRAME_IDLE;
|
||||
ESP_LOGD(TAG, "FRAME_DATA_LEN_H ERROR value:%x", value);
|
||||
}
|
||||
break;
|
||||
case FRAME_DATA_LEN_L:
|
||||
this->sg_data_len_ += value;
|
||||
if (this->sg_data_len_ > 32) {
|
||||
this->sg_data_len_ = value;
|
||||
if (this->sg_data_len_ == 0 || this->sg_data_len_ > 32) {
|
||||
ESP_LOGD(TAG, "len=%d, FRAME_DATA_LEN_L ERROR value:%x", this->sg_data_len_, value);
|
||||
this->sg_data_len_ = 0;
|
||||
this->sg_recv_data_state_ = FRAME_IDLE;
|
||||
@@ -320,9 +318,8 @@ void MR24HPC1Component::r24_split_data_frame_(uint8_t value) {
|
||||
}
|
||||
break;
|
||||
case FRAME_DATA_BYTES:
|
||||
this->sg_data_len_ -= 1;
|
||||
this->sg_frame_buf_[this->sg_frame_len_++] = value;
|
||||
if (this->sg_data_len_ <= 0) {
|
||||
if (--this->sg_data_len_ == 0) {
|
||||
this->sg_recv_data_state_ = FRAME_DATA_CRC;
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace esphome::sha256 {
|
||||
/// hasher.init();
|
||||
/// hasher.add(data, len);
|
||||
/// hasher.calculate();
|
||||
class SHA256 : public esphome::HashBase {
|
||||
class SHA256 final : public esphome::HashBase {
|
||||
public:
|
||||
SHA256() = default;
|
||||
~SHA256() override;
|
||||
|
||||
@@ -134,7 +134,8 @@ void socket_wake() {
|
||||
// code (CONT context) — they never preempt each other, so no locking is needed.
|
||||
//
|
||||
// esphome::LwIPLock is the platform-provided RAII guard (see helpers.h/helpers.cpp).
|
||||
// On RP2040, it acquires cyw43_arch_lwip_begin/end. On ESP8266, it's a no-op.
|
||||
// On RP2040, it acquires cyw43_arch_lwip_begin/end (WiFi) or ethernet_arch_lwip_begin/end
|
||||
// (Ethernet). On ESP8266, it's a no-op.
|
||||
#define LWIP_LOCK() esphome::LwIPLock lwip_lock_guard // NOLINT
|
||||
|
||||
static const char *const TAG = "socket.lwip";
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "st7701s.h"
|
||||
#include "esphome/core/gpio.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <driver/gpio.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace st7701s {
|
||||
@@ -27,14 +28,14 @@ void ST7701S::setup() {
|
||||
config.clk_src = LCD_CLK_SRC_PLL160M;
|
||||
size_t data_pin_count = sizeof(this->data_pins_) / sizeof(this->data_pins_[0]);
|
||||
for (size_t i = 0; i != data_pin_count; i++) {
|
||||
config.data_gpio_nums[i] = this->data_pins_[i]->get_pin();
|
||||
config.data_gpio_nums[i] = static_cast<gpio_num_t>(this->data_pins_[i]->get_pin());
|
||||
}
|
||||
config.data_width = data_pin_count;
|
||||
config.disp_gpio_num = -1;
|
||||
config.hsync_gpio_num = this->hsync_pin_->get_pin();
|
||||
config.vsync_gpio_num = this->vsync_pin_->get_pin();
|
||||
config.de_gpio_num = this->de_pin_->get_pin();
|
||||
config.pclk_gpio_num = this->pclk_pin_->get_pin();
|
||||
config.disp_gpio_num = GPIO_NUM_NC;
|
||||
config.hsync_gpio_num = static_cast<gpio_num_t>(this->hsync_pin_->get_pin());
|
||||
config.vsync_gpio_num = static_cast<gpio_num_t>(this->vsync_pin_->get_pin());
|
||||
config.de_gpio_num = static_cast<gpio_num_t>(this->de_pin_->get_pin());
|
||||
config.pclk_gpio_num = static_cast<gpio_num_t>(this->pclk_pin_->get_pin());
|
||||
esp_err_t err = esp_lcd_new_rgb_panel(&config, &this->handle_);
|
||||
if (err != ESP_OK) {
|
||||
esph_log_e(TAG, "lcd_new_rgb_panel failed: %s", esp_err_to_name(err));
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -416,7 +416,7 @@ void USBUartTypeCdcAcm::on_connected() {
|
||||
for (auto *channel : this->channels_) {
|
||||
if (i == cdc_devs.size()) {
|
||||
ESP_LOGE(TAG, "No configuration found for channel %d", channel->index_);
|
||||
this->status_set_warning("No configuration found for channel");
|
||||
this->status_set_warning(LOG_STR("No configuration found for channel"));
|
||||
break;
|
||||
}
|
||||
channel->cdc_dev_ = cdc_devs[i++];
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
namespace esphome::version {
|
||||
|
||||
class VersionTextSensor : public text_sensor::TextSensor, public Component {
|
||||
class VersionTextSensor final : public text_sensor::TextSensor, public Component {
|
||||
public:
|
||||
void set_hide_hash(bool hide_hash);
|
||||
void set_hide_timestamp(bool hide_timestamp);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -399,7 +399,7 @@ class WiFiPowerSaveListener {
|
||||
};
|
||||
|
||||
/// This component is responsible for managing the ESP WiFi interface.
|
||||
class WiFiComponent : public Component {
|
||||
class WiFiComponent final : public Component {
|
||||
public:
|
||||
/// Construct a WiFiComponent.
|
||||
WiFiComponent();
|
||||
|
||||
@@ -90,7 +90,7 @@ void ZWaveProxy::process_uart_() {
|
||||
while (this->available()) {
|
||||
uint8_t byte;
|
||||
if (!this->read_byte(&byte)) {
|
||||
this->status_set_warning("UART read failed");
|
||||
this->status_set_warning(LOG_STR("UART read failed"));
|
||||
return;
|
||||
}
|
||||
if (this->parse_byte_(byte)) {
|
||||
|
||||
@@ -314,7 +314,7 @@ class Version:
|
||||
|
||||
@classmethod
|
||||
def parse(cls, value: str) -> Version:
|
||||
match = re.match(r"^(\d+).(\d+).(\d+)-?(\w*)$", value)
|
||||
match = re.match(r"^(\d+).(\d+).(\d+)[-.]?(\w*)$", value)
|
||||
if match is None:
|
||||
raise ValueError(f"Not a valid version number {value}")
|
||||
major = int(match[1])
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -392,6 +392,7 @@ bool Component::set_status_flag_(uint8_t flag) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void Component::status_set_warning() { this->status_set_warning((const LogString *) nullptr); }
|
||||
void Component::status_set_warning(const char *message) {
|
||||
if (!this->set_status_flag_(STATUS_LED_WARNING))
|
||||
return;
|
||||
@@ -509,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);
|
||||
@@ -523,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.
|
||||
@@ -534,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"
|
||||
@@ -240,7 +241,8 @@ class Component {
|
||||
|
||||
bool status_has_error() const { return this->component_state_ & STATUS_LED_ERROR; }
|
||||
|
||||
void status_set_warning(const char *message = nullptr);
|
||||
void status_set_warning(); // Set warning flag without message
|
||||
void status_set_warning(const char *message);
|
||||
void status_set_warning(const LogString *message);
|
||||
|
||||
void status_set_error(); // Set error flag without message
|
||||
@@ -574,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:
|
||||
@@ -591,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;
|
||||
|
||||
@@ -600,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
|
||||
|
||||
@@ -278,6 +278,14 @@
|
||||
#define USE_ETHERNET_JL1101
|
||||
#define USE_ETHERNET_KSZ8081
|
||||
#define USE_ETHERNET_LAN8670
|
||||
#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
|
||||
#define USE_ETHERNET_MANUAL_IP
|
||||
#define USE_ETHERNET_IP_STATE_LISTENERS
|
||||
#define USE_ETHERNET_CONNECT_TRIGGER
|
||||
@@ -345,6 +353,12 @@
|
||||
#define USE_SOCKET_IMPL_LWIP_TCP
|
||||
#define USE_RP2040_BLE
|
||||
#define USE_SPI
|
||||
#ifndef USE_ETHERNET
|
||||
#define USE_ETHERNET
|
||||
#endif
|
||||
#ifndef USE_ETHERNET_SPI
|
||||
#define USE_ETHERNET_SPI
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef USE_LIBRETINY
|
||||
|
||||
+21
-6
@@ -1866,19 +1866,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,13 +31,49 @@ 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:
|
||||
- if: "target in [esp32s2, esp32s3, esp32p4]"
|
||||
esphome/esp-hub75:
|
||||
version: 0.3.2
|
||||
version: 0.3.4
|
||||
rules:
|
||||
- if: "target in [esp32, esp32s2, esp32s3, esp32c6, esp32p4]"
|
||||
espressif/mqtt:
|
||||
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
|
||||
|
||||
@@ -7,17 +7,26 @@ import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
from esphome import config_validation as cv
|
||||
from esphome.components.esp32 import PLATFORM_VERSION_LOOKUP
|
||||
from esphome.helpers import write_file_if_changed
|
||||
|
||||
ver = PLATFORM_VERSION_LOOKUP["recommended"]
|
||||
version_str = f"{ver.major}.{ver.minor:02d}.{ver.patch:02d}"
|
||||
root = Path(__file__).parent.parent
|
||||
boards_file_path = root / "esphome" / "components" / "esp32" / "boards.py"
|
||||
|
||||
|
||||
def get_boards():
|
||||
with tempfile.TemporaryDirectory() as tempdir:
|
||||
if isinstance(ver, cv.Version):
|
||||
branch = f"{ver.major}.{ver.minor:02d}.{ver.patch:02d}"
|
||||
if ver.extra:
|
||||
branch += f"-{ver.extra}"
|
||||
repo = "https://github.com/pioarduino/platform-espressif32"
|
||||
else:
|
||||
# URL format: "https://github.com/user/repo.git#branch"
|
||||
url = str(ver)
|
||||
repo, branch = url.rsplit("#", 1) if "#" in url else (url, "main")
|
||||
subprocess.run(
|
||||
[
|
||||
"git",
|
||||
@@ -28,8 +37,8 @@ def get_boards():
|
||||
"--depth",
|
||||
"1",
|
||||
"--branch",
|
||||
f"{ver.major}.{ver.minor:02d}.{ver.patch:02d}",
|
||||
"https://github.com/pioarduino/platform-espressif32",
|
||||
branch,
|
||||
repo,
|
||||
tempdir,
|
||||
],
|
||||
check=True,
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
ethernet:
|
||||
type: W5500
|
||||
clk_pin: 18
|
||||
mosi_pin: 19
|
||||
miso_pin: 16
|
||||
cs_pin: 17
|
||||
interrupt_pin: 21
|
||||
reset_pin: 20
|
||||
manual_ip:
|
||||
static_ip: 192.168.178.56
|
||||
gateway: 192.168.178.1
|
||||
subnet: 255.255.255.0
|
||||
domain: .local
|
||||
mac_address: "02:AA:BB:CC:DD:01"
|
||||
on_connect:
|
||||
- logger.log: "Ethernet connected!"
|
||||
on_disconnect:
|
||||
- logger.log: "Ethernet disconnected!"
|
||||
@@ -0,0 +1 @@
|
||||
<<: !include common-w5500-rp2040.yaml
|
||||
@@ -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));
|
||||
@@ -3,7 +3,7 @@ esphome:
|
||||
|
||||
host:
|
||||
api:
|
||||
batch_delay: 0ms # Disable batching to receive all state updates
|
||||
batch_delay: 0ms # Disable batching to receive all state updates
|
||||
logger:
|
||||
level: DEBUG
|
||||
|
||||
@@ -15,8 +15,8 @@ sensor:
|
||||
|
||||
- platform: copy
|
||||
source_id: source_nan_sensor
|
||||
name: "Min NaN Sensor"
|
||||
id: min_nan_sensor
|
||||
name: "Min NaN"
|
||||
id: min_nan
|
||||
filters:
|
||||
- min:
|
||||
window_size: 5
|
||||
@@ -25,8 +25,8 @@ sensor:
|
||||
|
||||
- platform: copy
|
||||
source_id: source_nan_sensor
|
||||
name: "Max NaN Sensor"
|
||||
id: max_nan_sensor
|
||||
name: "Max NaN"
|
||||
id: max_nan
|
||||
filters:
|
||||
- max:
|
||||
window_size: 5
|
||||
@@ -42,7 +42,7 @@ script:
|
||||
- delay: 20ms
|
||||
- sensor.template.publish:
|
||||
id: source_nan_sensor
|
||||
state: !lambda 'return NAN;'
|
||||
state: !lambda "return NAN;"
|
||||
- delay: 20ms
|
||||
- sensor.template.publish:
|
||||
id: source_nan_sensor
|
||||
@@ -50,7 +50,7 @@ script:
|
||||
- delay: 20ms
|
||||
- sensor.template.publish:
|
||||
id: source_nan_sensor
|
||||
state: !lambda 'return NAN;'
|
||||
state: !lambda "return NAN;"
|
||||
- delay: 20ms
|
||||
- sensor.template.publish:
|
||||
id: source_nan_sensor
|
||||
@@ -62,7 +62,7 @@ script:
|
||||
- delay: 20ms
|
||||
- sensor.template.publish:
|
||||
id: source_nan_sensor
|
||||
state: !lambda 'return NAN;'
|
||||
state: !lambda "return NAN;"
|
||||
- delay: 20ms
|
||||
- sensor.template.publish:
|
||||
id: source_nan_sensor
|
||||
@@ -74,7 +74,7 @@ script:
|
||||
- delay: 20ms
|
||||
- sensor.template.publish:
|
||||
id: source_nan_sensor
|
||||
state: !lambda 'return NAN;'
|
||||
state: !lambda "return NAN;"
|
||||
|
||||
button:
|
||||
- platform: template
|
||||
|
||||
@@ -3,7 +3,7 @@ esphome:
|
||||
|
||||
host:
|
||||
api:
|
||||
batch_delay: 0ms # Disable batching to receive all state updates
|
||||
batch_delay: 0ms # Disable batching to receive all state updates
|
||||
logger:
|
||||
level: DEBUG
|
||||
|
||||
@@ -18,8 +18,8 @@ sensor:
|
||||
# Window of 5, send every 2 values
|
||||
- platform: copy
|
||||
source_id: source_sensor
|
||||
name: "Sliding Min Sensor"
|
||||
id: sliding_min_sensor
|
||||
name: "Sliding Min"
|
||||
id: sliding_min
|
||||
filters:
|
||||
- min:
|
||||
window_size: 5
|
||||
@@ -28,8 +28,8 @@ sensor:
|
||||
|
||||
- platform: copy
|
||||
source_id: source_sensor
|
||||
name: "Sliding Max Sensor"
|
||||
id: sliding_max_sensor
|
||||
name: "Sliding Max"
|
||||
id: sliding_max
|
||||
filters:
|
||||
- max:
|
||||
window_size: 5
|
||||
@@ -38,8 +38,8 @@ sensor:
|
||||
|
||||
- platform: copy
|
||||
source_id: source_sensor
|
||||
name: "Sliding Median Sensor"
|
||||
id: sliding_median_sensor
|
||||
name: "Sliding Median"
|
||||
id: sliding_median
|
||||
filters:
|
||||
- median:
|
||||
window_size: 5
|
||||
@@ -48,8 +48,8 @@ sensor:
|
||||
|
||||
- platform: copy
|
||||
source_id: source_sensor
|
||||
name: "Sliding Moving Avg Sensor"
|
||||
id: sliding_moving_avg_sensor
|
||||
name: "Sliding Moving Avg"
|
||||
id: sliding_moving_avg
|
||||
filters:
|
||||
- sliding_window_moving_average:
|
||||
window_size: 5
|
||||
|
||||
@@ -3,20 +3,20 @@ esphome:
|
||||
|
||||
host:
|
||||
api:
|
||||
batch_delay: 0ms # Disable batching to receive all state updates
|
||||
batch_delay: 0ms # Disable batching to receive all state updates
|
||||
logger:
|
||||
level: DEBUG
|
||||
|
||||
sensor:
|
||||
- platform: template
|
||||
name: "Source Wraparound Sensor"
|
||||
name: "Source Wraparound"
|
||||
id: source_wraparound
|
||||
accuracy_decimals: 2
|
||||
|
||||
- platform: copy
|
||||
source_id: source_wraparound
|
||||
name: "Wraparound Min Sensor"
|
||||
id: wraparound_min_sensor
|
||||
name: "Wraparound Min"
|
||||
id: wraparound_min
|
||||
filters:
|
||||
- min:
|
||||
window_size: 3
|
||||
|
||||
@@ -88,7 +88,7 @@ def build_key_to_entity_mapping(
|
||||
|
||||
Args:
|
||||
entities: List of entity info objects from the API
|
||||
entity_names: List of entity names to search for in object_ids
|
||||
entity_names: List of entity names to match exactly against object_ids
|
||||
|
||||
Returns:
|
||||
Dictionary mapping entity keys to entity names
|
||||
@@ -97,7 +97,7 @@ def build_key_to_entity_mapping(
|
||||
for entity in entities:
|
||||
obj_id = entity.object_id.lower()
|
||||
for entity_name in entity_names:
|
||||
if entity_name in obj_id:
|
||||
if entity_name == obj_id:
|
||||
key_to_entity[entity.key] = entity_name
|
||||
break
|
||||
return key_to_entity
|
||||
|
||||
Reference in New Issue
Block a user