A PIR motion sensor notices you walk in and then loses you as soon as you sit still with a book, so the light goes off at the worst possible moment. A 24 GHz radar module such as the Hi-Link LD2410 also picks up a person who is not moving, which is what an office, bathroom or living room actually needs. ESPHome supports the module directly, so an ESP32 and four wires are enough to get a presence sensor into Home Assistant.

Wiring and YAML take half an hour. Tuning takes longer, and it decides whether the sensor ends up useful or annoying.

Which LD2410 to buy

ESPHome's ld2410 component supports three variants with similar communication protocols: LD2410, LD2410B and LD2410C. Hi-Link's datasheets for the B and C list the same core figures: an FMCW radar working at 24-24.25 GHz, detection distance adjustable from 0.75 to 6 m, a detection angle of ±60°, a 5 V supply able to deliver more than 200 mA, and 3.3 V logic on the serial port and the OUT pin.

For a DIY build the main difference is the connector:

  • LD2410C measures 16 x 22 mm and has pins at 2.54 mm pitch, the usual spacing for breadboards and Dupont jumper wires. It is the easiest one to connect.
  • LD2410B is a 7 x 35 mm strip with holes at 1.27 mm pitch, and according to its datasheet it ships without pins. Expect thin wire and some careful soldering.

Both the B and the C have Bluetooth for Hi-Link's HLKRadarTool phone app, and it is switched on by default. More on that further down.

Wiring it to an ESP32

The module has five pins: VCC, GND, TX, RX and OUT. Hi-Link's own documents do not agree on their order (the LD2410C datasheet lists TX, RX, OUT, GND, VCC, while the LD2410C serial protocol document puts OUT first), so go by the labels printed on your board, not by a diagram from the internet.

LD2410 ESP32
VCC 5V (often labelled VIN)
GND GND
TX GPIO16 (receives on the ESP side)
RX GPIO17 (transmits on the ESP side)
OUT optional, any free GPIO

A few things to watch:

  • Power it from 5 V, not 3.3 V. The datasheets call for 5 V and more than 200 mA. On most ESP32 development boards powered over USB, the 5V/VIN pin is the simplest source.
  • No level shifter is needed. The module's I/O works at 3.3 V, the same as the ESP32.
  • TX goes to RX. The module's TX connects to the pin the ESP receives on, and the other way round. ESPHome's UART documentation notes that pin names depend on whose perspective you take, so if no data arrives, swap the two wires first.
  • GPIO16 and GPIO17 are only an example. On the ESP32, ESPHome uses the chip's hardware UARTs (the original ESP32 has three), and any pair of GPIO pins with suitable input and output modes will do.
  • An ESP32 is a better choice than an ESP8266 here. The LD2410 talks at 256000 baud by default and ESPHome strongly recommends hardware UART pins for it. On the ESP8266, ESPHome falls back to a software UART that can glitch at higher baud rates, and the only full hardware UART (UART0, on GPIO1/GPIO3 or GPIO15/GPIO13) is used by the logger by default.

The OUT pin goes high when the module detects someone. You do not need it, because the serial connection carries the same information and much more, but you can read it with an ordinary gpio binary sensor if you want a second signal.

The ESPHome configuration

This is a working starting point for an ESP32. It exposes presence, distances and the settings you will need for tuning:

esphome:
  name: office-presence

esp32:
  board: esp32dev

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

logger:

api:
  encryption:
    key: !secret encryption_key

ota:
  platform: esphome

uart:
  id: ld2410_uart
  tx_pin: GPIO17
  rx_pin: GPIO16
  baud_rate: 256000
  parity: NONE
  stop_bits: 1

ld2410:
  uart_id: ld2410_uart

binary_sensor:
  - platform: ld2410
    has_target:
      name: Presence
    has_moving_target:
      name: Moving target
    has_still_target:
      name: Still target

sensor:
  - platform: ld2410
    moving_distance:
      name: Moving distance
    still_distance:
      name: Still distance
    detection_distance:
      name: Detection distance

number:
  - platform: ld2410
    timeout:
      name: Timeout
    max_move_distance_gate:
      name: Max move distance gate
    max_still_distance_gate:
      name: Max still distance gate

switch:
  - platform: ld2410
    engineering_mode:
      name: Engineering mode
    bluetooth:
      name: Bluetooth

button:
  - platform: ld2410
    restart:
      name: Restart
    factory_reset:
      name: Factory reset
    query_params:
      name: Query params

What the less obvious parts do:

  • parity: NONE and stop_bits: 1 are the UART defaults anyway. ESPHome's LD2410 page says they must have exactly these values, and writing them out keeps anyone from changing them by accident.
  • has_target is the entity to use in automations. It is on when the module sees either a moving or a still target. The separate moving and still entities are mainly useful for diagnosis.
  • Every ld2410 binary sensor comes with a default settle: 1000ms filter, and every sensor with throttle_with_priority: 1000ms, so that Home Assistant is not flooded with updates. If you add your own filters:, they replace the default, so add the default back if you still want it.
  • The number, switch and button entities change the module's own configuration from Home Assistant, without reflashing the ESP32.
  • There is also a baud_rate select. Leave it alone unless you have a reason: after a change, all sensors stop working until you update the uart: block and install the configuration again.

Flash it over USB the first time, as with any new ESPHome device. The device will then appear in Home Assistant under Settings > Devices & services.

How distance gates work

