From 6fb490f49b35d4610677e2f91b2230f5e5a012c4 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Wed, 29 Oct 2025 12:40:22 -0400 Subject: [PATCH] [remote_transmitter] Add non-blocking mode (#11524) --- .../components/remote_transmitter/__init__.py | 20 +++++++++++++ .../remote_transmitter/remote_transmitter.h | 3 ++ .../remote_transmitter_esp32.cpp | 30 +++++++++++++++---- .../remote_transmitter/esp32-common.yaml | 1 + 4 files changed, 48 insertions(+), 6 deletions(-) diff --git a/esphome/components/remote_transmitter/__init__.py b/esphome/components/remote_transmitter/__init__.py index cb98c017f1..faa6c827f7 100644 --- a/esphome/components/remote_transmitter/__init__.py +++ b/esphome/components/remote_transmitter/__init__.py @@ -1,3 +1,5 @@ +import logging + from esphome import automation, pins import esphome.codegen as cg from esphome.components import esp32, esp32_rmt, remote_base @@ -18,9 +20,12 @@ from esphome.const import ( ) from esphome.core import CORE +_LOGGER = logging.getLogger(__name__) + AUTO_LOAD = ["remote_base"] CONF_EOT_LEVEL = "eot_level" +CONF_NON_BLOCKING = "non_blocking" CONF_ON_TRANSMIT = "on_transmit" CONF_ON_COMPLETE = "on_complete" CONF_TRANSMITTER_ID = remote_base.CONF_TRANSMITTER_ID @@ -65,11 +70,25 @@ CONFIG_SCHEMA = cv.Schema( esp32_c6=48, esp32_h2=48, ): cv.All(cv.only_on_esp32, cv.int_range(min=2)), + cv.Optional(CONF_NON_BLOCKING): cv.All(cv.only_on_esp32, cv.boolean), cv.Optional(CONF_ON_TRANSMIT): automation.validate_automation(single=True), cv.Optional(CONF_ON_COMPLETE): automation.validate_automation(single=True), } ).extend(cv.COMPONENT_SCHEMA) + +def _validate_non_blocking(config): + if CORE.is_esp32 and CONF_NON_BLOCKING not in config: + _LOGGER.warning( + "'non_blocking' is not set for 'remote_transmitter' and will default to 'true'.\n" + "The default behavior changed in 2025.11.0; previously blocking mode was used.\n" + "To silence this warning, explicitly set 'non_blocking: true' (or 'false')." + ) + config[CONF_NON_BLOCKING] = True + + +FINAL_VALIDATE_SCHEMA = _validate_non_blocking + DIGITAL_WRITE_ACTION_SCHEMA = cv.maybe_simple_value( { cv.GenerateID(CONF_TRANSMITTER_ID): cv.use_id(RemoteTransmitterComponent), @@ -95,6 +114,7 @@ async def to_code(config): if CORE.is_esp32: var = cg.new_Pvariable(config[CONF_ID], pin) cg.add(var.set_rmt_symbols(config[CONF_RMT_SYMBOLS])) + cg.add(var.set_non_blocking(config[CONF_NON_BLOCKING])) if CONF_CLOCK_RESOLUTION in config: cg.add(var.set_clock_resolution(config[CONF_CLOCK_RESOLUTION])) if CONF_USE_DMA in config: diff --git a/esphome/components/remote_transmitter/remote_transmitter.h b/esphome/components/remote_transmitter/remote_transmitter.h index b5d8e8d83f..cc3b82ad61 100644 --- a/esphome/components/remote_transmitter/remote_transmitter.h +++ b/esphome/components/remote_transmitter/remote_transmitter.h @@ -54,6 +54,7 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase, #if defined(USE_ESP32) void set_with_dma(bool with_dma) { this->with_dma_ = with_dma; } void set_eot_level(bool eot_level) { this->eot_level_ = eot_level; } + void set_non_blocking(bool non_blocking) { this->non_blocking_ = non_blocking; } #endif Trigger<> *get_transmit_trigger() const { return this->transmit_trigger_; }; @@ -74,6 +75,7 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase, #ifdef USE_ESP32 void configure_rmt_(); + void wait_for_rmt_(); #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1) RemoteTransmitterComponentStore store_{}; @@ -90,6 +92,7 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase, esp_err_t error_code_{ESP_OK}; std::string error_string_{""}; bool inverted_{false}; + bool non_blocking_{false}; #endif uint8_t carrier_duty_percent_; diff --git a/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp b/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp index 27bbf3c210..59c85c99a8 100644 --- a/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp +++ b/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp @@ -196,12 +196,29 @@ void RemoteTransmitterComponent::configure_rmt_() { } } +void RemoteTransmitterComponent::wait_for_rmt_() { + esp_err_t error = rmt_tx_wait_all_done(this->channel_, -1); + if (error != ESP_OK) { + ESP_LOGW(TAG, "rmt_tx_wait_all_done failed: %s", esp_err_to_name(error)); + this->status_set_warning(); + } + + this->complete_trigger_->trigger(); +} + #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1) void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t send_wait) { + uint64_t total_duration = 0; + if (this->is_failed()) { return; } + // if the timeout was cancelled, block until the tx is complete + if (this->non_blocking_ && this->cancel_timeout("complete")) { + this->wait_for_rmt_(); + } + if (this->current_carrier_frequency_ != this->temp_.get_carrier_frequency()) { this->current_carrier_frequency_ = this->temp_.get_carrier_frequency(); this->configure_rmt_(); @@ -212,6 +229,7 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen // encode any delay at the start of the buffer to simplify the encoder callback // this will be skipped the first time around + total_duration += send_wait * (send_times - 1); send_wait = this->from_microseconds_(static_cast(send_wait)); while (send_wait > 0) { int32_t duration = std::min(send_wait, uint32_t(RMT_SYMBOL_DURATION_MAX)); @@ -229,6 +247,7 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen if (!level) { value = -value; } + total_duration += value * send_times; value = this->from_microseconds_(static_cast(value)); while (value > 0) { int32_t duration = std::min(value, int32_t(RMT_SYMBOL_DURATION_MAX)); @@ -260,13 +279,12 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen } else { this->status_clear_warning(); } - error = rmt_tx_wait_all_done(this->channel_, -1); - if (error != ESP_OK) { - ESP_LOGW(TAG, "rmt_tx_wait_all_done failed: %s", esp_err_to_name(error)); - this->status_set_warning(); - } - this->complete_trigger_->trigger(); + if (this->non_blocking_) { + this->set_timeout("complete", total_duration / 1000, [this]() { this->wait_for_rmt_(); }); + } else { + this->wait_for_rmt_(); + } } #else void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t send_wait) { diff --git a/tests/components/remote_transmitter/esp32-common.yaml b/tests/components/remote_transmitter/esp32-common.yaml index 8b26c45149..79fd47ae21 100644 --- a/tests/components/remote_transmitter/esp32-common.yaml +++ b/tests/components/remote_transmitter/esp32-common.yaml @@ -2,6 +2,7 @@ remote_transmitter: - id: xmitr pin: ${pin} carrier_duty_percent: 50% + non_blocking: true clock_resolution: ${clock_resolution} rmt_symbols: ${rmt_symbols}