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

This commit is contained in:
J. Nick Koston
2025-12-01 14:52:51 -06:00
57 changed files with 400 additions and 175 deletions

View File

@@ -1,7 +1,6 @@
#include "micronova_button.h"
namespace esphome {
namespace micronova {
namespace esphome::micronova {
void MicroNovaButton::press_action() {
switch (this->get_function()) {
@@ -14,5 +13,4 @@ void MicroNovaButton::press_action() {
this->micronova_->update();
}
} // namespace micronova
} // namespace esphome
} // namespace esphome::micronova

View File

@@ -4,8 +4,7 @@
#include "esphome/core/component.h"
#include "esphome/components/button/button.h"
namespace esphome {
namespace micronova {
namespace esphome::micronova {
class MicroNovaButton : public Component, public button::Button, public MicroNovaButtonListener {
public:
@@ -19,5 +18,4 @@ class MicroNovaButton : public Component, public button::Button, public MicroNov
void press_action() override;
};
} // namespace micronova
} // namespace esphome
} // namespace esphome::micronova

View File

@@ -1,8 +1,7 @@
#include "micronova.h"
#include "esphome/core/log.h"
namespace esphome {
namespace micronova {
namespace esphome::micronova {
void MicroNova::setup() {
if (this->enable_rx_pin_ != nullptr) {
@@ -144,5 +143,4 @@ void MicroNova::write_address(uint8_t location, uint8_t address, uint8_t data) {
}
}
} // namespace micronova
} // namespace esphome
} // namespace esphome::micronova

View File

@@ -8,8 +8,7 @@
#include <vector>
namespace esphome {
namespace micronova {
namespace esphome::micronova {
static const char *const TAG = "micronova";
static const int STOVE_REPLY_DELAY = 60;
@@ -160,5 +159,4 @@ class MicroNova : public PollingComponent, public uart::UARTDevice {
MicroNovaSwitchListener *stove_switch_{nullptr};
};
} // namespace micronova
} // namespace esphome
} // namespace esphome::micronova

View File

@@ -1,7 +1,6 @@
#include "micronova_number.h"
namespace esphome {
namespace micronova {
namespace esphome::micronova {
void MicroNovaNumber::process_value_from_stove(int value_from_stove) {
float new_sensor_value = 0;
@@ -41,5 +40,4 @@ void MicroNovaNumber::control(float value) {
this->micronova_->update();
}
} // namespace micronova
} // namespace esphome
} // namespace esphome::micronova

View File

@@ -3,8 +3,7 @@
#include "esphome/components/micronova/micronova.h"
#include "esphome/components/number/number.h"
namespace esphome {
namespace micronova {
namespace esphome::micronova {
class MicroNovaNumber : public number::Number, public MicroNovaSensorListener {
public:
@@ -24,5 +23,4 @@ class MicroNovaNumber : public number::Number, public MicroNovaSensorListener {
uint8_t memory_write_location_ = 0;
};
} // namespace micronova
} // namespace esphome
} // namespace esphome::micronova

View File

@@ -1,7 +1,6 @@
#include "micronova_sensor.h"
namespace esphome {
namespace micronova {
namespace esphome::micronova {
void MicroNovaSensor::process_value_from_stove(int value_from_stove) {
if (value_from_stove == -1) {
@@ -31,5 +30,4 @@ void MicroNovaSensor::process_value_from_stove(int value_from_stove) {
this->publish_state(new_sensor_value);
}
} // namespace micronova
} // namespace esphome
} // namespace esphome::micronova

View File

@@ -3,8 +3,7 @@
#include "esphome/components/micronova/micronova.h"
#include "esphome/components/sensor/sensor.h"
namespace esphome {
namespace micronova {
namespace esphome::micronova {
class MicroNovaSensor : public sensor::Sensor, public MicroNovaSensorListener {
public:
@@ -23,5 +22,4 @@ class MicroNovaSensor : public sensor::Sensor, public MicroNovaSensorListener {
int fan_speed_offset_ = 0;
};
} // namespace micronova
} // namespace esphome
} // namespace esphome::micronova

View File

@@ -1,7 +1,6 @@
#include "micronova_switch.h"
namespace esphome {
namespace micronova {
namespace esphome::micronova {
void MicroNovaSwitch::write_state(bool state) {
switch (this->get_function()) {
@@ -31,5 +30,4 @@ void MicroNovaSwitch::write_state(bool state) {
}
}
} // namespace micronova
} // namespace esphome
} // namespace esphome::micronova

View File

@@ -4,8 +4,7 @@
#include "esphome/core/component.h"
#include "esphome/components/switch/switch.h"
namespace esphome {
namespace micronova {
namespace esphome::micronova {
class MicroNovaSwitch : public Component, public switch_::Switch, public MicroNovaSwitchListener {
public:
@@ -25,5 +24,4 @@ class MicroNovaSwitch : public Component, public switch_::Switch, public MicroNo
void write_state(bool state) override;
};
} // namespace micronova
} // namespace esphome
} // namespace esphome::micronova

View File

@@ -1,7 +1,6 @@
#include "micronova_text_sensor.h"
namespace esphome {
namespace micronova {
namespace esphome::micronova {
void MicroNovaTextSensor::process_value_from_stove(int value_from_stove) {
if (value_from_stove == -1) {
@@ -27,5 +26,4 @@ void MicroNovaTextSensor::process_value_from_stove(int value_from_stove) {
}
}
} // namespace micronova
} // namespace esphome
} // namespace esphome::micronova

View File

@@ -3,8 +3,7 @@
#include "esphome/components/micronova/micronova.h"
#include "esphome/components/text_sensor/text_sensor.h"
namespace esphome {
namespace micronova {
namespace esphome::micronova {
class MicroNovaTextSensor : public text_sensor::TextSensor, public MicroNovaSensorListener {
public:
@@ -16,5 +15,4 @@ class MicroNovaTextSensor : public text_sensor::TextSensor, public MicroNovaSens
void process_value_from_stove(int value_from_stove) override;
};
} // namespace micronova
} // namespace esphome
} // namespace esphome::micronova

View File

@@ -114,6 +114,7 @@ CONFIG_SCHEMA = remote_base.validate_triggers(
bk72xx="1000b",
ln882x="1000b",
rtl87xx="1000b",
rp2040="1000b",
): cv.validate_bytes,
cv.Optional(CONF_FILTER, default="50us"): cv.All(
cv.positive_time_period_microseconds,
@@ -213,6 +214,7 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform(
PlatformFramework.BK72XX_ARDUINO,
PlatformFramework.RTL87XX_ARDUINO,
PlatformFramework.LN882X_ARDUINO,
PlatformFramework.RP2040_ARDUINO,
},
}
)

View File

@@ -3,7 +3,7 @@
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#if defined(USE_LIBRETINY) || defined(USE_ESP8266)
#if defined(USE_LIBRETINY) || defined(USE_ESP8266) || defined(USE_RP2040)
namespace esphome {
namespace remote_receiver {

View File

@@ -12,7 +12,7 @@
namespace esphome {
namespace remote_receiver {
#if defined(USE_ESP8266) || defined(USE_LIBRETINY)
#if defined(USE_ESP8266) || defined(USE_LIBRETINY) || defined(USE_RP2040)
struct RemoteReceiverComponentStore {
static void gpio_intr(RemoteReceiverComponentStore *arg);
@@ -84,7 +84,7 @@ class RemoteReceiverComponent : public remote_base::RemoteReceiverBase,
std::string error_string_{""};
#endif
#if defined(USE_ESP8266) || defined(USE_LIBRETINY) || defined(USE_ESP32)
#if defined(USE_ESP8266) || defined(USE_LIBRETINY) || defined(USE_ESP32) || defined(USE_RP2040)
RemoteReceiverComponentStore store_;
HighFrequencyLoopRequester high_freq_;
#endif

View File

@@ -156,6 +156,7 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform(
PlatformFramework.BK72XX_ARDUINO,
PlatformFramework.RTL87XX_ARDUINO,
PlatformFramework.LN882X_ARDUINO,
PlatformFramework.RP2040_ARDUINO,
},
}
)

View File

@@ -2,7 +2,7 @@
#include "esphome/core/log.h"
#include "esphome/core/application.h"
#if defined(USE_LIBRETINY) || defined(USE_ESP8266)
#if defined(USE_LIBRETINY) || defined(USE_ESP8266) || defined(USE_RP2040)
namespace esphome {
namespace remote_transmitter {
@@ -40,8 +40,8 @@ void RemoteTransmitterComponent::await_target_time_() {
if (this->target_time_ == 0) {
this->target_time_ = current_time;
} else if ((int32_t) (this->target_time_ - current_time) > 0) {
#if defined(USE_LIBRETINY)
// busy loop for libretiny is required (see the comment inside micros() in wiring.c)
#if defined(USE_LIBRETINY) || defined(USE_RP2040)
// busy loop is required for libretiny and rp2040 as interrupts are disabled
while ((int32_t) (this->target_time_ - micros()) > 0)
;
#else

View File

@@ -62,7 +62,7 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase,
protected:
void send_internal(uint32_t send_times, uint32_t send_wait) override;
#if defined(USE_ESP8266) || defined(USE_LIBRETINY)
#if defined(USE_ESP8266) || defined(USE_LIBRETINY) || defined(USE_RP2040)
void calculate_on_off_time_(uint32_t carrier_frequency, uint32_t *on_time_period, uint32_t *off_time_period);
void mark_(uint32_t on_time, uint32_t off_time, uint32_t usec);

View File

@@ -1,3 +1,4 @@
from dataclasses import dataclass
from logging import getLogger
import math
import re
@@ -32,13 +33,26 @@ from esphome.const import (
PLATFORM_HOST,
PlatformFramework,
)
from esphome.core import CORE, ID
from esphome.core import CORE, ID, CoroPriority, coroutine_with_priority
import esphome.final_validate as fv
from esphome.yaml_util import make_data_base
_LOGGER = getLogger(__name__)
CODEOWNERS = ["@esphome/core"]
DOMAIN = "uart"
def AUTO_LOAD() -> list[str]:
"""Ideally, we would only auto-load socket only when wake_loop_on_rx is requested;
however, AUTO_LOAD is examined before wake_loop_on_rx is set, so instead, since ESP32
always uses socket select support in the main app, we'll just ensure it's loaded here.
"""
if CORE.is_esp32:
return ["socket"]
return []
uart_ns = cg.esphome_ns.namespace("uart")
UARTComponent = uart_ns.class_("UARTComponent")
@@ -52,6 +66,7 @@ LibreTinyUARTComponent = uart_ns.class_(
)
HostUartComponent = uart_ns.class_("HostUartComponent", UARTComponent, cg.Component)
NATIVE_UART_CLASSES = (
str(IDFUARTComponent),
str(ESP8266UartComponent),
@@ -100,6 +115,38 @@ MULTI_CONF = True
MULTI_CONF_NO_DEFAULT = True
@dataclass
class UARTData:
"""State data for UART component configuration generation."""
wake_loop_on_rx: bool = False
def _get_data() -> UARTData:
"""Get UART component data from CORE.data."""
if DOMAIN not in CORE.data:
CORE.data[DOMAIN] = UARTData()
return CORE.data[DOMAIN]
def request_wake_loop_on_rx() -> None:
"""Request that the UART wake the main loop when data is received.
Components that need low-latency notification of incoming UART data
should call this function during their code generation.
This enables the RX event task which wakes the main loop when data arrives.
"""
data = _get_data()
if not data.wake_loop_on_rx:
data.wake_loop_on_rx = True
# UART RX event task uses wake_loop_threadsafe() to notify the main loop
# Automatically enable the socket wake infrastructure when RX wake is requested
from esphome.components import socket
socket.require_wake_loop_threadsafe()
def validate_raw_data(value):
if isinstance(value, str):
return value.encode("utf-8")
@@ -335,6 +382,8 @@ async def to_code(config):
if CONF_DEBUG in config:
await debug_to_code(config[CONF_DEBUG], var)
CORE.add_job(final_step)
# A schema to use for all UART devices, all UART integrations must extend this!
UART_DEVICE_SCHEMA = cv.Schema(
@@ -472,6 +521,13 @@ async def uart_write_to_code(config, action_id, template_arg, args):
return var
@coroutine_with_priority(CoroPriority.FINAL)
async def final_step():
"""Final code generation step to configure optional UART features."""
if _get_data().wake_loop_on_rx:
cg.add_define("USE_UART_WAKE_LOOP_ON_RX")
FILTER_SOURCE_FILES = filter_source_files_from_platform(
{
"uart_component_esp_idf.cpp": {

View File

@@ -5,8 +5,7 @@
#include <vector>
namespace esphome {
namespace uart {
namespace esphome::uart {
template<typename... Ts> class UARTWriteAction : public Action<Ts...>, public Parented<UARTComponent> {
public:
@@ -41,5 +40,4 @@ template<typename... Ts> class UARTWriteAction : public Action<Ts...>, public Pa
} code_;
};
} // namespace uart
} // namespace esphome
} // namespace esphome::uart

View File

@@ -1,8 +1,7 @@
#include "uart_button.h"
#include "esphome/core/log.h"
namespace esphome {
namespace uart {
namespace esphome::uart {
static const char *const TAG = "uart.button";
@@ -13,5 +12,4 @@ void UARTButton::press_action() {
void UARTButton::dump_config() { LOG_BUTTON("", "UART Button", this); }
} // namespace uart
} // namespace esphome
} // namespace esphome::uart

View File

@@ -6,8 +6,7 @@
#include <vector>
namespace esphome {
namespace uart {
namespace esphome::uart {
class UARTButton : public button::Button, public UARTDevice, public Component {
public:
@@ -21,5 +20,4 @@ class UARTButton : public button::Button, public UARTDevice, public Component {
std::vector<uint8_t> data_;
};
} // namespace uart
} // namespace esphome
} // namespace esphome::uart

View File

@@ -2,8 +2,7 @@
#include "esphome/core/application.h"
#include "uart_transport.h"
namespace esphome {
namespace uart {
namespace esphome::uart {
static const char *const TAG = "uart_transport";
@@ -84,5 +83,5 @@ void UARTTransport::send_packet(const std::vector<uint8_t> &buf) const {
this->write_byte_(crc >> 8);
this->parent_->write_byte(FLAG_BYTE);
}
} // namespace uart
} // namespace esphome
} // namespace esphome::uart

View File

@@ -5,8 +5,7 @@
#include <vector>
#include "../uart.h"
namespace esphome {
namespace uart {
namespace esphome::uart {
/**
* A transport protocol for sending and receiving packets over a UART connection.
@@ -37,5 +36,4 @@ class UARTTransport : public packet_transport::PacketTransport, public UARTDevic
bool rx_control_{};
};
} // namespace uart
} // namespace esphome
} // namespace esphome::uart

View File

@@ -2,8 +2,7 @@
#include "esphome/core/log.h"
#include "esphome/core/application.h"
namespace esphome {
namespace uart {
namespace esphome::uart {
static const char *const TAG = "uart.switch";
@@ -58,5 +57,4 @@ void UARTSwitch::dump_config() {
}
}
} // namespace uart
} // namespace esphome
} // namespace esphome::uart

View File

@@ -7,8 +7,7 @@
#include <cinttypes>
#include <vector>
namespace esphome {
namespace uart {
namespace esphome::uart {
class UARTSwitch : public switch_::Switch, public UARTDevice, public Component {
public:
@@ -33,5 +32,4 @@ class UARTSwitch : public switch_::Switch, public UARTDevice, public Component {
uint32_t last_transmission_;
};
} // namespace uart
} // namespace esphome
} // namespace esphome::uart

View File

@@ -5,8 +5,7 @@
#include "esphome/core/log.h"
#include <cinttypes>
namespace esphome {
namespace uart {
namespace esphome::uart {
static const char *const TAG = "uart";
@@ -43,5 +42,4 @@ const LogString *parity_to_str(UARTParityOptions parity) {
}
}
} // namespace uart
} // namespace esphome
} // namespace esphome::uart

View File

@@ -6,8 +6,7 @@
#include "esphome/core/log.h"
#include "uart_component.h"
namespace esphome {
namespace uart {
namespace esphome::uart {
class UARTDevice {
public:
@@ -74,5 +73,4 @@ class UARTDevice {
UARTComponent *parent_{nullptr};
};
} // namespace uart
} // namespace esphome
} // namespace esphome::uart

View File

@@ -1,7 +1,6 @@
#include "uart_component.h"
namespace esphome {
namespace uart {
namespace esphome::uart {
static const char *const TAG = "uart";
@@ -28,5 +27,4 @@ void UARTComponent::set_rx_full_threshold_ms(uint8_t time) {
this->set_rx_full_threshold(val);
}
} // namespace uart
} // namespace esphome
} // namespace esphome::uart

View File

@@ -11,8 +11,7 @@
#include "esphome/core/automation.h"
#endif
namespace esphome {
namespace uart {
namespace esphome::uart {
enum UARTParityOptions {
UART_CONFIG_PARITY_NONE,
@@ -199,5 +198,4 @@ class UARTComponent {
#endif
};
} // namespace uart
} // namespace esphome
} // namespace esphome::uart

View File

@@ -9,8 +9,7 @@
#include "esphome/components/logger/logger.h"
#endif
namespace esphome {
namespace uart {
namespace esphome::uart {
static const char *const TAG = "uart.arduino_esp8266";
bool ESP8266UartComponent::serial0_in_use = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
@@ -331,6 +330,5 @@ int ESP8266SoftwareSerial::available() {
return avail;
}
} // namespace uart
} // namespace esphome
} // namespace esphome::uart
#endif // USE_ESP8266

View File

@@ -9,8 +9,7 @@
#include "esphome/core/log.h"
#include "uart_component.h"
namespace esphome {
namespace uart {
namespace esphome::uart {
class ESP8266SoftwareSerial {
public:
@@ -88,7 +87,5 @@ class ESP8266UartComponent : public UARTComponent, public Component {
static bool serial0_in_use; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
};
} // namespace uart
} // namespace esphome
} // namespace esphome::uart
#endif // USE_ESP8266

View File

@@ -14,8 +14,8 @@
#include "esphome/components/logger/logger.h"
#endif
namespace esphome {
namespace uart {
namespace esphome::uart {
static const char *const TAG = "uart.idf";
uart_config_t IDFUARTComponent::get_config_() {
@@ -112,6 +112,12 @@ void IDFUARTComponent::load_settings(bool dump_config) {
esp_err_t err;
if (uart_is_driver_installed(this->uart_num_)) {
#ifdef USE_UART_WAKE_LOOP_ON_RX
if (this->rx_event_task_handle_ != nullptr) {
vTaskDelete(this->rx_event_task_handle_);
this->rx_event_task_handle_ = nullptr;
}
#endif
err = uart_driver_delete(this->uart_num_);
if (err != ESP_OK) {
ESP_LOGW(TAG, "uart_driver_delete failed: %s", esp_err_to_name(err));
@@ -204,6 +210,11 @@ void IDFUARTComponent::load_settings(bool dump_config) {
return;
}
#ifdef USE_UART_WAKE_LOOP_ON_RX
// Start the RX event task to enable low-latency data notifications
this->start_rx_event_task_();
#endif // USE_UART_WAKE_LOOP_ON_RX
if (dump_config) {
ESP_LOGCONFIG(TAG, "Reloaded UART %u", this->uart_num_);
this->dump_config();
@@ -226,7 +237,11 @@ void IDFUARTComponent::dump_config() {
" Baud Rate: %" PRIu32 " baud\n"
" Data Bits: %u\n"
" Parity: %s\n"
" Stop bits: %u",
" Stop bits: %u"
#ifdef USE_UART_WAKE_LOOP_ON_RX
"\n Wake on data RX: ENABLED"
#endif
,
this->baud_rate_, this->data_bits_, LOG_STR_ARG(parity_to_str(this->parity_)), this->stop_bits_);
this->check_logger_conflict();
}
@@ -337,7 +352,58 @@ void IDFUARTComponent::flush() {
void IDFUARTComponent::check_logger_conflict() {}
} // namespace uart
} // namespace esphome
#ifdef USE_UART_WAKE_LOOP_ON_RX
void IDFUARTComponent::start_rx_event_task_() {
// Create FreeRTOS task to monitor UART events
BaseType_t result = xTaskCreate(rx_event_task_func, // Task function
"uart_rx_evt", // Task name (max 16 chars)
2240, // Stack size in bytes (~2.2KB); increase if needed for logging
this, // Task parameter (this pointer)
tskIDLE_PRIORITY + 1, // Priority (low, just above idle)
&this->rx_event_task_handle_ // Task handle
);
if (result != pdPASS) {
ESP_LOGE(TAG, "Failed to create RX event task");
return;
}
ESP_LOGV(TAG, "RX event task started");
}
void IDFUARTComponent::rx_event_task_func(void *param) {
auto *self = static_cast<IDFUARTComponent *>(param);
uart_event_t event;
ESP_LOGV(TAG, "RX event task running");
// Run forever - task lifecycle matches component lifecycle
while (true) {
// Wait for UART events (blocks efficiently)
if (xQueueReceive(self->uart_event_queue_, &event, portMAX_DELAY) == pdTRUE) {
switch (event.type) {
case UART_DATA:
// Data available in UART RX buffer - wake the main loop
ESP_LOGVV(TAG, "Data event: %d bytes", event.size);
App.wake_loop_threadsafe();
break;
case UART_FIFO_OVF:
case UART_BUFFER_FULL:
ESP_LOGW(TAG, "FIFO overflow or ring buffer full - clearing");
uart_flush_input(self->uart_num_);
App.wake_loop_threadsafe();
break;
default:
// Ignore other event types
ESP_LOGVV(TAG, "Event type: %d", event.type);
break;
}
}
}
}
#endif // USE_UART_WAKE_LOOP_ON_RX
} // namespace esphome::uart
#endif // USE_ESP32

View File

@@ -6,8 +6,7 @@
#include "esphome/core/component.h"
#include "uart_component.h"
namespace esphome {
namespace uart {
namespace esphome::uart {
class IDFUARTComponent : public UARTComponent, public Component {
public:
@@ -53,9 +52,15 @@ class IDFUARTComponent : public UARTComponent, public Component {
bool has_peek_{false};
uint8_t peek_byte_;
#ifdef USE_UART_WAKE_LOOP_ON_RX
// RX notification support
void start_rx_event_task_();
static void rx_event_task_func(void *param);
TaskHandle_t rx_event_task_handle_{nullptr};
#endif // USE_UART_WAKE_LOOP_ON_RX
};
} // namespace uart
} // namespace esphome
} // namespace esphome::uart
#endif // USE_ESP32

View File

@@ -96,8 +96,7 @@ speed_t get_baud(int baud) {
} // namespace
namespace esphome {
namespace uart {
namespace esphome::uart {
static const char *const TAG = "uart.host";
@@ -296,7 +295,5 @@ void HostUartComponent::update_error_(const std::string &error) {
ESP_LOGE(TAG, "Port error: %s", error.c_str());
}
} // namespace uart
} // namespace esphome
} // namespace esphome::uart
#endif // USE_HOST

View File

@@ -6,8 +6,7 @@
#include "esphome/core/log.h"
#include "uart_component.h"
namespace esphome {
namespace uart {
namespace esphome::uart {
class HostUartComponent : public UARTComponent, public Component {
public:
@@ -32,7 +31,5 @@ class HostUartComponent : public UARTComponent, public Component {
uint8_t peek_byte_;
};
} // namespace uart
} // namespace esphome
} // namespace esphome::uart
#endif // USE_HOST

View File

@@ -14,8 +14,7 @@
#include <SoftwareSerial.h>
#endif
namespace esphome {
namespace uart {
namespace esphome::uart {
static const char *const TAG = "uart.lt";
@@ -187,7 +186,5 @@ void LibreTinyUARTComponent::check_logger_conflict() {
#endif
}
} // namespace uart
} // namespace esphome
} // namespace esphome::uart
#endif // USE_LIBRETINY

View File

@@ -8,8 +8,7 @@
#include "esphome/core/log.h"
#include "uart_component.h"
namespace esphome {
namespace uart {
namespace esphome::uart {
class LibreTinyUARTComponent : public UARTComponent, public Component {
public:
@@ -37,7 +36,5 @@ class LibreTinyUARTComponent : public UARTComponent, public Component {
int8_t hardware_idx_{-1};
};
} // namespace uart
} // namespace esphome
} // namespace esphome::uart
#endif // USE_LIBRETINY

View File

@@ -11,8 +11,7 @@
#include "esphome/components/logger/logger.h"
#endif
namespace esphome {
namespace uart {
namespace esphome::uart {
static const char *const TAG = "uart.arduino_rp2040";
@@ -193,7 +192,5 @@ void RP2040UartComponent::flush() {
this->serial_->flush();
}
} // namespace uart
} // namespace esphome
} // namespace esphome::uart
#endif // USE_RP2040

View File

@@ -11,8 +11,7 @@
#include "esphome/core/log.h"
#include "uart_component.h"
namespace esphome {
namespace uart {
namespace esphome::uart {
class RP2040UartComponent : public UARTComponent, public Component {
public:
@@ -40,7 +39,5 @@ class RP2040UartComponent : public UARTComponent, public Component {
HardwareSerial *serial_{nullptr};
};
} // namespace uart
} // namespace esphome
} // namespace esphome::uart
#endif // USE_RP2040

View File

@@ -6,8 +6,7 @@
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
namespace uart {
namespace esphome::uart {
static const char *const TAG = "uart_debug";
@@ -197,6 +196,5 @@ void UARTDebug::log_binary(UARTDirection direction, std::vector<uint8_t> bytes,
delay(10);
}
} // namespace uart
} // namespace esphome
} // namespace esphome::uart
#endif

View File

@@ -8,8 +8,7 @@
#include "uart.h"
#include "uart_component.h"
namespace esphome {
namespace uart {
namespace esphome::uart {
/// The UARTDebugger class adds debugging support to a UART bus.
///
@@ -96,6 +95,5 @@ class UARTDebug {
static void log_binary(UARTDirection direction, std::vector<uint8_t> bytes, uint8_t separator);
};
} // namespace uart
} // namespace esphome
} // namespace esphome::uart
#endif

View File

@@ -6,6 +6,8 @@ namespace esphome::wifi_info {
static const char *const TAG = "wifi_info";
#ifdef USE_WIFI_LISTENERS
static constexpr size_t MAX_STATE_LENGTH = 255;
/********************
@@ -98,6 +100,8 @@ void BSSIDWiFiInfo::on_wifi_connect_state(const std::string &ssid, const wifi::b
this->publish_state(buf);
}
#endif
/*********************
* MacAddressWifiInfo
********************/

View File

@@ -9,6 +9,7 @@
namespace esphome::wifi_info {
#ifdef USE_WIFI_LISTENERS
class IPAddressWiFiInfo final : public Component, public text_sensor::TextSensor, public wifi::WiFiIPStateListener {
public:
void setup() override;
@@ -62,6 +63,7 @@ class BSSIDWiFiInfo final : public Component, public text_sensor::TextSensor, pu
// WiFiConnectStateListener interface
void on_wifi_connect_state(const std::string &ssid, const wifi::bssid_t &bssid) override;
};
#endif
class MacAddressWifiInfo final : public Component, public text_sensor::TextSensor {
public:

View File

@@ -41,3 +41,6 @@ async def to_code(config):
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
cg.add_define("USE_ZWAVE_PROXY")
# Request UART to wake the main loop when data arrives for low-latency processing
uart.request_wake_loop_on_rx()

View File

@@ -5,8 +5,7 @@
#include "esphome/core/log.h"
#include "esphome/core/util.h"
namespace esphome {
namespace zwave_proxy {
namespace esphome::zwave_proxy {
static const char *const TAG = "zwave_proxy";
@@ -144,6 +143,7 @@ void ZWaveProxy::zwave_proxy_request(api::APIConnection *api_connection, api::en
this->api_connection_ = api_connection;
ESP_LOGV(TAG, "API connection is now subscribed");
break;
case api::enums::ZWAVE_PROXY_REQUEST_TYPE_UNSUBSCRIBE:
if (this->api_connection_ != api_connection) {
ESP_LOGV(TAG, "API connection is not subscribed");
@@ -151,6 +151,7 @@ void ZWaveProxy::zwave_proxy_request(api::APIConnection *api_connection, api::en
}
this->api_connection_ = nullptr;
break;
default:
ESP_LOGW(TAG, "Unknown request type: %d", type);
break;
@@ -342,5 +343,4 @@ bool ZWaveProxy::response_handler_() {
ZWaveProxy *global_zwave_proxy = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
} // namespace zwave_proxy
} // namespace esphome
} // namespace esphome::zwave_proxy

View File

@@ -8,8 +8,7 @@
#include <array>
namespace esphome {
namespace zwave_proxy {
namespace esphome::zwave_proxy {
static constexpr size_t MAX_ZWAVE_FRAME_SIZE = 257; // Maximum Z-Wave frame size
@@ -89,5 +88,4 @@ class ZWaveProxy : public uart::UARTDevice, public Component {
extern ZWaveProxy *global_zwave_proxy; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
} // namespace zwave_proxy
} // namespace esphome
} // namespace esphome::zwave_proxy

View File

@@ -108,6 +108,7 @@
#define USE_TIME
#define USE_TOUCHSCREEN
#define USE_UART_DEBUGGER
#define USE_UART_WAKE_LOOP_ON_RX
#define USE_UPDATE
#define USE_VALVE
#define USE_ZWAVE_PROXY

View File

@@ -1,8 +1,12 @@
from collections.abc import Callable
import importlib
import logging
import os
from pathlib import Path
import re
import shutil
import stat
from types import TracebackType
from esphome import loader
from esphome.config import iter_component_configs, iter_components
@@ -301,9 +305,24 @@ def clean_cmake_cache():
pioenvs_cmake_path.unlink()
def clean_build(clear_pio_cache: bool = True):
import shutil
def _rmtree_error_handler(
func: Callable[[str], object],
path: str,
exc_info: tuple[type[BaseException], BaseException, TracebackType | None],
) -> None:
"""Error handler for shutil.rmtree to handle read-only files on Windows.
On Windows, git pack files and other files may be marked read-only,
causing shutil.rmtree to fail with "Access is denied". This handler
removes the read-only flag and retries the deletion.
"""
if os.access(path, os.W_OK):
raise exc_info[1].with_traceback(exc_info[2])
os.chmod(path, stat.S_IWUSR | stat.S_IRUSR)
func(path)
def clean_build(clear_pio_cache: bool = True):
# Allow skipping cache cleaning for integration tests
if os.environ.get("ESPHOME_SKIP_CLEAN_BUILD"):
_LOGGER.warning("Skipping build cleaning (ESPHOME_SKIP_CLEAN_BUILD set)")
@@ -312,11 +331,11 @@ def clean_build(clear_pio_cache: bool = True):
pioenvs = CORE.relative_pioenvs_path()
if pioenvs.is_dir():
_LOGGER.info("Deleting %s", pioenvs)
shutil.rmtree(pioenvs)
shutil.rmtree(pioenvs, onerror=_rmtree_error_handler)
piolibdeps = CORE.relative_piolibdeps_path()
if piolibdeps.is_dir():
_LOGGER.info("Deleting %s", piolibdeps)
shutil.rmtree(piolibdeps)
shutil.rmtree(piolibdeps, onerror=_rmtree_error_handler)
dependencies_lock = CORE.relative_build_path("dependencies.lock")
if dependencies_lock.is_file():
_LOGGER.info("Deleting %s", dependencies_lock)
@@ -337,12 +356,10 @@ def clean_build(clear_pio_cache: bool = True):
cache_dir = Path(config.get("platformio", "cache_dir"))
if cache_dir.is_dir():
_LOGGER.info("Deleting PlatformIO cache %s", cache_dir)
shutil.rmtree(cache_dir)
shutil.rmtree(cache_dir, onerror=_rmtree_error_handler)
def clean_all(configuration: list[str]):
import shutil
data_dirs = []
for config in configuration:
item = Path(config)
@@ -364,7 +381,7 @@ def clean_all(configuration: list[str]):
if item.is_file() and not item.name.endswith(".json"):
item.unlink()
elif item.is_dir() and item.name != "storage":
shutil.rmtree(item)
shutil.rmtree(item, onerror=_rmtree_error_handler)
# Clean PlatformIO project files
try:
@@ -378,7 +395,7 @@ def clean_all(configuration: list[str]):
path = Path(config.get("platformio", pio_dir))
if path.is_dir():
_LOGGER.info("Deleting PlatformIO %s %s", pio_dir, path)
shutil.rmtree(path)
shutil.rmtree(path, onerror=_rmtree_error_handler)
GITIGNORE_CONTENT = """# Gitignore settings for ESPHome

View File

@@ -0,0 +1,12 @@
remote_receiver:
id: rcvr
pin: GPIO5
dump: all
<<: !include common-actions.yaml
binary_sensor:
- platform: remote_receiver
name: Panasonic Remote Input
panasonic:
address: 0x4004
command: 0x100BCBD

View File

@@ -0,0 +1,7 @@
remote_transmitter:
id: xmitr
pin: GPIO5
carrier_duty_percent: 50%
packages:
buttons: !include common-buttons.yaml

View File

@@ -0,0 +1,8 @@
wifi:
ssid: MySSID
password: password1
text_sensor:
- platform: wifi_info
mac_address:
name: MAC Address

View File

@@ -13,6 +13,6 @@ text_sensor:
bssid:
name: BSSID
mac_address:
name: Mac Address
name: MAC Address
dns_address:
name: DNS ADdress

View File

@@ -0,0 +1,4 @@
packages:
i2c: !include ../../test_build_components/common/i2c/esp32-idf.yaml
<<: !include common-mac.yaml

View File

@@ -0,0 +1,4 @@
packages:
i2c: !include ../../test_build_components/common/i2c/esp8266-ard.yaml
<<: !include common-mac.yaml

View File

@@ -0,0 +1,4 @@
packages:
i2c: !include ../../test_build_components/common/i2c/rp2040-ard.yaml
<<: !include common-mac.yaml

View File

@@ -1,7 +1,9 @@
"""Test writer module functionality."""
from collections.abc import Callable
import os
from pathlib import Path
import stat
from typing import Any
from unittest.mock import MagicMock, patch
@@ -15,6 +17,7 @@ from esphome.writer import (
CPP_INCLUDE_BEGIN,
CPP_INCLUDE_END,
GITIGNORE_CONTENT,
clean_all,
clean_build,
clean_cmake_cache,
storage_should_clean,
@@ -1063,3 +1066,103 @@ def test_clean_all_preserves_json_files(
# Verify logging mentions cleaning
assert "Cleaning" in caplog.text
assert str(build_dir) in caplog.text
@patch("esphome.writer.CORE")
def test_clean_build_handles_readonly_files(
mock_core: MagicMock,
tmp_path: Path,
) -> None:
"""Test clean_build handles read-only files (e.g., git pack files on Windows)."""
# Create directory structure with read-only files
pioenvs_dir = tmp_path / ".pioenvs"
pioenvs_dir.mkdir()
git_dir = pioenvs_dir / ".git" / "objects" / "pack"
git_dir.mkdir(parents=True)
# Create a read-only file (simulating git pack files on Windows)
readonly_file = git_dir / "pack-abc123.pack"
readonly_file.write_text("pack data")
os.chmod(readonly_file, stat.S_IRUSR) # Read-only
# Setup mocks
mock_core.relative_pioenvs_path.return_value = pioenvs_dir
mock_core.relative_piolibdeps_path.return_value = tmp_path / ".piolibdeps"
mock_core.relative_build_path.return_value = tmp_path / "dependencies.lock"
# Verify file is read-only
assert not os.access(readonly_file, os.W_OK)
# Call the function - should not crash
clean_build()
# Verify directory was removed despite read-only files
assert not pioenvs_dir.exists()
@patch("esphome.writer.CORE")
def test_clean_all_handles_readonly_files(
mock_core: MagicMock,
tmp_path: Path,
) -> None:
"""Test clean_all handles read-only files."""
# Create config directory
config_dir = tmp_path / "config"
config_dir.mkdir()
build_dir = config_dir / ".esphome"
build_dir.mkdir()
# Create a subdirectory with read-only files
subdir = build_dir / "subdir"
subdir.mkdir()
readonly_file = subdir / "readonly.txt"
readonly_file.write_text("content")
os.chmod(readonly_file, stat.S_IRUSR) # Read-only
# Verify file is read-only
assert not os.access(readonly_file, os.W_OK)
# Call the function - should not crash
clean_all([str(config_dir)])
# Verify directory was removed despite read-only files
assert not subdir.exists()
assert build_dir.exists() # .esphome dir itself is preserved
@patch("esphome.writer.CORE")
def test_clean_build_reraises_for_other_errors(
mock_core: MagicMock,
tmp_path: Path,
) -> None:
"""Test clean_build re-raises errors that are not read-only permission issues."""
# Create directory structure with a read-only subdirectory
# This prevents file deletion and triggers the error handler
pioenvs_dir = tmp_path / ".pioenvs"
pioenvs_dir.mkdir()
subdir = pioenvs_dir / "subdir"
subdir.mkdir()
test_file = subdir / "test.txt"
test_file.write_text("content")
# Make subdir read-only so files inside can't be deleted
os.chmod(subdir, stat.S_IRUSR | stat.S_IXUSR)
# Setup mocks
mock_core.relative_pioenvs_path.return_value = pioenvs_dir
mock_core.relative_piolibdeps_path.return_value = tmp_path / ".piolibdeps"
mock_core.relative_build_path.return_value = tmp_path / "dependencies.lock"
try:
# Mock os.access in writer module to return True (writable)
# This simulates a case where the error is NOT due to read-only permissions
# so the error handler should re-raise instead of trying to fix permissions
with (
patch("esphome.writer.os.access", return_value=True),
pytest.raises(PermissionError),
):
clean_build()
finally:
# Cleanup - restore write permission so tmp_path cleanup works
os.chmod(subdir, stat.S_IRWXU)