/ESPHome-HLK-LD2450

ESPHome support for the HI-Link HLK-LD2450 millimeter wave sensor (external component) with support for custom zones using convex polygons.

Primary LanguageC++MIT LicenseMIT

ESPHome Hi-Link HKL-LD2450 CI

This external ESPHome component adds support for the Hi-Link HKL-LD2450 Human presence sensor to ESPHome. In addition to a basic binary presence sensor, this component adds various different sensors for each detected target and supports custom presence detection Zones.

Recommended configuration

  1. Set up a single target with debugging enabled and view the logs (i.e. using esphome logs).
  2. Determine the corners of your zones by moving around and taking note of the x and y coordinates.
  3. Create zones based on these coordinates and add an occupancy sensor to each zone.
  4. Remove the debugging target

This will give you binary occupancy sensors for individual zones. Adding a small off_delay filter is recommended for tracking stability. Zones can overlap one another. A good use-case example for this is the following: Consider a room in which the sensor is mounted, such that it is off-angle with a door. If the sensor can track through the door, this may be undesired behavior. Creating a zone that outlines the room can help solve this issue.

Zone and Margin Sketch

This sketch is intended to help understand zones and margins. The y-axis is referred to as the axis pointing straight away from the sensor. The x-axis is located perpendicularly. A Target is tracked once it enters the polygon given by the configuration (green polygon within the sketch). Once tracked, a Target is allowed to move freely within the yellow polygon. This larger area is given by the base polygon and the margin. If a tracked target leaves the yellow area it is not tracked anymore and the zone becomes unoccupied. Additionally, it won't be tracked immediately again since it is now outside of the green zone.

Configuration variables

LD2450:
  uart_id: uart_bus
  occupancy:
    name: Occupancy

This is the most basic useful configuration. You can find this example alongside other more complex examples in the example folder of this repository. An example configuration with all possible sensors and configurations can be found here.

LD2450

  • uart_id(Required, string): ID of the UART-Component connected to the LD2450 Sensor.
  • name(Optional, string): The name of this sensor, which is used during logging. Defaults to LD2450.
  • flip_x_axis(Optional, boolean): If set to true, values along the X-axis will be flipped. Defaults to false.
  • fast_off_detection(Optional, boolean): If set to true, fast-away detection will be used for targets, which leave the visible range of the sensor. Defaults to false.
  • max_detection_tilt_angle(Optional, number or angle): The highest allowed detection tilt angle. All targets outside this angle will not be tracked. Either a configuration for a number input or a fixed angle value. See: Max Tilt Angle Number.
  • min_detection_tilt_angle(Optional, number or angle): The lowest allowed detection tilt angle. All targets outside this angle will not be tracked. Either a configuration for a number input or a fixed angle value. See: Min Tilt Angle Number.
  • tilt_angle_margin(Optional, ): The margin which is added to the maximum/minimum allowed tilt angle. Targets that are already being tracked, will still be tracked within the additional margin. This prevents on-off-flickering of related sensors. Defaults to .
  • max_detection_distance(Optional, number or distance): The furthest allowed detection distance. All targets further than this distance will not be tracked. Either a configuration for a number input or a fixed distance value. See: Max Distance Number.
  • max_distance_margin(Optional, ): The margin which is added to the maximum allowed distance. Targets that are already being tracked, will still be tracked within the additional margin. This prevents on-off-flickering of related sensors. Defaults to 25cm.
  • occupancy(Optional, binary sensor): A binary sensor, which will be triggered if at least one target is present. id or name required. All options from Binary Sensor.
  • target_count(Optional, sensor): A sensor that provides the number of currently tracked targets. id or name required. All other options from Sensor.
  • restart_button(Optional, button): Restart the connected LD2450 Sensor. All other options from Button.
  • factory_reset_button(Optional, button): Resets the connected LD2450 Sensor to it's factory default state and restarts the module. All other options from Button.
  • tracking_mode_switch(Optional, switch): Enables the multiple target tracking mode of the sensor. If disabled, only a single target will be tracked. All other options from Switch.
  • bluetooth_switch(Optional, switch): Enables or disables bluetooth on the LD2450 Sensor. All other options from Switch.
  • baud_rate_select(Optional, select): Select component which sets the sensors baud rate.
  • targets(Optional, list of targets): A list of at most 3 Targets. Each target has its own configuration and sensors. See Target.
  • zones(Optional, list of zones): A list Zones. Each zone has its own configuration and sensors. See Zone.

