Tutorial

This tutorial covers the major API methods with real examples and commands.

Links to the API reference will be provided all along so you can get more details.

Before going further on this tutorial, make sure to have a suitable python environment (see Installation).

Connecting to the device

First you need to identify your device, its name depends on your platform! You can ask pyserial to list all available console devices on your system:

$ python -m serial.tools.list_ports

/dev/ttyS0
/dev/ttyS1
/dev/ttyUSB0
3 ports found

Here I use an USB serial adapter on /dev/ttyUSB0, which is confirmed by the system logs. On windows this command most likely returns devices with name in COM*.

For this tutorial I will use /dev/ttyUSB0, replace with yours when applicable.

The following code simply opens the device:

examples/open_device.py
 1#!/usr/bin/env python
 2
 3import asyncio
 4from aio_ld2410 import LD2410
 5
 6async def main():
 7    async with LD2410('/dev/ttyUSB0') as device:
 8        ...
 9
10if __name__ == '__main__':
11    asyncio.run(main())

See also

LD2410.__init__() if you ever need to change the baud rate (defaults to 256000).

Entering the configuration mode

Entering configuration mode is also implemented as an asynchronous context. You cannot call configuration commands outside of this context! This context is a requirement before any other command can be issued.

See also

LD2410.configure() for more details.

In the following example the configuration context spreads over the emphasized lines:

examples/read_firmware_version.py
 1#!/usr/bin/env python
 2
 3import asyncio
 4from aio_ld2410 import LD2410
 5
 6async def main():
 7    async with LD2410('/dev/ttyUSB0') as device:
 8        async with device.configure():
 9            ver = await device.get_firmware_version()
10
11        print(f'[+] Running with firmware v{ver}')
12
13if __name__ == '__main__':
14    asyncio.run(main())

Debug steps if this code does not work

If you ever have an issue with this example, perform the following checks:

  • Check that the device you provided is correct (and is a LD2410)

  • Check that your device does not expect a different baud rate

Notice that on LD2410B and LD2410C, some features may not be available if the firmware printed by this code is too old.

Reading configuration

Standard parameters

The following example used LD2410.get_parameters() to read all the standard parameters from the device (returning a ParametersStatus):

examples/read_simple_configuration.py
 1#!/usr/bin/env python
 2
 3import asyncio
 4from aio_ld2410 import LD2410
 5from collections.abc import Iterable
 6
 7def format_values(values: Iterable[int]) -> str:
 8    return ' | '.join(map('{:3d}'.format, values))
 9
10async def main():
11    async with LD2410('/dev/ttyUSB0') as device:
12        async with device.configure():
13            cfg = await device.get_parameters()
14
15    print(f'Max distance gate           {cfg.max_distance_gate}')
16    print(f'Max motion detection gate   {cfg.moving_max_distance_gate}')
17    print(f'Max static detection gate   {cfg.static_max_distance_gate}')
18    print(f'Presence timeout            {cfg.presence_timeout}')
19    print('Detection thresholds:')
20    print('  Gate    ' + format_values(range(cfg.max_distance_gate + 1)))
21    print('  Moving  ' + format_values(cfg.moving_threshold))
22    print('  Static  ' + format_values(cfg.static_threshold))
23
24if __name__ == '__main__':
25    asyncio.run(main())

This code produces the following output:

$ ./examples/read_simple_configuration.py
Max distance gate           8
Max motion detection gate   8
Max static detection gate   8
Presence timeout            5
Detection thresholds:
  Gate       0 |   1 |   2 |   3 |   4 |   5 |   6 |   7 |   8
  Moving    50 |  50 |  40 |  30 |  20 |  15 |  15 |  15 |  15
  Static     0 |   0 |  40 |  40 |  30 |  30 |  20 |  20 |  20

Distance resolution

Not available on all models / firmwares

This command seems to only be available for LD2410B and LD2410C devices with a (quite) recent firmware.

Using LD2410.get_distance_resolution() we can read the range covered by gates:

examples/read_distance_resolution.py
 1#!/usr/bin/env python
 2
 3import asyncio
 4from aio_ld2410 import LD2410
 5
 6async def main():
 7    async with LD2410('/dev/ttyUSB0') as device:
 8        async with device.configure():
 9            res = await device.get_distance_resolution()
10
11    print(f'Each gate covers {res} centimeters')
12
13if __name__ == '__main__':
14    asyncio.run(main())

Writing configuration

Gate configuration comes with two methods that are when combined analogous to the LD2410.get_parameters() seen above.

General configuration

The following example simply sets the maximum detection gate for moving targets to 4 (3 meters) and the maximum detection gate for static targets to 6 (4.5 meters).

It means that detected targets are either close to the sensor and moving, or can be farther away but standing still.

examples/write_simple_configuration.py
 1#!/usr/bin/env python
 2
 3import asyncio
 4from aio_ld2410 import LD2410
 5
 6async def main():
 7    async with LD2410('/dev/ttyUSB0') as device:
 8        async with device.configure():
 9            await device.set_parameters(
10                moving_max_distance_gate=4,
11                static_max_distance_gate=6,
12                presence_timeout=10,
13            )
14
15if __name__ == '__main__':
16    asyncio.run(main())

Gate sensitivity configuration

To be perfectly complementary to read_simple_configuration, we have to use LD2410.set_gate_sensitivity(), as shown in the following example:

