[remote_receiver] Size the RMT ring buffer from receive symbols by default (#19100)

This commit is contained in:
J. Nick Koston
2026-09-11 18:20:40 -05:00
committed by GitHub
parent 9f9df85aa0
commit ff9b2a1c83
8 changed files with 96 additions and 23 deletions
@@ -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]))
@@ -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};
};
@@ -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_),
@@ -0,0 +1,10 @@
esphome:
name: test
esp32:
board: esp32dev
remote_receiver:
- id: rcvr
pin: GPIO4
buffer_size: 2kb
@@ -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
@@ -0,0 +1,9 @@
esphome:
name: test
esp8266:
board: d1_mini
remote_receiver:
- id: rcvr
pin: GPIO4
@@ -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
@@ -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