From ff9b2a1c83edb7b542fa04f93407b86a5c04bcde Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 11 Sep 2026 18:20:40 -0500 Subject: [PATCH] [remote_receiver] Size the RMT ring buffer from receive symbols by default (#19100) --- .../components/remote_receiver/__init__.py | 14 ++++--- .../remote_receiver/remote_receiver.h | 4 +- .../remote_receiver/remote_receiver_rmt.cpp | 38 +++++++++++-------- .../config/receiver_buffer_size.yaml | 10 +++++ .../config/receiver_esp32_c2.yaml | 12 ++++++ .../config/receiver_esp8266.yaml | 9 +++++ .../remote_receiver/test_buffer_size.py | 28 ++++++++++++++ .../remote_receiver/test_slot_counts.py | 4 +- 8 files changed, 96 insertions(+), 23 deletions(-) create mode 100644 tests/component_tests/remote_receiver/config/receiver_buffer_size.yaml create mode 100644 tests/component_tests/remote_receiver/config/receiver_esp32_c2.yaml create mode 100644 tests/component_tests/remote_receiver/config/receiver_esp8266.yaml create mode 100644 tests/component_tests/remote_receiver/test_buffer_size.py diff --git a/esphome/components/remote_receiver/__init__.py b/esphome/components/remote_receiver/__init__.py index b2fd87165e..6eaecf7ab0 100644 --- a/esphome/components/remote_receiver/__init__.py +++ b/esphome/components/remote_receiver/__init__.py @@ -114,15 +114,18 @@ CONFIG_SCHEMA = remote_base.validate_triggers( cv.Optional(CONF_TOLERANCE, default="25%"): validate_tolerance, cv.SplitDefault( CONF_BUFFER_SIZE, - esp32="10000b", - esp32_c2="1000b", - esp32_c61="1000b", + esp32=cv.UNDEFINED, + # the pulse ring needs a size; only RMT targets size themselves in setup() + **{ + f"esp32_{variant.removeprefix('ESP32').lower()}": "1000b" + for variant in esp32_rmt.VARIANTS_NO_RMT + }, esp8266="1000b", bk72xx="1000b", ln882x="1000b", rtl87xx="1000b", rp2="1000b", - ): cv.validate_bytes, + ): cv.All(cv.validate_bytes, cv.int_range(min=64)), cv.Optional(CONF_FILTER, default="50us"): cv.All( cv.positive_time_period_microseconds, cv.Range(max=TimePeriod(microseconds=4294967295)), @@ -233,7 +236,8 @@ async def to_code(config: ConfigType) -> None: config[CONF_TOLERANCE][CONF_VALUE], config[CONF_TOLERANCE][CONF_TYPE] ) ) - cg.add(var.set_buffer_size(config[CONF_BUFFER_SIZE])) + if CONF_BUFFER_SIZE in config: + cg.add(var.set_buffer_size(config[CONF_BUFFER_SIZE])) cg.add(var.set_filter_us(config[CONF_FILTER])) cg.add(var.set_idle_us(config[CONF_IDLE])) diff --git a/esphome/components/remote_receiver/remote_receiver.h b/esphome/components/remote_receiver/remote_receiver.h index f9ec054fe3..e59a8b2557 100644 --- a/esphome/components/remote_receiver/remote_receiver.h +++ b/esphome/components/remote_receiver/remote_receiver.h @@ -47,7 +47,7 @@ struct RemoteReceiverComponentStore { /// The position last read from volatile uint32_t buffer_read{0}; bool overflow{false}; - uint32_t buffer_size{1000}; + uint32_t buffer_size{0}; uint32_t receive_size{0}; uint32_t filter_symbols{0}; esp_err_t error{ESP_OK}; @@ -101,7 +101,7 @@ class RemoteReceiverComponent final : public remote_base::RemoteReceiverBase, HighFrequencyLoopRequester high_freq_; #endif - uint32_t buffer_size_{}; + uint32_t buffer_size_{}; // 0 on RMT targets: sized from receive_symbols in setup() uint32_t filter_us_{10}; uint32_t idle_us_{10000}; }; diff --git a/esphome/components/remote_receiver/remote_receiver_rmt.cpp b/esphome/components/remote_receiver/remote_receiver_rmt.cpp index 632ca9763a..4eebbbb16f 100644 --- a/esphome/components/remote_receiver/remote_receiver_rmt.cpp +++ b/esphome/components/remote_receiver/remote_receiver_rmt.cpp @@ -10,6 +10,7 @@ namespace esphome::remote_receiver { static const char *const TAG = "remote_receiver"; +static constexpr uint32_t DEFAULT_BUFFER_SLOTS = 4; static bool IRAM_ATTR HOT rmt_callback(rmt_channel_handle_t channel, const rmt_rx_done_event_data_t *event, void *arg) { RemoteReceiverComponentStore *store = (RemoteReceiverComponentStore *) arg; @@ -104,7 +105,11 @@ void RemoteReceiverComponent::setup() { this->store_.config.signal_range_max_ns = this->idle_us_ * 1000; this->store_.filter_symbols = this->filter_symbols_; this->store_.receive_size = this->receive_symbols_ * sizeof(rmt_symbol_word_t); - this->store_.buffer_size = std::max((event_size + this->store_.receive_size) * 2, this->buffer_size_); + // one slot per pending rmt_receive; two are the floor (one filling while one is decoded), and + // the default of four covers a few frames queued across a stalled loop pass + const uint32_t slot_size = event_size + this->store_.receive_size; + this->store_.buffer_size = + this->buffer_size_ != 0 ? std::max(slot_size * 2, this->buffer_size_) : slot_size * DEFAULT_BUFFER_SLOTS; this->store_.buffer = new uint8_t[this->store_.buffer_size]; error = rmt_receive(this->channel_, (uint8_t *) this->store_.buffer + event_size, this->store_.receive_size, &this->store_.config); @@ -117,20 +122,23 @@ void RemoteReceiverComponent::setup() { } void RemoteReceiverComponent::dump_config() { - ESP_LOGCONFIG(TAG, - "Remote Receiver:\n" - " Clock resolution: %" PRIu32 " hz\n" - " RMT symbols: %" PRIu32 "\n" - " Filter symbols: %" PRIu32 "\n" - " Receive symbols: %" PRIu32 "\n" - " Tolerance: %" PRIu32 "%s\n" - " Carrier frequency: %" PRIu32 " hz\n" - " Carrier duty: %u%%\n" - " Filter out pulses shorter than: %" PRIu32 " us\n" - " Signal is done after %" PRIu32 " us of no changes", - this->clock_resolution_, this->rmt_symbols_, this->filter_symbols_, this->receive_symbols_, - this->tolerance_, (this->tolerance_mode_ == remote_base::TOLERANCE_MODE_TIME) ? " us" : "%", - this->carrier_frequency_, this->carrier_duty_percent_, this->filter_us_, this->idle_us_); + ESP_LOGCONFIG( + TAG, + "Remote Receiver:\n" + " Clock resolution: %" PRIu32 " hz\n" + " RMT symbols: %" PRIu32 "\n" + " Filter symbols: %" PRIu32 "\n" + " Receive symbols: %" PRIu32 "\n" + " Buffer size: %" PRIu32 " bytes\n" + " Tolerance: %" PRIu32 "%s\n" + " Carrier frequency: %" PRIu32 " hz\n" + " Carrier duty: %u%%\n" + " Filter out pulses shorter than: %" PRIu32 " us\n" + " Signal is done after %" PRIu32 " us of no changes", + this->clock_resolution_, this->rmt_symbols_, this->filter_symbols_, this->receive_symbols_, + this->store_.buffer_size, this->tolerance_, + (this->tolerance_mode_ == remote_base::TOLERANCE_MODE_TIME) ? LOG_STR_LITERAL(" us") : LOG_STR_LITERAL("%"), + this->carrier_frequency_, this->carrier_duty_percent_, this->filter_us_, this->idle_us_); LOG_PIN(" Pin: ", this->pin_); if (this->is_failed()) { ESP_LOGE(TAG, "Configuring RMT driver failed: %s (%s)", esp_err_to_name(this->error_code_), diff --git a/tests/component_tests/remote_receiver/config/receiver_buffer_size.yaml b/tests/component_tests/remote_receiver/config/receiver_buffer_size.yaml new file mode 100644 index 0000000000..0b334954eb --- /dev/null +++ b/tests/component_tests/remote_receiver/config/receiver_buffer_size.yaml @@ -0,0 +1,10 @@ +esphome: + name: test + +esp32: + board: esp32dev + +remote_receiver: + - id: rcvr + pin: GPIO4 + buffer_size: 2kb diff --git a/tests/component_tests/remote_receiver/config/receiver_esp32_c2.yaml b/tests/component_tests/remote_receiver/config/receiver_esp32_c2.yaml new file mode 100644 index 0000000000..c4497fefd8 --- /dev/null +++ b/tests/component_tests/remote_receiver/config/receiver_esp32_c2.yaml @@ -0,0 +1,12 @@ +esphome: + name: test + +esp32: + board: esp32-c2-devkitm-1 + variant: esp32c2 + framework: + type: esp-idf + +remote_receiver: + - id: rcvr + pin: GPIO4 diff --git a/tests/component_tests/remote_receiver/config/receiver_esp8266.yaml b/tests/component_tests/remote_receiver/config/receiver_esp8266.yaml new file mode 100644 index 0000000000..f22d00d630 --- /dev/null +++ b/tests/component_tests/remote_receiver/config/receiver_esp8266.yaml @@ -0,0 +1,9 @@ +esphome: + name: test + +esp8266: + board: d1_mini + +remote_receiver: + - id: rcvr + pin: GPIO4 diff --git a/tests/component_tests/remote_receiver/test_buffer_size.py b/tests/component_tests/remote_receiver/test_buffer_size.py new file mode 100644 index 0000000000..9bfd12d9f5 --- /dev/null +++ b/tests/component_tests/remote_receiver/test_buffer_size.py @@ -0,0 +1,28 @@ +"""buffer_size reaches the receiver when set, and always on the pulse ring targets.""" + +from collections.abc import Callable +from pathlib import Path + + +def test_explicit_buffer_size_is_passed_through( + generate_main: Callable[[str | Path], str], + component_config_path: Callable[[str], Path], +) -> None: + main_cpp = generate_main(component_config_path("receiver_buffer_size.yaml")) + assert "rcvr->set_buffer_size(2000);" in main_cpp + + +def test_pulse_ring_target_keeps_a_default( + generate_main: Callable[[str | Path], str], + component_config_path: Callable[[str], Path], +) -> None: + main_cpp = generate_main(component_config_path("receiver_esp8266.yaml")) + assert "rcvr->set_buffer_size(1000);" in main_cpp + + +def test_esp32_variant_without_rmt_keeps_a_default( + generate_main: Callable[[str | Path], str], + component_config_path: Callable[[str], Path], +) -> None: + main_cpp = generate_main(component_config_path("receiver_esp32_c2.yaml")) + assert "rcvr->set_buffer_size(1000);" in main_cpp diff --git a/tests/component_tests/remote_receiver/test_slot_counts.py b/tests/component_tests/remote_receiver/test_slot_counts.py index f381a64092..4d69e6d923 100644 --- a/tests/component_tests/remote_receiver/test_slot_counts.py +++ b/tests/component_tests/remote_receiver/test_slot_counts.py @@ -27,7 +27,9 @@ def test_bare_receiver_emits_no_counts( generate_main: Callable[[str | Path], str], component_config_path: Callable[[str], Path], ) -> None: - generate_main(component_config_path("receiver_bare.yaml")) + main_cpp = generate_main(component_config_path("receiver_bare.yaml")) + # the RMT ring is sized in setup() unless buffer_size is set + assert "set_buffer_size" not in main_cpp assert get_define_value("REMOTE_BASE_DUMPER_COUNT") is None assert get_define_value("REMOTE_BASE_LISTENER_COUNT") is None