esphome: name: uart-mock-ld2420-simple-test host: api: batch_delay: 0ms # Disable batching to receive all state updates logger: level: VERBOSE external_components: - source: type: local path: EXTERNAL_COMPONENT_PATH # Dummy uart entry to satisfy ld2420's DEPENDENCIES = ["uart"] uart: baud_rate: 115200 port: /dev/null uart_mock: id: mock_uart baud_rate: 115200 auto_start: false responses: # Catch-all response only (no version-specific response). # Without a version response, firmware_ver_ stays at default "v0.0.0". # get_firmware_int("v0.0.0") = 0 < 154 → simple mode (CMD_SYSTEM_MODE_SIMPLE). - expect_tx: [0x04, 0x03, 0x02, 0x01] inject_rx: [ 0xFD, 0xFC, 0xFB, 0xFA, 0x04, 0x00, 0xFF, 0x01, 0x00, 0x00, 0x04, 0x03, 0x02, 0x01, ] injections: # Phase 1 (t=100ms): Valid simple mode text frame - happy path # "ON Range 0100\r\n" → presence=true, distance=100 # Simple mode frames end with \r\n (0x0D 0x0A), triggering handle_simple_mode_. - delay: 100ms inject_rx: [ 0x4F, 0x4E, 0x20, 0x52, 0x61, 0x6E, 0x67, 0x65, 0x20, 0x30, 0x31, 0x30, 0x30, 0x0D, 0x0A, ] # Phase 2 (t=300ms): Garbage bytes # LD2420's readline_ stores all bytes regardless of header. buffer_pos_ = 7. - delay: 200ms inject_rx: [0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x11, 0x22] # Phase 3 (t=500ms): Overflow - inject 50 bytes of 0xFF (MAX_LINE_LENGTH=50) # buffer_pos_ starts at 7 (from Phase 2 garbage). # Positions 7-48 fill (42 bytes), byte 43 triggers overflow (buffer_pos_=49). # After overflow: buffer_pos_=0, remaining 7 bytes fill positions 0-6. # Final buffer_pos_ = 7. - delay: 200ms inject_rx: [ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, ] # Phase 4 (t=1400ms): Recovery after overflow # buffer_pos_ = 7 (from overflow remainder). These 15 bytes fill positions 7-21. # At position 21 (0x0A), \r\n detected → handle_simple_mode_(buffer, 22). # Parser skips 0xFF bytes at positions 0-6, finds "ON" at positions 7-8, # parses digits "0050" → distance=50. # Delay=900ms ensures >1000ms gap from Phase 1 for REFRESH_RATE_MS throttle. - delay: 900ms inject_rx: [ 0x4F, 0x4E, 0x20, 0x52, 0x61, 0x6E, 0x67, 0x65, 0x20, 0x30, 0x30, 0x35, 0x30, 0x0D, 0x0A, ] # Phase 5 (t=2500ms): 16-digit distance - tests PR #14458 bug #1 # "ON Range 0000000000000000\r\n" has 16 digit characters. # handle_simple_mode_ outbuf is 16 bytes, can hold 15 digits (index 0-14). # # Pre-fix: At the 16th digit, index=15, (index < bufsize-1) is false. # The digit branch doesn't increment pos. The else branch is skipped. # pos stays at the 16th digit FOREVER → INFINITE LOOP. # The binary hangs, no more state updates, test times out. # Post-fix: pos always increments (moved outside digit branch). # 16th digit skipped, loop continues to \r\n. distance=0. - delay: 1100ms inject_rx: [ 0x4F, 0x4E, 0x20, 0x52, 0x61, 0x6E, 0x67, 0x65, 0x20, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x0D, 0x0A, ] # Phase 6 (t=3700ms): Post-bug-trigger recovery # If Phase 5 didn't hang, this frame should parse correctly. # "ON Range 0025\r\n" → distance=25 # Delay=1200ms ensures >1000ms gap from Phase 5 for throttle. - delay: 1200ms inject_rx: [ 0x4F, 0x4E, 0x20, 0x52, 0x61, 0x6E, 0x67, 0x65, 0x20, 0x30, 0x30, 0x32, 0x35, 0x0D, 0x0A, ] button: - platform: template name: "Start Scenario" on_press: - lambda: 'id(mock_uart).start_scenario();' ld2420: id: ld2420_dev uart_id: mock_uart sensor: - platform: ld2420 ld2420_id: ld2420_dev moving_distance: name: "Moving Distance" filters: - timeout: timeout: 50ms value: last - throttle_with_priority: 50ms binary_sensor: - platform: ld2420 ld2420_id: ld2420_dev has_target: name: "Has Target" filters: - settle: 50ms