From 338e4989601de87d4bf371997b3fa0714c8bc796 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 16 Aug 2026 18:41:32 -0500 Subject: [PATCH] [ld2420] Address review findings Clear cmd_active_ when the blocking engine exhausts retries so loop() does not stay skipped forever, send config mode exit blind before abandoning a startup sequence so the module resumes streaming, and repeat the first frame in the delayed boot fixture so the initial state swallow cannot make the test flaky. --- esphome/components/ld2420/ld2420.cpp | 17 ++++++++++++ esphome/components/ld2420/ld2420.h | 1 + .../uart_mock_ld2420_delayed_boot.yaml | 27 ++++++++++++++++--- 3 files changed, 41 insertions(+), 4 deletions(-) diff --git a/esphome/components/ld2420/ld2420.cpp b/esphome/components/ld2420/ld2420.cpp index dc54debcc0..1117b43b81 100644 --- a/esphome/components/ld2420/ld2420.cpp +++ b/esphome/components/ld2420/ld2420.cpp @@ -308,6 +308,7 @@ bool LD2420Component::startup_ack_check_() { this->send_startup_cmd_(); return false; } + this->abort_startup_cmd_(); if (this->startup_sequence_retries_ < STARTUP_SEQUENCE_MAX_RETRIES) { this->startup_sequence_retries_++; ESP_LOGW(TAG, "Module setup attempt %u failed; retrying", this->startup_sequence_retries_); @@ -323,6 +324,19 @@ bool LD2420Component::startup_ack_check_() { return false; } +void LD2420Component::abort_startup_cmd_() { + // If the module already acknowledged config mode it stops streaming until + // config mode is exited, so send the exit command blind before abandoning + // the sequence; otherwise the stream never resumes and neither passive + // parsing nor the next listen phase would ever see data. + if (this->startup_state_ == StartupState::STARTUP_STATE_ENTER_CONFIG) { + return; // Config mode was never acknowledged; the module is still streaming + } + CmdFrameT frame; + this->build_config_mode_frame_(frame, false); + this->write_cmd_frame_(frame); +} + void LD2420Component::loop_startup_() { switch (this->startup_state_) { case StartupState::STARTUP_STATE_LISTEN: { @@ -875,6 +889,9 @@ int LD2420Component::send_cmd_from_array(CmdFrameT frame) { this->handle_cmd_error(this->cmd_reply_.error); } } + // On ack the reply parser already cleared this; clear it here as well so an + // exhausted retry loop cannot leave loop() skipping all processing forever. + this->cmd_active_ = false; return error; } diff --git a/esphome/components/ld2420/ld2420.h b/esphome/components/ld2420/ld2420.h index a8821f732f..6321de1701 100644 --- a/esphome/components/ld2420/ld2420.h +++ b/esphome/components/ld2420/ld2420.h @@ -172,6 +172,7 @@ class LD2420Component final : public Component, public uart::UARTDevice { void loop_startup_(); void start_startup_cmd_(StartupState state); void send_startup_cmd_(); + void abort_startup_cmd_(); bool startup_ack_check_(); void drain_rx_(); void write_cmd_frame_(const CmdFrameT &frame); diff --git a/tests/integration/fixtures/uart_mock_ld2420_delayed_boot.yaml b/tests/integration/fixtures/uart_mock_ld2420_delayed_boot.yaml index 5aee2b792b..f29866d8db 100644 --- a/tests/integration/fixtures/uart_mock_ld2420_delayed_boot.yaml +++ b/tests/integration/fixtures/uart_mock_ld2420_delayed_boot.yaml @@ -45,10 +45,29 @@ uart_mock: 0xF8, 0xF7, 0xF6, 0xF5, ] - # Post-setup frame (t=3300ms): distance=50 proves streaming still works - # after the setup handshake. Delay=1300ms keeps >1000ms publish throttle - # gap from the first frame. - - delay: 1300ms + # Repeat frame (t=3100ms): distance=100 again. If the API client happens + # to subscribe after the first frame, the first published state is + # swallowed as the entity's initial state; repeating the value makes the + # test's first collected state deterministic. Delay=1100ms keeps >1000ms + # publish throttle gap from the first frame. + - delay: 1100ms + inject_rx: + [ + 0xF4, 0xF3, 0xF2, 0xF1, + 0x23, 0x00, + 0x01, + 0x64, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xF8, 0xF7, 0xF6, 0xF5, + ] + + # Post-setup frame (t=4200ms): distance=50 proves streaming still works + # after the setup handshake. Delay=1100ms keeps >1000ms publish throttle + # gap from the repeat frame. + - delay: 1100ms inject_rx: [ 0xF4, 0xF3, 0xF2, 0xF1,