esphome:
  name: uart-mock-ld2450-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 ld2450's DEPENDENCIES = ["uart"]
# The actual UART bus used is the uart_mock component below
uart:
  baud_rate: 115200
  port: /dev/null

uart_mock:
  id: mock_uart
  baud_rate: 256000
  auto_start: false
  responses:
    # Catch-all response: match any command footer (04 03 02 01).
    # Returns a generic ACK to unblock setup commands.
    #
    # Response layout:
    #   [0-3]  FD FC FB FA = header
    #   [4-5]  04 00       = length 4
    #   [6]    FF          = cmd (handled as CMD_ENABLE_CONF)
    #   [7]    01          = status (ACK)
    #   [8-9]  00 00       = error = 0
    #   [10-13] 04 03 02 01 = footer
    - 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 LD2450 periodic data frame - happy path
    # The buffer is clean at this point, so this frame should parse correctly.
    #
    # Target 1: X=-500mm, Y=1000mm, Speed=-50mm/s (approaching), Res=320mm
    #   X: magnitude=500 (0x01F4), negative → high=0x01, low=0xF4
    #   Y: magnitude=1000 (0x03E8), positive → high=0x83, low=0xE8
    #   Speed: raw=5, negative (approaching) → high=0x00, low=0x05, decoded=-50mm/s
    #   Resolution: 320 → low=0x40, high=0x01
    #   Distance: sqrt(500²+1000²) = sqrt(1250000) ≈ 1118mm
    #
    # Target 2: X=200mm, Y=500mm, Speed=0 (stationary), Res=100mm
    #   X: magnitude=200 (0x00C8), positive → high=0x80, low=0xC8
    #   Y: magnitude=500 (0x01F4), positive → high=0x81, low=0xF4
    #   Speed: 0 → 0x00, 0x00
    #   Resolution: 100 → low=0x64, high=0x00
    #   Distance: sqrt(200²+500²) = sqrt(290000) ≈ 538mm
    #
    # Target 3: No target (all zeros)
    #   Distance: 0 → sensors publish unknown/NaN
    #
    # Counts: target_count=2, moving_target_count=1, still_target_count=1
    #
    # Frame layout (30 bytes):
    #   [0-3]   AA FF 03 00 = periodic data header
    #   [4-11]  Target 1 (8 bytes): X_L X_H Y_L Y_H SPD_L SPD_H RES_L RES_H
    #   [12-19] Target 2 (8 bytes)
    #   [20-27] Target 3 (8 bytes)
    #   [28-29] 55 CC = periodic data footer
    - delay: 100ms
      inject_rx:
        [
          0xAA, 0xFF, 0x03, 0x00,
          0xF4, 0x01, 0xE8, 0x83, 0x05, 0x00, 0x40, 0x01,
          0xC8, 0x80, 0xF4, 0x81, 0x00, 0x00, 0x64, 0x00,
          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
          0x55, 0xCC,
        ]

    # Phase 2 (t=300ms): Garbage bytes
    # LD2450's readline_ does NOT reject bytes at position 0 (unlike LD2412),
    # so these bytes accumulate in the buffer. buffer_pos_ goes from 0 to 7.
    - delay: 200ms
      inject_rx: [0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x11, 0x22]

    # Phase 3 (t=400ms): Truncated frame (header + partial data, no footer)
    # More bytes accumulating in the buffer without a footer match.
    # After this, buffer_pos_ = 7 + 8 = 15.
    - delay: 100ms
      inject_rx: [0xAA, 0xFF, 0x03, 0x00, 0x01, 0x02, 0x03, 0x04]

    # Phase 4 (t=600ms): Overflow - inject 75 bytes of 0xFF (MAX_LINE_LENGTH=45)
    # Buffer has 15 bytes from phases 2+3.
    # readline_() stores bytes while buffer_pos_ < 44. When buffer_pos_ == 44,
    # the next byte triggers overflow: logs warning, resets buffer_pos_ to 0,
    # and discards that byte.
    #
    # First overflow: 29 bytes fill positions 15-43 (buffer_pos_=44), byte 30
    #   triggers overflow (discarded). Total consumed: 30 bytes.
    # Second overflow: 44 bytes fill positions 0-43 (buffer_pos_=44), byte 45
    #   triggers overflow (discarded). Total consumed: 30+45 = 75 bytes.
    # After both overflows, buffer_pos_ = 0 (clean state for recovery frame).
    - 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,
          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 5 (t=700ms): Valid frame after overflow - recovery test
    # Buffer was reset by overflow. This valid frame should parse correctly.
    #
    # Target 1: X=300mm, Y=400mm, Speed=30mm/s (moving away), Res=100mm
    #   X: magnitude=300 (0x012C), positive → high=0x81, low=0x2C
    #   Y: magnitude=400 (0x0190), positive → high=0x81, low=0x90
    #   Speed: raw=3, positive (moving away) → high=0x80, low=0x03, decoded=30mm/s
    #   Resolution: 100 → low=0x64, high=0x00
    #   Distance: sqrt(300²+400²) = 500mm
    #
    # Target 2 & 3: No target (all zeros)
    # Counts: target_count=1, moving_target_count=1, still_target_count=0
    - delay: 100ms
      inject_rx:
        [
          0xAA, 0xFF, 0x03, 0x00,
          0x2C, 0x81, 0x90, 0x81, 0x03, 0x80, 0x64, 0x00,
          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
          0x55, 0xCC,
        ]

ld2450:
  id: ld2450_dev
  uart_id: mock_uart

sensor:
  - platform: ld2450
    ld2450_id: ld2450_dev
    target_count:
      name: "Target Count"
      filters: &sensor_filters
        - timeout:
            timeout: 50ms
            value: last
        - throttle_with_priority: 50ms
    still_target_count:
      name: "Still Target Count"
      filters: *sensor_filters
    moving_target_count:
      name: "Moving Target Count"
      filters: *sensor_filters
    target_1:
      x:
        name: "Target 1 X"
        filters: *sensor_filters
      y:
        name: "Target 1 Y"
        filters: *sensor_filters
      speed:
        name: "Target 1 Speed"
        filters: *sensor_filters
      distance:
        name: "Target 1 Distance"
        filters: *sensor_filters
      resolution:
        name: "Target 1 Resolution"
        filters: *sensor_filters
      angle:
        name: "Target 1 Angle"
        filters: *sensor_filters
    target_2:
      x:
        name: "Target 2 X"
        filters: *sensor_filters
      y:
        name: "Target 2 Y"
        filters: *sensor_filters
      speed:
        name: "Target 2 Speed"
        filters: *sensor_filters
      distance:
        name: "Target 2 Distance"
        filters: *sensor_filters

binary_sensor:
  - platform: ld2450
    ld2450_id: ld2450_dev
    has_target:
      name: "Has Target"
      filters: &binary_sensor_filters
        - settle: 50ms
    has_moving_target:
      name: "Has Moving Target"
      filters: *binary_sensor_filters
    has_still_target:
      name: "Has Still Target"
      filters: *binary_sensor_filters

text_sensor:
  - platform: ld2450
    ld2450_id: ld2450_dev
    target_1:
      direction:
        name: "Target 1 Direction"

button:
  - platform: template
    name: "Start Scenario"
    id: start_scenario_btn
    on_press:
      - lambda: 'id(mock_uart).start_scenario();'