Max Tilt Angle Number

Instead of a fixed maximum tilt angle, a user-defined number component can be defined. The values of this component will be used as the highest allowed angle.

  • name(Required, string): Name of the maximum tilt angle number component.
  • initial_value(Optional, distance): The initial value of this sensor. Defaults to 90° - required unit is ° or deg. This value will be ignored if restore_value is enabled.
  • step(Optional, distance): Step size of the input number. Defaults to .
  • restore_value(Optional, boolean): Tries to restore the last value from Flash upon reboot. Defaults to true.

All other options from number.

Min Tilt Angle Number

Instead of a fixed minimum tilt angle, a user-defined number component can be defined. The values of this component will be used as the highest allowed angle.

  • name(Required, string): Name of the minimum tilt angle number component.
  • initial_value(Optional, distance): The initial value of this sensor. Defaults to -90° - required unit is ° or deg. This value will be ignored if restore_value is enabled.
  • step(Optional, distance): Step size of the input number. Defaults to .
  • restore_value(Optional, boolean): Tries to restore the last value from Flash upon reboot. Defaults to true.

All other options from number.

Max Distance Number

Instead of a fixed maximum distance, a user-defined number component can be defined. The values of this component will be used as the furthest allowed distance.

  • name(Required, string): Name of the maximum distance number component.
  • initial_value(Optional, distance): The initial value of this sensor. Defaults to 6m. This value will be ignored if restore_value is enabled.
  • step(Optional, distance): Step size of the input number. Defaults to 10cm.
  • restore_value(Optional, boolean): Tries to restore the last value from Flash upon reboot. Defaults to true.

All other options from number.

Target

The name of target sub-sensor will be prefixed with the target name. For instance, if the Target is called Target 1 and a sub-sensor of this target is named X Position, the actual name of the Sensor will be Target 1 X Position. Each sensor requires at least an id or a name configuration. The default polling rate is 1s. If you end up using these sensors I would recommend excluding them from your Home Assistant Recorder.

  • name(Optional, string): The name of the target. This name will be used as a prefix for sub-sensors. Per default, targets will be named Target x with increasing values for x.
  • debug(Optional, boolean): Enables debugging for this target. The raw sensor values for this target will be logged periodically. Defaults to false.
  • x_position(Optional, sensor): A sensor that reports the X Position of the target. The default name is X Position. All other options from Sensor.
  • y_position(Optional, sensor): A sensor that reports the Y Position of the target. The default name is Y Position. All other options from Sensor.
  • speed(Optional, sensor): A sensor that reports the measured speed of the target in m/s. The default name is Speed. All other options from Sensor.
  • distance_resolution(Optional, sensor): A sensor that reports the reported distance resolution of the target. The default name is Distance Resolution. All other options from Sensor.
  • angle(Optional, sensor): A sensor that reports the angle of the target in relation to the sensor in °. The default name is Angle. All other options from Sensor.
  • distance(Optional, sensor): A sensor that reports the distance from the target to the sensor in m. The default name is Distance. All other options from Sensor.

A valid configuration may look like this or this:

  targets:
    - target:
        debug: true
        angle:
            id: t1_angle

Zone

The name of zone sub-sensor will be prefixed with the zone name. For instance, if the zone is called Dining Table and a sub-sensor of this zone is named Occupancy, the actual name of the Sensor will be Dining Table Occupancy.

  • name(Required, string): The name of the zone. This name will be used as a prefix for sub-sensors.
  • polygon(Required, polygon): A simple convex polygon with at least 3 vertices or a template polygon. See Polygon.
  • margin(Optional, distance): The margin that is added to the zone. Targets that are already being tracked, will still be tracked within the additional margin. This prevents on-off-flickering of related sensors. Defaults to 25cm.
  • target_timeout(Optional, time): The time after which a target within the zone is considered absent. This helps with continuous detection of non-moving targets. Targets which leave the zone via polygon boundaries are still detected as absent form the zone immediately. Defaults to 5s.
  • occupancy(Optional, binary sensor): A binary sensor, that will be triggered if at least one target is tracked inside the zone. id or name required. The default name is empty, which results in the sensor being named after the zone. All options from Binary Sensor.
  • target_count(Optional, sensor): A sensor that provides the number of currently tracked targets within the zone. id or name required. The default name is empty, which results in the sensor being named after the zone. All options from Sensor.