examples/write_gate_sensitivity.py
 1#!/usr/bin/env python
 2
 3import asyncio
 4from aio_ld2410 import LD2410
 5
 6async def main():
 7    # We want it less sensitive for moving people.
 8    MOVING_CONFIG = [50, 50, 40, 40, 35, 30]
 9    # But a little bit more for people standing in front of the sensor.
10    STATIC_CONFIG = [0, 0, 40, 35, 30, 25, 20]
11
12    async with LD2410('/dev/ttyUSB0') as device:
13        async with device.configure():
14            for i in range(len(MOVING_CONFIG)):
15                await device.set_gate_sensitivity(
16                    distance_gate=i,
17                    moving_threshold=MOVING_CONFIG[i],
18                    static_threshold=STATIC_CONFIG[i],
19                )
20
21if __name__ == '__main__':
22    asyncio.run(main())

The result of both scripts can be read again with read_simple_configuration:

$ ./examples/read_simple_configuration.py
Max distance gate           8
Max motion detection gate   4
Max static detection gate   6
Presence timeout            10
Detection thresholds:
  Gate       0 |   1 |   2 |   3 |   4 |   5 |   6 |   7 |   8
  Moving    50 |  50 |  40 |  40 |  35 |  30 |  15 |  15 |  15
  Static     0 |   0 |  40 |  35 |  30 |  25 |  20 |  20 |  20

Set distance resolution

Distance resolution can easily be set through LD2410.set_distance_resolution() but it requires a device restart to be effective on the sensor itself.

Module restart can be performed through LD2410.restart_module().

Your device lies to you

If you forget to restart, a call to LD2410.get_distance_resolution() will return value you just configured and not the value currently applied.

examples/write_distance_configuration.py
 1#!/usr/bin/env python
 2
 3import asyncio
 4from aio_ld2410 import LD2410
 5
 6async def main():
 7    async with LD2410('/dev/ttyUSB0') as device:
 8        async with device.configure():
 9            await device.set_distance_resolution(20)
10            await device.restart_module()
11
12        print('....DEVICE IS RESTARTING....')
13        await asyncio.sleep(2.0)
14
15        async with device.configure():
16            res = await device.get_distance_resolution()
17
18    print(f'Each gate covers {res} centimeters')
19
20if __name__ == '__main__':
21    asyncio.run(main())

Notice the emphasized line, we have to wait for a little bit before we can send new commands to the device (as it is restarting).

Reading reports

Reports are pushed by the device to the serial link regularly and contain detection results. Advanced reports cat be requested using the engineering mode, which will not be covered in this tutorial

See also

LD2410.set_engineering_mode() to toggle the engineering mode. ReportEngineeringStatus to get the content of advanced reports.

Three methods are provided to get reports: - LD2410.get_last_report() to get the latest received report immediately (if any) - LD2410.get_next_report() to wait and get the next available report - LD2410.get_reports() to get reports as they arrive with an asynchronous iterator

Configuration mode conflict

Note that reports are not generated while the configuration mode is active.

The following example runs with the already configured value and reports static and moving targets as they arrive. Run this and hang around in front of the sensor to see changes.

examples/read_basic_reports.py
 1#!/usr/bin/env python
 2
 3import asyncio
 4from aio_ld2410 import LD2410, ReportBasicStatus, TargetStatus
 5
 6def format_basic_report(rep: ReportBasicStatus) -> str:
 7    items = []
 8
 9    if rep.target_status & TargetStatus.STATIC:
10        items.append(
11            f'STATIC > dist {rep.static_distance:3d}'
12            f' (energy {rep.static_energy:3d})'
13        )
14    else:
15        items.append(30 * ' ')
16
17    if rep.target_status & TargetStatus.MOVING:
18        items.append(
19            f'MOVING > dist {rep.moving_distance:3d}'
20            f' (energy {rep.moving_energy:3d})'
21        )
22    else:
23        items.append(30 * ' ')
24
25    if rep.target_status:
26        items.append(f'DETECT > dist {rep.detection_distance:3d}')
27    else:
28        items.append('')
29
30    return ' | '.join(items)
31
32
33async def main():
34    async with LD2410('/dev/ttyUSB0') as device:
35        async for report in device.get_reports():
36            print('  ' + format_basic_report(report.basic))
37
38if __name__ == '__main__':
39    asyncio.run(main())

Here is a sample output of this command:

$ ./examples/read_basic_reports.py
  STATIC > dist  37 (energy  56) | MOVING > dist  27 (energy  71) | DETECT > dist  33
  STATIC > dist  27 (energy  56) | MOVING > dist  34 (energy  45) | DETECT > dist  32
  STATIC > dist  34 (energy  54) |                                | DETECT > dist  32
  STATIC > dist  34 (energy  54) | MOVING > dist  38 (energy  41) | DETECT > dist  31
  STATIC > dist  38 (energy  53) | MOVING > dist  41 (energy  41) | DETECT > dist  32
  STATIC > dist  41 (energy  55) | MOVING > dist  47 (energy  71) | DETECT > dist  32
  STATIC > dist  47 (energy  56) |                                | DETECT > dist  33
  STATIC > dist  47 (energy  56) |                                | DETECT > dist  34
  STATIC > dist  47 (energy  57) |                                | DETECT > dist  36
  STATIC > dist  47 (energy  57) |                                | DETECT > dist  36

Depending on your use case, you might want to change the configuration parameters and run this script again to finely tune the configuration values.

This tutorial is now complete, to go further consider taking a look at the API reference.