The radar divides its range into nine distance gates, g0 to g8. By default each gate is 0.75 m deep. ESPHome's distance_resolution select can switch that to 0.2 m for finer steps.

Each gate has two thresholds from 0 to 100, one for movement and one for a still target. If the energy the radar measures in a gate rises above that gate's threshold, the module reports a detection. ESPHome lists these factory defaults:

Gate Move threshold Still threshold
0 50 0
1 50 0
2 40 40
3 30 40
4 20 30
5 15 30
6 15 20
7 15 20
8 15 20

Three more settings matter in everyday use:

  • Max move distance gate and Max still distance gate (2 to 8, default 8) cut detection off beyond a given gate. At 0.75 m per gate, a value of 3 means the sensor ignores anything farther than about 2.25 m. That is the quickest fix for a small room next to a hallway.
  • Timeout is how many seconds presence stays on after the target is gone. The default is 5 s, which is short for lighting. Raise it if the light goes off every time you pause.

Tuning in engineering mode

In engineering mode the module reports the movement and still energy for each gate separately. That tells you which gate a fan, a radiator or the neighbour's hallway is lighting up. First add the per-gate entities to the existing - platform: ld2410 entries under sensor: and number::

sensor:
  - platform: ld2410
    # ...the entries from above, plus:
    g0:
      move_energy:
        name: G0 move energy
      still_energy:
        name: G0 still energy
    g1:
      move_energy:
        name: G1 move energy
      still_energy:
        name: G1 still energy
    # repeat for g2 to g8

number:
  - platform: ld2410
    # ...the entries from above, plus:
    g0:
      move_threshold:
        name: G0 move threshold
      still_threshold:
        name: G0 still threshold
    g1:
      move_threshold:
        name: G1 move threshold
      still_threshold:
        name: G1 still threshold
    # repeat for g2 to g8

Within a gX block under number:, both move_threshold and still_threshold are required. ESPHome's calibration procedure is short: turn on engineering mode, watch the energy sensors, adjust the thresholds and repeat until you are happy, then turn engineering mode off. ESPHome's LD2410 page also includes a ready-made dashboard card with all of these entities. You just replace DEVICE with your device name.

In practice it helps to do it in two passes:

  1. Empty room. Leave running whatever the sensor should ignore (a fan, a curtain moving in the draught from a window), walk out and watch each gate for a few minutes. Set each threshold a little above the highest value you saw.
  2. You, sitting still. Sit where you usually sit and check that the still energy in your gate stays above its threshold. A relaxed, breathing person gives a weak signal, and a still threshold set too high makes the sensor drop you after the timeout.

Energy tops out at 100 and detection needs a value above the threshold, so a threshold of 100 effectively switches a gate off. Do not leave engineering mode on when you are done. ESPHome notes that it needs more resources, and Akamatis, which sells LD2410-based sensors, warns that it produces a lot of UART data and can leave entities showing "unknown".

Placement: radar sees more than you think

Radar waves pass through drywall, ceilings and floors. Akamatis lists typical sources of unexpected detections: people behind a wall or in the next flat, movement upstairs or downstairs, water running in a toilet, ventilation and ceiling fans, curtains and hanging objects. That is normal radar behaviour, not a faulty module.

  • Point the module at the area you care about and limit the maximum gates to the depth of the room.
  • Keep it out of the direct path of fans and curtains, or find their gate in engineering mode and raise its thresholds.
  • If it still triggers on its own, an extra filter can help. In a long Home Assistant forum thread on the LD2410, one user with a ceiling fan reported good results from angling the module slightly downwards and adding delayed_on: 5s and delayed_off: 10s filters to the presence sensor after calibrating it.

Switch off Bluetooth if you don't use it

Hi-Link's documentation for the LD2410B says its Bluetooth is enabled by default and that the HLKRadarTool app works within Bluetooth range, up to about 4 m. ESPHome's bluetooth switch for the module also defaults to on, and the default Bluetooth password, HiLink, is printed in the ESPHome documentation. If you configure everything through ESPHome, turn off the Bluetooth switch. According to the LD2410B protocol document, the change takes effect after the module restarts, which is what the Restart button is for. If you want to keep using the app, set your own six-character password with the bluetooth_password.set action instead.

Using it in automations

The basic automation needs one entity: turn the light on when Presence goes on and off when it goes off. Set the hold time with the Timeout number, which lives in the module and can be changed from Home Assistant at any time. If you prefer to smooth things in ESPHome, add a filter to has_target. Since your own filters replace the default settle, write it back in:

binary_sensor:
  - platform: ld2410
    has_target:
      name: Presence
      filters:
        - settle: 1000ms
        - delayed_off: 30s

Treat the distance sensors as a rough hint. With 0.75 m gates they move in coarse steps, and presence is the more reliable signal to build on.

When something doesn't work

  • All entities stay unknown. Check TX and RX (swap them), the baud rate in the uart: block and the 5 V supply.
  • Sensors stopped after a settings change. If you changed the module's baud rate, update baud_rate under uart: to match and install the configuration again. Factory reset restores the module's default settings.
  • Presence never clears. Something in the room is above the still threshold of some gate. Find it in engineering mode, then raise that gate's threshold or reduce Max still distance gate.
  • Presence drops while you sit still. The still threshold in your gate is too high, or Timeout is too short.
  • Random glitches on an ESP8266. That is the software UART struggling at 256000 baud. Move the module to an ESP32.