A valid configuration may look like this or this:

  zones:
    - zone:
        name: "Dining table"
        margin: 25cm
        polygon:
          - point:
          ...
        occupancy:
          id: or_occupancy
        target_count:
          id: or_target_count

Polygon

A polygon is made up of multiple points in physical space. The polygon must be a simple convex polygon.

  • x(Required, distance): Distance along the x-axis from the sensor.
  • y(Required, distance): Distance along the y-axis from the sensor.

A valid configuration may look like this or this:

        # Polygon which covers one half of the sensor
        polygon:
          - point:
              x: 0m
              y: 0m
          - point:
              x: 0m
              y: 6m
          - point:
              x: 6m
              y: 6m
          - point:
              x: 6m
              y: 0m

Alternatively, a polygon can also be defined via a template expression like this:

        # Template polygon which updated every 2 seconds
        polygon:
          lambda: !lambda |-
            return {ld2450::Point(1500,10), ld2450::Point(6000,10), ld2450::Point(6000,2600), ld2450::Point(-1500,2600)};
          update_interval: 2s
  • lambda(Required, return std::vector<ld2450::Point>;): List of Points which make up a convex polygon. The expression is evaluated every update_interval, if the provided polygon is invalid (i.e. not convex or too small) the previously used polygon is kept.
  • update_interval(Optional, time): Interval in which the template polygon is evaluated. Set to 0s to disable. Defaults to 1s.

LD2450.zone.update_polygon

The polygon used by a zone can be updated with the zone.update_polygon action. The change is only effective, if the provided polygon is valid.

on_...:
  - LD2450.zone.update_polygon:
      id: z4
      polygon: !lambda |-
        return {ld2450::Point(1500,10), ld2450::Point(6000,10), ld2450::Point(6000,2600), ld2450::Point(-1500,2600)};
  • id(Required, id): Id of the zone which should be updated
  • polygon(Required, return std::vector<ld2450::Point>;): List of Points which make up a convex polygon. The new polygon is only used if it's valid.

With the help of this action, a user editable dynamic polygon can be defined. Note, that this allows for the definition of non-convex polygons. In the referenced example, the number components are only updated if the new polygon is valid. When using the Home Assistant front end, number sliders may not reflect this change (or rather lack thereof) properly. A partial example configuration for dynamic template polygons can be found here.

Troubleshooting

When using Dupont connectors make sure they make proper contact. The very short pins on the LD2450 Sensor can easily go loose or break.

I recommend using a 5V power supply and the 5V input pin whenever possible. Using the the 3.3V pin on the LD2450 sensor may cause instabilities during operation depending on the power supply, voltage regulator and other components used on the same power circuit. In a test scenario using a 3.3V voltage regulator, an ESP-01 and a DHT22 the LD2450 sensor was not able to provide measurements. Observing the LD2450's TX pin using an oscilloscope yielded no updates and noisy behavior around 3.3V. Switching to 5V on the LD2450 sensor resolved this issue and consistent updates from the sensor were observed. In a different scenario, when using 3.3V provided by an ESP32 development board, sensor updates arrived inconsistently/went missing.

In some configurations, this LD2450 component may not perform ideally. If this component is used in combination with other components on a single ESP, especially ones that require long processing times, some updates provided by the sensor may be missed. As a symptom (zone) count sensors may report as Unknown for short periods of time and switches become unresponsive. If this is the case try the following steps:

  • increase rx_buffer_size
  • use a minimal ESPHome yaml configuration for troubleshooting
  • use a reliable 5V power source for the sensor