Skip to content

[ld2410] Add frame header synchronization to readline_()#14136

Merged
swoboda1337 merged 1 commit intoesphome:devfrom
swoboda1337:fix/ld2410-frame-sync
Feb 20, 2026
Merged

[ld2410] Add frame header synchronization to readline_()#14136
swoboda1337 merged 1 commit intoesphome:devfrom
swoboda1337:fix/ld2410-frame-sync

Conversation

@swoboda1337
Copy link
Member

What does this implement/fix?

Adds frame header synchronization to readline_() to prevent the parser from getting stuck in an infinite overflow loop when it loses sync with the UART byte stream.

This is the same latent bug as fixed in LD2450 (#14135). Without header validation, if the parser starts reading mid-frame (e.g. after module restart during setup(), or due to UART noise), the accumulated bytes never form a valid frame. The buffer overflows, resets to position 0, and immediately starts accumulating the next byte — which is still mid-frame garbage. This cycle repeats indefinitely, producing "Max command length exceeded; ignoring" log spam and preventing initialization.

The fix validates the first 4 bytes of each frame match a known header (DATA F4 F3 F2 F1 or CMD FD FC FB FA) before accumulating data. Non-header bytes are discarded. On header byte mismatch mid-header, the buffer resets and checks if the mismatched byte starts a new frame. Also adds a return after buffer overflow reset to prevent immediately re-accumulating the overflowed byte.

Types of changes

  • Bugfix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Developer breaking change (an API change that could break external components)
  • Code quality improvements to existing code or addition of tests
  • Other

Related issue or feature (if applicable):

Pull request in esphome-docs with documentation (if applicable):

  • N/A

Test Environment

  • ESP32
  • ESP32 IDF
  • ESP8266
  • RP2040
  • BK72xx
  • RTL87xx
  • LN882x
  • nRF52840

Example entry for config.yaml:

# No config changes required - this is an internal parser fix
uart:
  tx_pin: GPIO1
  rx_pin: GPIO3
  baud_rate: 256000

ld2410:

Checklist:

  • The code change is tested and works locally.
  • Tests have been added to verify that the new code works (under tests/ folder).

If user exposed functionality or configuration variables are added/changed:

Add frame header validation to prevent the parser from getting stuck
in an overflow loop when it loses sync with the UART byte stream
(e.g. after module restart or UART noise). This is the same latent
bug fixed in ld2450 (PR esphome#14135) and present in ld2420.

The fix validates the first 4 bytes of each frame match a known
header (DATA or CMD) before accumulating data. On header byte
mismatch, the buffer resets and checks if the mismatched byte starts
a new frame. Also adds a return after buffer overflow reset to
prevent immediately re-accumulating the overflowed byte.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings February 20, 2026 02:58
@github-actions
Copy link
Contributor

To use the changes from this PR as an external component, add the following to your ESPHome configuration YAML file:

external_components:
  - source: github://pr#14136
    components: [ld2410]
    refresh: 1h

(Added by the PR bot)

@github-actions
Copy link
Contributor

👋 Hi there! This PR modifies 1 file(s) with codeowners.

@regevbr, @sebcaps - As codeowner(s) of the affected files, your review would be appreciated! 🙏

Note: Automatic review request may have failed, but you're still welcome to review.

@codecov-commenter
Copy link

codecov-commenter commented Feb 20, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 74.11%. Comparing base (c1265a9) to head (e9aeb42).
⚠️ Report is 3 commits behind head on dev.

Additional details and impacted files
@@           Coverage Diff           @@
##              dev   #14136   +/-   ##
=======================================
  Coverage   74.11%   74.11%           
=======================================
  Files          55       55           
  Lines       11590    11590           
  Branches     1578     1578           
=======================================
  Hits         8590     8590           
  Misses       2598     2598           
  Partials      402      402           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@bdraco
Copy link
Member

bdraco commented Feb 20, 2026

[21:01:09.812][D][ld2410:208]: LD2410 Sun Light: 7%
[21:01:09.812][D][sensor:118]: 'Sun Light' >> 7 %
[21:01:09.924][D][binary_sensor:044]: 'Online' >> ON
[21:01:10.598][D][sensor:118]: 'Moving Distance' >> 84 cm
[21:01:10.598][D][sensor:118]: 'Still Distance' >> 80 cm
[21:01:10.598][D][sensor:118]: 'Detection Distance' >> 70 cm
[21:01:10.598][D][ld2410:208]: LD2410 Sun Light: 5%
[21:01:10.598][D][sensor:118]: 'Sun Light' >> 5 %
[21:01:11.699][D][sensor:118]: 'Moving Distance' >> 80 cm
[21:01:11.699][D][sensor:118]: 'Still Distance' >> 83 cm
[21:01:11.699][D][sensor:118]: 'Detection Distance' >> 71 cm
[21:01:11.714][D][ld2410:208]: LD2410 Sun Light: 8%
[21:01:11.714][D][sensor:118]: 'Sun Light' >> 8 %
[21:01:12.770][D][sensor:118]: 'Moving Distance' >> 84 cm
[21:01:12.773][D][sensor:118]: 'Still Distance' >> 81 cm

👍

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds UART frame header synchronization to the LD2410 byte-stream parser to prevent endless buffer overflows/log spam when the parser starts mid-frame (e.g., restart/noise), improving initialization robustness.

Changes:

  • Validate the first 4 bytes of an incoming frame against known DATA/CMD headers before accumulating the frame.
  • Discard non-header bytes to re-synchronize on valid frame boundaries.
  • Return immediately after buffer overflow reset to avoid continuing processing in the overflow path.
Comments suppressed due to low confidence (1)

esphome/components/ld2410/ld2410.cpp:640

  • HEADER_FOOTER_SIZE was introduced/used for header handling, but the footer checks still use the literal - 4 when indexing into buffer_data_. For consistency and to avoid future mistakes if the header/footer size changes, use HEADER_FOOTER_SIZE in those index calculations as well (e.g., buffer_pos_ - HEADER_FOOTER_SIZE).
  if (ld2410::validate_header_footer(DATA_FRAME_FOOTER, &this->buffer_data_[this->buffer_pos_ - 4])) {
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
    char hex_buf[format_hex_pretty_size(MAX_LINE_LENGTH)];
    ESP_LOGV(TAG, "Handling Periodic Data: %s", format_hex_pretty_to(hex_buf, this->buffer_data_, this->buffer_pos_));
#endif
    this->handle_periodic_data_();
    this->buffer_pos_ = 0;  // Reset position index for next message
  } else if (ld2410::validate_header_footer(CMD_FRAME_FOOTER, &this->buffer_data_[this->buffer_pos_ - 4])) {

@swoboda1337 swoboda1337 enabled auto-merge (squash) February 20, 2026 03:02
@swoboda1337
Copy link
Member Author

Thanks

@swoboda1337 swoboda1337 merged commit a2f0607 into esphome:dev Feb 20, 2026
43 checks passed
swoboda1337 added a commit to swoboda1337/esphome that referenced this pull request Feb 20, 2026
…me resync

Reverts the frame header synchronization added in esphome#14135 and esphome#14136
in favor of a simpler fix: increasing MAX_LINE_LENGTH so that the
existing footer-based resynchronization can recover after losing sync.

Both components already check for frame footers at every byte position,
which naturally resyncs the parser. The problem was that the buffers
were sized exactly to fit the largest frame, so a desynced parser's
footer could land at the overflow boundary and get discarded. Increasing
the buffer by 4 bytes (footer size) ensures the footer always lands
inside the buffer.

- ld2450: 41 -> 45 (zone query response = 40 bytes + 1 null + 4 footer)
- ld2410: 46 -> 50 (engineering data frame = 45 bytes + 1 null + 4 footer)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
swoboda1337 added a commit to swoboda1337/esphome that referenced this pull request Feb 20, 2026
…me resync

Reverts the frame header synchronization added in esphome#14135 and esphome#14136
in favor of a simpler fix: increasing MAX_LINE_LENGTH so that the
existing footer-based resynchronization can recover after losing sync.

Both components already check for frame footers at every byte position,
which naturally resyncs the parser. The problem was that the buffers
were sized exactly to fit the largest frame, so a desynced parser's
footer could land at the overflow boundary and get discarded. Increasing
the buffer by 4 bytes (footer size) ensures the footer always lands
inside the buffer.

- ld2450: 41 -> 45 (zone query response = 40 bytes + 1 null + 4 footer)
- ld2410: 46 -> 50 (engineering data frame = 45 bytes + 1 null + 4 footer)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
swoboda1337 added a commit to swoboda1337/esphome that referenced this pull request Feb 20, 2026
…me resync

Reverts the frame header synchronization added in esphome#14135 and esphome#14136
in favor of a simpler fix: increasing MAX_LINE_LENGTH so that the
existing footer-based resynchronization can recover after losing sync.

Both components already check for frame footers at every byte position,
which naturally resyncs the parser. The problem was that the buffers
were sized exactly to fit the largest frame, so a desynced parser's
footer could land at the overflow boundary and get discarded. Increasing
the buffer by 4 bytes (footer size) ensures the footer always lands
inside the buffer.

- ld2450: 41 -> 45 (zone query response = 40 bytes + 1 null + 4 footer)
- ld2410: 46 -> 50 (engineering data frame = 45 bytes + 1 null + 4 footer)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
swoboda1337 added a commit to swoboda1337/esphome that referenced this pull request Feb 20, 2026
…me resync

Reverts the frame header synchronization added in esphome#14135 and esphome#14136
in favor of a simpler fix: increasing MAX_LINE_LENGTH so that the
existing footer-based resynchronization can recover after losing sync.

Both components already check for frame footers at every byte position,
which naturally resyncs the parser. The problem was that the buffers
were sized exactly to fit the largest frame, so a desynced parser's
footer could land at the overflow boundary and get discarded. Increasing
the buffer by 4 bytes (footer size) ensures the footer always lands
inside the buffer.

- ld2450: 41 -> 45 (zone query response = 40 bytes + 1 null + 4 footer)
- ld2410: 46 -> 50 (engineering data frame = 45 bytes + 1 null + 4 footer)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
swoboda1337 added a commit to swoboda1337/esphome that referenced this pull request Feb 20, 2026
…me resync

Reverts the frame header synchronization added in esphome#14135 and esphome#14136
in favor of a simpler fix: increasing MAX_LINE_LENGTH so that the
existing footer-based resynchronization can recover after losing sync.

Both components already check for frame footers at every byte position,
which naturally resyncs the parser. The problem was that the buffers
were sized exactly to fit the largest frame, so a desynced parser's
footer could land at the overflow boundary and get discarded. Increasing
the buffer by 4 bytes (footer size) ensures the footer always lands
inside the buffer.

- ld2450: 41 -> 45 (zone query response = 40 bytes + 1 null + 4 footer)
- ld2410: 46 -> 50 (engineering data frame = 45 bytes + 1 null + 4 footer)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
swoboda1337 added a commit that referenced this pull request Feb 20, 2026
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
@swoboda1337 swoboda1337 mentioned this pull request Feb 20, 2026
@github-actions github-actions bot locked and limited conversation to collaborators Feb 22, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants