ProtoCore v0.0.1
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
ld2410.h
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file ld2410.h
6 * @brief HLK-LD2410 24 GHz mmWave presence / motion radar codec (PC_ENABLE_LD2410).
7 *
8 * The LD2410 streams a framed serial report at 256000 baud: header `F4 F3 F2 F1`, a
9 * little-endian intra-frame length, the payload, and footer `F8 F7 F6 F5`. The payload carries
10 * the target state (none / moving / stationary / both), the moving and stationary target
11 * distance (cm) and energy (0-100), the overall detection distance, and - in "engineering
12 * mode" - the per-gate energy of all nine range gates. Configuration is a second frame kind
13 * (header `FD FC FB FA`, footer `04 03 02 01`) carrying a 2-byte command word.
14 *
15 * This codec is pure and host-tested: ::pc_ld2410_parse_report decodes one report frame, and
16 * ::Ld2410Stream reassembles frames byte-by-byte from a UART with resync on noise (no heap,
17 * fixed buffer). The command encoders build the config frames. On an ESP32 the binding pumps a
18 * `HardwareSerial` and keeps the latest report; only that read/write touches hardware.
19 *
20 * A cheap solder-and-bench-test breakout: wire it to a UART, wave a hand, watch presence.
21 *
22 * @author Douglas Quigg (dstroy0)
23 * @date 2026
24 */
25
26#ifndef PROTOCORE_LD2410_H
27#define PROTOCORE_LD2410_H
28
29#include <stddef.h>
30#include <stdint.h>
31
32/** @brief Range gates the LD2410 reports energy for in engineering mode (gate 0..8). */
33#define LD2410_MAX_GATES 9
34
35/** @brief Largest assembled frame: header(4) + len(2) + payload(<=60) + footer(4), rounded up. */
36#define LD2410_FRAME_MAX 72
37
38/** @brief Target presence state (report payload byte 2). */
40{
41 static constexpr uint8_t LD2410_STATE_NONE = 0x00; ///< no target
42 static constexpr uint8_t LD2410_STATE_MOVING = 0x01; ///< moving target only
43 static constexpr uint8_t LD2410_STATE_STATIC = 0x02; ///< stationary target only
44 static constexpr uint8_t LD2410_STATE_BOTH = 0x03; ///< both a moving and a stationary target
45};
46
47/** @brief A decoded LD2410 target report. Engineering fields are 0 unless @ref engineering. */
49{
50 uint8_t engineering; ///< 1 if this was an engineering-mode frame (per-gate energies valid)
51 uint8_t state; ///< one of LD2410_STATE_*
52 uint16_t moving_cm; ///< moving target distance (cm)
53 uint8_t moving_energy; ///< moving target energy (0-100)
54 uint16_t static_cm; ///< stationary target distance (cm)
55 uint8_t static_energy; ///< stationary target energy (0-100)
56 uint16_t detect_cm; ///< overall detection distance (cm)
57 // Engineering mode only:
58 uint8_t max_moving_gate; ///< highest configured moving gate
59 uint8_t max_static_gate; ///< highest configured stationary gate
60 uint8_t moving_gate_energy[LD2410_MAX_GATES]; ///< per-gate moving energy (0-100)
61 uint8_t static_gate_energy[LD2410_MAX_GATES]; ///< per-gate stationary energy (0-100)
62 uint8_t light; ///< photosensor level (0-255)
63 uint8_t out_pin; ///< OUT pin level (0/1)
64};
65
66/**
67 * @brief Decode one whole LD2410 report frame (header `F4 F3 F2 F1` .. footer `F8 F7 F6 F5`).
68 * Pure - no I/O. Validates the header/footer, the intra-frame length, the data-type byte
69 * (0x02 basic / 0x01 engineering), the `0xAA` head marker and the `0x55` tail.
70 * @return true on a valid frame; false on any mismatch or a short buffer.
71 */
72bool pc_ld2410_parse_report(const uint8_t *frame, size_t len, Ld2410Report *out);
73
74/** @brief Byte-by-byte report-frame reassembler (fixed buffer, resyncs on noise). */
76{
77 uint8_t buf[LD2410_FRAME_MAX]; ///< frame under construction
78 uint16_t pos; ///< bytes collected so far
79 uint16_t total; ///< expected full-frame length (known after the length field)
80 uint8_t hdr_match; ///< header bytes matched while syncing (phase = pos<4)
81 uint8_t phase; ///< 0 sync, 1 length, 2 body
82};
83
84/** @brief Reset a stream to the syncing state. */
86
87/**
88 * @brief Feed one received byte. When it completes a valid report frame, fills @p out and
89 * returns true; otherwise returns false (still syncing / mid-frame / bad frame - it resyncs).
90 */
91bool pc_ld2410_stream_push(Ld2410Stream *s, uint8_t byte, Ld2410Report *out);
92
93/** @brief true if @p r shows any target (moving or stationary). */
95
96/** @brief Best available target distance (cm): the moving distance if moving, else stationary. */
98
99// --- Config-command encoders (build a full `FD FC FB FA` .. `04 03 02 01` frame) -------------
100// Each returns the frame length written, or 0 if @p cap is too small. Config commands must be
101// bracketed by enable/end; engineering + restart take effect inside that bracket.
102
103/** @brief "Enable configuration" (word 0x00FF, value 0x0001). */
104size_t pc_ld2410_cmd_config_enable(uint8_t *buf, size_t cap);
105/** @brief "End configuration" (word 0x00FE). */
106size_t pc_ld2410_cmd_config_end(uint8_t *buf, size_t cap);
107/** @brief Enable (0x0062) or disable (0x0063) engineering mode. */
108size_t pc_ld2410_cmd_engineering(uint8_t *buf, size_t cap, bool on);
109/** @brief Restart the module (word 0x00A3). */
110size_t pc_ld2410_cmd_restart(uint8_t *buf, size_t cap);
111
112// --- LD2410B-only config commands ------------------------------------------------------------
113// The LD2410B is HiLink's BLE-equipped build of the same radar and speaks this identical
114// `FD FC FB FA` protocol, so everything above applies to it unchanged. The three commands below
115// exist only on the B: they configure the Bluetooth radio over the *wired* UART. The BLE control
116// channel itself is out of scope here - this is the serial side only. Each is still bracketed by
117// enable/end like any other config command.
118
119/** @brief LD2410B: turn the Bluetooth radio on (value 0x0001) or off (0x0000). Word 0x00A4. */
120size_t pc_ld2410_cmd_bluetooth(uint8_t *buf, size_t cap, bool on);
121
122/** @brief LD2410B: query the module's Bluetooth MAC address (word 0x00A5, value 0x0001). */
123size_t pc_ld2410_cmd_get_mac(uint8_t *buf, size_t cap);
124
125/**
126 * @brief LD2410B: set the 6-octet Bluetooth control password (word 0x00A9). Takes effect after a
127 * restart and survives power loss. @p password is exactly 6 octets, sent in natural order
128 * (the factory default is the ASCII "HiLink" -> 48 69 4C 69 6E 6B); it is not NUL-terminated
129 * and is not padded, so pass 6 octets.
130 *
131 * The companion "obtain Bluetooth access" command (0x00A8) is deliberately absent: the protocol
132 * document states it answers only over Bluetooth and not the serial port, so it belongs to the BLE
133 * control channel, which this wired driver does not cover.
134 */
135size_t pc_ld2410_cmd_set_bt_password(uint8_t *buf, size_t cap, const char password[6]);
136
137// --- Command-ACK decoding --------------------------------------------------------------------
138// The module answers every config command with a frame in the same `FD FC FB FA` envelope, whose
139// command word is the request's word with 0x0100 set (0x00A5 -> 0x01A5) followed by a 2-octet
140// status. Needed to read a query's result (the MAC) and to tell an accepted command from a
141// rejected one; the report stream (`F4 F3 F2 F1`) is a separate frame kind and is unaffected.
142
143/** @brief A decoded command-ACK frame. @ref payload points into the caller's frame (not copied). */
145{
146 uint16_t command; ///< ACK command word: the request word | 0x0100 (e.g. 0x01A5 for get-MAC).
147 uint16_t status; ///< 0 = success, 1 = failure.
148 const uint8_t *payload; ///< command-specific data after the status word (nullptr if none).
149 size_t payload_len; ///< octets at @ref payload.
150};
151
152/**
153 * @brief Decode one command-ACK frame (header, intra-frame length, footer and length agreement all
154 * checked). @return false if @p frame is not a well-formed ACK.
155 */
156bool pc_ld2410_parse_ack(const uint8_t *frame, size_t len, Ld2410Ack *out);
157
158/** @brief True if @p ack reports success (@ref Ld2410Ack::status == 0). */
160
161/**
162 * @brief Extract the 6-octet MAC from a get-MAC ACK (word 0x01A5) into @p mac, in wire order.
163 * @return false unless @p ack is a successful get-MAC ACK carrying at least 6 payload octets.
164 */
165bool pc_ld2410_ack_mac(const Ld2410Ack *ack, uint8_t mac[6]);
166
167// --- ESP32 binding (Serial pump; no-ops on a host build) -------------------------------------
168
169/** @brief Open UART2 at PC_LD2410_BAUD on @p rx_pin / @p tx_pin. @return true on ESP32. */
170bool pc_ld2410_begin(int rx_pin, int tx_pin);
171
172/** @brief Pump the UART through the stream. @return true if a fresh report was decoded. */
174
175/** @brief The most recently decoded report, or nullptr before the first one arrives. */
177
178/** @brief Enable/disable engineering mode (brackets the command with enable/end). */
180
181/** @brief Restart the module (brackets the command with enable/end). */
183
184#endif // PROTOCORE_LD2410_H
bool pc_ld2410_ack_ok(const Ld2410Ack *ack)
True if ack reports success (Ld2410Ack::status == 0).
size_t pc_ld2410_cmd_bluetooth(uint8_t *buf, size_t cap, bool on)
LD2410B: turn the Bluetooth radio on (value 0x0001) or off (0x0000). Word 0x00A4.
size_t pc_ld2410_cmd_engineering(uint8_t *buf, size_t cap, bool on)
Enable (0x0062) or disable (0x0063) engineering mode.
size_t pc_ld2410_cmd_get_mac(uint8_t *buf, size_t cap)
LD2410B: query the module's Bluetooth MAC address (word 0x00A5, value 0x0001).
bool pc_ld2410_stream_push(Ld2410Stream *s, uint8_t byte, Ld2410Report *out)
Feed one received byte. When it completes a valid report frame, fills out and returns true; otherwise...
bool pc_ld2410_parse_ack(const uint8_t *frame, size_t len, Ld2410Ack *out)
Decode one command-ACK frame (header, intra-frame length, footer and length agreement all checked).
bool pc_ld2410_poll()
Pump the UART through the stream.
bool pc_ld2410_set_engineering(bool on)
Enable/disable engineering mode (brackets the command with enable/end).
size_t pc_ld2410_cmd_set_bt_password(uint8_t *buf, size_t cap, const char password[6])
LD2410B: set the 6-octet Bluetooth control password (word 0x00A9). Takes effect after a restart and s...
bool pc_ld2410_parse_report(const uint8_t *frame, size_t len, Ld2410Report *out)
Decode one whole LD2410 report frame (header F4 F3 F2 F1 .. footer F8 F7 F6 F5). Pure - no I/O....
bool pc_ld2410_present(const Ld2410Report *r)
true if r shows any target (moving or stationary).
#define LD2410_MAX_GATES
Range gates the LD2410 reports energy for in engineering mode (gate 0..8).
Definition ld2410.h:33
size_t pc_ld2410_cmd_restart(uint8_t *buf, size_t cap)
Restart the module (word 0x00A3).
void pc_ld2410_stream_reset(Ld2410Stream *s)
Reset a stream to the syncing state.
uint16_t pc_ld2410_distance_cm(const Ld2410Report *r)
Best available target distance (cm): the moving distance if moving, else stationary.
const Ld2410Report * pc_ld2410_last()
The most recently decoded report, or nullptr before the first one arrives.
size_t pc_ld2410_cmd_config_end(uint8_t *buf, size_t cap)
"End configuration" (word 0x00FE).
#define LD2410_FRAME_MAX
Largest assembled frame: header(4) + len(2) + payload(<=60) + footer(4), rounded up.
Definition ld2410.h:36
bool pc_ld2410_restart()
Restart the module (brackets the command with enable/end).
bool pc_ld2410_begin(int rx_pin, int tx_pin)
Open UART2 at PC_LD2410_BAUD on rx_pin / tx_pin.
bool pc_ld2410_ack_mac(const Ld2410Ack *ack, uint8_t mac[6])
Extract the 6-octet MAC from a get-MAC ACK (word 0x01A5) into mac, in wire order.
size_t pc_ld2410_cmd_config_enable(uint8_t *buf, size_t cap)
"Enable configuration" (word 0x00FF, value 0x0001).
A decoded command-ACK frame. payload points into the caller's frame (not copied).
Definition ld2410.h:145
uint16_t status
0 = success, 1 = failure.
Definition ld2410.h:147
size_t payload_len
octets at payload.
Definition ld2410.h:149
const uint8_t * payload
command-specific data after the status word (nullptr if none).
Definition ld2410.h:148
uint16_t command
ACK command word: the request word | 0x0100 (e.g. 0x01A5 for get-MAC).
Definition ld2410.h:146
A decoded LD2410 target report. Engineering fields are 0 unless engineering.
Definition ld2410.h:49
uint16_t detect_cm
overall detection distance (cm)
Definition ld2410.h:56
uint8_t static_gate_energy[LD2410_MAX_GATES]
per-gate stationary energy (0-100)
Definition ld2410.h:61
uint16_t moving_cm
moving target distance (cm)
Definition ld2410.h:52
uint8_t out_pin
OUT pin level (0/1)
Definition ld2410.h:63
uint8_t max_static_gate
highest configured stationary gate
Definition ld2410.h:59
uint8_t state
one of LD2410_STATE_*
Definition ld2410.h:51
uint8_t static_energy
stationary target energy (0-100)
Definition ld2410.h:55
uint8_t engineering
1 if this was an engineering-mode frame (per-gate energies valid)
Definition ld2410.h:50
uint8_t moving_gate_energy[LD2410_MAX_GATES]
per-gate moving energy (0-100)
Definition ld2410.h:60
uint8_t moving_energy
moving target energy (0-100)
Definition ld2410.h:53
uint8_t max_moving_gate
highest configured moving gate
Definition ld2410.h:58
uint8_t light
photosensor level (0-255)
Definition ld2410.h:62
uint16_t static_cm
stationary target distance (cm)
Definition ld2410.h:54
Target presence state (report payload byte 2).
Definition ld2410.h:40
static constexpr uint8_t LD2410_STATE_NONE
no target
Definition ld2410.h:41
static constexpr uint8_t LD2410_STATE_BOTH
both a moving and a stationary target
Definition ld2410.h:44
static constexpr uint8_t LD2410_STATE_STATIC
stationary target only
Definition ld2410.h:43
static constexpr uint8_t LD2410_STATE_MOVING
moving target only
Definition ld2410.h:42
Byte-by-byte report-frame reassembler (fixed buffer, resyncs on noise).
Definition ld2410.h:76
uint16_t pos
bytes collected so far
Definition ld2410.h:78
uint8_t hdr_match
header bytes matched while syncing (phase = pos<4)
Definition ld2410.h:80
uint16_t total
expected full-frame length (known after the length field)
Definition ld2410.h:79
uint8_t buf[LD2410_FRAME_MAX]
frame under construction
Definition ld2410.h:77
uint8_t phase
0 sync, 1 length, 2 body
Definition ld2410.h:81