diff --git a/.github/workflows/arduinoLint.yml b/.github/workflows/arduinoLint.yml
new file mode 100644
index 0000000..09c16c7
--- /dev/null
+++ b/.github/workflows/arduinoLint.yml
@@ -0,0 +1,21 @@
+name: Arduino Lint
+
+on:
+ push:
+ branches: [ master ]
+ pull_request:
+ branches: [ master ]
+ workflow_dispatch:
+
+jobs:
+ lint:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+
+ - name: Arduino Lint
+ uses: arduino/arduino-lint-action@v1
+ with:
+ compliance: strict
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index f9eab32..5b7ef89 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -32,10 +32,7 @@ jobs:
- name: Checkout
uses: actions/checkout@v3
- - name: Arduino Lint
- uses: arduino/arduino-lint-action@v1
- with:
- compliance: strict
+
- name: Cache Arduino platform
uses: actions/cache@v3
@@ -52,7 +49,10 @@ jobs:
with:
platforms: ${{ matrix.platforms }}
fqbn: ${{ matrix.board.fqbn }}
- libraries: ${{ env.LIBRARIES }}
+ libraries: |
+ - name: "Async TCP"
+ - name: "ESP Async WebServer"
+ - source-path: .
enable-deltas-report: false
sketch-paths: |
- examples
diff --git a/.github/workflows/createRelease.yml b/.github/workflows/createRelease.yml
new file mode 100644
index 0000000..129834f
--- /dev/null
+++ b/.github/workflows/createRelease.yml
@@ -0,0 +1,56 @@
+name: Create Release
+
+on:
+ release:
+ types: [published]
+ workflow_dispatch:
+
+jobs:
+ release:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Extract version from release tag
+ id: version
+ run: |
+ TAG_NAME="${{ github.event.release.tag_name }}"
+ echo "tag_name=$TAG_NAME" >> $GITHUB_OUTPUT
+ echo "Version detected: $TAG_NAME"
+
+ - name: Validate library version
+ run: |
+ echo "Checking library.properties..."
+ grep -E '^version=' library.properties || { echo "Error: version missing"; exit 1; }
+
+ PROP_VERSION=$(grep -E '^version=' library.properties | cut -d'=' -f2)
+ TAG_NAME="${{ github.event.release.tag_name }}"
+ TAG_VERSION="${TAG_NAME#v}" # Remove leading 'v' in Bash
+
+ if [ "$PROP_VERSION" != "$TAG_VERSION" ]; then
+ echo "Version mismatch: library.properties ($PROP_VERSION) vs tag ($TAG_NAME)"
+ exit 1
+ fi
+
+ echo "Version match confirmed"
+
+ - name: Package library
+ run: |
+ mkdir -p dist
+ ZIP_FILE="LD2410Async-${{ steps.version.outputs.tag_name }}.zip"
+ echo "Packaging library as $ZIP_FILE ..."
+ zip -r "dist/$ZIP_FILE" . -x "*.git*" "dist/*" ".github/*" "docs/*" "__MACOSX"
+
+ - name: Upload ZIP to existing GitHub Release
+ uses: softprops/action-gh-release@v2
+ with:
+ tag_name: ${{ steps.version.outputs.tag_name }}
+ name: "LD2410Async ${{ steps.version.outputs.tag_name }}"
+ files: dist/*.zip
+ append_body: false
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/generateDoxygenDocu.yml b/.github/workflows/generateDoxygenDocu.yml
new file mode 100644
index 0000000..f06a451
--- /dev/null
+++ b/.github/workflows/generateDoxygenDocu.yml
@@ -0,0 +1,33 @@
+name: Generate Documentation
+
+on:
+ # Run when a release is created or published
+ release:
+ types: [published]
+
+ # Allow manual trigger from the Actions tab
+ workflow_dispatch:
+
+jobs:
+ doxygen:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0 # Ensures tags are available (useful for versioned docs)
+
+ - name: Generate Doxygen documentation
+ uses: mattnotmitt/doxygen-action@v1
+ with:
+ doxyfile-path: ./Doxyfile
+
+ - name: Deploy documentation to GitHub Pages
+ uses: peaceiris/actions-gh-pages@v3
+ with:
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ publish_dir: ./docs/html
+ publish_branch: gh-pages
+ force_orphan: true
+ keep_files: false
diff --git a/Doxyfile b/Doxyfile
new file mode 100644
index 0000000..8c2a735
--- /dev/null
+++ b/Doxyfile
@@ -0,0 +1,74 @@
+# Doxyfile for LD2410Async C++ Library
+# Suitable for GitHub Actions workflow in .github/workflows/generateDoxygenDocu.yml
+
+# Project related configuration
+PROJECT_NAME = "LD2410Async"
+PROJECT_BRIEF = "Asynchronous Arduino ESP32 library for the LD2410 mmWave radar sensor"
+OUTPUT_DIRECTORY = docs
+CREATE_SUBDIRS = NO
+OUTPUT_LANGUAGE = English
+EXTRACT_ALL = YES
+EXTRACT_PRIVATE = NO
+EXTRACT_STATIC = NO
+EXTRACT_LOCAL_CLASSES = YES
+EXTRACT_ANON_NSPACES = YES
+SORT_MEMBER_DOCS = YES
+SORT_GROUP_NAMES = YES
+
+
+
+# Input
+INPUT = src examples dox
+FILE_PATTERNS = *.h *.cpp *.ino *.dox *.md
+EXTENSION_MAPPING = ino=C++
+RECURSIVE = YES
+IMAGE_PATH = gfx
+
+# Source code browsing
+SOURCE_BROWSER = YES
+INLINE_SOURCES = FALSE
+STRIP_CODE_COMMENTS = NO
+
+# HTML output
+GENERATE_HTML = YES
+HTML_OUTPUT = html
+HTML_FILE_EXTENSION = .html
+HTML_COLORSTYLE_HUE = 220
+HTML_COLORSTYLE_SAT = 100
+HTML_COLORSTYLE_GAMMA = 80
+
+# LaTeX output (disabled)
+GENERATE_LATEX = NO
+
+# Dot/Graphviz support
+HAVE_DOT = NO
+CALL_GRAPH = NO
+CALLER_GRAPH = NO
+CLASS_DIAGRAMS = NO
+COLLABORATION_GRAPH= NO
+INCLUDE_GRAPH = NO
+INCLUDED_BY_GRAPH = NO
+GRAPHICAL_HIERARCHY= NO
+DOT_MULTI_TARGETS = NO
+
+# Warnings
+WARN_IF_UNDOCUMENTED = YES
+WARN_IF_DOC_ERROR = YES
+
+# Misc
+QUIET = NO
+GENERATE_TREEVIEW = YES
+FULL_PATH_NAMES = NO
+STRIP_FROM_PATH = src examples
+
+# Repository link (optional, for GitHub integration)
+# Set to your repo if you want source browsing links
+# HTML_DYNAMIC_SECTIONS = YES
+# PROJECT_LOGO =
+
+# If you want Markdown support
+MARKDOWN_SUPPORT = YES
+
+# If you want to document private members, set EXTRACT_PRIVATE = YES
+
+# End of Doxyfile
\ No newline at end of file
diff --git a/LD2410Async.sln b/LD2410Async.sln
index 9c08f7b..0370531 100644
--- a/LD2410Async.sln
+++ b/LD2410Async.sln
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
-VisualStudioVersion = 17.14.36511.14 d17.14
+VisualStudioVersion = 17.14.36511.14
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LD2410Async", "LD2410Async.vcxitems", "{76DB4135-DB0F-4430-958C-2109370DCD97}"
EndProject
diff --git a/LD2410Async.vcxitems b/LD2410Async.vcxitems
index 7e25552..e315440 100644
--- a/LD2410Async.vcxitems
+++ b/LD2410Async.vcxitems
@@ -22,10 +22,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -34,6 +52,11 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/LD2410Async.vcxitems.filters b/LD2410Async.vcxitems.filters
index 05cab3b..51a4a24 100644
--- a/LD2410Async.vcxitems.filters
+++ b/LD2410Async.vcxitems.filters
@@ -24,13 +24,31 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -39,5 +57,14 @@
Header Files
+
+ Header Files
+
+
+ Header Files
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 88420b3..7710cc4 100644
--- a/README.md
+++ b/README.md
@@ -1,39 +1,62 @@
-# LD2410Async – Asynchronous ESP32 Arduino Library for the LD2410 mmWave Radar Sensor
+# LD2410Async - Asynchronous ESP32 Arduino Library for the LD2410 mmWave Radar Sensor
-[](https://www.ardu-badge.com/LD2410Async)[](https://github.com/lizardking/LD2410Async/actions/workflows/build.yml)
+[](https://www.ardu-badge.com/LD2410Async) [](https://github.com/lizardking/LD2410Async/releases/latest)
+ [](https://github.com/lizardking/LD2410Async/actions/workflows/build.yml) [](https://lizardking.github.io/LD2410Async/)
+ 
+
## Introduction
-The **LD2410Async** library provides a asynchronous interface for the **Hi-Link LD2410** human presence radar sensor on Arduino/ESP32 platforms.
+The **LD2410Async** library provides an asynchronous interface for the **Hi-Link LD2410** human presence radar sensor on Arduino/ESP32 platforms.
LD2410Async runs in the background using FreeRTOS tasks. This allows your main loop to remain responsive while the library continuously processes radar data, handles configuration, and manages timeouts.
-The library offers:
-
-- **Non-blocking operation** – sensor data is parsed in the background, no polling loops required.
-- **Async command API** – send commands (e.g., request firmware, change settings) without blocking the main loop().
-- **Callbacks** – are executed when detection data has arrived or when commands complete. If you only care about presence detection and dont need additional data, you only have to check the `presenceDetected` flag that is sent with the DetectionDataReceivedCallback.
-- **Full access to sensor data** – if you need more than just the basic `presenceDetected` information. All data that is sent by the sensor is available.
-- **All LD2410 commands** are available in the library.
--
-This makes it ideal for applications such as smart lighting, security, automation, and energy-saving systems, where fast and reliable presence detection is essential.
+The library has the following functionality:
+- **All LD2410 commands supported** are available in the library.
+- **Full access to sensor data** - all data that is sent by the sensor is available.
+- **Async command API** - send commands (e.g., request firmware, change settings) without blocking the main loop().
+- **Callbacks** - are executed when detection data has arrived or when commands complete.
+- **Non-blocking operation** - sensor data is parsed in the background, no polling loops required.
+
---
+## Documentation
+
+Full API and usage documentation is generated automatically with Doxygen and published on GitHub Pages:
+
+**[View the LD2410Async Documentation](https://lizardking.github.io/LD2410Async/)**
+
+The docs include:
+- A complete [LD2410Async class reference](https://lizardking.github.io/LD2410Async/classLD2410Async.html)
+- Full details for all functions, callbacks, and data structures.
+
+### Direct links to main sections
+
+- [Installation](https://lizardking.github.io/LD2410Async/Installation.html)
+- [Async Commands & Processing](https://lizardking.github.io/LD2410Async/Async_Commands_And_Processing.html)
+- [Data Structures](https://lizardking.github.io/LD2410Async/Data_Structures.html)
+- [Operation Modes](https://lizardking.github.io/LD2410Async/Operation_Modes.html)
+- [Inactivity Handling](https://lizardking.github.io/LD2410Async/Inactivity_Handling.html)
+- [Examples](https://lizardking.github.io/LD2410Async/Examples.html)
+- [Best Practices](https://lizardking.github.io/LD2410Async/BestPractices.html)
+- [Troubleshooting Guide](https://lizardking.github.io/LD2410Async/Troubleshooting.html)
+---
+
## Installation
You can install this library in two ways:
### 1. Using Arduino Library Manager (recommended)
1. Open the Arduino IDE.
-2. Go to **Tools → Manage Libraries…**.
+2. Go to **Tools - Manage Libraries...**.
3. Search for **LD2410Async**.
4. Click **Install**.
### 2. Manual installation
1. Download this repository as a ZIP file.
-2. In the Arduino IDE, go to **Sketch → Include Library → Add .ZIP Library…**, or
+2. In the Arduino IDE, go to **Sketch - Include Library - Add .ZIP Library...**, or
unzip the file manually into your Arduino `libraries` folder:
- Windows: `Documents/Arduino/libraries/`
- Linux/macOS: `~/Arduino/libraries/`
@@ -46,6 +69,8 @@ You can install this library in two ways:
Include the library, create an instance for your radar, and start it with `begin()`.
Register a callback to get notified when new detection data arrives.
+Check the [examples](docu/Examples.html) section for more examples.
+
### Simply sensing presence
```cpp
@@ -82,7 +107,7 @@ void setup() {
}
// Register detection callback
- radar.registerDetectionDataReceivedCallback(onDetectionData, 0);
+ radar.onDetectionDataReceived(onDetectionData, 0);
}
void loop() {
@@ -92,142 +117,8 @@ void loop() {
}
```
-### Configuration Commands
-
-To request or update configuration, use the async API. For example:
-
-```cpp
-// Config data callback function
-void onConfigData(LD2410Async* sender, AsyncCommandResult result, byte userData) {
- if (result == AsyncCommandResult::SUCCESS) {
- Serial.println("Config data received.");
- auto config = sender->getConfigData();
- Serial.print("Number of gates: ");
- Serial.println(config.numberOfGates);
- } else {
- Serial.println("Failed to read config.");
- }
-}
-
-void someFunction() {
- // Request all configuration parameters from the radar
- radar.requestAllConfigData(onConfigData, 0);
-}
-```
-
-### More examples
-
-Well-commented example sketches can be found in the examples folder of the library.
-
---
-
-## Important Notes & Best Practices
-
-When working with the LD2410Async library, keep the following points in mind:
-
-- **Config Mode Handling**
- - The sensor must be in *config mode* to change settings.
- - All methods/commands that talk to the sensor enable and disable config mode for you if necessary. This means that they will enable and disable the config mode if the config mode has not been active when the command is called. If the config mode is alredy active when calling the command, it will remain active after the command has completed resp. fires its callback.
- - Activating the config mode is a time consuming operation (often more than 1.5 secs). Therefore sending a lot of commands can take quite a while. If a sequence with several commands has to be sent, it is a good idea to enable config mode first and deactivate it after the last command (make sure this also happends if commands fail/return false or dont report success in their callback).
- - Avoid leaving the sensor in config mode longer than necessary, since the sensor will not deliver any detection data while in config mode.
-
-- **Engineering Mode**
- - Engineering mode provides detailed gate signal data for development and debugging.
- - Do **not** enable engineering mode in production unless required — it increases the amount of data sent and will trigger the detection callback more often.
- - Leaving it on continuously can increase CPU load and reduce performance.
-
- **Keep Callbacks Short**
- - Callbacks are executed inside the radar’s processing task.
- - Keep them **short and non-blocking** (e.g., update a variable or post to a queue).
- - Avoid long delay code, heavy computations, or blocking I/O inside callbacks — otherwise, sensor data processing may be delayed or datection data can get lost.
-
-- **Presence Detection**
- - Use the dedicated detection callback with the `presenceDetected` flag of the DetectionDataReceivedCallback (use registerDetectionDataReceivedCallback() to register for that callback) if you only care about *whether* something is present.
- - For advanced scenarios (distances, signals, engineering mode), you can access the full `DetectionData` via `getDetectionData()`.
-
-- **Task Management**
- - The library runs a FreeRTOS background task for receiving and processing data.
- - You must use `begin()` to start it. If you ever need to stop it again, use `end()` to stop it gracefully.
- - Do not block the main loop for long periods; the radar’s task will continue to run, but your application logic may lag.
-
-- **Inactivity Handling**
- - The library can automatically handle situation where the sensor doesnt send any data for a configurable period. Sice such situations are typically a result of the config mode being active accidentally, the lib will first try to disable the config mode and if that does not help it will try to reboot the sensor.
- - You can enable or disable this behavior with `setInactivityHandling(bool enable)` depending on your use case.
-
-- **Async Command Busy State**
- - The library ensures only one async command or sequence runs at a time.
- - Check `asyncIsBusy()` before sending a new command.
- - Alway check the return value of the async commands. If false is returned the command has not been sent (either due to busy state or due to invalid paras). True means that the command has been sent and that the callback of the method will execute after completition of the command or after the timeout period.
- - If chaining commands, trigger the next command from the callback of the previous command. If the next command should only be executed, if the previous command was successfull, check the success para of the callback.
-
-- - **Config Memory Wear**
- - The LD2410 stores configuration in internal non-volatile memory.
- - Repeatedly writing unnecessary config updates can wear out the memory over time.
- - Only send config changes when values really need to be updated.
-
-Following these practices will help you get stable, reliable operation from the LD2410 sensor while extending its lifetime.
-
-## Troubleshooting
-
-If you run into issues when using the library, check the following common problems:
-
-- **No data received**
- - Make sure the radar is connected to the correct serial port and pins.
- - Verify the baud rate: the LD2410 uses `256000` by default.
- - Ensure `radar.begin()` was called after initializing the serial port.
- - Make sure the config mode is not acctive accidentally — in config mode, no detection data is sent.
-
-- **Callbacks not firing**
- - Confirm that you registered the callback before expecting data.
- - Check that the sensor is not stuck in config mode — in config mode, no detection data is sent.
- - If you enabled engineering mode, expect more frequent callbacks with more data.
-
-- **Async commands not working**
- - Only one async command can be active at a time. Use `asyncIsBusy()` to check before sending a new one and/or check the return value of the async command (true indicates that the command has been sent, false indicates that another async command is pending or that a para is invalid).
- - All commands require the sensor to be in config mode. The methods handle this automatically, but if you manually enable config mode, remember to disable it afterward.
-
-- **Unexpected reboots of Sensor**
- - Check if inactivity handling is enabled (`setInactivityHandling(true)`).
- - If enabled, the library may reboot the sensor automatically after a long period of inactivity (no data sent).
-
-- **Data loss**
- - Review your callback functions. Keep them short and avoid heavy work. Long callback functions can block the sensors thread, which can result in data loss. If you need to do more complex processing, copy the data in the callback and handle it later in your main loop or another task.
- - You can try to increase the size of the receive buffer of the serial that is receiving the sensor data. Under normal circumstances this should never be necessary, since the amount of data sent by the sensor is rather small.
-
-- **Strange or invalid data values**
- - This can happen if the sensor is still in config mode. Ensure it is in normal detection mode.
- - If you are experimenting with engineering mode, note that it sends extra raw data which may appear unusual if not parsed correctly.
-
-## Debugging Tips
-
-The library includes optional debug output to help you see what is happening inside.
-This is controlled by preprocessor defines that you can enable **before including** the library header in your sketch.
-
-### Enabling Debug Output
-
-- Add one or more of the following `#define` statements at the very top of your main `.ino` file (before any `#include`):
-
-```cpp
-#define ENABLE_DEBUG // General debug output (mostly on config data and commands)
-#define ENABLE_DEBUG_DATA // Extra output for received data frames
-
-#include
-```
-
-- When enabled, the library will print debug messages to the default Serial port at runtime.
-
-- You can use this to verify that commands are sent, acknowledgements are received, and data frames are being processed correctly.
-
-### Notes
-
-- Debug output is only for troubleshooting. It should **be disabled** in production builds. If active it will add many Serial.print() calls to the build, resulting in a larger size and solwer execution of the build
-
-- The debug macros are internal to the library. They are not meant to be used in your own code.
-
-- If you need diagnostic output in your own project, use standard Serial.print() calls instead.
-
-By selectively enabling these flags, you can get detailed insight into the communication with the LD2410 sensor whenever you need to troubleshoot.
-
+
## License
diff --git a/dox/Async Commands And Processing.md b/dox/Async Commands And Processing.md
new file mode 100644
index 0000000..f3bf286
--- /dev/null
+++ b/dox/Async Commands And Processing.md
@@ -0,0 +1,202 @@
+@page Async_Commands_And_Processing Async Commands & Processing
+
+@section Async_Commands_And_Processing_Why Why asynchronous processing matters for the LD2410
+
+The LD2410 radar sensor continuously streams detection frames and requires explicit configuration commands to adjust its behavior.
+If the library were implemented with blocking (synchronous) calls, every command would have to wait for the sensor’s response before the application could continue.
+Since enabling config mode or requesting data can take several seconds, blocking calls would freeze the main program loop and would disrupt other tasks.
+
+An asynchronous approach avoids this problem. Commands are sent non-blocking, and the library reports back via callbacks when acknowledgements or data arrive. This has several benefits:
+- Responsiveness - the main loop and other tasks keep running while waiting for the sensor.
+- Efficiency - incoming data frames are parsed in the background without polling or busy-waiting.
+- Scalability - multiple sensors or other asynchronous components (Wi-Fi, MQTT, UI updates) can run in parallel without interfering with each other.
+- Robustness - timeouts and retries can be handled cleanly without stalling the system.
+
+@section Async_Commands_And_Processing_Detection_Data_Callback Detection Data Callback
+
+Whenever the LD2410 sensor transmits a valid data frame, the library automatically parses it and invokes the detection data callback. This allows your application to react immediately to sensor input without the need for polling.
+
+Use @ref LD2410Async::onDetectionDataReceived "onDetectionDataReceived()" to register a callback function for detection events.
+
+The callback delivers two parameters:
+- A pointer to the LD2410Async instance that triggered the event. This allows convenient access to members of that instance.
+- A simple presenceDetected flag (true if presence is detected, false otherwise).
+
+@note
+The callback is triggered for every received frame, not only when presenceDetected changes. This means you can always rely on it to reflect the most recent sensor state.
+
+For applications that need more than just the quick presence flag, the library provides full access to the updated @ref LD2410Types::DetectionData "DetectionData" struct. This struct contains distances, signal strengths and other info.
+
+@section Async_Commands_And_Processing_Dtection_Data_Callback_Example_ Detection Data Callback Example
+
+@code{.cpp}
+#include "LD2410Async.h"
+
+HardwareSerial RadarSerial(1);
+LD2410Async radar(RadarSerial);
+
+void onDetection(LD2410Async* sender, bool presenceDetected) {
+ if (presenceDetected) {
+ Serial.println("Presence detected!");
+ } else {
+ Serial.println("No presence.");
+ }
+}
+
+void setup() {
+ Serial.begin(115200);
+ RadarSerial.begin(256000, SERIAL_8N1, 32, 33); // RX=Pin 32, TX= Pin 33
+
+ radar.begin();
+ radar.onDetectionDataReceived(onDetection);
+}
+
+void loop() {
+ // Nothing needed here - data is processed asynchronously.
+}
+@endcode
+
+@section Async_Commands_And_Processing_Configuration_Callbacks Configuration Callbacks
+
+Whenever the LD2410 sensor executes or reports configuration changes, the library provides two separate callback mechanisms to keep your application informed.
+
+Use @ref LD2410Async::onConfigChanged "onConfigChanged()" to register a callback that is invoked whenever the sensor acknowledges and applies a configuration-changing command (for example, after setting sensitivities or timeouts). This event serves as a notification that the sensor has accepted a change, but it does not mean that the local configuration data has been refreshed. If you need updated values, you must explicitly request them from the sensor (e.g. with @ref LD2410Async::requestAllConfigSettingsAsync "requestAllConfigSettingsAsync()").
+
+Use @ref LD2410Async::onConfigDataReceived "onConfigDataReceived()" to register a callback that is invoked whenever new configuration data has actually been received from the sensor. This happens after request commands such as @ref LD2410Async::requestAllConfigSettingsAsync "requestAllConfigSettingsAsync()" or @ref LD2410Async::requestGateParametersAsync "requestGateParametersAsync()". Within this callback, the library guarantees that the internal @ref LD2410Types::ConfigData "ConfigData" structure has been updated, so you can safely access it via @ref LD2410Async::getConfigData "getConfigData()" or @ref LD2410Async::getConfigDataRef "getConfigDataRef()".
+
+@note
+Configuration data is **not sent automatically** by the sensor and is **not updated automatically** when internal changes occur. To refresh the local configuration structure, you must explicitly request the latest values from the sensor. The recommended way is to call @ref LD2410Async::requestAllConfigSettingsAsync "requestAllConfigSettingsAsync()", which retrieves the complete configuration in one operation.
+
+@section Async_Commands_And_Processing_Configuration_Callbacks_Exampample Configuration Callbacks Example
+
+@code{.cpp}
+// Define the callback function
+void onConfigChanged(LD2410Async* sender) {
+ Serial.println("Sensor acknowledged a configuration change.");
+
+ // If you want the latest config values, explicitly request them
+ sender->requestAllConfigSettingsAsync(onConfigReceived);
+}
+
+// Define a helper callback for when config data arrives
+void onConfigReceived(LD2410Async* sender, LD2410Async::AsyncCommandResult result) {
+ if (result == LD2410Async::AsyncCommandResult::SUCCESS) {
+ LD2410Types::ConfigData cfg = sender->getConfigData();
+ Serial.print("No one timeout: ");
+ Serial.println(cfg.noOneTimeout);
+ }
+}
+
+// Somewhere in setup():
+radar.onConfigChanged(onConfigChanged);
+@endcode
+
+
+@section Async_Commands_And_Processing_Async_Commands_Basics Async Commands Basics
+
+The LD2410 sensor can be configured and queried using a large set of commands.
+
+All commands that need to communicate with the sensor are implemented as asynchronous operations:
+- They return immediately without blocking the main loop.
+- Each command call itself returns a simple true or false:
+ - true means the command was accepted for processing.
+ - false means it could not be started (for example, because another command is already pending).
+- When the sensor sends back an acknowledgement (ACK) or a response, the library automatically calls the user-provided callback with a result of type @ref LD2410Async::AsyncCommandResult "AsyncCommandResult".
+- Callbacks allow your application to handle SUCCESS, FAILED, TIMEOUT, or CANCELED in a clean and non-blocking way.
+
+@section Async_Commands_And_Processing_Async_Commands_List List of Asynchronous Commands
+
+- Config mode control
+ - @ref LD2410Async::enableConfigModeAsync "enableConfigModeAsync()" - enable configuration mode.
+ - @ref LD2410Async::disableConfigModeAsync "disableConfigModeAsync()" - disable configuration mode, return to normal detection.
+- Engineering mode
+ - @ref LD2410Async::enableEngineeringModeAsync "enableEngineeringModeAsync()" - enable detailed per-gate reporting.
+ - @ref LD2410Async::disableEngineeringModeAsync "disableEngineeringModeAsync()" - disable engineering mode.
+- Request configuration data
+ - @ref LD2410Async::requestAllConfigSettingsAsync "requestAllConfigSettingsAsync()" - query all configuration parameters at once (bundles the next 3 commands into one). **Use this command whenever possible.**
+ - @ref LD2410Async::requestGateParametersAsync "requestGateParametersAsync()" - query max gates, timeouts, sensitivities.
+ - @ref LD2410Async::requestAuxControlSettingsAsync "requestAuxControlSettingsAsync()" - query auxiliary control settings.
+ - @ref LD2410Async::requestDistanceResolutionAsync "requestDistanceResolutionAsync()" - query current resolution.
+- Write configuration data
+ - @ref LD2410Async::configureAllConfigSettingsAsync "configureAllConfigSettingsAsync()" - writes all config settings (combines the next 4 commands). Requires reboot if display resolution has changed. **Use this command whenever possible.**
+ - @ref LD2410Async::configureMaxGateAndNoOneTimeoutAsync "configureMaxGateAndNoOneTimeoutAsync()" - set max gates and timeout.
+ - @ref LD2410Async::configureDistanceGateSensitivityAsync "configureDistanceGateSensitivityAsync()" - set per-gate sensitivity (single or all).
+ - @ref LD2410Async::configureDistanceResolutionAsync "configureDistanceResolutionAsync()" - set resolution (20 cm or 75 cm). Requires reboot.
+ - @ref LD2410Async::configureAuxControlSettingsAsync "configureAuxControlSettingsAsync()" - configure light/output pins.
+ - @ref LD2410Async::configureBaudRateAsync "configureBaudRateAsync()" - change UART baud rate. Requires reboot.
+- Request static data
+ - @ref LD2410Async::requestAllStaticDataAsync "requestAllStaticDataAsync()" - query all static information (protocol version, buffer size, firmware version and bluetooth mac).
+ - @ref LD2410Async::requestFirmwareAsync "requestFirmwareAsync()" - query firmware version.
+- Bluetooth functions
+ - @ref LD2410Async::enableBluetoothAsync "enableBluetoothAsync()" - turn on Bluetooth.
+ - @ref LD2410Async::disableBluetoothAsync "disableBluetoothAsync()" - turn off Bluetooth.
+ - @ref LD2410Async::requestBluetoothMacAddressAsync "requestBluetoothMacAddressAsync()" - query Bluetooth MAC address.
+ - @ref LD2410Async::configureBluetoothPasswordAsync "configureBluetoothPasswordAsync()" - set custom password.
+ - @ref LD2410Async::configureDefaultBluetoothPasswordAsync "configureDefaultBluetoothPasswordAsync()" - reset password to default.
+- Auto-config
+ - @ref LD2410Async::beginAutoConfigAsync "beginAutoConfigAsync()" - start auto-configuration routine.
+ - @ref LD2410Async::requestAutoConfigStatusAsync "requestAutoConfigStatusAsync()" - query status of auto-config.
+- Other
+ - @ref LD2410Async::rebootAsync "rebootAsync()" - reboot the sensor (needed after some config changes).
+ - @ref LD2410Async::restoreFactorySettingsAsync "restoreFactorySettingsAsync()" - restore default settings. Requires reboot.
+- Async Command helpers
+ - @ref LD2410Async::asyncIsBusy "asyncIsBusy()" - checks if another async command is pending
+ - @ref LD2410Async::asyncCancel "asyncCancel()" - Cancels any currently pending async command. This will just abort the waiting for the Ack of the command. The actual command (or parts of it) have probably already been executed anyway.
+
+
+@section Async_Commands_And_Processing_AsyncCommands__Example Async Command Example
+
+@code{.cpp}
+#include "LD2410Async.h"
+
+HardwareSerial RadarSerial(1);
+LD2410Async radar(RadarSerial);
+
+// Callback method gets triggered when all config data has been received
+void onConfigReceived(LD2410Async* sender, LD2410Async::AsyncCommandResult result) {
+ if (result == LD2410Async::AsyncCommandResult::SUCCESS) {
+ // Access latest config via getConfigData()
+ LD2410Types::ConfigData cfg = sender->getConfigData();
+ Serial.print("Max moving gate: ");
+ Serial.println(cfg.maxMotionDistanceGate);
+ Serial.print("No one timeout: ");
+ Serial.println(cfg.noOneTimeout);
+ } else {
+ Serial.println("Failed to retrieve config settings.");
+ }
+}
+
+void setup() {
+ Serial.begin(115200);
+ RadarSerial.begin(256000, SERIAL_8N1, 32, 33); // RX=Pin 32, TX=Pin 33
+
+ if (!radar.begin()) {
+ Serial.println("Failed to initialize the LD2410Async library");
+ return;
+ }
+
+ // Query all configuration parameters and register callback
+ if (!radar.requestAllConfigSettingsAsync(onConfigReceived)) {
+ Serial.println("Could not send config request (busy or failed).");
+ }
+}
+
+void loop() {
+ // Nothing required here – everything runs asynchronously
+}
+
+@endcode
+
+
+@section Async_Commands_And_Processing_Async_Commands_Best_Practices Best Practices for Async Commands
+
+- **Check the return value** - all async methods return true if the command is getting executed or false when execution of the commands is not possible for some reason (e.g. another async command pending or a para is inavlid/out of range)
+- **Check the result of the callback** - handle @ref LD2410Async::AsyncCommandResult "AsyncCommandResult" values such as `SUCCESS`, `FAILED`, `TIMEOUT`, or `CANCELED` as necessary.
+- **Be aware of busy state** – before sending a command, ensure the library is not already executing another one.
+ Use @ref LD2410Async::asyncIsBusy "asyncIsBusy()" if needed. All async commands check the busy state internally and will return `false` if another command is already pending.
+- **Don’t block in callbacks** - keep callbacks short and non-blocking; offload heavy work to the main loop or a task.
+- **Config mode handling** – you usually don’t need to manually enable or disable config mode; the library automatically handles this for commands that require it.
+ Only call @ref LD2410Async::enableConfigModeAsync "enableConfigModeAsync()" or @ref LD2410Async::disableConfigModeAsync "disableConfigModeAsync()" directly if you explicitly want to keep the sensor in config mode across multiple operations.
+- **Avoid overlapping commands** – sending a new command while another one is still pending can cause failures.
+ Always wait for the callback to complete before sending the next command, or call @ref LD2410Async::asyncCancel "asyncCancel()" if you need to abort the current operation.
+
diff --git a/dox/Data Structures.md b/dox/Data Structures.md
new file mode 100644
index 0000000..f5da11c
--- /dev/null
+++ b/dox/Data Structures.md
@@ -0,0 +1,130 @@
+@page Data_Structures Data Structures
+
+This section documents the main data structures provided by the library for accessing sensor state
+and configuration:
+@ref LD2410Types::DetectionData "DetectionData" and @ref LD2410Types::ConfigData "ConfigData".
+
+---
+
+@section Data_Structures_DetectionData DetectionData
+
+The @ref LD2410Types::DetectionData "DetectionData" struct holds the most recent detection data
+reported by the radar. It is continuously updated as new frames arrive.
+
+You can access it through:
+- @ref LD2410Async::getDetectionData "getDetectionData()" - returns a copy
+- @ref LD2410Async::getDetectionDataRef "getDetectionDataRef()" - returns a const reference (efficient, no copy)
+
+@subsection Data_Structures_DetectionData_Members Members
+
+@subsubsection Data_Structures_DetectionData_General General
+- **timestamp** (`unsigned long`)
+ Milliseconds since boot when this data was received.
+
+- **engineeringMode** (`bool`)
+ True if engineering mode data was received.
+
+@subsubsection Data_Structures_DetectionData_Basic Basic Detection Results
+- **presenceDetected** (`bool`)
+ True if any target is detected.
+
+- **movingPresenceDetected** (`bool`)
+ True if a moving target is detected.
+
+- **stationaryPresenceDetected** (`bool`)
+ True if a stationary target is detected.
+
+- **targetState** (@ref LD2410Types::TargetState "TargetState")
+ Current detection state (no target, moving, stationary, both, auto config).
+
+- **movingTargetDistance** (`unsigned int`)
+ Distance in cm to the nearest moving target.
+
+- **movingTargetSignal** (`byte`)
+ Signal strength (0-100) of the moving target.
+
+- **stationaryTargetDistance** (`unsigned int`)
+ Distance in cm to the nearest stationary target.
+
+- **stationaryTargetSignal** (`byte`)
+ Signal strength (0-100) of the stationary target.
+
+- **detectedDistance** (`unsigned int`)
+ General detection distance in cm.
+
+@subsubsection Data_Structures_DetectionData_Engineering Engineering Mode Data
+(Only valid if **engineeringMode** is true.)
+
+- **movingTargetGateSignalCount** (`byte`)
+ Number of gates with moving target signals.
+
+- **movingTargetGateSignals[9]** (`byte[9]`)
+ Per-gate signal strengths for moving targets.
+
+- **stationaryTargetGateSignalCount** (`byte`)
+ Number of gates with stationary target signals.
+
+- **stationaryTargetGateSignals[9]** (`byte[9]`)
+ Per-gate signal strengths for stationary targets.
+
+- **lightLevel** (`byte`)
+ Reported ambient light level (0-255).
+
+- **outPinStatus** (`bool`)
+ Current status of the OUT pin (true = high, false = low).
+
+---
+
+@section Data_Structures_ConfigData
+
+The @ref LD2410Types::ConfigData "ConfigData" struct stores the radar’s configuration parameters.
+This includes both static capabilities (like number of gates) and adjustable settings (like sensitivities and timeouts).
+
+Values are filled by commands such as:
+- @ref LD2410Async::requestAllConfigSettingsAsync "requestAllConfigSettingsAsync()"
+- @ref LD2410Async::requestGateParametersAsync "requestGateParametersAsync()"
+- @ref LD2410Async::requestAuxControlSettingsAsync "requestAuxControlSettingsAsync()"
+- @ref LD2410Async::requestDistanceResolutionAsync "requestDistanceResolutionAsync()"
+
+You can access it through:
+- @ref LD2410Async::getConfigData "getConfigData()" - returns a copy
+- @ref LD2410Async::getConfigDataRef "getConfigDataRef()" - returns a const reference (efficient, no copy)
+
+@subsection Data_Structures_ConfigData_Members Members
+
+@subsubsection Data_Structures_ConfigData_Members_Gates Radar Gates
+- **numberOfGates** (`byte`)
+ Number of distance gates (2-8). Read-only; changing has no effect.
+
+- **maxMotionDistanceGate** (`byte`)
+ Furthest gate used for motion detection.
+
+- **maxStationaryDistanceGate** (`byte`)
+ Furthest gate used for stationary detection.
+
+- **distanceGateMotionSensitivity[9]** (`byte[9]`)
+ Motion sensitivity values per gate (0-100).
+
+- **distanceGateStationarySensitivity[9]** (`byte[9]`)
+ Stationary sensitivity values per gate (0-100).
+
+@subsubsection Data_Structures_ConfigData_Members_Timeout Timeout
+- **noOneTimeout** (`unsigned short`)
+ Timeout in seconds until “no presence” is declared.
+
+@subsubsection Data_Structures_ConfigData_Members_Distance_Resolution Distance Resolution
+- **distanceResolution** (@ref LD2410Types::DistanceResolution "DistanceResolution")
+ Current distance resolution (20 cm or 75 cm).
+ Requires reboot to apply after change.
+
+@subsubsection Data_Structures_ConfigData_Members_Aux Auxiliary Controls
+- **lightThreshold** (`byte`)
+ Threshold for auxiliary light control (0-255).
+
+- **lightControl** (@ref LD2410Types::LightControl "LightControl")
+ Light-dependent auxiliary control mode.
+
+- **outputControl** (@ref LD2410Types::OutputControl "OutputControl")
+ Logic configuration of the OUT pin.
+
+
diff --git a/dox/Examples.md b/dox/Examples.md
new file mode 100644
index 0000000..cba70e3
--- /dev/null
+++ b/dox/Examples.md
@@ -0,0 +1,65 @@
+@page Examples Examples
+
+The library has several example sketches that demonstrate typical usage as well as specialized test scenarios.
+All examples reside in the `examples/` folder of the library.
+
+@section Examples_Callback Example: Using callback for presence detection updates
+
+ @code{.cpp}
+ radar.onDetectionDataReceived([](LD2410Async* sender, bool presenceDetetced, byte userData) {
+ sender->getDetectionDataRef().print(); // direct access, no copy
+ });
+ @endcode
+
+ **Commands used in this example:**
+- @ref LD2410Async::onDetectionDataReceived "LD2410Async::onDetectionDataReceived()"
+- @ref LD2410Async::getDetectionDataRef "LD2410Async::getDetectionDataRef()"
+
+
+@section Examples_Modify_Config Example: Clone config data, modify, and write back
+ @code{.cpp}
+ ConfigData cfg = radar.getConfigData(); // clone
+ cfg.noOneTimeout = 60;
+ radar.configureAllConfigSettingsAsync(cfg, false, [](LD2410Async* sender, AsyncCommandResult result, byte) {
+ if (result == AsyncCommandResult::SUCCESS) {
+ Serial.println("Config updated successfully!");
+ }
+ });
+ @endcode
+
+
+ **Commands used in this example:**
+- @ref LD2410Async::getConfigData "LD2410Async::getConfigData()"
+- @ref LD2410Async::configureAllConfigSettingsAsync "LD2410Async::configureAllConfigSettingsAsync()"
+
+
+@section Examples_Usage Usage Example Sketches
+
+- @ref basicPresenceDetection_8ino-example "basicPresenceDetection.ino"
+ Minimal example showing how to detect presence and print state changes via Serial.
+
+- @ref receiveData_8ino-example "receiveData.ino"
+ Demonstrates continuous reception of detection frames and printing all available sensor data.
+
+- @ref changeConfig_8ino-example "changeConfig.ino"
+ Shows how to request and modify configuration settings (e.g. timeouts, sensitivities) using async commands.
+
+- @ref changeDistanceResolution_8ino-example "changeDistanceResolution.ino"
+ Illustrates how to change the sensor’s distance resolution (20 cm vs 75 cm) and handle the required reboot.
+
+- - @ref simplePresenceDetectionWebservice_8ino-example "simplePresenceDetectionWebservice.ino"
+ Integrates presence detection with a simple web service endpoint, useful as a starting point for IoT or home automation setups.
+
+@section Examples_Test Test Sketches
+
+- @ref enableConfigModeTest_8ino-example "enableConfigModeTest.ino"
+ Test sketch to explicitly measure entering configuration mode and handling success/failure cases.
+ The main use of this test is to check, whther enableConfigMode experiences timeouts.
+
+- @ref tortureTest_8ino-example "tortureTest.ino"
+ Stress test that issues random async commands in a loop, tracking execution times and statistics.
+ Used to check whther any errors accour over time.
+
+- @ref unitTest_8ino-example "unitTest.ino"
+ Tests all methods of the lib. Useful for regression testing during development.
+
diff --git a/dox/Inactivity Handling.md b/dox/Inactivity Handling.md
new file mode 100644
index 0000000..92b3a62
--- /dev/null
+++ b/dox/Inactivity Handling.md
@@ -0,0 +1,49 @@
+@page Inactivity_Handling Inactivity Handling
+
+
+@section Inactivity_Handling_Basics Basics
+
+The library includes built-in logic to detect long inactivity of the sensor, i.e. long periods without any data being received.
+The most common cause for a "silent" sensor is that **configuration mode** was enabled but never disabled.
+
+If long silent periods are detected, the library attempts to bring both its internal state and the sensor back to **normal operation mode**.
+Recovery is performed in the following steps (each step is separated by the current async command timeout period):
+
+1. Cancel any pending async commands, in case the user’s code is still waiting for a callback.
+ - @ref LD2410Async::asyncCancel "asyncCancel()"
+2. If canceling did not help, try to disable config mode — even if the library believes config mode is already disabled.
+ - @ref LD2410Async::disableConfigModeAsync "disableConfigModeAsync()"
+3. As a last step, attempt to reboot the sensor.
+ - @ref LD2410Async::rebootAsync "rebootAsync()"
+
+If none of these recovery steps succeed, the library will retry after the inactivity timeout period has elapsed again.
+
+@section Inactivity_Handling_EnableDisable Enabling / Disabling Inactivity Handling
+
+Inactivity handling can be enabled or disabled using:
+
+- @ref LD2410Async::enableInactivityHandling "enableInactivityHandling()"
+- @ref LD2410Async::disableInactivityHandling "disableInactivityHandling()"
+- @ref LD2410Async::setInactivityHandling "setInactivityHandling(bool enable)"
+
+By default, inactivity handling is **enabled**.
+
+@section Inactivity_Handling_Timeout Timeout Configuration
+
+The inactivity timeout can be configured with:
+
+- @ref LD2410Async::setInactivityTimeoutMs "setInactivityTimeoutMs(timeoutInMilliseconds)"
+- @ref LD2410Async::getInactivityTimeoutMs "getInactivityTimeoutMs()"
+
+The default timeout is **60000 ms (1 minute)**.
+Setting the time out to 00 will disable inactivity handling.
+
+**Important:**
+Always set a value larger than the timeout for async commands, which is controlled by:
+- @ref LD2410Async::setAsyncCommandTimeoutMs "setAsyncCommandTimeoutMs()"
+- @ref LD2410Async::getAsyncCommandTimeoutMs "getAsyncCommandTimeoutMs()"
+
+If the inactivity timeout is shorter than the async command timeout, inactivity handling could trigger while a command is still pending (usually waiting for an ACK). This would cause premature cancellation of the command.
+
+To avoid this, the library automatically adjusts the inactivity timeout:
+- If the configured inactivity timeout is shorter than the async command timeout, it will instead use **(async command timeout + 1000 ms)**.
diff --git a/dox/Installation.md b/dox/Installation.md
new file mode 100644
index 0000000..95da2ca
--- /dev/null
+++ b/dox/Installation.md
@@ -0,0 +1,17 @@
+@page Installation Installation
+
+You can install this library in two ways:
+
+@section Installation_LibManager Using Arduino Library Manager (recommended)
+1. Open the Arduino IDE.
+2. Go to **Tools - Manage Libraries…**.
+3. Search for **LD2410Async**.
+4. Click **Install**.
+
+@section Installation_Manual Manual installation
+1. Download this repository as a ZIP file.
+2. In the Arduino IDE, go to **Sketch - Include Library - Add .ZIP Library…**, or
+ unzip the file manually into your Arduino `libraries` folder:
+ - Windows: `Documents/Arduino/libraries/`
+ - Linux/macOS: `~/Arduino/libraries/`
+3. Restart the Arduino IDE.
\ No newline at end of file
diff --git a/dox/Notes And Best Practices.md b/dox/Notes And Best Practices.md
new file mode 100644
index 0000000..50d70c7
--- /dev/null
+++ b/dox/Notes And Best Practices.md
@@ -0,0 +1,48 @@
+@page BestPractices Important Notes and Best Practices
+
+When working with the LD2410Async library, keep the following points in mind:
+
+- **Consider if you really need the lib**
+ - The lib makes communication with the LD2410 easy and straightforward. However, for usages where you never need anything else than a simple presence detected signal, it might be easier more more efficent to just connect the out pin of the sensor to the microcontroller and not to connect the serial pins.
+ This will allow you to query presence with a simple digital read command, without having to care about anything else.
+ The downside of this is that you cant get more detailed detection data or do anything about the config of the sensor. This is where this lib comes in.
+
+- **Keep Callbacks Short**
+ - Callbacks are executed inside the radar’s processing task.
+ - Keep them **short and non-blocking** (e.g., update a variable or post to a queue).
+ - Avoid long delay code, heavy computations, or blocking I/O inside callbacks - otherwise, sensor data processing may be delayed or datection data can get lost.
+
+- **Async Command Busy State**
+ - The library ensures only one async command or sequence runs at a time.
+ - Check `asyncIsBusy()` before sending a new command.
+ - Alway check the return value of the async commands. If false is returned the command has not been sent (either due to busy state or due to invalid paras). True means that the command has been sent and that the callback of the method will execute after completition of the command or after the timeout period.
+ - If chaining commands, trigger the next command from the callback of the previous command. If the next command should only be executed, if the previous command was successfull, check the success para of the callback.
+
+- **Config Mode Handling**
+ - The sensor must be in *config mode* to change settings.
+ - All methods/commands that talk to the sensor enable and disable config mode for you if necessary. This means that they will enable and disable the config mode if the config mode has not been active when the command is called. If the config mode is alredy active when calling the command, it will remain active after the command has completed resp. fires its callback.
+ - Activating the config mode is a time consuming operation (often more than 1.5 secs). Therefore sending a lot of commands can take quite a while. If a sequence with several commands has to be sent, it is a good idea to enable config mode first and deactivate it after the last command (make sure this also happends if commands fail/return false or dont report success in their callback).
+ - Avoid leaving the sensor in config mode longer than necessary, since the sensor will not deliver any detection data while in config mode.
+
+- **Engineering Mode**
+ - Engineering mode provides detailed gate signal data for development and debugging.
+ - Do **not** enable engineering mode in production unless required - it increases the amount of data sent and will trigger the detection callback more often.
+ - Leaving it on continuously can increase CPU load and reduce performance.
+
+- **Presence Detection**
+ - Use the dedicated detection callback with the `presenceDetected` flag of the DetectionDataReceivedCallback (use onDetectionDataReceived() to register for that callback) if you only care about *whether* something is present.
+ - For advanced scenarios (distances, signals, engineering mode), you can access the full `DetectionData` via `getDetectionData()`.
+
+- **Task Management**
+ - The library runs a FreeRTOS background task for receiving and processing data.
+ - You must use `begin()` to start it. If you ever need to stop it again, use `end()` to stop it gracefully.
+ - Do not block the main loop for long periods; the radar’s task will continue to run, but your application logic may lag.
+
+- **Inactivity Handling**
+ - The library can automatically handle situation where the sensor doesnt send any data for a configurable period. Sice such situations are typically a result of the config mode being active accidentally, the lib will first try to disable the config mode and if that does not help it will try to reboot the sensor.
+ - You can enable or disable this behavior with `setInactivityHandling(bool enable)` depending on your use case.
+
+- **Config Memory Wear**
+ - The LD2410 stores configuration in internal non-volatile memory.
+ - Repeatedly writing unnecessary config updates can wear out the memory over time.
+ - Only send config changes when values really need to be updated.
\ No newline at end of file
diff --git a/dox/Operation Modes.md b/dox/Operation Modes.md
new file mode 100644
index 0000000..407ad38
--- /dev/null
+++ b/dox/Operation Modes.md
@@ -0,0 +1,67 @@
+@page Operation_Modes Operation Modes
+
+
+The LD2410 sensor supports several operation modes, each with different behavior and purpose.
+
+@section Operation_Modes_Normal_Mode Normal Detection Mode
+
+In normal detection mode the sensor automatically transmits new detection data, usually several times per second.
+
+The transmitted data includes:
+- Detection state
+- Distance (cm) to the nearest moving target
+- Signal strength (0-100) of the moving target
+- Distance (cm) to the nearest stationary target
+- Signal strength (0-100) of the stationary target
+- General detection distance (cm)
+
+Detection state is the most relevant. It can have the following values:
+- 0 - No Target
+- 1 - Moving target
+- 2 - Stationary target
+- 3 - Moving and stationary target
+- 4 - Auto config in progress
+- 5 - Auto config success
+- 6 - Auto config failed
+
+Unless auto config has been explicitly triggered, only the values 0-3 occur.
+
+All detection data is represented in the @ref LD2410Types::DetectionData "DetectionData" struct, which also includes an `engineeringMode` flag that indicates whether engineering data fields in the struct are valid.
+
+@section Operation_Modes_Engineering_Mode Engineering Mode
+
+Engineering mode behaves similar to normal detection mode but provides additional data for detailed analysis:
+
+- Number of gates with moving target signals
+- Per-gate signal strengths for moving targets
+- Number of gates with stationary target signals
+- Per-gate signal strengths for stationary targets
+- Reported ambient light level (0-255)
+- Current status of the OUT pin (true = high, false = low)
+
+This mode allows deeper inspection of what the sensor is detecting. The extended data is also part of the @ref LD2410Types::DetectionData "DetectionData" struct and can be distinguished using its `engineeringMode` flag.
+
+To control engineering mode:
+- @ref LD2410Async::enableEngineeringModeAsync "enableEngineeringModeAsync()"
+- @ref LD2410Async::disableEngineeringModeAsync "disableEngineeringModeAsync()"
+
+To check whether engineering mode is currently active:
+- @ref LD2410Async::isEngineeringModeEnabled "isEngineeringModeEnabled()"
+
+@section Operation_Modes_Configuration_Mode Configuration Mode
+
+Configuration mode is required to send configuration commands to the sensor.
+
+- Before any configuration command can be executed, the sensors config mode must be enabled.
+- While config mode is active, the sensor will **not** send detection data. It remains silent except for acknowledgements (ACKs) in response to commands.
+- After finishing configuration, it is good practice to return the sensor as soon as possible.
+
+All commands in the @ref LD2410Async "LD2410Async" library automatically handle enabling and disabling config mode as needed.
+
+Because entering config mode can take significant time (120-3300 ms) and may occasionally fail and then requiring retries (handled internally), it can be more efficient to explicitly control config mode when sending multiple commands in sequence. In this case:
+- Enable config mode once with @ref LD2410Async::enableConfigModeAsync "enableConfigModeAsync()"
+- Send multiple commands (make sure to wait for the callbacks between the commands).
+- Disable config mode afterward with @ref LD2410Async::disableConfigModeAsync "disableConfigModeAsync()"
+
+To check whether config mode is currently active:
+- @ref LD2410Async::isConfigModeEnabled "isConfigModeEnabled()"
diff --git a/dox/Troubleshooting.md b/dox/Troubleshooting.md
new file mode 100644
index 0000000..f3d2071
--- /dev/null
+++ b/dox/Troubleshooting.md
@@ -0,0 +1,72 @@
+@page Troubleshooting Troubleshooting Guide
+
+This page lists common issues you may encounter when using the **LD2410Async** library and provides steps to resolve them.
+
+@section Troubleshooting_Problems Problem Scenarios
+
+## No Data Received
+- Verify wiring: ensure the LD2410 radar is connected to the correct UART pins.
+- Check the baud rate: the LD2410 defaults to **256000**.
+- Make sure you called @ref LD2410Async::begin "begin()" **after** initializing the serial port.
+- Ensure the sensor is not stuck in @ref Operation_Modes "Config Mode" - in config mode no detection data is sent.
+
+
+## Callbacks Not Firing
+- Confirm that you registered the callback before expecting data (e.g. with @ref LD2410Async::onDetectionDataReceived "onDetectionDataReceived()").
+- Ensure the sensor is not in config mode (@ref Operation_Modes).
+- If @ref Operation_Modes "Engineering Mode" is enabled, expect more frequent callbacks with additional data.
+
+## Async Commands Not Working
+- Only one async command can run at a time.
+ - Use @ref LD2410Async::asyncIsBusy "asyncIsBusy()" to check before sending another command.
+ - Always check the return value of async calls:
+ - **true** - command sent successfully (callback will fire).
+ - **false** - busy state or invalid parameters.
+- All commands require the sensor to be in config mode.
+ - The library enables/disables config mode automatically.
+ - If you manually enable it with @ref LD2410Async::enableConfigModeAsync "enableConfigModeAsync()", remember to disable it afterward with @ref LD2410Async::disableConfigModeAsync "disableConfigModeAsync()".
+
+For more details see @ref Async_Commands_And_Processing.
+
+
+## Unexpected Sensor Reboots
+- Check if @ref Inactivity_Handling "inactivity handling" is enabled with @ref LD2410Async::setInactivityHandling "setInactivityHandling(true)".
+- If enabled, the library may automatically call @ref LD2410Async::rebootAsync "rebootAsync()" after long inactivity.
+
+---
+
+## Data Loss
+- Keep callbacks **short and non-blocking**.
+ - Long or blocking callbacks may cause dropped frames.
+ - Best practice: copy the data in the callback and process later in your loop or another task.
+- Example: use @ref LD2410Async::onDetectionDataReceived "onDetectionDataReceived()" to capture `presenceDetected` quickly, then defer heavy work.
+- In rare cases, increasing the serial receive buffer size may help (though normally unnecessary).
+
+See also @ref Async_Commands_And_Processing "Async Commands & Processing".
+
+---
+
+## Strange or Invalid Data
+- Ensure the sensor is in **Normal Detection Mode** (see @ref Operation_Modes).
+- When in @ref Operation_Modes "Engineering Mode", the sensor produces additional raw data which may appear unusual if not parsed correctly.
+
+---
+
+@section Troubleshooting_Debugging Debugging
+
+Enable debug output for troubleshooting communication issues. The following defines can be set in the LD2410Async.h file.
+
+@code{.cpp}
+#define LD2410ASYNC_DEBUG_LEVEL 1
+#define LD2410ASYNC_DEBUG_DATA_LEVEL 2
+@endcode
+
+Debug Levels:
+
+- 0 - No debug (default).
+- 1 - Basic debug messages.
+- 2 - Include raw hex buffer dumps.
+
+@warning
+Do not enable debug in production:
+Debugging adds overhead, negatively impacts performance, increases binary size, and will flood Serial output.
\ No newline at end of file
diff --git a/dox/mainPage.md b/dox/mainPage.md
new file mode 100644
index 0000000..ad32ffe
--- /dev/null
+++ b/dox/mainPage.md
@@ -0,0 +1,57 @@
+@mainpage
+
+## Introduction
+
+ The LD2410 is a mmWave radar sensor capable of detecting both moving and
+ stationary targets, reporting presence, distance, and per-gate signal strength.
+
+ This class implements a non-blocking, asynchronous interface for communicating
+ with the sensor over a UART stream.
+
+ ## Features
+ - Fully asynchronous operation with no blocking calls and callback.
+ - Support for all native commands of the LD2410.
+ - High level commands to allow for more consistent communication with the sensor.
+ - Support for several instances if you have more than one sensor.
+ - Continuous background task that parses incoming frames and updates data.
+ - Automatic inactivity handling if sensor is stuck or unreposive (tries to disable config mode or reboot).
+
+## Documentation
+
+The following pages provide detailed explanations of the libs components and usage patterns:
+
+- @subpage Installation
+ How to install the LD2410Async library via Arduino Library Manager or manually.
+
+- @subpage Async_Commands_And_Processing
+ Covers the non-blocking design, callbacks for detection and configuration, and the full list of asynchronous commands supported by the LD2410.
+
+- @subpage Data_Structures
+ Documents the core data structures: @ref LD2410Types::DetectionData "DetectionData" and @ref LD2410Types::ConfigData "ConfigData". Includes detailed descriptions of all members and their meanings.
+
+- @subpage Operation_Modes
+ Explains the sensor’s Normal, Engineering, and Configuration modes, including how to switch between them.
+
+- @subpage Inactivity_Handling
+ Describes the libs automatic recovery logic when the sensor becomes unresponsive, including how to configure and disable inactivity handling.
+
+- @subpage Examples
+ Various examples that demonstrate and the the abilities of the lib.
+
+- @subpage BestPractices
+ Important notes and best practices for using the library effectively.
+
+- @subpage Troubleshooting
+ A guide to solving common issues such as no data received, callbacks not firing, async commands failing, or unexpected sensor reboots.
+
+
+## Main Class Reference
+
+All interaction with the sensor is performed through the main class:
+- @ref LD2410Async "LD2410Async"
+
+
+ ## Examples
+
+ Details on the example sketches in the examples folder can be found at the @ref Examples "Examples page".
+
diff --git a/examples/basicPresenceDetection/basicPresenceDetection.ino b/examples/basicPresenceDetection/basicPresenceDetection.ino
new file mode 100644
index 0000000..d6f267a
--- /dev/null
+++ b/examples/basicPresenceDetection/basicPresenceDetection.ino
@@ -0,0 +1,106 @@
+/**
+ * @brief Example: Basic Presence Detection with LD2410Async
+ *
+ * @details
+ * This sketch demonstrates how to use the LD2410Async library to detect presence
+ * using only the presenceDetected variable from the detection data callback.
+ * It prints a message with a timestamp whenever the presence state changes.
+ *
+ * @warning
+ * Important!
+ * Adjust RADAR_RX_PIN and RADAR_TX_PIN to match your wiring.
+ */
+
+#include
+#include "LD2410Async.h"
+
+ // ========================= USER CONFIGURATION =========================
+
+ // UART pins for the LD2410 sensor
+#define RADAR_RX_PIN 16 // ESP32 pin that receives data from the radar (radar TX)
+#define RADAR_TX_PIN 17 // ESP32 pin that transmits data to the radar (radar RX)
+
+// UART baudrate for the radar sensor (default is 256000)
+#define RADAR_BAUDRATE 256000
+
+// ======================================================================
+
+/**
+* Create a HardwareSerial instance (ESP32 has multiple UARTs) bound to UART1
+*/
+HardwareSerial RadarSerial(1);
+
+/**
+* @brief Creates LD2410Async object bound to the serial port defined in RadarSerial
+*/
+LD2410Async radar(RadarSerial);
+
+
+// Track last presence state
+bool lastPresenceDetected = false;
+bool firstCallback = true;
+
+/**
+* @brief Callback function called whenever new detection data arrives
+*
+* @details
+* Since only basic presence information is required in the example, it is just using the presenceDetected para of the callback.
+* The logic in this methods just ensures that only changes in presence state are printed to the Serial Monitor.
+*
+ * @note
+ * If only basic presence detection is needed, use the presenceDetected variable directly instead of accessing the full detection data struct.
+ * For more advanced use cases, the full detection data can be accessed using getDetectionDataRef() or getDetectionData().
+*/
+void onDetectionDataReceived(LD2410Async* sender, bool presenceDetected) {
+ if (firstCallback || presenceDetected != lastPresenceDetected) {
+ unsigned long now = millis();
+ Serial.print("[");
+ Serial.print(now);
+ Serial.print(" ms] Presence detected: ");
+ Serial.println(presenceDetected ? "YES" : "NO");
+ lastPresenceDetected = presenceDetected;
+ firstCallback = false;
+ }
+}
+
+/**
+* @brief Arduino setup function which initializes the radar and registers the callback
+*
+* @details
+* radar.begin() starts the background task of the LD2410Async library which automatically handles
+* incoming data and triggers callbacks. The onDetectionDataReceived callback is registered to receive detection data.
+*
+*/
+void setup() {
+ // Initialize USB serial for debug output
+ Serial.begin(115200);
+ while (!Serial) {
+ ; // wait for Serial Monitor
+ }
+ Serial.println("LD2410Async Example: Basic Presence Detection");
+
+ // Initialize Serial1 with user-defined pins and baudrate
+ RadarSerial.begin(RADAR_BAUDRATE, SERIAL_8N1, RADAR_RX_PIN, RADAR_TX_PIN);
+
+ // Start the radar background task (parses incoming data frames)
+ if (radar.begin()) {
+ Serial.println("Radar task started successfully.");
+ // Register callback for detection updates
+ radar.onDetectionDataReceived(onDetectionDataReceived);
+ }
+ else {
+ Serial.println("ERROR! Could not start radar task.");
+ }
+}
+
+/**
+* @brief Arduino loop function which does nothing
+*
+* @details
+* The LD2410Async library runs a FreeRTOS background task that automatically handles all jobs that are related to the radar sensor.
+* Therefore the main loop doesnt have to da any LD2410 related work and is free for anything else you might want to do.
+*/
+void loop() {
+ // Nothing to do here!
+ delay(1000); // idle delay (optional)
+}
\ No newline at end of file
diff --git a/examples/changeConfig/changeConfig.ino b/examples/changeConfig/changeConfig.ino
index 0160d18..4fd3a0a 100644
--- a/examples/changeConfig/changeConfig.ino
+++ b/examples/changeConfig/changeConfig.ino
@@ -1,6 +1,7 @@
/**
- * Example sketch for LD2410Async library
+ * @brief Example: Change configuration of the LD2410
*
+ * @details
* This sketch shows how to:
* 1. Initialize the radar on Serial1.
* 2. Query all current configuration values from the sensor.
@@ -8,6 +9,7 @@
* 4. Modify some settings (timeout, sensitivities).
* 5. Write the modified config back to the sensor.
*
+ * @warning
* Important:
* Make sure to adjust RADAR_RX_PIN and ADAR_TX_PIN to match you actual wiring.
*/
@@ -26,72 +28,125 @@
// ======================================================================
-// Create a HardwareSerial instance (ESP32 has multiple UARTs)
+/**
+* Create a HardwareSerial instance (ESP32 has multiple UARTs) bound to UART1
+*/
HardwareSerial RadarSerial(1);
-// Create LD2410Async object bound to Serial1
+/**
+* @brief Creates LD2410Async object bound to the serial port defined in RadarSerial
+*/
LD2410Async radar(RadarSerial);
-// Callback after attempting to apply a modified config
-void onConfigApplied(LD2410Async* sender,
- LD2410Async::AsyncCommandResult result,
- byte userData) {
- if (result == LD2410Async::AsyncCommandResult::SUCCESS) {
- Serial.println("Config updated successfully!");
- }
- else {
- Serial.println("Failed to apply config.");
- }
+/**
+* @brief Callback after the new config has been written to the sensor
+*
+* @details
+* This method just checks and prints the result
+*/
+void onConfigApplied(LD2410Async* sender, LD2410Async::AsyncCommandResult result) {
+ if (result == LD2410Async::AsyncCommandResult::SUCCESS) {
+ Serial.println("Config updated successfully!");
+ }
+ else {
+ Serial.println("Failed to apply config.");
+ }
}
-// Callback after requesting config data
-void onConfigReceived(LD2410Async* sender,
- LD2410Async::AsyncCommandResult result,
- byte userData) {
- if (result != LD2410Async::AsyncCommandResult::SUCCESS) {
- Serial.println("Failed to request config data.");
- return;
- }
-
- Serial.println("Config data received. Cloning and modifying...");
-
- // Clone the current config
- LD2410Async::ConfigData cfg = sender->getConfigData();
-
- // Example modifications:
- cfg.noOneTimeout = 120; // Set "no-one" timeout to 120 seconds
- cfg.distanceGateMotionSensitivity[2] = 75; // Increase motion sensitivity on gate 2
- cfg.distanceGateStationarySensitivity[4] = 60; // Adjust stationary sensitivity on gate 4
-
- // Apply updated config to the radar
- sender->setConfigDataAsync(cfg, onConfigApplied);
-}
+/**
+* @brief Callback after receiving the config data
+*
+* @details
+* Checks the result of the async requestAllConfigSettingsAsync() command, gets a clone of the config data using getConfigData(), modifies it and writes back
+* using the async configureAllConfigSettingsAsync() command.
+*
+* @note
+* Always check the result of the async command that has triggered the callback. Otherweise async commands can fail, timeout or get canceled without you relizing it.
+* In callback methods is is generally advised to access members of the LD2410Async instance via the sender pointer.
+* This ensures that you are allways working with the correct instance, which is important if you have multiple LD2410Async instances.
+* Also keep in mind that callbacks should be as short and efficient as possible to avoid blocking the background task of the library.
+*/
+void onConfigReceived(LD2410Async* sender, LD2410Async::AsyncCommandResult result) {
+ if (result != LD2410Async::AsyncCommandResult::SUCCESS) {
+ Serial.println("Failed to request config data.");
+ return;
+ }
+
+ Serial.println("Config data received. Cloning and modifying...");
+
+ // Clone the current config
+ LD2410Types::ConfigData cfg = sender->getConfigData();
+ //alternatively you could also call the LD2410Async instance directly instead of using the pointer in sender:
+ //LD2410Types::ConfigData cfg = radar.getConfigData();
+ //However, working with the reference in sender is a good practice since it will always point to the LD2410Async instance that triggered the callback (important if you have several instances resp. more than one LD2410 connected to your board)..
+
+
+ // Example modifications:
+ cfg.noOneTimeout = 120; // Set "no-one" timeout to 120 seconds
+ cfg.distanceGateMotionSensitivity[2] = 75; // Increase motion sensitivity on gate 2
+ cfg.distanceGateStationarySensitivity[4] = 60; // Adjust stationary sensitivity on gate 4
+
+ // Apply updated config to the radar
+ if (!sender->configureAllConfigSettingsAsync(cfg, false, onConfigApplied)) {
+ //It is good practive to check the return value of commands for true/false.
+ //False indicates that the command could not be sent (e.g. because another async command is still pending)
+ Serial.println("Error! Could not update config on the sensor");
+ };
+
+ //alternatively you could also call the LD2410Async instance directly instead of using the pointer in sender:
+ //radar.configureAllConfigSettingsAsync(cfg, false, onConfigApplied);
+ //However, working with the reference in sender is a good practice since it will always point to the LD2410Async instance that triggered the callback (important if you have several instances resp. more than one LD2410 connected to your board)..
+
+};
+/**
+* @brief Arduino setup function which initializes the radar and starts the config change process
+*
+* @details
+* begin() starts the background task of the LD2410Async library which automatically handles
+* incoming data and triggers callbacks. The onDetectionDataReceived callback is registered to
+* receive detection data.
+* requestAllConfigSettingsAsync() will fetch all config data from the sensor and triggers the
+* onConfigReceived callback when done.
+*/
void setup() {
- // Initialize USB serial for debug output
- Serial.begin(115200);
- while (!Serial) {
- ; // wait for Serial Monitor
- }
- Serial.println("LD2410Async example: change radar config");
-
- // Initialize Serial1 with user-defined pins and baudrate
- RadarSerial.begin(RADAR_BAUDRATE, SERIAL_8N1, RADAR_RX_PIN, RADAR_TX_PIN);
-
- // Start the radar background task (parses incoming data frames)
- if (radar.begin()) {
- Serial.println("Radar task started successfully.");
- }
- else {
- Serial.println("Radar task already running.");
- }
-
- // Request all config data, then modify in callback
- radar.requestAllConfigData(onConfigReceived);
+ // Initialize USB serial for debug output
+ Serial.begin(115200);
+ while (!Serial) {
+ ; // wait for Serial Monitor
+ }
+ Serial.println("LD2410Async example: change radar config");
+
+ // Initialize Serial1 with user-defined pins and baudrate
+ RadarSerial.begin(RADAR_BAUDRATE, SERIAL_8N1, RADAR_RX_PIN, RADAR_TX_PIN);
+
+ // Start the radar background task (parses incoming data frames)
+ if (radar.begin()) {
+ Serial.println("Radar task started successfully.");
+
+ // Request all config data, then modify in callback
+ if (!radar.requestAllConfigSettingsAsync(onConfigReceived)) {
+ //It is good practive to check the return value of commands for true/false.
+ //False indicates that the command could not be sent (e.g. because another async command is still pending)
+ Serial.println("Error! Could not start config data request");
+ }
+
+ }
+ else {
+ Serial.println("Error! Could not start radar task");
+ }
+
}
+/**
+* @brief Arduino loop function which does nothing
+*
+* @details
+* The LD2410Async library runs a FreeRTOS background task that automatically handles all jobs that are related to the radar sensor.
+* Therefore the main loop doesnt have to da any LD2410 related work and is free for anything else you might want to do.
+*/
void loop() {
- // Nothing to do here.
- // The library handles communication and invokes callbacks automatically.
- delay(1000);
+ // Nothing to do here.
+ // The library handles communication and invokes callbacks automatically.
+ delay(1000);
}
diff --git a/examples/changeDistanceResolution/changeDistanceResolution.ino b/examples/changeDistanceResolution/changeDistanceResolution.ino
index 111e237..c747069 100644
--- a/examples/changeDistanceResolution/changeDistanceResolution.ino
+++ b/examples/changeDistanceResolution/changeDistanceResolution.ino
@@ -1,6 +1,7 @@
/**
- * Example sketch for LD2410Async library
+ * @brief Example: Changing detection resolution on LD2410
*
+ * @details
* This sketch demonstrates how to:
* 1. Initialize the radar on Serial1.
* 2. Query all current configuration values from the sensor.
@@ -9,6 +10,7 @@
* 5. Apply the new config.
* 6. Reboot the sensor to ensure changes take effect.
*
+ * @warning
* Important:
* Make sure to adjust RADAR_RX_PIN and ADAR_TX_PIN to match you actual wiring.
*/
@@ -27,84 +29,148 @@
// ======================================================================
-// Create a HardwareSerial instance (ESP32 has multiple UARTs)
+/**
+* Create a HardwareSerial instance (ESP32 has multiple UARTs) bound to UART1
+*/
HardwareSerial RadarSerial(1);
-// Create LD2410Async object bound to Serial1
+/**
+* @brief Creates LD2410Async object bound to the serial port defined in RadarSerial
+*/
LD2410Async radar(RadarSerial);
-// Callback after reboot command is sent
-void onReboot(LD2410Async* sender,
- LD2410Async::AsyncCommandResult result,
- byte userData) {
- if (result == LD2410Async::AsyncCommandResult::SUCCESS) {
- Serial.println("Radar reboot initiated.");
- }
- else {
- Serial.println("Failed to init reboot.");
- }
+/**
+* @brief Callback after the reboot command has been confirmed
+*
+* @details
+* This method just checks and prints the result
+*/
+void onReboot(LD2410Async* sender, LD2410Async::AsyncCommandResult result) {
+ if (result == LD2410Async::AsyncCommandResult::SUCCESS) {
+ Serial.println("Radar reboot initiated.");
+ }
+ else {
+ Serial.println("Failed to init reboot.");
+ }
}
-// Callback after applying modified config
-void onConfigApplied(LD2410Async* sender,
- LD2410Async::AsyncCommandResult result,
- byte userData) {
- if (result == LD2410Async::AsyncCommandResult::SUCCESS) {
- Serial.println("Config applied successfully. Rebooting radar...");
- sender->rebootAsync(onReboot);
- }
- else {
- Serial.println("Failed to apply config.");
- }
+/**
+* @brief Callback after applying modified config
+*
+* @details
+* After checking if the async configureAllConfigSettingsAsync() method was successful (always do that!), it will initiate a
+* reboot of the sensor to activate the changed distance resolution.
+*
+* @note
+* Always check the result of the async command that has triggered the callback. Otherweise async commands can fail, timeout or get canceled without you relizing it.
+* In callback methods is is generally advised to access members of the LD2410Async instance via the sender pointer.
+* This ensures that you are allways working with the correct instance, which is important if you have multiple LD2410Async instances.
+* Also keep in mind that callbacks should be as short and efficient as possible to avoid blocking the background task of the library.
+*/
+void onConfigApplied(LD2410Async* sender, LD2410Async::AsyncCommandResult result) {
+ if (result == LD2410Async::AsyncCommandResult::SUCCESS) {
+ Serial.println("Config applied successfully. Rebooting radar...");
+ if (!sender->rebootAsync(onReboot)) {
+ //It is good practive to check the return value of commands for true/false.
+ //False indicates that the command could not be sent (e.g. because another async command is still pending)
+ Serial.println("Error! Could not send reboot command to the sensor");
+ };
+ //alternatively you could also call the LD2410Async instance directly instead of using the pointer in sender:
+ //radar.rebootAsync(onReboot);
+ //However, working with the reference in sender is a good practice since it will always point to the LD2410Async instance that triggered the callback (important if you have several instances resp. more than one LD2410 connected to your board)..
+ }
+ else {
+ Serial.println("Failed to apply config.");
+ }
}
-// Callback after requesting config data
-void onConfigReceived(LD2410Async* sender,
- LD2410Async::AsyncCommandResult result,
- byte userData) {
- if (result != LD2410Async::AsyncCommandResult::SUCCESS) {
- Serial.println("Failed to request config data.");
- return;
- }
-
- Serial.println("Config data received. Cloning and modifying...");
-
- // Clone the current config
- LD2410Async::ConfigData cfg = sender->getConfigData();
-
- // Example modification: change resolution
- // RESOLUTION_75CM or RESOLUTION_20CM
- cfg.distanceResolution = LD2410Async::DistanceResolution::RESOLUTION_20CM;
-
- // Apply updated config
- sender->setConfigDataAsync(cfg, onConfigApplied);
+/**
+* @brief Callback after requesting config data
+*
+* @details
+* This method is called when the config data has been received from the sensor.
+* After checking if the async requestAllConfigSettingsAsync() method was successful (always do that!), it clones the config using getConfigData(),
+* modifies the distance resolution and applies the new config using configureAllConfigSettingsAsync()
+*
+* @note
+* Always check the result of the async command that has triggered the callback. Otherweise async commands can fail, timeout or get canceled without you relizing it.
+* In callback methods is is generally advised to access members of the LD2410Async instance via the sender pointer.
+* This ensures that you are allways working with the correct instance, which is important if you have multiple LD2410Async instances.
+* Also keep in mind that callbacks should be as short and efficient as possible to avoid blocking the background task of the library.
+*/
+void onConfigReceived(LD2410Async* sender, LD2410Async::AsyncCommandResult result) {
+ if (result != LD2410Async::AsyncCommandResult::SUCCESS) {
+ Serial.println("Failed to request config data.");
+ return;
+ }
+
+ Serial.println("Config data received. Cloning and modifying...");
+
+ // Clone the current config
+ LD2410Types::ConfigData cfg = sender->getConfigData();
+
+ // Example modification: change resolution
+ // RESOLUTION_75CM or RESOLUTION_20CM
+ cfg.distanceResolution = LD2410Types::DistanceResolution::RESOLUTION_20CM;
+
+ // Apply updated config to the radar
+ if (!sender->configureAllConfigSettingsAsync(cfg, false, onConfigApplied)) {
+ //It is good practive to check the return value of commands for true/false.
+ //False indicates that the command could not be sent (e.g. because another async command is still pending)
+ Serial.println("Error! Could not update config on the sensor");
+ };
+ //alternatively you could also call the LD2410Async instance directly instead of using the pointer in sender:
+ //radar.configureAllConfigSettingsAsync(cfg, false, onConfigApplied);
+ //However, working with the reference in sender is a good practice since it will always point to the LD2410Async instance that triggered the callback (important if you have several instances resp. more than one LD2410 connected to your board)..
}
+/**
+* @brief Arduino setup function which initializes the radar and starts the config change process
+*
+* @details
+* begin() starts the background task of the LD2410Async library which automatically handles
+* incoming data and triggers callbacks. The onDetectionDataReceived callback is registered to
+* receive detection data.
+* requestAllConfigSettingsAsync() will fetch all config data from the sensor and triggers the
+* onConfigReceived callback when done.
+*/
void setup() {
- // Initialize USB serial for debug output
- Serial.begin(115200);
- while (!Serial) {
- ; // wait for Serial Monitor
- }
- Serial.println("LD2410Async example: change resolution and reboot");
-
- // Initialize Serial1 with user-defined pins and baudrate
- RadarSerial.begin(RADAR_BAUDRATE, SERIAL_8N1, RADAR_RX_PIN, RADAR_TX_PIN);
-
- // Start the radar background task (parses incoming data frames)
- if (radar.begin()) {
- Serial.println("Radar task started successfully.");
- }
- else {
- Serial.println("Radar task already running.");
- }
-
- // Request all config data, then modify in callback
- radar.requestAllConfigData(onConfigReceived);
+ // Initialize USB serial for debug output
+ Serial.begin(115200);
+ while (!Serial) {
+ ; // wait for Serial Monitor
+ }
+ Serial.println("LD2410Async example: change resolution and reboot");
+
+ // Initialize Serial1 with user-defined pins and baudrate
+ RadarSerial.begin(RADAR_BAUDRATE, SERIAL_8N1, RADAR_RX_PIN, RADAR_TX_PIN);
+
+ // Start the radar background task (parses incoming data frames)
+ if (radar.begin()) {
+ Serial.println("Radar task started successfully.");
+
+ // Request all config data, then modify in callback
+ if (!radar.requestAllConfigSettingsAsync(onConfigReceived)) {
+ //It is good practive to check the return value of commands for true/false.
+ //False indicates that the command could not be sent (e.g. because another async command is still pending)
+ Serial.println("Error! Could not start config data request");
+ }
+ }
+ else {
+ Serial.println("Error! Could not start radar task.");
+ }
+
}
+/**
+* @brief Arduino loop function which does nothing
+*
+* @details
+* The LD2410Async library runs a FreeRTOS background task that automatically handles all jobs that are related to the radar sensor.
+* Therefore the main loop doesnt have to da any LD2410 related work and is free for anything else you might want to do.
+*/
void loop() {
- // Nothing to do here.
- // The library handles communication and invokes callbacks automatically.
- delay(1000);
+ // Nothing to do here.
+ // The library handles communication and invokes callbacks automatically.
+ delay(1000);
}
diff --git a/examples/enableConfigModeTest/enableConfigModeTest.ino b/examples/enableConfigModeTest/enableConfigModeTest.ino
new file mode 100644
index 0000000..1339380
--- /dev/null
+++ b/examples/enableConfigModeTest/enableConfigModeTest.ino
@@ -0,0 +1,302 @@
+/**
+ * @file measureEnableConfigMode.ino
+ * @brief
+ * Test sketch for LD2410Async library to evaluate the execution time
+ * of the enableConfigModeAsync() command.
+ *
+ * @details
+ * This sketch continuously executes a measurement sequence:
+ * - Calls enableConfigModeAsync() and measures how long it takes until
+ * the ACK (callback) is triggered.
+ * - Once config mode is enabled, executes one randomly chosen command
+ * (requestDistanceResolutionAsync, requestAuxControlSettingsAsync,
+ * requestAllConfigSettingsAsync, requestAllStaticDataAsync,
+ * requestFirmwareAsync, or no command at all).
+ * - Calls disableConfigModeAsync().
+ * - Waits a random timespan between 1–5000 ms using the Ticker library.
+ * - Repeats the sequence indefinitely.
+ *
+ * The sketch prints the minimum, maximum, average, and count of all
+ * measurements after each test cycle.
+ */
+
+#include
+#include
+#include "LD2410Async.h"
+
+ // ========================= USER CONFIGURATION =========================
+
+#define RADAR_RX_PIN 32 ///< ESP32 pin receiving data from the radar (radar TX)
+#define RADAR_TX_PIN 33 ///< ESP32 pin transmitting data to the radar (radar RX)
+#define RADAR_BAUDRATE 256000 ///< UART baudrate for radar sensor (default is 256000)
+#define LED_PIN 2 ///< GPIO for internal LED (adjust if needed)
+// ======================================================================
+// Global objects
+// ======================================================================
+
+HardwareSerial RadarSerial(1); ///< HardwareSerial instance bound to UART1
+LD2410Async ld2410(RadarSerial); ///< LD2410Async driver instance
+Ticker delayTicker; ///< Ticker for randomized delays
+
+// ======================================================================
+// Measurement variables
+// ======================================================================
+
+unsigned long enableStartMs = 0; ///< Timestamp when enableConfigModeAsync() was called
+unsigned long minEnableDurationMs = ULONG_MAX; ///< Minimum execution time measured so far
+unsigned long maxEnableDurationMs = 0; ///< Maximum execution time measured so far
+unsigned long totalEnableDurationMs = 0; ///< Sum of all measured durations
+unsigned long measurementCount = 0; ///< Number of successful measurements
+unsigned long failureCount = 0; ///< Number of failed measurements
+unsigned long delayMs = 0; ///< Delay before the tests
+unsigned long dataCount = 0; ///< Used to count received data frames
+// ======================================================================
+// Forward declarations of callbacks
+// ======================================================================
+
+void enableConfigModeCallback(LD2410Async* sender, LD2410Async::AsyncCommandResult result);
+void extraCommandCallback(LD2410Async* sender, LD2410Async::AsyncCommandResult result);
+void disableConfigModeCallback(LD2410Async* sender, LD2410Async::AsyncCommandResult result);
+
+
+// ======================================================================
+// Utility functions
+// ======================================================================
+
+/**
+ * @brief Print a number padded to 6 characters width.
+ *
+ * @param value Number to print
+ */
+void printPaddedNumber(unsigned long value) {
+ char buf[8];
+ snprintf(buf, sizeof(buf), "%6lu", value);
+ Serial.print(buf);
+}
+
+/**
+ * @brief Convert AsyncCommandResult enum to human-readable text.
+ *
+ * @param result Enum value from LD2410Async
+ * @return const char* String representation
+ */
+const char* resultToString(LD2410Async::AsyncCommandResult result) {
+ switch (result) {
+ case LD2410Async::AsyncCommandResult::SUCCESS: return "SUCCESS";
+ case LD2410Async::AsyncCommandResult::FAILED: return "FAILED";
+ case LD2410Async::AsyncCommandResult::TIMEOUT: return "TIMEOUT";
+ case LD2410Async::AsyncCommandResult::CANCELED: return "CANCELED";
+ default: return "UNKNOWN";
+ }
+}
+
+// ======================================================================
+// Test sequence functions
+// ======================================================================
+
+/**
+ * @brief Schedule the next test sequence.
+ *
+ * Waits a random time between 1–5000 ms before calling enableConfigModeAsync().
+ */
+void scheduleNextSequence() {
+ dataCount = 0;
+
+ switch (random(0, 4)) {
+ case 0:
+ delayMs = random(1, 11);
+ break;
+ case 1:
+ delayMs = random(1, 11) * 10;;
+ break;
+ case 2:
+ delayMs = random(0, 50) * 20 + 100;
+ break;
+ default:
+ delayMs = random(0, 70) * 100 + 1000;
+ break;
+ }
+
+ measurementCount++;
+
+ Serial.print("Test ");
+ printPaddedNumber(measurementCount);
+ Serial.print(" | Delay: ");
+ printPaddedNumber(delayMs);
+
+
+ delayTicker.once_ms(delayMs, []() {
+ Serial.print(" (Data cnt: ");
+ printPaddedNumber(dataCount);
+ Serial.print(")");
+ enableStartMs = millis();
+ dataCount = 0;
+ if (!ld2410.enableConfigModeAsync(enableConfigModeCallback)) {
+ Serial.println(" > enableConfigModeAsync could not be started (busy).");
+ failureCount++;
+ scheduleNextSequence();
+ }
+ else {
+ Serial.print(" > ");
+ }
+ });
+}
+
+/**
+ * @brief Callback for enableConfigModeAsync().
+ *
+ * Updates statistics, prints results and triggers an extra command.
+ *
+ * @param sender Pointer to the LD2410Async instance
+ * @param result Result of the command
+ */
+void enableConfigModeCallback(LD2410Async* sender, LD2410Async::AsyncCommandResult result) {
+ unsigned long duration = millis() - enableStartMs;
+
+ if (result == LD2410Async::AsyncCommandResult::SUCCESS) {
+ totalEnableDurationMs += duration;
+ if (duration > maxEnableDurationMs) maxEnableDurationMs = duration;
+ if (duration < minEnableDurationMs) minEnableDurationMs = duration;
+
+ unsigned long avg = totalEnableDurationMs / measurementCount;
+
+
+ Serial.print("Dur: ");
+ printPaddedNumber(duration);
+ Serial.print(" ms | Data Cnt:");
+ printPaddedNumber(dataCount);
+ Serial.print(" | Min: ");
+ printPaddedNumber(minEnableDurationMs);
+ Serial.print(" ms | Max: ");
+ printPaddedNumber(maxEnableDurationMs);
+ Serial.print(" ms | Avg: ");
+ printPaddedNumber(avg);
+ Serial.print(" ms | Fails: ");
+ printPaddedNumber(failureCount);
+ Serial.println();
+
+ // Execute a random extra command
+ int cmd = random(0, 6);
+ switch (cmd) {
+ case 0: ld2410.requestDistanceResolutionAsync(extraCommandCallback); break;
+ case 1: ld2410.requestAuxControlSettingsAsync(extraCommandCallback); break;
+ case 2: ld2410.requestAllConfigSettingsAsync(extraCommandCallback); break;
+ case 3: ld2410.requestAllStaticDataAsync(extraCommandCallback); break;
+ case 4: ld2410.requestFirmwareAsync(extraCommandCallback); break;
+ case 5: // No extra command
+ if (!ld2410.disableConfigModeAsync(disableConfigModeCallback)) {
+ Serial.println("Could not start disableConfigMode()");
+ scheduleNextSequence();
+ }
+ break;
+ }
+ }
+ else {
+ Serial.print("enableConfigModeAsync failed: ");
+ Serial.print(resultToString(result));
+ Serial.print(" Received ");
+ printPaddedNumber(dataCount);
+ Serial.println(" data frames while waiting for ACK");
+ failureCount++;
+ measurementCount--;
+ scheduleNextSequence();
+ }
+}
+
+/**
+ * @brief Callback for extra commands.
+ *
+ * @param sender Pointer to the LD2410Async instance
+ * @param result Result of the command
+ */
+void extraCommandCallback(LD2410Async* sender, LD2410Async::AsyncCommandResult result) {
+ if (result != LD2410Async::AsyncCommandResult::SUCCESS) {
+ Serial.print("Extra command failed ");
+ Serial.println(resultToString(result));
+ }
+ if (!ld2410.disableConfigModeAsync(disableConfigModeCallback)) {
+ Serial.println("Could not start disableConfigMode()");
+ scheduleNextSequence();
+ }
+}
+
+/**
+ * @brief Callback for disableConfigModeAsync().
+ *
+ * Once config mode is disabled, schedules the next test sequence.
+ *
+ * @param sender Pointer to the LD2410Async instance
+ * @param result Result of the command
+ */
+void disableConfigModeCallback(LD2410Async* sender, LD2410Async::AsyncCommandResult result) {
+ if (result != LD2410Async::AsyncCommandResult::SUCCESS) {
+ Serial.print("Disable config mode failed ");
+ Serial.println(resultToString(result));
+ }
+ scheduleNextSequence();
+}
+
+// ======================================================================
+// Callback function
+// ======================================================================
+
+/**
+ * @brief Callback triggered when new detection data is received.
+ *
+ * @param sender Pointer to the LD2410Async instance
+ * @param presenceDetected Convenience flag for presence detection
+ */
+void onDetectionDataReceived(LD2410Async* sender, bool presenceDetected) {
+ // Toggle LED whenever data arrives
+ int currentState = digitalRead(LED_PIN);
+ digitalWrite(LED_PIN, currentState == HIGH ? LOW : HIGH);
+
+ //Count data frames
+ dataCount++;
+}
+// ======================================================================
+// Setup and loop
+// ======================================================================
+
+/**
+ * @brief Arduino setup function.
+ *
+ * Initializes Serial, the radar UART, and the LD2410Async library.
+ * Starts the first test sequence by calling scheduleNextSequence().
+ */
+void setup() {
+ Serial.begin(115200);
+ delay(2000);
+ Serial.println("=== LD2410Async enableConfigModeAsync measurement test ===");
+
+ pinMode(LED_PIN, OUTPUT);
+ digitalWrite(LED_PIN, LOW);
+
+ RadarSerial.begin(RADAR_BAUDRATE, SERIAL_8N1, RADAR_RX_PIN, RADAR_TX_PIN);
+
+ if (!ld2410.begin()) {
+ Serial.println("Failed to start LD2410Async task!");
+ while (true) delay(1000);
+ }
+ else {
+ Serial.println("LD2410Async task started!");
+
+ //Callback reghistration can take place at any time
+ ld2410.onDetectionDataReceived(onDetectionDataReceived);
+ }
+
+ // Long timeout to ensure that we can really measure the actual execution time
+ ld2410.setAsyncCommandTimeoutMs(60000);
+
+ // Kick off the very first sequence
+ scheduleNextSequence();
+}
+
+/**
+ * @brief Arduino loop function.
+ *
+ * Empty because everything is event-driven using callbacks and ticker.
+ */
+void loop() {
+ delay(1000); // idle delay
+}
diff --git a/examples/receiveData/receiveData.ino b/examples/receiveData/receiveData.ino
index 5c7add2..a5ec46d 100644
--- a/examples/receiveData/receiveData.ino
+++ b/examples/receiveData/receiveData.ino
@@ -1,17 +1,25 @@
/**
- * Example sketch for LD2410Async library
- *
- * This sketch initializes the LD2410 radar sensor on Serial1 and
- * prints detection data to the Serial Monitor as soon as it arrives.
- *
- * Important:
- * Make sure to adjust RADAR_RX_PIN and ADAR_TX_PIN to match you actual wiring.
- */
+* @brief: Example: Receive detection data from the LD2410
+*
+* @details
+* This sketch initializes the LD2410 radar sensor on Serial1 and
+* prints detection data to the Serial Monitor as soon as it arrives.
+* This sketch demonstrates how to:
+* 1. Initialize the radar on Serial1.
+* 2. register a callback to receive detection data.
+* 3. Get a pointer to the detection data struct.
+*
+* @warning
+* Important:
+* Make sure to adjust RADAR_RX_PIN and RADAR_TX_PIN to match you actual wiring.
+*/
#include
#include "LD2410Async.h"
- // ========================= USER CONFIGURATION =========================
+
+
+// ========================= USER CONFIGURATION =========================
// UART pins for the LD2410 sensor
#define RADAR_RX_PIN 16 // ESP32 pin that receives data from the radar (radar TX)
@@ -22,21 +30,37 @@
// ======================================================================
-// Create a HardwareSerial instance (ESP32 has multiple UARTs)
+/**
+* Create a HardwareSerial instance (ESP32 has multiple UARTs) bound to UART1
+*/
HardwareSerial RadarSerial(1);
-// Create LD2410Async object bound to Serial1
+/**
+* @brief Creates LD2410Async object bound to the serial port defined in RadarSerial
+*/
LD2410Async radar(RadarSerial);
-// Callback function called whenever new detection data arrives
-void onDetectionDataReceived(LD2410Async* sender, bool presenceDetected, byte userData) {
+/*
+ * @brief Callback function called whenever new detection data arrives
+ *
+ * @details
+ * The detection data is accessed via a reference to avoid making a copy. This is more efficient and saves memory.
+ * The callback provides the presenceDetected variable for convenience, but all other detection data is available
+ * in the DetectionData struct that can be accessed using .getDetectionDataRef()
+ *
+ * @note
+ * In callback methods is is generally advised to access members of the LD2410Async instance via the sender pointer.
+ * This ensures that you are allways working with the correct instance, which is important if you have multiple LD2410Async instances.
+ * Also keep in mind that callbacks should be as short and efficient as possible to avoid blocking the background task of the library.
+ */
+void onDetectionDataReceived(LD2410Async* sender, bool presenceDetected) {
// Access detection data efficiently without making a copy
- const LD2410Async::DetectionData& data = sender->getDetectionDataRef();
+ const LD2410Types::DetectionData& data = sender->getDetectionDataRef();
Serial.println("=== Detection Data ===");
Serial.print("Target State: ");
- Serial.println(LD2410Async::targetStateToString(data.targetState));
+ Serial.println(LD2410Types::targetStateToString(data.targetState));
Serial.print("Presence detected: ");
Serial.println(data.presenceDetected ? "Yes" : "No");
@@ -59,13 +83,21 @@ void onDetectionDataReceived(LD2410Async* sender, bool presenceDetected, byte us
Serial.println("======================");
}
+/**
+* @brief Arduino setup function which initializes the radar and registers the callback
+*
+* @details
+* radar.begin() starts the background task of the LD2410Async library which automatically handles
+* incoming data and triggers callbacks. The onDetectionDataReceived callback is registered to receive detection data.
+*
+*/
void setup() {
// Initialize USB serial for debug output
Serial.begin(115200);
while (!Serial) {
; // wait for Serial Monitor
}
- Serial.println("LD2410Async example: print detection data");
+ Serial.println("LD2410Async Example: Receive Data");
// Initialize Serial1 with user-defined pins and baudrate
RadarSerial.begin(RADAR_BAUDRATE, SERIAL_8N1, RADAR_RX_PIN, RADAR_TX_PIN);
@@ -73,15 +105,22 @@ void setup() {
// Start the radar background task (parses incoming data frames)
if (radar.begin()) {
Serial.println("Radar task started successfully.");
+ // Register callback for detection updates
+ radar.onDetectionDataReceived(onDetectionDataReceived);
}
else {
- Serial.println("Radar task already running.");
+ Serial.println("ERROR! Could not start radar task.");
}
- // Register callback for detection updates
- radar.registerDetectionDataReceivedCallback(onDetectionDataReceived);
-}
+}
+/**
+* @brief Arduino loop function which does nothing
+*
+* @details
+* The LD2410Async library runs a FreeRTOS background task that automatically handles all jobs that are related to the radar sensor.
+* Therefore the main loop doesnt have to da any LD2410 related work and is free for anything else you might want to do.
+*/
void loop() {
// Nothing to do here!
// The LD2410Async library runs a FreeRTOS background task
diff --git a/examples/simplePresenceDetectionWebservice/simplePresenceDetectionWebservice.ino b/examples/simplePresenceDetectionWebservice/simplePresenceDetectionWebservice.ino
new file mode 100644
index 0000000..7fb6c4c
--- /dev/null
+++ b/examples/simplePresenceDetectionWebservice/simplePresenceDetectionWebservice.ino
@@ -0,0 +1,270 @@
+/**
+ * @brief Example: Webservice for Presence Detection with LD2410Async and ESP Async WebServer
+ *
+ * @details
+ * This example demonstrates how to use the LD2410Async library with an ESP32 to provide a web-based
+ * presence detection service. The ESP32 connects to a WiFi network, starts a web server, and serves
+ * a simple HTML page. The page connects via WebSocket to receive live presence detection data
+ * (presenceDetected, detectedDistance) from the LD2410 radar sensor.
+ *
+ *
+ * Dependencies:
+ * - ESPAsyncWebServer: https://github.com/me-no-dev/ESPAsyncWebServer
+ * - AsyncTCP: https://github.com/me-no-dev/AsyncTCP
+ * - LD2410Async library
+ *
+ * @warning
+ * Make sure to install the required libraries and adjust the configuration section below
+ * to match your hardware and network setup.
+ */
+
+#include
+#include
+#include
+#include "LD2410Async.h"
+
+ // ========================= USER CONFIGURATION =========================
+
+ /**
+ * @name UART Configuration
+ * @{
+ */
+#define RADAR_RX_PIN 16 ///< ESP32 pin that receives data from the radar (radar TX)
+#define RADAR_TX_PIN 17 ///< ESP32 pin that transmits data to the radar (radar RX)
+#define RADAR_BAUDRATE 256000 ///< UART baudrate for the radar sensor (default is 256000)
+ /** @} */
+
+ /**
+ * @name WiFi Configuration
+ * @{
+ */
+const char* WIFI_SSID = "YOUR_WIFI_SSID"; ///< WiFi SSID
+const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD"; ///< WiFi password
+
+// Set to true to use static IP, false for DHCP
+#define USE_STATIC_IP true
+
+// Static network configuration (only used if USE_STATIC_IP is true)
+IPAddress LOCAL_IP(192, 168, 1, 123); ///< ESP32 static IP address
+IPAddress GATEWAY(192, 168, 1, 1); ///< Network gateway
+IPAddress SUBNET(255, 255, 255, 0); ///< Network subnet mask
+/** @} */
+
+/**
+ * @name Webserver Configuration
+ * @{
+ */
+#define WEBSERVER_PORT 80 ///< HTTP/WebSocket server port
+ /** @} */
+
+ // ======================================================================
+
+ /**
+ * @brief HardwareSerial instance for UART1 (LD2410 sensor)
+ */
+HardwareSerial RadarSerial(1);
+
+/**
+ * @brief LD2410Async instance for radar communication
+ */
+LD2410Async radar(RadarSerial);
+
+/**
+ * @brief AsyncWebServer instance for HTTP/WebSocket
+ */
+AsyncWebServer server(WEBSERVER_PORT);
+
+/**
+ * @brief AsyncWebSocket instance for live data updates
+ */
+AsyncWebSocket ws("/ws");
+
+/**
+ * @brief Stores the latest presence detection state
+ */
+volatile bool latestPresenceDetected = false;
+
+/**
+ * @brief Stores the latest detected distance (cm)
+ */
+volatile uint16_t latestDetectedDistance = 0;
+
+/**
+ * @brief Notifies all connected WebSocket clients with the latest presence data.
+ *
+ * Sends a JSON string with the fields "presenceDetected" (bool) and "detectedDistance" (uint16_t).
+ */
+void notifyClients() {
+ String msg = "{\"presenceDetected\":";
+ msg += (latestPresenceDetected ? "true" : "false");
+ msg += ",\"detectedDistance\":";
+ msg += latestDetectedDistance;
+ msg += "}";
+ ws.textAll(msg);
+}
+
+/**
+ * @brief Callback function called whenever new detection data arrives from the radar.
+ *
+ * @param sender Pointer to the LD2410Async instance (for multi-sensor setups)
+ * @param presenceDetected True if presence is detected, false otherwise
+ */
+void onDetectionDataReceived(LD2410Async* sender, bool presenceDetected) {
+ const LD2410Types::DetectionData& data = sender->getDetectionDataRef();
+ latestPresenceDetected = data.presenceDetected;
+ latestDetectedDistance = data.detectedDistance;
+ notifyClients();
+}
+
+/**
+ * @brief Handles WebSocket events (client connect/disconnect).
+ *
+ * @param server Pointer to the AsyncWebSocket server
+ * @param client Pointer to the connected client
+ * @param type Event type (connect, disconnect, etc.)
+ * @param arg Event argument
+ * @param data Event data
+ * @param len Data length
+ */
+void onWsEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type,
+ void* arg, uint8_t* data, size_t len) {
+ if (type == WS_EVT_CONNECT) {
+ Serial.printf("WebSocket client #%u connected\n", client->id());
+ // Send initial state
+ notifyClients();
+ }
+ else if (type == WS_EVT_DISCONNECT) {
+ Serial.printf("WebSocket client #%u disconnected\n", client->id());
+ }
+}
+
+/**
+ * @brief HTML page served to clients.
+ *
+ * Connects to the WebSocket and displays live presence and distance data.
+ */
+const char index_html[] PROGMEM = R"rawliteral(
+
+
+
+
+ LD2410 Presence Detection
+
+
+
+ LD2410 Presence Detection
+ Connecting...
+
+
+
+
+)rawliteral";
+
+/**
+ * @brief Connects to WiFi using the user configuration.
+ *
+ * If USE_STATIC_IP is true, configures a static IP, subnet, and gateway.
+ * Prints connection status and IP address to Serial.
+ */
+void setupWiFi() {
+ Serial.print("Connecting to WiFi: ");
+ Serial.println(WIFI_SSID);
+ WiFi.mode(WIFI_STA);
+#if USE_STATIC_IP
+ if (!WiFi.config(LOCAL_IP, GATEWAY, SUBNET)) {
+ Serial.println("STA Failed to configure static IP");
+ }
+#endif
+ WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
+ int retries = 0;
+ while (WiFi.status() != WL_CONNECTED) {
+ delay(500);
+ Serial.print(".");
+ if (++retries > 40) { // 20 seconds timeout
+ Serial.println("\nFailed to connect to WiFi!");
+ return;
+ }
+ }
+ Serial.println("\nWiFi connected.");
+ Serial.print("IP address: ");
+ Serial.println(WiFi.localIP());
+}
+
+/**
+ * @brief Arduino setup function.
+ *
+ * Initializes serial, WiFi, radar sensor, and webserver.
+ * Registers the detection data callback and sets up the WebSocket and HTTP handlers.
+ */
+void setup() {
+ // Serial for debug output
+ Serial.begin(115200);
+ while (!Serial) { ; }
+ Serial.println("LD2410Async Web Presence Detection Example");
+
+ // Setup WiFi
+ setupWiFi();
+
+ // Start UART for radar
+ RadarSerial.begin(RADAR_BAUDRATE, SERIAL_8N1, RADAR_RX_PIN, RADAR_TX_PIN);
+
+ // Start radar background task
+ if (radar.begin()) {
+ Serial.println("Radar task started successfully.");
+ radar.onDetectionDataReceived(onDetectionDataReceived);
+ }
+ else {
+ Serial.println("ERROR! Could not start radar task.");
+ }
+
+ // WebSocket setup
+ ws.onEvent(onWsEvent);
+ server.addHandler(&ws);
+
+ // Serve HTML page at "/"
+ server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) {
+ request->send_P(200, "text/html", index_html);
+ });
+
+ // Start server
+ server.begin();
+ Serial.println("Webserver started.");
+}
+
+/**
+ * @brief Arduino loop function.
+ *
+ * No code required; all logic is handled by callbacks and background tasks.
+ */
+void loop() {
+ // Nothing to do here, everything is handled by callbacks and background tasks
+}
\ No newline at end of file
diff --git a/examples/tortureTest/tortureTest.ino b/examples/tortureTest/tortureTest.ino
new file mode 100644
index 0000000..94b5a5a
--- /dev/null
+++ b/examples/tortureTest/tortureTest.ino
@@ -0,0 +1,302 @@
+/**
+ * @file tortureRequestCommands.ino
+ * @brief
+ * LD2410Async torture test: endlessly executes random *request* commands,
+ * measures execution time, tracks stats, and prints a summary every 10 minutes
+ * or when the spacebar is pressed in the terminal window (will not work in a
+ * purely monitor window).
+ *
+ * Important!
+ * Adjust RADAR_RX_PIN and RADAR_TX_PIN to your wiring.
+ */
+
+#include
+#include
+#include "LD2410Async.h"
+
+ // ========================= USER CONFIGURATION =========================
+ // UART pins for the LD2410 sensor (ESP32 UART1 example)
+#define RADAR_RX_PIN 32 // ESP32 pin that receives data from the radar (radar TX)
+#define RADAR_TX_PIN 33 // ESP32 pin that transmits data to the radar (radar RX)
+
+// UART baudrate for the radar sensor (factory default is often 256000)
+#define RADAR_BAUDRATE 256000
+
+#define LED_PIN 2 //GPIO for internal LED (adjust if needed)
+// =====================================================================
+
+// Create a HardwareSerial instance bound to UART1
+HardwareSerial RadarSerial(1);
+
+// Create the LD2410Async instance
+LD2410Async ld2410(RadarSerial);
+
+// --------------------------- Stats ----------------------------
+//@cond Hid_this_from_the_docu
+struct CommandStats {
+ const char* name;
+ unsigned long minMs;
+ unsigned long maxMs;
+ unsigned long totalMs;
+ unsigned long runs;
+ unsigned long failures; // any non-SUCCESS
+ unsigned long timeouts;
+ unsigned long failed;
+ unsigned long canceled;
+};
+// @endcond
+
+enum RequestCommand : uint8_t {
+ REQ_GATE_PARAMS = 0,
+ REQ_FIRMWARE,
+ REQ_BT_MAC,
+ REQ_DIST_RES,
+ REQ_AUX,
+ REQ_ALL_CONFIG,
+ REQ_ALL_STATIC,
+ REQ_COUNT
+};
+
+CommandStats stats[REQ_COUNT] = {
+ { "requestGateParametersAsync", ULONG_MAX, 0, 0, 0, 0, 0, 0, 0 },
+ { "requestFirmwareAsync", ULONG_MAX, 0, 0, 0, 0, 0, 0, 0 },
+ { "requestBluetoothMacAddressAsync",ULONG_MAX, 0, 0, 0, 0, 0, 0, 0 },
+ { "requestDistanceResolutionAsync", ULONG_MAX, 0, 0, 0, 0, 0, 0, 0 },
+ { "requestAuxControlSettingsAsync", ULONG_MAX, 0, 0, 0, 0, 0, 0, 0 },
+ { "requestAllConfigSettingsAsync", ULONG_MAX, 0, 0, 0, 0, 0, 0, 0 },
+ { "requestAllStaticDataAsync", ULONG_MAX, 0, 0, 0, 0, 0, 0, 0 },
+};
+
+volatile bool commandInFlight = false;
+RequestCommand currentCmd = REQ_GATE_PARAMS;
+unsigned long cmdStartMs = 0;
+
+// We use a Ticker to retry quickly when a send is rejected (busy)
+Ticker retryTicker;
+
+//Just to delay the next command a bit
+Ticker delayNextCommandTicker;
+
+// Stats printing
+unsigned long lastStatsPrintMs = 0;
+const unsigned long STATS_PRINT_PERIOD_MS = 300000;
+
+// ----------------------- Forward decls ------------------------
+void runRandomCommand();
+void commandCallback(LD2410Async* sender, LD2410Async::AsyncCommandResult result);
+
+// ------------------------ Helpers ----------------------------
+void recordResult(RequestCommand cmd, LD2410Async::AsyncCommandResult result) {
+ const unsigned long dur = millis() - cmdStartMs;
+ CommandStats& s = stats[cmd];
+
+ if (result == LD2410Async::AsyncCommandResult::SUCCESS) {
+ if (dur < s.minMs) s.minMs = dur;
+ if (dur > s.maxMs) s.maxMs = dur;
+ s.totalMs += dur;
+ s.runs++;
+ }
+ else {
+ s.failures++;
+ switch (result) {
+ case LD2410Async::AsyncCommandResult::TIMEOUT: s.timeouts++; break;
+ case LD2410Async::AsyncCommandResult::FAILED: s.failed++; break;
+ case LD2410Async::AsyncCommandResult::CANCELED: s.canceled++; break;
+ default: break;
+ }
+ }
+}
+
+void printStats() {
+ Serial.println();
+ Serial.println("===== LD2410Async Torture Test (REQUEST COMMANDS) =====");
+
+ unsigned long totalRuns = 0;
+ unsigned long totalFailures = 0;
+ unsigned long totalTimeouts = 0;
+ unsigned long totalFailed = 0;
+ unsigned long totalCanceled = 0;
+
+ for (int i = 0; i < REQ_COUNT; i++) {
+ const CommandStats& s = stats[i];
+ const unsigned long minShown = (s.minMs == ULONG_MAX) ? 0 : s.minMs;
+ const unsigned long avg = (s.runs > 0) ? (s.totalMs / s.runs) : 0;
+
+ Serial.printf("%-32s | runs: %lu | failures: %lu", s.name, s.runs, s.failures);
+ Serial.printf(" (timeouts:%lu failed:%lu canceled:%lu)", s.timeouts, s.failed, s.canceled);
+ Serial.printf(" | min:%lums max:%lums avg:%lums\n", minShown, s.maxMs, avg);
+
+ // accumulate totals
+ totalRuns += s.runs;
+ totalFailures += s.failures;
+ totalTimeouts += s.timeouts;
+ totalFailed += s.failed;
+ totalCanceled += s.canceled;
+ }
+
+ // minutes since program start
+ unsigned long minutes = millis() / 60000;
+
+ Serial.println("-------------------------------------------------------");
+ Serial.printf("TOTALS | runs: %lu | failures: %lu",
+ totalRuns, totalFailures);
+ Serial.printf(" (timeouts:%lu failed:%lu canceled:%lu)\n",
+ totalTimeouts, totalFailed, totalCanceled);
+ Serial.printf("Uptime: %lu minute(s)\n", minutes);
+ Serial.println("=======================================================");
+}
+
+
+void scheduleRetry() {
+ // Retry very soon without blocking
+ retryTicker.once_ms(1, []() { runRandomCommand(); });
+}
+
+// --------------------- Command driver ------------------------
+bool sendCommand(RequestCommand cmd) {
+ // Start timing *only* when we are about to send.
+ digitalWrite(LED_PIN, HIGH);
+
+ cmdStartMs = millis();
+ bool accepted = false;
+
+ switch (cmd) {
+ case REQ_GATE_PARAMS:
+ accepted = ld2410.requestGateParametersAsync(commandCallback);
+ break;
+ case REQ_FIRMWARE:
+ accepted = ld2410.requestFirmwareAsync(commandCallback);
+ break;
+ case REQ_BT_MAC:
+ accepted = ld2410.requestBluetoothMacAddressAsync(commandCallback);
+ break;
+ case REQ_DIST_RES:
+ accepted = ld2410.requestDistanceResolutionAsync(commandCallback);
+ break;
+ case REQ_AUX:
+ accepted = ld2410.requestAuxControlSettingsAsync(commandCallback);
+ break;
+ case REQ_ALL_CONFIG:
+ accepted = ld2410.requestAllConfigSettingsAsync(commandCallback);
+ break;
+ case REQ_ALL_STATIC:
+ accepted = ld2410.requestAllStaticDataAsync(commandCallback);
+ break;
+ default:
+ accepted = false;
+ break;
+ }
+
+ return accepted;
+}
+
+void disableConfigModeCallback(LD2410Async* /*sender*/, LD2410Async::AsyncCommandResult result) {
+
+
+ runRandomCommand();
+}
+
+void runRandomCommand() {
+ if (commandInFlight) return; // safety
+
+ if (ld2410.isConfigModeEnabled()) {
+ //Need to diable config mode first
+ Serial.print("!");
+ if (ld2410.disableConfigModeAsync(disableConfigModeCallback)) {
+ return;
+ }
+ }
+
+
+ currentCmd = static_cast(random(REQ_COUNT));
+ commandInFlight = true;
+
+
+ if (!sendCommand(currentCmd)) {
+ // Library said "no" (likely busy) — back off and try again
+ commandInFlight = false;
+ scheduleRetry();
+ }
+}
+
+void runRandomCommandAfterDelay() {
+ switch (random(4)) {
+ case 0:
+ runRandomCommand();
+ break;
+ case 1:
+ delayNextCommandTicker.once_ms(random(10) + 1, runRandomCommand);
+ break;
+ case 2:
+ delayNextCommandTicker.once_ms((random(10) + 1) * 10, runRandomCommand);
+ break;
+ default:
+ delayNextCommandTicker.once_ms((random(30) + 1) * 100, runRandomCommand);
+ break;
+ }
+}
+
+
+void commandCallback(LD2410Async* /*sender*/, LD2410Async::AsyncCommandResult result) {
+ digitalWrite(LED_PIN, LOW);
+ if (result == LD2410Async::AsyncCommandResult::SUCCESS) {
+ Serial.print(".");
+ }
+ else {
+ Serial.print("x");
+ }
+
+ recordResult(currentCmd, result);
+ commandInFlight = false;
+
+
+ runRandomCommandAfterDelay();
+
+}
+
+// --------------------- Arduino lifecycle ---------------------
+void setup() {
+ Serial.begin(115200);
+ delay(500);
+ Serial.println("LD2410Async torture test starting...");
+
+
+ pinMode(LED_PIN, OUTPUT);
+ digitalWrite(LED_PIN, LOW);
+
+ // Setup UART to the radar
+ RadarSerial.begin(RADAR_BAUDRATE, SERIAL_8N1, RADAR_RX_PIN, RADAR_TX_PIN);
+
+ // Start LD2410Async background task (required for async operation)
+ if (!ld2410.begin()) {
+ Serial.println("ERROR: ld2410.begin() failed (already running?)");
+ }
+
+
+
+ // Kick off the first command
+ runRandomCommand();
+
+ lastStatsPrintMs = millis();
+}
+
+void loop() {
+ // Print stats every 10 minutes
+ const unsigned long now = millis();
+ if (now - lastStatsPrintMs >= STATS_PRINT_PERIOD_MS) {
+ printStats();
+ lastStatsPrintMs = now;
+ }
+
+ // Print stats immediately if user presses spacebar
+ if (Serial.available()) {
+ int c = Serial.read();
+ if (c == ' ') {
+ printStats();
+ }
+ }
+
+
+ // Nothing else to do: LD2410Async runs its own FreeRTOS task after begin()
+ // Keep loop() lean to maximize timing accuracy for callbacks.
+}
diff --git a/examples/unitTest/unitTest.ino b/examples/unitTest/unitTest.ino
new file mode 100644
index 0000000..1f3d80f
--- /dev/null
+++ b/examples/unitTest/unitTest.ino
@@ -0,0 +1,1764 @@
+/**
+* @brief Unit Test for most methods of the lib
+*
+* @details
+* This sketch tests most methods of the LD2410Async lib and prints the test rusults to the serial monitor.
+*
+* Important:
+* Dont forget to adjust RADAR_RX_PIN and RADAR_TX_PIN according to your wiring.
+*/
+
+
+
+
+#include
+#include
+
+#include "LD2410Async.h"
+
+
+
+/********************************************************************************************************************************
+** Hardware configuration
+********************************************************************************************************************************/
+// UART pins for the LD2410 sensor
+//#define RADAR_RX_PIN 16 // ESP32 pin that receives data from the radar (radar TX)
+//#define RADAR_TX_PIN 17 // ESP32 pin that transmits data to the radar (radar RX)
+
+#define RADAR_RX_PIN 32
+#define RADAR_TX_PIN 33
+
+
+// UART baudrate for the radar sensor (default is 256000)
+#define RADAR_BAUDRATE 256000
+
+/********************************************************************************************************************************
+** Variables
+********************************************************************************************************************************/
+// Create a HardwareSerial instance (ESP32 has multiple UARTs)
+HardwareSerial RadarSerial(1);
+
+// Create LD2410Async object bound to Serial1
+LD2410Async ld2410(RadarSerial);
+
+
+
+
+
+// Ticker used for delays in the test methods
+Ticker onceTicker;
+
+// Config data as found on the sensor, so we can restore it when done.
+LD2410Types::ConfigData orgConfigData;
+
+//Used to determine the duration of the tests
+unsigned long testStartMs = 0;
+//Name of the currently running tests
+String currentTestName;
+
+//Counts test outcomes
+int testSuccessCount = 0;
+int testFailCount = 0;
+
+//Number of test that is currently executed. Is used to iterate through the test sequence
+int currentTestIndex = -1;
+//Indicates whether tests are currently being executed
+bool testSequenceRunning = false;
+
+//Line length for the printed outputs
+const int LINE_LEN = 80;
+
+
+/********************************************************************************************************************************
+** Definitions and declarations for the test sequence handling
+********************************************************************************************************************************/
+typedef void (*TestAction)();
+
+/// @cond Hide_this_from_the_docu
+struct TestEntry {
+ TestAction action;
+ bool configModeAtTestEnd;
+};
+// @endcond
+//
+// Forward declarations so functions above can use them
+extern TestEntry actions[];
+extern const int NUM_ACTIONS;
+
+
+/********************************************************************************************************************************
+** Print functions
+** They just help to get nice output from the tests
+********************************************************************************************************************************/
+void printLine(char c, int len) {
+ for (int i = 0; i < len; i++) Serial.print(c);
+ Serial.println();
+}
+
+void printBigMessage(const String msg, char lineChar = '*') {
+ Serial.println();
+ printLine(lineChar, LINE_LEN);
+ printLine(lineChar, LINE_LEN);
+ Serial.print(lineChar);
+ Serial.println(lineChar);
+ Serial.print(lineChar);
+ Serial.print(lineChar);
+ Serial.print(" ");
+ Serial.println(msg);
+ Serial.print(lineChar);
+ Serial.println(lineChar);
+ printLine(lineChar, LINE_LEN);
+ printLine(lineChar, LINE_LEN);
+}
+
+/********************************************************************************************************************************
+** Generic print methods so we can pass parameters with differenrt types
+********************************************************************************************************************************/
+template
+void printOne(const T& value) {
+ Serial.print(value);
+}
+
+template
+void printOne(const T& first, const Args&... rest) {
+ Serial.print(first);
+ printOne(rest...); // recurse
+}
+
+/********************************************************************************************************************************
+** Test start
+** Must be called at the start of each test.
+** Outputs the test name, plus comment and records the starting time of the test.
+********************************************************************************************************************************/
+void testStart(String name, String comment = "") {
+ currentTestName = name;
+
+ printLine('*', LINE_LEN);
+ Serial.print("** ");
+ Serial.println(name);
+ if (comment.length() > 0) {
+ Serial.print("** ");
+ printLine('-', LINE_LEN - 3);
+ Serial.print("** ");
+ Serial.println(comment);
+ }
+ printLine('*', LINE_LEN);
+ testStartMs = millis();
+}
+
+
+/********************************************************************************************************************************
+** Test End
+** Must be called at the end of each test.
+** Outputs the test result, duration and comment.
+** Also starts the next test on success or aborts the test sequence on failure.
+********************************************************************************************************************************/
+Ticker testDelayTicker;
+
+template
+void testEnd(bool success, const Args&... commentParts) {
+
+
+ char lineChar = success ? '-' : '=';
+
+ printLine(lineChar, LINE_LEN);
+
+ Serial.print(lineChar);
+ Serial.print(lineChar);
+ Serial.print(" ");
+ Serial.print(currentTestName);
+ Serial.print(": ");
+ Serial.println(success ? "Success" : "Failed");
+
+ printLine(lineChar, LINE_LEN);
+
+ Serial.print(lineChar);
+ Serial.print(lineChar);
+ Serial.print(" Test duration (ms): ");
+ Serial.println(millis() - testStartMs);
+
+ if constexpr (sizeof...(commentParts) > 0) {
+ Serial.print(lineChar);
+ Serial.print(lineChar);
+ Serial.print(" ");
+ printOne(commentParts...);
+ Serial.println();
+ }
+ printLine(lineChar, LINE_LEN);
+ Serial.println();
+
+ if (actions[currentTestIndex].configModeAtTestEnd != ld2410.isConfigModeEnabled()) {
+ printLine(lineChar, LINE_LEN);
+ Serial.print(lineChar);
+ Serial.print(lineChar);
+ Serial.println("Failed,due to config mode failure!!!");
+ Serial.print(lineChar);
+ Serial.print(lineChar);
+ Serial.print("Config mode must be ");
+ Serial.print(actions[currentTestIndex].configModeAtTestEnd ? "enabled" : "disabled");
+ Serial.print(" after the previous test, but is ");
+ Serial.println(ld2410.isConfigModeEnabled() ? "enabled" : "disabled");
+ printLine(lineChar, LINE_LEN);
+ Serial.println();
+ success = false;
+
+ }
+
+
+ // update counters
+ if (success) {
+ testSuccessCount++;
+ // Schedule the next test after 2 second (2000 ms)
+ testDelayTicker.once_ms(2000, startNextTest);
+ }
+ else {
+ testFailCount++;
+ testSequenceRunning = false;
+ printBigMessage("TESTS FAILED", '-');
+ }
+
+}
+
+
+/********************************************************************************************************************************
+** Test print
+** Outputs intermadate states and results of the tests
+********************************************************************************************************************************/
+// Versatile testPrint
+template
+void testPrint(const Args&... args) {
+ unsigned long elapsed = millis() - testStartMs;
+ Serial.print(elapsed);
+ Serial.print("ms ");
+ printOne(args...);
+ Serial.println();
+}
+
+/********************************************************************************************************************************
+** Helper function to convert async test results into a printable string
+********************************************************************************************************************************/
+String asyncCommandResultToString(LD2410Async::AsyncCommandResult result) {
+ switch (result) {
+ case LD2410Async::AsyncCommandResult::SUCCESS: return "SUCCESS";
+ case LD2410Async::AsyncCommandResult::FAILED: return "FAILED (Command has failed)";
+ case LD2410Async::AsyncCommandResult::TIMEOUT: return "TIMEOUT (Async command has timed out)";
+ case LD2410Async::AsyncCommandResult::CANCELED: return "CANCELED (Async command has been canceled by the user)";
+ default: return "UNKNOWN (Unsupported command result, verify code of lib)";
+ }
+}
+
+/********************************************************************************************************************************
+** Helper function to initalize config data strzucts with valid values
+********************************************************************************************************************************/
+void initializeConfigDataStruct(LD2410Types::ConfigData& configData)
+{
+ configData.numberOfGates = 9; // This is a read-only value, but we set it here to avoid validation errors
+ configData.lightControl = LD2410Types::LightControl::LIGHT_ABOVE_THRESHOLD;
+ configData.outputControl = LD2410Types::OutputControl::DEFAULT_LOW_DETECTED_HIGH;
+ configData.distanceResolution = LD2410Types::DistanceResolution::RESOLUTION_75CM;
+
+ for (int i = 0; i < 9; i++) {
+ configData.distanceGateMotionSensitivity[i] = 5 + 5 * i;
+ configData.distanceGateStationarySensitivity[i] = 100 - i;
+ }
+ configData.maxMotionDistanceGate = 5;
+ configData.maxStationaryDistanceGate = 4;
+ configData.lightThreshold = 128;
+ configData.noOneTimeout = 120;
+}
+
+
+/********************************************************************************************************************************
+** Data update counting
+** The following methods are used, to monitor whether the LD2410 sends data
+********************************************************************************************************************************/
+
+//Counter variables
+int dataUpdateCounter_normalModeCount = 0;
+int dataUpdateCounter_engineeringModeCount = 0;
+
+//Resets the counter variables
+void dataUpdateCounterReset() {
+ dataUpdateCounter_normalModeCount = 0;
+ dataUpdateCounter_engineeringModeCount = 0;
+}
+
+//Callback method for data received event
+void dataUpdateCounterCallback(LD2410Async* sender, bool presenceDetected) {
+ //Output . or * depending on presence detected
+ Serial.print(presenceDetected ? "x" : ".");
+
+ //Record whether normal or engineering mode data has been received
+ const LD2410Types::DetectionData& data = ld2410.getDetectionDataRef();
+ if (data.engineeringMode) {
+ dataUpdateCounter_engineeringModeCount++;
+ }
+ else {
+ dataUpdateCounter_normalModeCount++;
+ }
+}
+
+//Start the data update counting
+void startDataUpdateCounter() {
+ dataUpdateCounterReset();
+ ld2410.onDetectionDataReceived(dataUpdateCounterCallback);
+}
+
+//Stop the data update counting
+void stopDataUpdateCounter() {
+ ld2410.onDetectionDataReceived(nullptr);
+ Serial.println();
+}
+
+/********************************************************************************************************************************
+** Begin test
+********************************************************************************************************************************/
+void beginTest() {
+
+ testStart("begin() Test", "Calling begin() is always the first thing we have to do.");
+ if (ld2410.begin()) {
+ testEnd(true, "LD2410Async task started successfully.");
+ }
+ else {
+ testEnd(false, "LD2410Async task already running.");
+ }
+}
+
+/********************************************************************************************************************************
+** Reboot test
+********************************************************************************************************************************/
+
+void rebootTestCallback(LD2410Async* sender, LD2410Async::AsyncCommandResult asyncResult) {
+ testPrint("Callback for rebootAsync() executed. Result: ", asyncCommandResultToString(asyncResult));
+
+ if (asyncResult == LD2410Async::AsyncCommandResult::SUCCESS) {
+ testEnd(true, "LD2410 has rebooted");
+ }
+ else {
+ testEnd(false, "Test has failed due to ", asyncCommandResultToString(asyncResult));
+ }
+}
+
+
+void rebootTest() {
+ testStart("rebootAsync() Test", "First real test is a reboot of the LD2410 to ensure it is in normal operation mode.");
+
+ bool ret = ld2410.rebootAsync(rebootTestCallback);
+
+ if (ret) {
+ testPrint("rebootAsync() conmpleted. Expecting callback.");
+ }
+ else {
+ testEnd(false, "rebootAsync() has returned false. This should only happen if another async command is pending pending");
+ }
+}
+
+
+
+
+/********************************************************************************************************************************
+** Normal mode data receive test
+********************************************************************************************************************************/
+
+void normalModeDataReceiveTestEnd() {
+ stopDataUpdateCounter();
+ Serial.println();
+
+ if (dataUpdateCounter_normalModeCount > 0 && dataUpdateCounter_engineeringModeCount == 0) {
+ testEnd(true, dataUpdateCounter_normalModeCount, " normal mode data updates received");
+ }
+ else if (dataUpdateCounter_normalModeCount == 0 && dataUpdateCounter_engineeringModeCount > 0) {
+ testPrint("Test failed. No normal mode data updates received, ");
+ testPrint(" but got ", dataUpdateCounter_normalModeCount, " engineering mode data updates.");
+ testPrint(" Since only engineering mode data was excpected, the test has failed");
+
+ testEnd(false, "Got only engineering mode data instead of normal mode data as expected.");
+ }
+ else if (dataUpdateCounter_normalModeCount == 0 && dataUpdateCounter_engineeringModeCount == 0) {
+ testPrint("Test failed. No data updates received ");
+ testEnd(false, "No data updates received");
+ }
+ else {
+ testPrint("Test failed. Received normal mode and engineering mode data,");
+ testPrint(" but expected only normal mode data.");
+ testEnd(false, "Received a mix of normal mode and engineering mode data, but expected only normal mode data.");
+
+ }
+}
+
+void normalModeDataReceiveTest() {
+ testStart("Normal Mode Data Receive Test", "Tests whether the sensor sends data in normal mode");
+
+ startDataUpdateCounter();
+
+ testPrint("Callback to get data updates registered with onDetectionDataReceived().");
+ testPrint("Counting and checking received data for 10 secs");
+
+ onceTicker.once_ms(10000, normalModeDataReceiveTestEnd);
+
+}
+
+
+
+/********************************************************************************************************************************
+** Enable engineering mode test
+********************************************************************************************************************************/
+
+void enableEngineeringModeCallback(LD2410Async* sender, LD2410Async::AsyncCommandResult asyncResult) {
+ testPrint("Callback for enableEngineeringModeAsync() executed. Result: ", asyncCommandResultToString(asyncResult));
+
+ if (asyncResult == LD2410Async::AsyncCommandResult::SUCCESS) {
+ testEnd(true);
+ }
+ else {
+ testEnd(false, "Test has failed due to ", asyncCommandResultToString(asyncResult));
+ }
+}
+
+
+void enableEngineeringModeTest() {
+ testStart("enableEngineeringModeAsync() Test", "Enables engineering mode, so we get more detailed detection data");
+
+ bool ret = ld2410.enableEngineeringModeAsync(enableEngineeringModeCallback);
+
+ if (ret) {
+ testPrint("enableEngineeringModeAsync() conmpleted. Expecting callback.");
+ }
+ else {
+ testEnd(false, "enableEngineeringModeAsync() has returned false. This should only happen if another async command is pending pending");
+ }
+}
+
+/********************************************************************************************************************************
+** Engineering mode data receive test
+********************************************************************************************************************************/
+
+void engineeringModeDataReceiveTestEnd() {
+ stopDataUpdateCounter();
+
+ if (dataUpdateCounter_engineeringModeCount > 0 && dataUpdateCounter_normalModeCount == 0) {
+ testEnd(true, dataUpdateCounter_engineeringModeCount, " engineering mode data updates received");
+ }
+ else if (dataUpdateCounter_engineeringModeCount == 0 && dataUpdateCounter_normalModeCount > 0) {
+ testPrint("Test failed. No engineering mode data updates received, ");
+ testPrint(" but got ", dataUpdateCounter_engineeringModeCount, " normal mode data updates.");
+ testPrint(" Since only normal mode data was excpected, the test has failed");
+
+ testEnd(false, "Got only normal mode data instead of engineering mode data as expected.");
+ }
+ else if (dataUpdateCounter_engineeringModeCount == 0 && dataUpdateCounter_engineeringModeCount == 0) {
+ testPrint("Test failed. No data updates received ");
+ testEnd(false, "No data updates received");
+ }
+ else {
+ testPrint("Test failed. Received engineering mode and normal mode data,");
+ testPrint(" but expected only engineering mode data.");
+ testEnd(false, "Received a mix of engineering mode and normal mode data, but expected only engineering mode data.");
+
+ }
+}
+
+void engineeringModeDataReceiveTest() {
+ testStart("Engineering Mode Data Receive Test", "Tests whether the sensor sends data in engineering mode");
+
+ startDataUpdateCounter();
+
+ testPrint("Callback to get data updates registered with onDetectionDataReceived().");
+ testPrint("Counting and checking received data for 10 secs");
+
+ onceTicker.once_ms(10000, engineeringModeDataReceiveTestEnd);
+
+}
+
+
+
+/********************************************************************************************************************************
+** Disable engineering mode test
+********************************************************************************************************************************/
+
+void disableEngineeringModeCallback(LD2410Async* sender, LD2410Async::AsyncCommandResult asyncResult) {
+ testPrint("Callback for disableEngineeringModeAsync() executed. Result: ", asyncCommandResultToString(asyncResult));
+
+ if (asyncResult == LD2410Async::AsyncCommandResult::SUCCESS) {
+ testEnd(true);
+ }
+ else {
+ testEnd(false, "Test has failed due to ", asyncCommandResultToString(asyncResult));
+ }
+}
+
+
+void disableEngineeringModeTest() {
+ testStart("disableEngineeringModeAsync() Test", "Disables engineering mode.");
+
+ bool ret = ld2410.disableEngineeringModeAsync(disableEngineeringModeCallback);
+
+ if (ret) {
+ testPrint("disableEngineeringModeAsync() conmpleted. Expecting callback.");
+ }
+ else {
+ testEnd(false, "disableEngineeringModeAsync() has returned false. This should only happen if another async command is pending pending");
+ }
+}
+
+/********************************************************************************************************************************
+** Enable config mode test
+********************************************************************************************************************************/
+
+void enableConfigModeTestEnd() {
+ stopDataUpdateCounter();
+ if (dataUpdateCounter_engineeringModeCount == 0 && dataUpdateCounter_engineeringModeCount == 0) {
+ testEnd(true, "Config mode has been enabled successfully");
+ }
+ else {
+ Serial.println();
+ testEnd(false, "enableConfigModeAsync() reports success, but the LD2410 still sends detection data.");
+ }
+}
+
+void enableConfigModeTestCheckForDataUpdates() {
+ testPrint("Waiting a 5 seconds, so we can be sure that we are really in config mode (checking if no data updates are sent).");
+
+ startDataUpdateCounter();
+
+ onceTicker.once_ms(5000, enableConfigModeTestEnd);
+}
+
+
+void enableConfigModeCallback(LD2410Async* sender, LD2410Async::AsyncCommandResult asyncResult) {
+ testPrint("Callback for enableConfigModeAsync() executed. Result: ", asyncCommandResultToString(asyncResult));
+
+ if (asyncResult == LD2410Async::AsyncCommandResult::SUCCESS) {
+ enableConfigModeTestCheckForDataUpdates();
+ }
+ else {
+ testEnd(false, "Test has failed due to ", asyncCommandResultToString(asyncResult));
+ }
+}
+
+
+void enableConfigModeTest() {
+ testStart("enableConfigModeAsync() Test", "Enables config mode, which is required if other commands have to be sent to the LD2410.");
+
+ bool ret = ld2410.enableConfigModeAsync(enableConfigModeCallback);
+
+ if (ret) {
+ testPrint("enableConfigModeAsync() conmpleted. Expecting callback.");
+ }
+ else {
+ testEnd(false, "enableConfigModeAsync() has returned false. This should only happen if another async command is pending pending");
+ }
+}
+
+
+/********************************************************************************************************************************
+** Config mode persistence test
+********************************************************************************************************************************/
+
+void configModePersistenceTestEnd() {
+ stopDataUpdateCounter();
+ if (dataUpdateCounter_engineeringModeCount == 0 && dataUpdateCounter_normalModeCount == 0) {
+ testEnd(true, "Config mode is still active as expected");
+ }
+ else {
+ testEnd(false, "Config mode has been disabled unexpectedly (we got data updates)");
+ }
+}
+
+void configModePersistenceTestCheckConfigMode() {
+ if (ld2410.isConfigModeEnabled()) {
+ testPrint("Config mode is still active according to isConfigModeEnabled().");
+ testPrint("Wait 5 secs to check if we receive any detection data (which would indicate that config mode has been disabled)");
+ startDataUpdateCounter();
+
+ onceTicker.once_ms(5000, configModePersistenceTestEnd);
+ }
+ else {
+ testEnd(false, "Config mode has been disabled according to isConfigModeEnabled().");
+ }
+}
+
+
+void configModePersistenceTestCallback(LD2410Async* sender, LD2410Async::AsyncCommandResult asyncResult) {
+ testPrint("Callback for requestFirmwareAsync() executed. Result: ", asyncCommandResultToString(asyncResult));
+
+ if (asyncResult == LD2410Async::AsyncCommandResult::SUCCESS) {
+ configModePersistenceTestCheckConfigMode();
+ }
+ else {
+ testEnd(false, "Test has failed due to requestFirmwareAsync() reporting ", asyncCommandResultToString(asyncResult));
+ }
+}
+
+void configModePersistenceTest() {
+ testStart("Config Mode Persistenc Test", "Checks whther config mode remains active when other commands are sent");
+
+ bool ret = ld2410.requestAllStaticDataAsync(configModePersistenceTestCallback);
+ if (ret) {
+ testPrint("requestFirmwareAsync() started. Waiting for callback (which should not change the config mode state).");
+ }
+ else {
+ testEnd(false, "requestFirmwareAsync() has returned false. This should only happen if another async command is pending pending");
+ }
+
+}
+
+/********************************************************************************************************************************
+** Disable config mode test
+********************************************************************************************************************************/
+void disableConfigModeTestEnd() {
+ stopDataUpdateCounter();
+ if (dataUpdateCounter_engineeringModeCount != 0 || dataUpdateCounter_normalModeCount != 0) {
+ Serial.println();
+ testEnd(true, "Config mode has been disabled successfully");
+ }
+ else {
+ testEnd(false, "disableConfigModeAsync() reports success, but the LD2410 does not send detection data (that typically means that config mode is active).");
+ }
+}
+
+void disableConfigModeTestCheckForDataUpdates() {
+ testPrint("Waiting a 5 seconds, so we can be sure that config mode has really been disabled (checking if data updates are sent).");
+
+ startDataUpdateCounter();
+ onceTicker.once_ms(5000, disableConfigModeTestEnd);
+}
+
+
+
+void disableConfigModeTestDisableConfigModeCallback(LD2410Async* sender, LD2410Async::AsyncCommandResult asyncResult) {
+ testPrint("Callback for disableConfigModeAsync() executed. Result: ", asyncCommandResultToString(asyncResult));
+
+ if (asyncResult == LD2410Async::AsyncCommandResult::SUCCESS) {
+ disableConfigModeTestCheckForDataUpdates();
+ }
+ else {
+ testEnd(false, "Test has failed due to ", asyncCommandResultToString(asyncResult));
+ }
+}
+
+
+void disableConfigModeTest() {
+ testStart("disableConfigModeAsync() Test", "Disables config mode. LD2410 will return to normal data detection.");
+
+ bool ret = ld2410.disableConfigModeAsync(disableConfigModeTestDisableConfigModeCallback);
+
+ if (ret) {
+ testPrint("disableConfigModeAsync() conmpleted. Expecting callback.");
+ }
+ else {
+ testEnd(false, "disableConfigModeAsync() has returned false. This should only happen if another async command is pending pending");
+ }
+}
+
+
+/********************************************************************************************************************************
+** Disable disabled config mode test
+********************************************************************************************************************************/
+
+void disableDisabledConfigModeTestDisableConfigModeCallback(LD2410Async* sender, LD2410Async::AsyncCommandResult asyncResult) {
+ testPrint("Callback for disableConfigModeAsync() executed. Result: ", asyncCommandResultToString(asyncResult));
+
+ if (asyncResult == LD2410Async::AsyncCommandResult::SUCCESS) {
+ testEnd(true);
+ }
+ else {
+ testEnd(false, "Test has failed due to ", asyncCommandResultToString(asyncResult));
+ }
+}
+
+void disableDisabledConfigModeTest() {
+ testStart("disableConfigModeAsync() Test when config mode is already disabled", "Disables config mode, but config mode is not active. The command detects that config mode is inactive and fires the callback avter a minor (1ms) delay.");
+ if (ld2410.isConfigModeEnabled()) {
+ testEnd(false, "Config mode is enabled. This does not work, when config mode is enabled");
+ return;
+ }
+ bool ret = ld2410.disableConfigModeAsync(disableDisabledConfigModeTestDisableConfigModeCallback);
+
+ if (ret) {
+ testPrint("disableConfigModeAsync() conmpleted. Expecting callback.");
+ }
+ else {
+ testEnd(false, "disableConfigModeAsync() has returned false. This should only happen if another async command is pending pending");
+ }
+}
+
+//It seems disableconfig mode still sends an ack, even when config mode is disabled. No need to test this
+//
+///********************************************************************************************************************************
+//** Force Disable disabled config mode test
+//********************************************************************************************************************************/
+//
+//void forceDisableDisabledConfigModeTestDisableDisabledConfigModeCallback(LD2410Async* sender, LD2410Async::AsyncCommandResult asyncResult) {
+// testPrint("Callback for disableConfigModeAsync() executed. Result: ", asyncCommandResultToString(asyncResult));
+//
+// if (asyncResult == LD2410Async::AsyncCommandResult::TIMEOUT) {
+// testEnd(true, "Command has timmed out as expected");
+// }
+// else {
+// testEnd(false, "Test has failed due to command returning ", asyncCommandResultToString(asyncResult));
+// }
+//}
+//void forceDisableDisabledConfigModeTest() {
+// testStart("Forcing disableConfigModeAsync() Test when config mode is already disabled", "Since config mode is already inactive, the sensor will not send an ACK on the command and therefore the command will timeout");
+// if (ld2410.isConfigModeEnabled()) {
+// testEnd(false,"Config mode is enabled. This does not work, when config mode is enabled");
+// return;
+// }
+//
+//
+// bool ret = ld2410.disableConfigModeAsync(true, forceDisableDisabledConfigModeTestDisableDisabledConfigModeCallback);
+//
+// if (ret) {
+// testPrint("disableConfigModeAsync() with force para executed. Expecting callback.");
+// }
+// else {
+// testEnd(false, "disableConfigModeAsync() has returned false. This should only happen if another async command is pending pending");
+// }
+//}
+
+/********************************************************************************************************************************
+** asyncIsBusy() Test
+********************************************************************************************************************************/
+
+int asyncIsBusyTest_Count = 0;
+
+void asyncIsBusyTestDisableConfigModeCallback(LD2410Async* sender, LD2410Async::AsyncCommandResult asyncResult) {
+ if (asyncResult == LD2410Async::AsyncCommandResult::SUCCESS) {
+ testEnd(true, "asyncIsBusy() reported true / busy ", asyncIsBusyTest_Count, " times.");
+ }
+ else {
+ testEnd(false, "Test has failed due to diableConfigModeAsync() returning ", asyncCommandResultToString(asyncResult), ". Cant complete asyncIsBusy() test");
+ }
+}
+
+void asyncIsBusyTestEnableConfigModeCallback(LD2410Async* sender, LD2410Async::AsyncCommandResult asyncResult) {
+ onceTicker.detach();
+
+ if (asyncResult == LD2410Async::AsyncCommandResult::SUCCESS) {
+ testPrint("asyncIsBusy() reported true/busy ", asyncIsBusyTest_Count, " times while waiting for the enableConfigModeAsync() command to complete.");
+
+ bool ret = ld2410.disableConfigModeAsync(asyncIsBusyTestDisableConfigModeCallback);
+
+ if (ret) {
+ testPrint("Waiting for disableConfigModeAsync() to complete test.");
+ }
+ else {
+ testEnd(false, "disableConfigModeAsync() has returned false. Cant complete asyncIsBusy() test");
+ }
+ }
+ else {
+ testEnd(false, "Test has failed due to enableConfigModeAsync() returning ", asyncCommandResultToString(asyncResult), ". Cant complete asyncIsBusy() test");
+ }
+}
+
+void asyncIsBusyTestCheckBusy() {
+ if (ld2410.asyncIsBusy()) {
+ asyncIsBusyTest_Count++;
+ }
+
+ onceTicker.once_ms(20, asyncIsBusyTestCheckBusy);
+}
+
+
+void asyncIsBusyTest() {
+ testStart("asyncIsBusyTest() Test", "Tries to enable config mode and checks for busy while waiting for the ack");
+
+ bool ret = ld2410.enableConfigModeAsync(asyncIsBusyTestEnableConfigModeCallback);
+
+ if (ret) {
+ testPrint("enableConfigModeAsync() started. Checking for asyncIsBusy() while waiting for callback.");
+ asyncIsBusyTest_Count = 0;
+ asyncIsBusyTestCheckBusy();
+ }
+ else {
+ testEnd(false, "enableConfigModeAsync() has returned false. Cant execute asyncIsBusy() test");
+ }
+
+}
+
+
+/********************************************************************************************************************************
+** asyncCancel() Test
+********************************************************************************************************************************/
+
+int asyncCancelTest_Count = 0;
+
+void asyncCancelTestDisableConfigModeCallback(LD2410Async* sender, LD2410Async::AsyncCommandResult asyncResult) {
+ if (asyncResult == LD2410Async::AsyncCommandResult::SUCCESS) {
+ testEnd(true, "asyncCancel() has successfully canceled the disableConfigModeAsync() command");
+ }
+ else {
+ testEnd(false, "Test has failed due to disableConfigModeAsync() returning ", asyncCommandResultToString(asyncResult), ". Cant complete asyncCancel() test");
+ }
+}
+
+void asyncCancelTestDiableConfigMode() {
+ bool ret = ld2410.disableConfigModeAsync(asyncCancelTestDisableConfigModeCallback);
+
+ if (ret) {
+ testPrint("Waiting for disableConfigModeAsync() callback to complete test.");
+ }
+ else {
+ testEnd(false, "disableConfigModeAsync() has returned false. Cant complete asyncCancel() test");
+ }
+}
+
+void asyncCancelTestEnableConfigModeCallback(LD2410Async* sender, LD2410Async::AsyncCommandResult asyncResult) {
+ onceTicker.detach();
+
+ if (asyncResult == LD2410Async::AsyncCommandResult::CANCELED) {
+ testPrint("Calledback reportet CANCELED as expected.");
+ testPrint("Will wait 5 secs so the disableConfigModeAsync() used for the test can complete, before we disable config mode again.");
+
+ onceTicker.once_ms(5000, asyncCancelTestDiableConfigMode);
+
+
+ }
+ else {
+ testEnd(false, "Test has failed due to enableConfigModeAsync() returning ", asyncCommandResultToString(asyncResult), ". Cant complete asyncCancel() test");
+ }
+}
+
+void asyncCancelCancelCommand() {
+ testPrint("Executing asyncCancel()");
+ ld2410.asyncCancel();
+}
+
+
+void asyncCancelTest() {
+ testStart("asyncCancelTest() Test", "Tries to enable config mode and cancels command.");
+
+ bool ret = ld2410.enableConfigModeAsync(asyncCancelTestEnableConfigModeCallback);
+
+ if (ret) {
+ testPrint("enableConfigModeAsync() started. Checking for asyncCancel() while waiting for callback.");
+ onceTicker.once_ms(20, asyncCancelCancelCommand);
+ }
+ else {
+ testEnd(false, "enableConfigModeAsync() has returned false. Cant execute asyncCancel() test");
+ }
+
+}
+
+
+
+/********************************************************************************************************************************
+** requestFirmwareAsync() Test
+********************************************************************************************************************************/
+
+void requestFirmwareAsyncTestCallback(LD2410Async* sender, LD2410Async::AsyncCommandResult asyncResult) {
+ if (asyncResult == LD2410Async::AsyncCommandResult::SUCCESS) {
+ testEnd(true, "requestFirmwareAsync() callback reports success");
+ }
+ else {
+ testEnd(false, "requestFirmwareAsyncTest has failed. Command result: ", asyncCommandResultToString(asyncResult));
+ }
+}
+
+void requestFirmwareAsyncTest() {
+ testStart("requestFirmwareAsync() Test");
+
+ bool ret = ld2410.requestFirmwareAsync(requestFirmwareAsyncTestCallback);
+
+ if (ret) {
+ testPrint("requestFirmwareAsync() started. Waiting for callback.");
+
+ }
+ else {
+ testEnd(false, "requestFirmwareAsync() has returned false. Cant execute test");
+ }
+}
+
+/********************************************************************************************************************************
+** requestBluetoothMacAddressAsync() Test
+********************************************************************************************************************************/
+
+void requestBluetoothMacAddressAsyncTestCallback(LD2410Async* sender, LD2410Async::AsyncCommandResult asyncResult) {
+ if (asyncResult == LD2410Async::AsyncCommandResult::SUCCESS) {
+ testEnd(true, "requestBluetoothMacAddressAsync() callback reports success");
+ }
+ else {
+ testEnd(false, "requestBluetoothMacAddressAsyncTest has failed. Command result: ", asyncCommandResultToString(asyncResult));
+ }
+}
+
+void requestBluetoothMacAddressAsyncTest() {
+ testStart("requestBluetoothMacAddressAsync() Test");
+
+ bool ret = ld2410.requestBluetoothMacAddressAsync(requestBluetoothMacAddressAsyncTestCallback);
+
+ if (ret) {
+ testPrint("requestBluetoothMacAddressAsync() started. Waiting for callback.");
+
+ }
+ else {
+ testEnd(false, "requestBluetoothMacAddressAsync() has returned false. Cant execute test");
+ }
+}
+
+/********************************************************************************************************************************
+** requestDistanceResolutionAsync() Test
+********************************************************************************************************************************/
+
+void requestDistanceResolutionAsyncTestCallback(LD2410Async* sender, LD2410Async::AsyncCommandResult asyncResult) {
+ if (asyncResult == LD2410Async::AsyncCommandResult::SUCCESS) {
+ const LD2410Types::ConfigData& cfg = sender->getConfigDataRef();
+ if (cfg.distanceResolution == LD2410Types::DistanceResolution::NOT_SET)
+ {
+ testEnd(false, "requestDistanceResolutionAsync() callback reports success, but the received data is invalid or no data has been received");
+ }
+ else {
+ testEnd(true, "requestDistanceResolutionAsync() callback reports success and valid data has been received");
+ }
+ }
+ else {
+ testEnd(false, "requestDistanceResolutionAsyncTest has failed. Command result: ", asyncCommandResultToString(asyncResult));
+ }
+}
+
+void requestDistanceResolutionAsyncTest() {
+ testStart("requestDistanceResolutionAsync() Test");
+ ld2410.resetConfigData();
+ bool ret = ld2410.requestDistanceResolutionAsync(requestDistanceResolutionAsyncTestCallback);
+
+ if (ret) {
+ testPrint("requestDistanceResolutionAsync() started. Waiting for callback.");
+
+ }
+ else {
+ testEnd(false, "requestDistanceResolutionAsync() has returned false. Cant execute test");
+ }
+}
+
+
+
+
+/********************************************************************************************************************************
+** Inactivity handling Test
+********************************************************************************************************************************/
+
+void inactivityHandlingTestEnd() {
+ stopDataUpdateCounter();
+
+ ld2410.setInactivityTimeoutMs(60000); //Set inactivity timeout back to 60 secs
+
+ if (dataUpdateCounter_engineeringModeCount == 0 && dataUpdateCounter_normalModeCount == 0) {
+ testEnd(false, "Inactivity test has failed. LD2410 did not go back to normal mode inactivity handling.");
+ }
+ else {
+
+ testEnd(true, "Inactivity test has passed. LD2410 has started to send data after inactivity handling.");
+ }
+}
+
+void inactivityHandlingTestEnableConfigModeCallback(LD2410Async* sender, LD2410Async::AsyncCommandResult asyncResult) {
+ testPrint("Callback for enableConfigModeAsync() executed. Result: ", asyncCommandResultToString(asyncResult));
+ if (asyncResult == LD2410Async::AsyncCommandResult::SUCCESS) {
+ testPrint("LD2410 is now in config mode. Waiting 25 secs to see whether it goes back to detection mode after inactivity timeout (will try cancel without effect first and then try exit config mode).");
+
+ startDataUpdateCounter();
+
+ onceTicker.once_ms(25000, inactivityHandlingTestEnd); //Wait 25 secs, so we are sure that the inactivity timeout of 10 secs has passed and 2 attempts to recover (cancel async and exit config mode) have been made
+ }
+ else {
+ testEnd(false, "Test has failed due enableConfigModeAsync() failure. Return value: ", asyncCommandResultToString(asyncResult));
+ }
+}
+
+void inactivityHandlingTest() {
+ testStart("Inactivity Handling Test");
+
+ ld2410.enableInactivityHandling();
+
+ ld2410.setInactivityTimeoutMs(10000); //10 secs
+
+ if (ld2410.getInactivityTimeoutMs() != 10000) {
+ testEnd(false, "Inactivity timeout could not be set correctly");
+ return;
+ }
+
+ testPrint("Inacctivity handling enabled and timeout set to 10 secs");
+ testPrint("Will activate config mode, remain sillent and wait to see if sensor goes back to datection mode automatically.");
+
+ bool ret = ld2410.enableConfigModeAsync(inactivityHandlingTestEnableConfigModeCallback);
+ if (ret) {
+ testPrint("enableConfigModeAsync() executed. Expecting callback.");
+ }
+ else {
+ testEnd(false, "enableConfigModeAsync() has returned false. This should only happen if another async command is pending pending");
+ }
+}
+
+/********************************************************************************************************************************
+** Inactivity Handling Disable Test
+********************************************************************************************************************************/
+
+
+void disableInactivityHandlingTestConfigModeDisabled(LD2410Async* sender, LD2410Async::AsyncCommandResult asyncResult) {
+ if (asyncResult == LD2410Async::AsyncCommandResult::SUCCESS) {
+ testEnd(true, "As expected inactivity handling did not kick in an revert sensor to detection mode");
+ }
+ else {
+ testEnd(false, "As expected inactivity handling did not kick in an revert sensor to detection modee, but could not execute disableConfigModeAsync() did not succedd.");
+ }
+
+ testEnd(true, "Disable inactivity handling test has passed. LD2410 has not gone back into detection mode.");
+
+}
+
+void disableInactivityHandlingTestTimeElapsed() {
+ stopDataUpdateCounter();
+ ld2410.setInactivityTimeoutMs(60000); //Set inactivity timeout back to 60 secs
+ ld2410.enableInactivityHandling(); //reenable inactivity handling
+
+ if (dataUpdateCounter_engineeringModeCount == 0 && dataUpdateCounter_normalModeCount == 0) {
+ bool ret = ld2410.disableConfigModeAsync(disableInactivityHandlingTestConfigModeDisabled);
+ if (ret) {
+ testPrint("As expected inactivity handling did not kick in an revert sensor to detection mode.");
+ }
+ else {
+ testEnd(false, "As expected inactivity handling did not kick in an revert sensor to detection mode, but could not execute disableConfigModeAsync() at end of test.");
+ }
+ }
+ else {
+ testEnd(false, "Disable inactivity handling test has failed. LD2410 did go back into detection mode.");
+ }
+}
+
+
+void disableInactivityHandlingTestCallback(LD2410Async* sender, LD2410Async::AsyncCommandResult asyncResult) {
+ testPrint("Callback for enableConfigModeAsync() executed. Result: ", asyncCommandResultToString(asyncResult));
+ if (asyncResult == LD2410Async::AsyncCommandResult::SUCCESS) {
+ testPrint("Will wait for 25 secs to see whether the LD2410 goes back to detection mode (it should not do this since inactivity handling is disabled).");
+
+ startDataUpdateCounter();
+
+ onceTicker.once_ms(25000, disableInactivityHandlingTestTimeElapsed); //Wait 25 secs, so we are sure that the inactivity timeout of 10 secs has passed
+
+
+ }
+ else {
+ testEnd(false, "Test has failed due enableConfigModeAsync() failure. Return value: ", asyncCommandResultToString(asyncResult));
+ }
+}
+
+
+void disableInactivityHandlingTest() {
+ testStart("Disable Inactivity Handling Test");
+
+ testPrint("DIsabling inactivity handling and setting a 10 secs timeout");
+ ld2410.setInactivityTimeoutMs(10000); //10 secs
+ ld2410.disableInactivityHandling();
+ if (ld2410.isInactivityHandlingEnabled()) {
+ testEnd(false, "Inactivity handling could not be disabled");
+ return;
+ }
+ bool ret = ld2410.enableConfigModeAsync(disableInactivityHandlingTestCallback);
+ if (ret) {
+ testPrint("enableConfigModeAsync() executed. Expecting callback.");
+ }
+ else {
+ testEnd(false, "enableConfigModeAsync() has returned false. This should only happen if another async command is pending pending");
+ }
+}
+
+
+/********************************************************************************************************************************
+** Config data struct validation test
+********************************************************************************************************************************/
+
+void configDataStructValidationTest() {
+ testStart("ConfigData Struct Validation Test");
+
+ testPrint("Initialize new, empty config data struct.");
+ LD2410Types::ConfigData testConfigData;
+
+ if (testConfigData.isValid()) {
+ testEnd(false, "Newly initialized config data struct is valid. This should not be the case.");
+ return;
+ }
+ else {
+ testPrint("Newly initialized config data struct is not valid as expected.");
+ }
+
+ testPrint("Initialize config data struct with valid values.");
+ initializeConfigDataStruct(testConfigData);
+
+ if (!testConfigData.isValid()) {
+ testEnd(false, "Config data struct is not valid (but has been initialized with values.");
+ return;
+ }
+ else {
+ testPrint("Config data struct is valid");
+ }
+
+
+ testConfigData.lightControl = LD2410Types::LightControl::NOT_SET;
+ if (testConfigData.isValid()) {
+ testEnd(false, "Config data struct is valid, but lightControl value is NOT_SET (which is invalid)");
+ return;
+ }
+ else {
+ testPrint("Config data struct with lightControl value NOT_SET is not valid as expected.");
+ }
+ testConfigData.lightControl = LD2410Types::LightControl::LIGHT_ABOVE_THRESHOLD;
+
+ testConfigData.outputControl = LD2410Types::OutputControl::NOT_SET;
+ if (testConfigData.isValid()) {
+ testEnd(false, "Config data struct is valid, but outputControl value is NOT_SET (which is invalid)");
+ return;
+ }
+ else {
+ testPrint("Config data struct with outputControl value NOT_SET is not valid as expected.");
+ }
+ testConfigData.outputControl = LD2410Types::OutputControl::DEFAULT_LOW_DETECTED_HIGH;
+
+ testConfigData.distanceResolution = LD2410Types::DistanceResolution::NOT_SET;
+ if (testConfigData.isValid()) {
+ testEnd(false, "Config data struct is valid, but distanceResolution value is NOT_SET (which is invalid)");
+ return;
+ }
+ else {
+ testPrint("Config data struct with distanceResolution value NOT_SET is not valid as expected.");
+ };
+ testConfigData.distanceResolution = LD2410Types::DistanceResolution::RESOLUTION_75CM;
+
+ for (int i = 0; i < 9; i++) {
+ testConfigData.distanceGateMotionSensitivity[i] = 210;
+ if (testConfigData.isValid()) {
+ testEnd(false, "distanceGateMotionSensitivity is out of range, but config data struct is reported as valid");
+ return;
+ }
+ testConfigData.distanceGateMotionSensitivity[i] = 5 + 5 * i;
+
+ testConfigData.distanceGateStationarySensitivity[i] = 200;
+ if (testConfigData.isValid()) {
+ testEnd(false, "distanceGateStationarySensitivity is out of range, but config data struct is reported as valid");
+ return;
+ }
+
+ testConfigData.distanceGateStationarySensitivity[i] = 100 - i;
+ }
+ testPrint("Checking distanceGateMotionSensitivity and distanceGateStationarySensitivity with out of range values reported invalid as excpected.");
+
+
+ testConfigData.maxMotionDistanceGate = 0;
+ if (testConfigData.isValid()) {
+ testEnd(false, "maxMotionDistanceGate is out of range, but config data struct is reported as valid");
+ return;
+ };
+ testConfigData.maxMotionDistanceGate = 5;
+ testConfigData.maxStationaryDistanceGate = 10;
+ if (testConfigData.isValid()) {
+ testEnd(false, "maxStationaryDistanceGate is out of range, but config data struct is reported as valid");
+ return;
+ };
+ testConfigData.maxStationaryDistanceGate = 4;
+
+ testEnd(true, "Config data struct validation test has passed.");
+}
+
+
+/********************************************************************************************************************************
+** requestAllConfigSettingsAsync() Test
+********************************************************************************************************************************/
+
+void requestAllConfigSettingsAsyncTestCallback(LD2410Async* sender, LD2410Async::AsyncCommandResult asyncResult) {
+ if (asyncResult == LD2410Async::AsyncCommandResult::SUCCESS) {
+ const LD2410Types::ConfigData& cfg = sender->getConfigDataRef();
+ if (!cfg.isValid())
+ {
+ testEnd(false, "requestAllConfigSettingsAsync() callback reports success, but no data has been received or data is invalid.");
+ }
+ else {
+ testEnd(true, "requestAllConfigSettingsAsync() callback reports success and data has been received");
+ }
+ }
+ else {
+ testEnd(false, "requestAllConfigSettingsAsyncTest has failed. Command result: ", asyncCommandResultToString(asyncResult));
+ }
+}
+
+void requestAllConfigSettingsAsyncTest() {
+ testStart("requestAllConfigSettingsAsync() Test");
+
+ ld2410.resetConfigData();
+ bool ret = ld2410.requestAllConfigSettingsAsync(requestAllConfigSettingsAsyncTestCallback);
+
+ if (ret) {
+ testPrint("requestAllConfigSettingsAsync() started. Waiting for callback.");
+
+ }
+ else {
+ testEnd(false, "requestAllConfigSettingsAsync() has returned false. Cant execute test");
+ }
+}
+
+/********************************************************************************************************************************
+** getConfigData() & getConfigDataRef() Test
+********************************************************************************************************************************/
+
+
+
+void getConfigDataTest() {
+ testStart("getConfigData() & getConfigDataRef() Test");
+
+ testPrint("Getting a clone of the config data struct with getConfigData()");
+ orgConfigData = ld2410.getConfigData();
+ testPrint("The cloned struct has been stored in variable orgConfigData so we can use it to restore the org data at the end of the tests");
+
+ testPrint("Getting a reference to the config data struct with getConfigDataRef()");
+ const LD2410Types::ConfigData& configDataRef = ld2410.getConfigDataRef();
+
+ testPrint("Check if the are both the same (they should be)");
+ if (!orgConfigData.equals(configDataRef)) {
+ testEnd(false, "getConfigData() test has failed. The two config data structs are not equal.");
+ return;
+ }
+ testPrint("The cloned config data struct and the referenced struct are the same as expected.");
+ testEnd(true, "getConfigData() & getConfigDataRef() test has passed.");
+
+}
+
+
+/********************************************************************************************************************************
+** Aux control settings test
+********************************************************************************************************************************/
+
+
+LD2410Types::LightControl lightControlToApply;
+LD2410Types::OutputControl outputControlToApply;
+byte lightThresholdToApply;
+
+void auxControlSettingsTestRequestCallback(LD2410Async* sender, LD2410Async::AsyncCommandResult asyncResult) {
+ if (asyncResult == LD2410Async::AsyncCommandResult::SUCCESS) {
+
+ //Get reference to the last read config data
+ const LD2410Types::ConfigData& configDataRef = ld2410.getConfigDataRef();
+
+ //Check if the read values are what we have configured
+ if (configDataRef.lightControl == lightControlToApply
+ && configDataRef.lightThreshold == lightThresholdToApply
+ && configDataRef.outputControl == outputControlToApply)
+ {
+ testPrint("requestAuxControlSettingsAsync() has returned the expected values");
+ testEnd(true, "configureAuxControlSettingsAsync() and requestAuxControlSettingsAsync() have worked as expected");
+ }
+ else {
+
+ testEnd(false, "requestAuxControlSettingsAsync() has retuned unexpected value. Either the requesyt command or the configure command did not work as expected.");
+ }
+ }
+ else {
+ testEnd(false, "requestAuxControlSettingsAsync() has failed. Command result: ", asyncCommandResultToString(asyncResult));
+ }
+}
+
+void auxControlSettingsTestConfigureCallback(LD2410Async* sender, LD2410Async::AsyncCommandResult asyncResult) {
+ if (asyncResult == LD2410Async::AsyncCommandResult::SUCCESS) {
+ testPrint("configureAuxControlSettingsAsync() has reported success. Fetch the relevant settings so we can check if they have been configured as expected.");
+
+ bool ret = ld2410.requestAuxControlSettingsAsync(auxControlSettingsTestRequestCallback);
+ if (ret) {
+ testPrint("requestAuxControlSettingsAsync() started. Waiting for callback.");
+ }
+ else {
+ testEnd(false, "requestAuxControlSettingsAsync() has returned false. Cant execute test");
+ }
+ }
+ else {
+ testEnd(false, "configureAuxControlSettingsAsync() has failed. Command result: ", asyncCommandResultToString(asyncResult));
+ }
+}
+
+void auxControlSettingsTest() {
+ testStart("configureAuxControlSettingsAsync() & requestAuxControlSettingsAsync() Test", "Tries to change the lightContol, lightThreshold and outputControl config settings und reads back the values");
+
+ //Get reference to the last read config data
+ const LD2410Types::ConfigData& configDataRef = ld2410.getConfigDataRef();
+
+ //Get changed values for all the paras of the command
+ lightControlToApply = (configDataRef.lightControl == LD2410Types::LightControl::LIGHT_ABOVE_THRESHOLD ? LD2410Types::LightControl::LIGHT_BELOW_THRESHOLD : LD2410Types::LightControl::LIGHT_ABOVE_THRESHOLD);
+ outputControlToApply = (configDataRef.outputControl == LD2410Types::OutputControl::DEFAULT_HIGH_DETECTED_LOW ? LD2410Types::OutputControl::DEFAULT_LOW_DETECTED_HIGH : LD2410Types::OutputControl::DEFAULT_HIGH_DETECTED_LOW);
+ lightThresholdToApply = configDataRef.lightThreshold != 99 ? 99 : 101;
+
+ //Call the command to configure the aux control settings
+ bool ret = ld2410.configureAuxControlSettingsAsync(lightControlToApply, lightThresholdToApply, outputControlToApply, auxControlSettingsTestConfigureCallback);
+ if (ret) {
+ testPrint("configureAuxControlSettingsAsync() started. Waiting for callback.");
+ }
+ else {
+ testEnd(false, "configureAuxControlSettingsAsync() has returned false. Cant execute test");
+ }
+}
+
+
+/********************************************************************************************************************************
+** Gate parameters test
+********************************************************************************************************************************/
+
+byte movingThresholdToApply;
+byte stationaryThresholdToApply;
+
+
+void gateParametersTestRequestGateParametersCallback(LD2410Async* sender, LD2410Async::AsyncCommandResult asyncResult) {
+ if (asyncResult == LD2410Async::AsyncCommandResult::SUCCESS) {
+
+ //Get reference to the last read config data
+ const LD2410Types::ConfigData& configDataRef = ld2410.getConfigDataRef();
+
+ //Check if the read values are what we have configured
+ if (configDataRef.distanceGateMotionSensitivity[4] == movingThresholdToApply
+ && configDataRef.distanceGateStationarySensitivity[4] == stationaryThresholdToApply)
+ {
+ testPrint("requestGateParametersAsync() has returned the expected values");
+ testEnd(true, "configureDistanceGateSensitivityAsync() and requestGateParametersAsync() have worked as expected");
+ }
+ else {
+ testEnd(false, "requestGateParametersAsync() has retuned unexpected values. Either the request command or the configure command did not work as expected.");
+ }
+ }
+ else {
+ testEnd(false, "requestGateParametersAsync() has failed. Command result: ", asyncCommandResultToString(asyncResult));
+ }
+}
+
+void gateParametersTestConfigureSingleGateCallback(LD2410Async* sender, LD2410Async::AsyncCommandResult asyncResult) {
+ if (asyncResult == LD2410Async::AsyncCommandResult::SUCCESS) {
+ testPrint("configureDistanceGateSensitivityAsync() has reported success. Fetch the relevant settings so we can check if they have been configured as expected.");
+
+ bool ret = ld2410.requestGateParametersAsync(gateParametersTestRequestGateParametersCallback);
+ if (ret) {
+ testPrint("requestGateParametersAsync() started. Waiting for callback.");
+ }
+ else {
+ testEnd(false, "requestGateParametersAsync() has returned false. Cant execute test");
+ }
+ }
+ else {
+ testEnd(false, "configureDistanceGateSensitivityAsync() has failed. Command result: ", asyncCommandResultToString(asyncResult));
+ }
+}
+
+void gateParametersTest() {
+ testStart("configureDistanceGateSensitivityAsync() & requestGateParametersAsync() Test", "Tries to change gate sensitivity parameter config settings und reads back the values");
+
+ //Get reference to the last read config data
+ const LD2410Types::ConfigData& configDataRef = ld2410.getConfigDataRef();
+
+ //Determine changed values to apply
+ movingThresholdToApply = configDataRef.distanceGateMotionSensitivity[4] != 55 ? 55 : 44;
+ stationaryThresholdToApply = configDataRef.distanceGateStationarySensitivity[4] != 66 ? 66 : 77;
+
+
+ //Call the command to configure gate parameters for gate 4 (could be any other gate as well).
+ bool ret = ld2410.configureDistanceGateSensitivityAsync(4, movingThresholdToApply, stationaryThresholdToApply, gateParametersTestConfigureSingleGateCallback);
+ if (ret) {
+ testPrint("configureDistanceGateSensitivityAsync() started. Waiting for callback.");
+ }
+ else {
+ testEnd(false, "configureDistanceGateSensitivityAsync() has returned false. Cant execute test");
+ }
+}
+
+
+/********************************************************************************************************************************
+** Max Gate test
+********************************************************************************************************************************/
+
+byte maxMovingGateToApply;;
+byte maxStationaryGateToApply;
+short nooneTimeoutToApply;
+
+
+void configureMaxGateAndNoOneTimeoutAsyncTestRequestCallback(LD2410Async* sender, LD2410Async::AsyncCommandResult asyncResult) {
+ if (asyncResult == LD2410Async::AsyncCommandResult::SUCCESS) {
+
+ //Get reference to the last read config data
+ const LD2410Types::ConfigData& configDataRef = ld2410.getConfigDataRef();
+
+ //Check if the read values are what we have configured
+ if (configDataRef.maxMotionDistanceGate == maxMovingGateToApply
+ && configDataRef.maxStationaryDistanceGate == maxStationaryGateToApply
+ && configDataRef.noOneTimeout == nooneTimeoutToApply)
+ {
+ testPrint("requestGateParametersAsync() has returned the expected values");
+ testEnd(true, "configureMaxGateAndNoOneTimeoutAsync() and requestGateParametersAsync() have worked as expected");
+ }
+ else {
+ testEnd(false, "requestGateParametersAsync() has retuned unexpected values. Either the request command or the configure command did not work as expected.");
+ }
+ }
+ else {
+ testEnd(false, "requestGateParametersAsync() has failed. Command result: ", asyncCommandResultToString(asyncResult));
+ }
+}
+
+void configureMaxGateAndNoOneTimeoutAsyncTestConfigureCallback(LD2410Async* sender, LD2410Async::AsyncCommandResult asyncResult) {
+ if (asyncResult == LD2410Async::AsyncCommandResult::SUCCESS) {
+ testPrint("configureMaxGateAndNoOneTimeoutAsync() has reported success. Fetch the relevant settings so we can check if they have been configured as expected.");
+
+ bool ret = ld2410.requestGateParametersAsync(configureMaxGateAndNoOneTimeoutAsyncTestRequestCallback);
+ if (ret) {
+ testPrint("requestGateParametersAsync() started. Waiting for callback.");
+ }
+ else {
+ testEnd(false, "requestGateParametersAsync() has returned false. Cant execute test");
+ }
+ }
+ else {
+ testEnd(false, "configureMaxGateAndNoOneTimeoutAsync() has failed. Command result: ", asyncCommandResultToString(asyncResult));
+ }
+}
+
+void configureMaxGateAndNoOneTimeoutAsyncTest() {
+ testStart("configureMaxGateAndNoOneTimeoutAsync() & requestGateParametersAsync() Test", "Tries to change max gate parameter and noone timeout config settings und reads back the values");
+
+ //Get reference to the last read config data
+ const LD2410Types::ConfigData& configDataRef = ld2410.getConfigDataRef();
+
+ //Determine changed values to apply
+ maxMovingGateToApply = configDataRef.maxMotionDistanceGate != 6 ? 6 : 7;
+ maxStationaryGateToApply = configDataRef.maxStationaryDistanceGate != 4 ? 4 : 5;
+ nooneTimeoutToApply = configDataRef.noOneTimeout != 33 ? 33 : 22;
+
+ //Call the command to configure gate parameters for gate 4 (could be any other gate as well).
+ bool ret = ld2410.configureMaxGateAndNoOneTimeoutAsync(maxMovingGateToApply, maxStationaryGateToApply, nooneTimeoutToApply, configureMaxGateAndNoOneTimeoutAsyncTestConfigureCallback);
+ if (ret) {
+ testPrint("configureMaxGateAndNoOneTimeoutAsync() started. Waiting for callback.");
+ }
+ else {
+ testEnd(false, "configureMaxGateAndNoOneTimeoutAsync() has returned false. Cant execute test");
+ }
+}
+
+
+/********************************************************************************************************************************
+** Distance resolution test
+********************************************************************************************************************************/
+
+LD2410Types::DistanceResolution distanceResolutionToApply;
+
+void distanceResolutionTestRequestCallback(LD2410Async* sender, LD2410Async::AsyncCommandResult asyncResult) {
+ if (asyncResult == LD2410Async::AsyncCommandResult::SUCCESS) {
+
+ //Get reference to the last read config data
+ const LD2410Types::ConfigData& configDataRef = ld2410.getConfigDataRef();
+
+ //Check if the read values are what we have configured
+ if (configDataRef.distanceResolution == distanceResolutionToApply)
+ {
+ testPrint("requestDistanceResolutionAsync() has returned the expected values");
+ testEnd(true, "configureDistanceResolutionAsync() and requestDistanceResolutionAsync() have worked as expected");
+ }
+ else {
+ testEnd(false, "requestDistanceResolutionAsync() has retuned unexpected values. Either the request command or the configure command did not work as expected.");
+ }
+ }
+ else {
+ testEnd(false, "requestDistanceResolutionAsync() has failed. Command result: ", asyncCommandResultToString(asyncResult));
+ }
+}
+
+void distanceResolutionTestConfigureCallback(LD2410Async* sender, LD2410Async::AsyncCommandResult asyncResult) {
+ if (asyncResult == LD2410Async::AsyncCommandResult::SUCCESS) {
+ testPrint("configureDistanceResolutionAsync() has reported success. Fetch the relevant settings so we can check if they have been configured as expected.");
+
+ bool ret = ld2410.requestDistanceResolutionAsync(distanceResolutionTestRequestCallback);
+ if (ret) {
+ testPrint("requestDistanceResolutionAsync() started. Waiting for callback.");
+ }
+ else {
+ testEnd(false, "requestDistanceResolutionAsync() has returned false. Cant execute test");
+ }
+ }
+ else {
+ testEnd(false, "configureDistanceResolutionAsync() has failed. Command result: ", asyncCommandResultToString(asyncResult));
+ }
+}
+
+void distanceResolutionTest() {
+ testStart("configureDistanceResolutionAsync() & requestDistanceResolutionAsync() Test", "Tries distance resolution settings and reads back the values");
+
+ //Get reference to the last read config data
+ const LD2410Types::ConfigData& configDataRef = ld2410.getConfigDataRef();
+
+ //Determine changed values to apply
+ distanceResolutionToApply = configDataRef.distanceResolution == LD2410Types::DistanceResolution::RESOLUTION_20CM ? LD2410Types::DistanceResolution::RESOLUTION_75CM : LD2410Types::DistanceResolution::RESOLUTION_20CM;
+
+ //Call the command to configure distance resolution
+ bool ret = ld2410.configureDistanceResolutionAsync(distanceResolutionToApply, distanceResolutionTestConfigureCallback);
+ if (ret) {
+ testPrint("configureDistanceResolutionAsync() started. Waiting for callback.");
+ }
+ else {
+ testEnd(false, "configureDistanceResolutionAsync() has returned false. Cant execute test");
+ }
+}
+
+
+/********************************************************************************************************************************
+** Configure all settings test
+********************************************************************************************************************************/
+//Also used in restore factory settings test
+LD2410Types::ConfigData configDataToApply;
+
+void configureAllSettingsTestRequestCallback(LD2410Async* sender, LD2410Async::AsyncCommandResult asyncResult) {
+ if (asyncResult == LD2410Async::AsyncCommandResult::SUCCESS) {
+
+ //Get reference to the last read config data
+ const LD2410Types::ConfigData& configDataRef = ld2410.getConfigDataRef();
+
+ //Check if the read values are what we have configured
+ if (configDataToApply.equals(configDataRef))
+ {
+ testPrint("requestAllConfigSettingsAsync() has returned the expected values");
+ testEnd(true, "configureAllConfigSettingsAsync() and requestAllConfigSettingsAsync() have worked as expected");
+ }
+ else {
+ testEnd(false, "requestAllConfigSettingsAsync() has retuned unexpected values. Either the request command or the configure command did not work as expected.");
+ }
+ }
+ else {
+ testEnd(false, "requestAllConfigSettingsAsync() has failed. Command result: ", asyncCommandResultToString(asyncResult));
+ }
+}
+
+void configureAllSettingsTestConfigureCallback(LD2410Async* sender, LD2410Async::AsyncCommandResult asyncResult) {
+ if (asyncResult == LD2410Async::AsyncCommandResult::SUCCESS) {
+ testPrint("configureAllConfigSettingsAsync() has reported success. Fetch the relevant settings so we can check if they have been configured as expected.");
+
+ bool ret = ld2410.requestAllConfigSettingsAsync(configureAllSettingsTestRequestCallback);
+ if (ret) {
+ testPrint("requestAllConfigSettingsAsync() started. Waiting for callback.");
+ }
+ else {
+ testEnd(false, "requestAllConfigSettingsAsync() has returned false. Cant execute test");
+ }
+ }
+ else {
+ testEnd(false, "configureAllConfigSettingsAsync() has failed. Command result: ", asyncCommandResultToString(asyncResult));
+ }
+}
+
+void configureAllSettingsTest() {
+ testStart("configureAllConfigSettingsAsync() & requestAllConfigSettingsAsync() Test", "Tries to change all config parameters and reads back the values");
+
+ //Get clone of the last read config data
+ configDataToApply = ld2410.getConfigData();
+
+ //Modify the config data
+ configDataToApply.distanceResolution = configDataToApply.distanceResolution == LD2410Types::DistanceResolution::RESOLUTION_20CM ? LD2410Types::DistanceResolution::RESOLUTION_75CM : LD2410Types::DistanceResolution::RESOLUTION_20CM;
+ configDataToApply.maxMotionDistanceGate = configDataToApply.maxMotionDistanceGate != 4 ? 4 : 5;
+ configDataToApply.maxStationaryDistanceGate = configDataToApply.maxStationaryDistanceGate != 6 ? 6 : 7;
+ configDataToApply.noOneTimeout = configDataToApply.noOneTimeout != 44 ? 44 : 55;
+ configDataToApply.lightControl = (configDataToApply.lightControl == LD2410Types::LightControl::LIGHT_ABOVE_THRESHOLD ? LD2410Types::LightControl::LIGHT_BELOW_THRESHOLD : LD2410Types::LightControl::LIGHT_ABOVE_THRESHOLD);
+ configDataToApply.outputControl = (configDataToApply.outputControl == LD2410Types::OutputControl::DEFAULT_HIGH_DETECTED_LOW ? LD2410Types::OutputControl::DEFAULT_LOW_DETECTED_HIGH : LD2410Types::OutputControl::DEFAULT_HIGH_DETECTED_LOW);
+ configDataToApply.lightThreshold = configDataToApply.lightThreshold != 88 ? 88 : 77;
+
+ for (size_t i = 0; i < 9; i++)
+ {
+ configDataToApply.distanceGateMotionSensitivity[i] = configDataToApply.distanceGateMotionSensitivity[i] != 10 + i ? 10 + i : 20 + i;
+ configDataToApply.distanceGateStationarySensitivity[i] = configDataToApply.distanceGateMotionSensitivity[i] != 30 + i ? 30 + i : 40 + i;
+ }
+
+ //Call the command to apply all config settings
+ bool ret = ld2410.configureAllConfigSettingsAsync(configDataToApply, false, configureAllSettingsTestConfigureCallback);
+ if (ret) {
+ testPrint("configureAllConfigSettingsAsync() started. Waiting for callback.");
+ }
+ else {
+ testEnd(false, "configureAllConfigSettingsAsync() has returned false. Cant execute test");
+ }
+}
+
+/********************************************************************************************************************************
+** Restore factory settings test
+********************************************************************************************************************************/
+
+
+
+void restoreFactorySettingsTesttRequestCallback(LD2410Async* sender, LD2410Async::AsyncCommandResult asyncResult) {
+ if (asyncResult == LD2410Async::AsyncCommandResult::SUCCESS) {
+
+ //Get reference to the last read config data
+ const LD2410Types::ConfigData& configDataRef = ld2410.getConfigDataRef();
+
+ //Check if the read values are different from what we configured earlier
+ if (!configDataToApply.equals(configDataRef))
+ {
+ testPrint("requestAllConfigSettingsAsync() has returned the expected values");
+ testEnd(true, "restoreFactorySettingsAsync() and requestAllConfigSettingsAsync() have worked as expected");
+ }
+ else {
+ testEnd(false, "requestAllConfigSettingsAsync() has returned unexpected values (previous config). Either the request command or more likely the configure command did not work as expected.");
+ }
+ }
+ else {
+ testEnd(false, "requestAllConfigSettingsAsync() has failed. Command result: ", asyncCommandResultToString(asyncResult));
+ }
+}
+
+void restoreFactorySettingsTestResetCallback(LD2410Async* sender, LD2410Async::AsyncCommandResult asyncResult) {
+ if (asyncResult == LD2410Async::AsyncCommandResult::SUCCESS) {
+ testPrint("configureAllConfigSettingsAsync() has reported success. Fetch the relevant settings so we can check if they have been configured as expected.");
+
+ bool ret = ld2410.requestAllConfigSettingsAsync(restoreFactorySettingsTesttRequestCallback);
+ if (ret) {
+ testPrint("requestAllConfigSettingsAsync() started. Waiting for callback.");
+ }
+ else {
+ testEnd(false, "requestAllConfigSettingsAsync() has returned false. Cant execute test");
+ }
+ }
+ else {
+ testEnd(false, "restoreFactorySettingsAsync() has failed. Command result: ", asyncCommandResultToString(asyncResult));
+ }
+}
+
+void restoreFactorySettingsTest() {
+ testStart("restoreFactorySettingsAsync() Test", "Restores factory settings and checks if the have changed, compared to the previous settings");
+
+
+ //Call the command to restore factory settings
+ bool ret = ld2410.restoreFactorySettingsAsync(restoreFactorySettingsTestResetCallback);
+ if (ret) {
+ testPrint("restoreFactorySettingsAsync() started. Waiting for callback.");
+ }
+ else {
+ testEnd(false, "restoreFactorySettingsAsync() has returned false. Cant execute test");
+ }
+}
+
+
+
+/********************************************************************************************************************************
+** Write back org config data
+**
+** This is not really a test. It just writes back the org config data that was found on the sensor
+** at the start of the configuration tests.
+********************************************************************************************************************************/
+void writeBackOrgConfigDataCallback(LD2410Async* sender, LD2410Async::AsyncCommandResult asyncResult) {
+ if (asyncResult == LD2410Async::AsyncCommandResult::SUCCESS) {
+ testEnd(true, "configureAllConfigSettingsAsync() has reported success. Fetch the relevant settings so we can check if they have been configured as expected.");
+ }
+ else {
+ testEnd(false, "configureAllConfigSettingsAsync() has failed. Command result: ", asyncCommandResultToString(asyncResult));
+ }
+}
+
+void writeBackOrgConfigData() {
+ testStart("Write Back Org Config Data", "Tries to write back the org config data that was read at the begin of the tests");
+
+ //Call the command to apply all config settings
+ bool ret = ld2410.configureAllConfigSettingsAsync(orgConfigData, true, writeBackOrgConfigDataCallback);
+ if (ret) {
+ testPrint("configureAllConfigSettingsAsync() started. Waiting for callback.");
+ }
+ else {
+ testEnd(false, "configureAllConfigSettingsAsync() has returned false. Cant execute test");
+ }
+}
+
+/********************************************************************************************************************************
+** Test sequence
+** Defines the sequence of the tests
+********************************************************************************************************************************/
+
+
+TestEntry actions[] = {
+ { beginTest, false },
+ { rebootTest, false },
+ { normalModeDataReceiveTest, false },
+ { enableEngineeringModeTest, false },
+ { engineeringModeDataReceiveTest, false },
+ { disableEngineeringModeTest, false },
+ { enableConfigModeTest, true },
+ { configModePersistenceTest, true },
+ { disableConfigModeTest, false },
+ { disableDisabledConfigModeTest, false },
+ //{ forceDisableDisabledConfigModeTest, false },
+ { asyncIsBusyTest, false },
+ { asyncCancelTest, false },
+ { inactivityHandlingTest, false },
+ { disableInactivityHandlingTest, false },
+
+ // Config data Tests
+ { configDataStructValidationTest, false },
+ { requestDistanceResolutionAsyncTest, false },
+
+ { requestAllConfigSettingsAsyncTest, false }, //This steps fetches all config data
+ { getConfigDataTest, false }, //This test does also save the orgConfigData
+
+ { auxControlSettingsTest, false },
+ { gateParametersTest, false },
+ { configureMaxGateAndNoOneTimeoutAsyncTest, false },
+ { distanceResolutionTest, false },
+ { configureAllSettingsTest, false },
+ { restoreFactorySettingsTest, false },
+ { writeBackOrgConfigData, false }, //Writes back the earlier saved org config data
+
+ // Static data tests
+ { requestBluetoothMacAddressAsyncTest, false },
+ { requestFirmwareAsyncTest, false }
+};
+
+const int NUM_ACTIONS = sizeof(actions) / sizeof(actions[0]);
+
+
+/********************************************************************************************************************************
+** Start next test
+** Executes the next test in the test sequence
+********************************************************************************************************************************/
+
+void startNextTest() {
+ currentTestIndex++;
+ if (currentTestIndex < NUM_ACTIONS) {
+ actions[currentTestIndex].action(); // call the function
+ }
+ else {
+ printBigMessage("ALL TESTS PASSED", '#');
+ testSequenceRunning = false;
+ }
+}
+/********************************************************************************************************************************
+** Starts the first test
+********************************************************************************************************************************/
+void startTests() {
+ currentTestIndex = -1;
+ testSequenceRunning = true;
+ startNextTest();
+};
+
+/********************************************************************************************************************************
+** Setup
+********************************************************************************************************************************/
+void setup() {
+ //Using a big output buffer, since this test sketch prints a lot of data.
+ Serial.setTxBufferSize(2048);
+ // Initialize USB serial for debug output
+ Serial.begin(115200);
+ while (!Serial) {
+ ; // wait for Serial Monitor
+ }
+
+ // Initialize Serial1 with user-defined pins and baudrate
+ RadarSerial.begin(RADAR_BAUDRATE, SERIAL_8N1, RADAR_RX_PIN, RADAR_TX_PIN);
+
+ //Delay a few ms to ensure that Serial is available
+ delay(10);
+
+ //Start the tests
+ startTests();
+
+}
+
+/********************************************************************************************************************************
+** Loop
+** Loop is empty since all test are executed in a async fashion
+********************************************************************************************************************************/
+void loop() {
+ //Nothing to do
+ delay(1000);
+}
diff --git a/gfx/LD2410.jpg b/gfx/LD2410.jpg
new file mode 100644
index 0000000..81bde63
Binary files /dev/null and b/gfx/LD2410.jpg differ
diff --git a/keyword.txt b/keyword.txt
index 4450ac5..f063e36 100644
--- a/keyword.txt
+++ b/keyword.txt
@@ -6,14 +6,17 @@
# Datatypes (KEYWORD1)
#######################################
LD2410Async KEYWORD1
+LD2410Types KEYWORD1
DetectionData KEYWORD1
ConfigData KEYWORD1
+StaticData KEYWORD1
TargetState KEYWORD1
DistanceResolution KEYWORD1
LightControl KEYWORD1
OutputControl KEYWORD1
AutoConfigStatus KEYWORD1
AsyncCommandResult KEYWORD1
+Baudrate KEYWORD1
#######################################
# Methods and Functions (KEYWORD2)
@@ -24,41 +27,56 @@ asyncIsBusy KEYWORD2
asyncCancel KEYWORD2
getDetectionData KEYWORD2
+getDetectionDataRef KEYWORD2
getConfigData KEYWORD2
+getConfigDataRef KEYWORD2
+setInactivityHandling KEYWORD2
+enableInactivityHandling KEYWORD2
+disableInactivityHandling KEYWORD2
+isInactivityHandlingEnabled KEYWORD2
+setInactivityTimeoutMs KEYWORD2
+getInactivityTimeoutMs KEYWORD2
registerDetectionDataReceivedCallback KEYWORD2
-registerConfigUpdateReceivedCallback KEYWORD2
registerConfigChangedCallback KEYWORD2
+registerConfigUpdateReceivedCallback KEYWORD2
-requestAllConfigData KEYWORD2
-requestAllStaticData KEYWORD2
-requestGateParametersAsync KEYWORD2
-requestFirmwareAsync KEYWORD2
-requestBluetoothMacAddressAsync KEYWORD2
-requestAuxControlSettingsAsync KEYWORD2
-requestDistanceResolutioncmAsync KEYWORD2
-requestAutoConfigStatusAsync KEYWORD2
+requestAllConfigSettingsAsync KEYWORD2
+requestAllStaticDataAsync KEYWORD2
+requestGateParametersAsync KEYWORD2
+requestFirmwareAsync KEYWORD2
+requestBluetoothMacAddressAsync KEYWORD2
+requestAuxControlSettingsAsync KEYWORD2
+requestDistanceResolutioncmAsync KEYWORD2
+requestAutoConfigStatusAsync KEYWORD2
-setMaxGateAndNoOneTimeoutAsync KEYWORD2
-setDistanceGateSensitivityAsync KEYWORD2
-setConfigDataAsync KEYWORD2
-setBaudRateAsync KEYWORD2
-setBluetoothpasswordAsync KEYWORD2
-setAuxControlSettingsAsync KEYWORD2
-setDistanceResolutionAsync KEYWORD2
-setDistanceResolution75cmAsync KEYWORD2
-setDistanceResolution20cmAsync KEYWORD2
-enableEngineeringModeAsync KEYWORD2
-disableEngineeringModeAsync KEYWORD2
-enableBluetoothAsync KEYWORD2
-disableBluetoothAsync KEYWORD2
-enableConfigModeAsync KEYWORD2
-disableConfigModeAsync KEYWORD2
-beginAutoConfigAsync KEYWORD2
-restoreFactorySettingsAsync KEYWORD2
-rebootAsync KEYWORD2
+configureMaxGateAndNoOneTimeoutAsync KEYWORD2
+configureDistanceGateSensitivityAsync KEYWORD2
+configureBaudRateAsync KEYWORD2
+configureBluetoothPasswordAsync KEYWORD2
+configureDefaultBluetoothPasswordAsync KEYWORD2
+configureAuxControlSettingsAsync KEYWORD2
+configureDistanceResolutionAsync KEYWORD2
+configureDistanceResolution75cmAsync KEYWORD2
+configuresDistanceResolution20cmAsync KEYWORD2
+restoreFactorySettingsAsync KEYWORD2
+rebootAsync KEYWORD2
+enableEngineeringModeAsync KEYWORD2
+disableEngineeringModeAsync KEYWORD2
+enableBluetoothAsync KEYWORD2
+disableBluetoothAsync KEYWORD2
+enableConfigModeAsync KEYWORD2
+disableConfigModeAsync KEYWORD2
+beginAutoConfigAsync KEYWORD2
+configureAllConfigSettingsAsync KEYWORD2
-setInactivityHandling KEYWORD2
+# Typen aus LD2410Types.h
+toTargetState KEYWORD2
+toDistanceResolution KEYWORD2
+toLightControl KEYWORD2
+toOutputControl KEYWORD2
+toAutoConfigStatus KEYWORD2
+targetStateToString KEYWORD2
#######################################
# Constants and Literals (LITERAL1)
@@ -66,3 +84,12 @@ setInactivityHandling KEYWORD2
ENABLE_DEBUG LITERAL1
ENABLE_DEBUG_DATA LITERAL1
ENABLE_DEBUG_CONFIG LITERAL1
+DEBUG_PRINT_MILLIS LITERAL1
+DEBUG_PRINT LITERAL1
+DEBUG_PRINTLN LITERAL1
+DEBUG_PRINTBUF LITERAL1
+DEBUG_PRINT_DATA LITERAL1
+DEBUG_PRINTLN_DATA LITERAL1
+DEBUG_PRINTBUF_DATA LITERAL1
+LD2410_Buffer_Size LITERAL1
+LD2410ASYNC_DEBUG_LEVEL LITERAL1
\ No newline at end of file
diff --git a/library.properties b/library.properties
index 21844a1..56a88a9 100644
--- a/library.properties
+++ b/library.properties
@@ -1,5 +1,5 @@
name=LD2410Async
-version=0.9.1
+version=0.9.3
author=Lizard King
maintainer=Lizard King
sentence=Asynchronous driver for the Hi-Link LD2410 human presence radar sensor.
diff --git a/src/LD2410Async.cpp b/src/LD2410Async.cpp
index 6d38f13..e8c2d12 100644
--- a/src/LD2410Async.cpp
+++ b/src/LD2410Async.cpp
@@ -1,5 +1,8 @@
+#include "Arduino.h"
+#include "Ticker.h"
#include "LD2410Async.h"
#include "LD2410Defs.h"
+#include "LD2410Types.h"
#include "LD2410CommandBuilder.h"
@@ -9,15 +12,13 @@
******************************************************************************************/
/**
-* @brief Checks whther the last 4 bytes of a buffer match the value supplied in pattern
+* @brief Checks whether the last 4 bytes of a buffer match the value supplied in pattern
*/
bool bufferEndsWith(const byte* buffer, int iMax, const byte* pattern)
{
for (int j = 3; j >= 0; j--)
{
- //if (--iMax < 0) {
- // iMax = 3; //This is not clean. The assigned value should be the length of the buffer
- //}
+ --iMax;
if (buffer[iMax] != pattern[j]) {
return false;
}
@@ -35,15 +36,7 @@ String byte2hex(byte b, bool addZero = true)
}
-void printBuf(const byte* buf, byte size)
-{
- for (byte i = 0; i < size; i++)
- {
- Serial.print(byte2hex(buf[i]));
- Serial.print(' ');
- }
- Serial.println();
-}
+
/******************************************************************************************
* Read frame methods
@@ -52,33 +45,37 @@ bool LD2410Async::readFramePayloadSize(byte b, ReadFrameState nextReadFrameState
receiveBuffer[receiveBufferIndex++] = b;
if (receiveBufferIndex >= 2) {
payloadSize = receiveBuffer[0] | (receiveBuffer[1] << 8);
- if (payloadSize <= 0 || payloadSize > sizeof(receiveBuffer) - 4) {
- //Serial.print("Invalid payload size read: ");
- //printBuf(receiveBuffer, sizeof(receiveBuffer));
-
- //Invalid payload size, wait for header.
- readFrameState = ReadFrameState::WAITING_FOR_HEADER;
- }
- else {
-
- receiveBufferIndex = 0;
- readFrameState = nextReadFrameState;
- }
+ if (payloadSize <= 0 || payloadSize > sizeof(receiveBuffer) - 4) {
+ // Invalid payload size, wait for header.
+ DEBUG_PRINT("Invalid payload size read: ");
+ DEBUG_PRINTLN(payloadSize);
+
+ readFrameState = ReadFrameState::WAITING_FOR_HEADER;
+ }
+ else {
+ receiveBufferIndex = 0;
+ readFrameState = nextReadFrameState;
+ }
return true;
}
return false;
}
-LD2410Async::FrameReadResponse LD2410Async::readFramePayload(byte b, const byte* tailPattern, LD2410Async::FrameReadResponse succesResponseType) {
+LD2410Async::FrameReadResponse LD2410Async::readFramePayload(byte b, const byte* tailPattern, LD2410Async::FrameReadResponse successResponseType) {
receiveBuffer[receiveBufferIndex++] = b;
- //Add 4 for the tail bytes
+ // Add 4 for the tail bytes
if (receiveBufferIndex >= payloadSize + 4) {
readFrameState = ReadFrameState::WAITING_FOR_HEADER;
- if (bufferEndsWith(receiveBuffer, receiveBufferIndex, tailPattern)) {
- return succesResponseType;
+ if (bufferEndsWith(receiveBuffer, receiveBufferIndex, tailPattern)) {
+ return successResponseType;
+ }
+ else {
+ DEBUG_PRINTLN(" Invalid frame end: ");
+ DEBUG_PRINTBUF(receiveBuffer, receiveBufferIndex);
+
}
}
@@ -95,56 +92,70 @@ LD2410Async::FrameReadResponse LD2410Async::readFrame()
if (b == LD2410Defs::headData[0]) {
readFrameHeaderIndex = 1;
readFrameState = DATA_HEADER;
+
}
else if (b == LD2410Defs::headConfig[0]) {
readFrameHeaderIndex = 1;
readFrameState = ACK_HEADER;
+
}
break;
case DATA_HEADER:
if (b == LD2410Defs::headData[readFrameHeaderIndex]) {
readFrameHeaderIndex++;
+
if (readFrameHeaderIndex == sizeof(LD2410Defs::headData)) {
+ receiveBufferIndex = 0;
readFrameState = READ_DATA_SIZE;
}
}
else if (b == LD2410Defs::headData[0]) {
// fallback: this byte might be the start of a new header
+
readFrameHeaderIndex = 1;
+
// stay in DATA_HEADER
}
else if (b == LD2410Defs::headConfig[0]) {
// possible start of config header
readFrameHeaderIndex = 1;
readFrameState = ACK_HEADER;
+
}
else {
// not a header at all
readFrameState = WAITING_FOR_HEADER;
readFrameHeaderIndex = 0;
+
}
break;
case ACK_HEADER:
if (b == LD2410Defs::headConfig[readFrameHeaderIndex]) {
readFrameHeaderIndex++;
+
if (readFrameHeaderIndex == sizeof(LD2410Defs::headConfig)) {
+ receiveBufferIndex = 0;
readFrameState = READ_ACK_SIZE;
}
}
else if (b == LD2410Defs::headConfig[0]) {
readFrameHeaderIndex = 1;
// stay in ACK_HEADER
+
}
else if (b == LD2410Defs::headData[0]) {
// maybe start of data header
readFrameHeaderIndex = 1;
readFrameState = DATA_HEADER;
+
}
else {
readFrameState = WAITING_FOR_HEADER;
readFrameHeaderIndex = 0;
+
+
}
break;
@@ -176,19 +187,15 @@ LD2410Async::FrameReadResponse LD2410Async::readFrame()
/******************************************************************************************
* Generic Callbacks
******************************************************************************************/
-void LD2410Async::registerDetectionDataReceivedCallback(DetectionDataCallback callback, byte userData) {
+void LD2410Async::onDetectionDataReceived(DetectionDataCallback callback) {
detectionDataCallback = callback;
- detectionDataCallbackUserData = userData;
}
-void LD2410Async::registerConfigUpdateReceivedCallback(GenericCallback callback, byte userData) {
-
- configUpdateReceivedReceivedCallbackUserData = userData;
+void LD2410Async::onConfigDataReceived(GenericCallback callback) {
configUpdateReceivedReceivedCallback = callback;
}
-void LD2410Async::registerConfigChangedCallback(GenericCallback callback, byte userData) {
- configChangedCallbackUserData = userData;
+void LD2410Async::onConfigChanged(GenericCallback callback) {
configChangedCallback = callback;
}
@@ -197,7 +204,7 @@ void LD2410Async::registerConfigChangedCallback(GenericCallback callback, byte u
void LD2410Async::executeConfigUpdateReceivedCallback() {
if (configUpdateReceivedReceivedCallback) {
- configUpdateReceivedReceivedCallback(this, configUpdateReceivedReceivedCallbackUserData);
+ configUpdateReceivedReceivedCallback(this);
}
}
@@ -205,7 +212,7 @@ void LD2410Async::executeConfigUpdateReceivedCallback() {
void LD2410Async::executeConfigChangedCallback() {
if (configChangedCallback) {
- configChangedCallback(this, configChangedCallbackUserData);
+ configChangedCallback(this);
}
}
/******************************************************************************************
@@ -225,7 +232,7 @@ bool LD2410Async::processAck()
DEBUG_PRINT_MILLIS;
DEBUG_PRINT("FAIL for command: ");
DEBUG_PRINTLN(byte2hex(command));
- executeAsyncCommandCallback(command, AsyncCommandResult::FAILED);
+ sendCommandAsyncExecuteCallback(command, LD2410Async::AsyncCommandResult::FAILED);
return false;
};
@@ -234,8 +241,8 @@ bool LD2410Async::processAck()
{
case LD2410Defs::configEnableCommand: // entered config mode
configModeEnabled = true;
- protocolVersion = receiveBuffer[4] | (receiveBuffer[5] << 8);
- bufferSize = receiveBuffer[6] | (receiveBuffer[7] << 8);
+ staticData.protocolVersion = receiveBuffer[4] | (receiveBuffer[5] << 8);
+ staticData.bufferSize = receiveBuffer[6] | (receiveBuffer[7] << 8);
DEBUG_PRINT_MILLIS;
DEBUG_PRINTLN("ACK for config mode enable received");
break;
@@ -278,7 +285,7 @@ bool LD2410Async::processAck()
DEBUG_PRINTLN("ACK for bluetoothSettingsCommand received");
break;
case LD2410Defs::getBluetoothPermissionsCommand:
- //This command is only relevant for bluetooth connection, but the sensor might send an ack for it at some point.
+ // This command is only relevant for a Bluetooth connection, but the sensor might send an ACK for it at some point.
DEBUG_PRINT_MILLIS;
DEBUG_PRINTLN("ACK for getBluetoothPermissionsCommand received");
break;
@@ -292,7 +299,7 @@ bool LD2410Async::processAck()
executeConfigChangedCallback();
break;
case LD2410Defs::requestDistanceResolutionCommand:
- configData.distanceResolution = toDistanceResolution(receiveBuffer[4]);
+ configData.distanceResolution = LD2410Types::toDistanceResolution(receiveBuffer[4]);
DEBUG_PRINT_MILLIS;
DEBUG_PRINTLN("ACK for requestDistanceResolutionCommand received");
executeConfigUpdateReceivedCallback();
@@ -304,29 +311,34 @@ bool LD2410Async::processAck()
break;
case LD2410Defs::requestMacAddressCommand:
for (int i = 0; i < 6; i++) {
- mac[i] = receiveBuffer[i + 4];
- };
- macString = byte2hex(mac[0])
- + ":" + byte2hex(mac[1])
- + ":" + byte2hex(mac[2])
- + ":" + byte2hex(mac[3])
- + ":" + byte2hex(mac[4])
- + ":" + byte2hex(mac[5]);
+ staticData.bluetoothMac[i] = receiveBuffer[i + 4];
+ }
+
+ // Format MAC as "AA:BB:CC:DD:EE:FF"
+ snprintf(staticData.bluetoothMacText, sizeof(staticData.bluetoothMacText),
+ "%02X:%02X:%02X:%02X:%02X:%02X",
+ staticData.bluetoothMac[0], staticData.bluetoothMac[1], staticData.bluetoothMac[2],
+ staticData.bluetoothMac[3], staticData.bluetoothMac[4], staticData.bluetoothMac[5]);
DEBUG_PRINT_MILLIS;
DEBUG_PRINTLN("ACK for requestBluetoothMacAddressAsyncCommand received");
break;
case LD2410Defs::requestFirmwareCommand:
- firmware = byte2hex(receiveBuffer[7], false)
- + "." + byte2hex(receiveBuffer[6])
- + "." + byte2hex(receiveBuffer[11]) + byte2hex(receiveBuffer[10]) + byte2hex(receiveBuffer[9]) + byte2hex(receiveBuffer[8]);
+ snprintf(staticData.firmwareText, sizeof(staticData.firmwareText),
+ "%X.%02X.%02X%02X%02X%02X",
+ receiveBuffer[7], // major (no leading zero)
+ receiveBuffer[6], // minor (keep 2 digits)
+ receiveBuffer[11],
+ receiveBuffer[10],
+ receiveBuffer[9],
+ receiveBuffer[8]);
DEBUG_PRINT_MILLIS;
DEBUG_PRINTLN("ACK for requestFirmwareAsyncCommand received");
break;
case LD2410Defs::requestAuxControlSettingsCommand:
- configData.lightControl = toLightControl(receiveBuffer[4]);
+ configData.lightControl = LD2410Types::toLightControl(receiveBuffer[4]);
configData.lightThreshold = receiveBuffer[5];
- configData.outputControl = toOutputControl(receiveBuffer[6]);
+ configData.outputControl = LD2410Types::toOutputControl(receiveBuffer[6]);
DEBUG_PRINT_MILLIS;
DEBUG_PRINTLN("ACK for requestAuxControlSettingsCommand received");
executeConfigUpdateReceivedCallback();
@@ -336,7 +348,7 @@ bool LD2410Async::processAck()
DEBUG_PRINTLN("ACK for beginAutoConfigCommand received");
break;
case LD2410Defs::requestAutoConfigStatusCommand:
- autoConfigStatus = toAutoConfigStatus(receiveBuffer[4]);
+ autoConfigStatus = LD2410Types::toAutoConfigStatus(receiveBuffer[4]);
DEBUG_PRINT_MILLIS;
DEBUG_PRINTLN("ACK for requestAutoConfigStatusCommand received");
break;
@@ -344,6 +356,7 @@ bool LD2410Async::processAck()
configData.numberOfGates = receiveBuffer[5];
configData.maxMotionDistanceGate = receiveBuffer[6];
configData.maxStationaryDistanceGate = receiveBuffer[7];
+ // Need to check if we should really do this or whether using a fixed number would be better.
for (byte i = 0; i <= configData.numberOfGates; i++)
configData.distanceGateMotionSensitivity[i] = receiveBuffer[8 + i];
for (byte i = 0; i <= configData.numberOfGates; i++)
@@ -366,7 +379,7 @@ bool LD2410Async::processAck()
};
if (command != 0) {
- executeAsyncCommandCallback(command, AsyncCommandResult::SUCCESS);
+ sendCommandAsyncExecuteCallback(command, LD2410Async::AsyncCommandResult::SUCCESS);
}
@@ -385,22 +398,22 @@ bool LD2410Async::processData()
configModeEnabled = false;
detectionData.timestamp = millis();
- //Basic data
- detectionData.targetState = toTargetState(receiveBuffer[2] & 7);
+ // Basic data
+ detectionData.targetState = LD2410Types::toTargetState(receiveBuffer[2] & 7);
switch (detectionData.targetState) {
- case TargetState::MOVING_TARGET:
+ case LD2410Types::TargetState::MOVING_TARGET:
detectionData.presenceDetected = true;
detectionData.movingPresenceDetected = true;
detectionData.stationaryPresenceDetected = false;
break;
- case TargetState::STATIONARY_TARGET:
+ case LD2410Types::TargetState::STATIONARY_TARGET:
detectionData.presenceDetected = true;
detectionData.movingPresenceDetected = false;
detectionData.stationaryPresenceDetected = true;
break;
- case TargetState::MOVING_AND_STATIONARY_TARGET:
+ case LD2410Types::TargetState::MOVING_AND_STATIONARY_TARGET:
detectionData.presenceDetected = true;
detectionData.movingPresenceDetected = true;
detectionData.stationaryPresenceDetected = true;
@@ -449,8 +462,12 @@ bool LD2410Async::processData()
detectionData.outPinStatus = false;
}
+ if (rebootAsyncPending) {
+ rebootAsyncFinialize(LD2410Async::AsyncCommandResult::SUCCESS);
+ }
+
if (detectionDataCallback != nullptr) {
- detectionDataCallback(this, detectionData.presenceDetected, detectionDataCallbackUserData);
+ detectionDataCallback(this, detectionData.presenceDetected);
};
DEBUG_PRINTLN_DATA("DATA received");
return true;
@@ -476,34 +493,29 @@ void LD2410Async::sendCommand(const byte* command) {
sensor->write(command, size);
sensor->write(LD2410Defs::tailConfig, 4);
- heartbeat();
}
/**********************************************************************************
* Send async command methods
***********************************************************************************/
-void LD2410Async::executeAsyncCommandCallback(byte commandCode, AsyncCommandResult result) {
- if (asyncCommandCallback != nullptr && asyncCommandCommandCode == commandCode) {
+void LD2410Async::sendCommandAsyncExecuteCallback(byte commandCode, LD2410Async::AsyncCommandResult result) {
+ if (sendCommandAsyncCommandPending && sendCommandAsyncCommandCode == commandCode) {
DEBUG_PRINT_MILLIS;
DEBUG_PRINT("Async command duration ms: ");
- DEBUG_PRINTLN(millis() - asyncCommandStartMs);
+ DEBUG_PRINTLN(millis() - sendCommandAsyncStartMs);
- //Just to be sure that no other task changes the callback data or registers a callback before the callback has been executed
+ // Just to be sure that no other task changes the callback data or registers a callback before the callback has been executed
vTaskSuspendAll();
- AsyncCommandCallback cb = asyncCommandCallback;
- byte userData = asyncCommandCallbackUserData;
- asyncCommandCallback = nullptr;
- asyncCommandCallbackUserData = 0;
- asyncCommandStartMs = 0;
- asyncCommandCommandCode = 0;
+ AsyncCommandCallback cb = sendCommandAsyncCallback;
+ sendCommandAsyncCallback = nullptr;
+ sendCommandAsyncStartMs = 0;
+ sendCommandAsyncCommandCode = 0;
+ sendCommandAsyncCommandPending = false;
xTaskResumeAll();
-
-
-
if (cb != nullptr) {
- cb(this, result, userData);
+ cb(this, result);
}
}
}
@@ -512,46 +524,70 @@ void LD2410Async::executeAsyncCommandCallback(byte commandCode, AsyncCommandResu
void LD2410Async::asyncCancel() {
- executeAsyncCommandCallback(asyncCommandCommandCode, AsyncCommandResult::CANCELED);
+ // Will also trigger the callback on command sequences
+ sendCommandAsyncExecuteCallback(sendCommandAsyncCommandCode, LD2410Async::AsyncCommandResult::CANCELED);
+
+ // Just in case reboot is still waiting
+ // since there might not be an active command or command sequence while we wait for normal operation.
+ rebootAsyncFinialize(LD2410Async::AsyncCommandResult::CANCELED);
}
-void LD2410Async::handleAsyncCommandCallbackTimeout() {
+void LD2410Async::sendCommandAsyncHandleTimeout() {
- if (asyncCommandCallback != nullptr && asyncCommandStartMs != 0) {
- if (millis() - asyncCommandStartMs > asyncCommandTimeoutMs) {
+ if (sendCommandAsyncCommandPending && sendCommandAsyncStartMs != 0) {
+ unsigned long elapsedTime = millis() - sendCommandAsyncStartMs;
+ if (elapsedTime > asyncCommandTimeoutMs) {
DEBUG_PRINT_MILLIS;
- DEBUG_PRINT("Command timeout detected. Start time ms is: ");
- DEBUG_PRINT(asyncCommandStartMs);
- DEBUG_PRINTLN(". Execute callback with timeout result.");
- executeAsyncCommandCallback(asyncCommandCommandCode, AsyncCommandResult::TIMEOUT);
+ DEBUG_PRINT("Command timeout detected. Elapsed time: ");
+ DEBUG_PRINT(elapsedTime);
+ DEBUG_PRINTLN("ms.");
+
+ if (sendCommandAsyncRetriesLeft > 0) {
+ DEBUG_PRINT("Retries left: ");
+ DEBUG_PRINTLN(sendCommandAsyncRetriesLeft);
+ sendCommandAsyncRetriesLeft--;
+
+ sendCommandAsyncStartMs = millis();
+ sendCommand(sendCommandAsyncCommandBuffer);
+ }
+ else {
+
+ DEBUG_PRINTLN("Execute callback with TIMEOUT result.");
+ sendCommandAsyncExecuteCallback(sendCommandAsyncCommandCode, LD2410Async::AsyncCommandResult::TIMEOUT);
+ }
}
}
}
+void LD2410Async::sendCommandAsyncStoreDataForCallback(const byte* command, byte retries, AsyncCommandCallback callback) {
+ sendCommandAsyncCommandPending = true;
+ sendCommandAsyncCallback = callback;
+ sendCommandAsyncCommandCode = command[2];
+ sendCommandAsyncRetriesLeft = retries;
+ if (retries > 0) { // No need to copy the data if we are not going to retry anyway
+ memcpy(sendCommandAsyncCommandBuffer, command, command[0] + 2);
+ }
+}
-
-bool LD2410Async::sendCommandAsync(const byte* command, AsyncCommandCallback callback, byte userData)
+bool LD2410Async::sendCommandAsync(const byte* command, byte retries, AsyncCommandCallback callback)
{
vTaskSuspendAll();
- //Dont check with asyncIsBusy() since this would also checks if sendConfigCommandAsync has started a command which is using this method and asyncIsBusy would therefore return true which would block the command.
- if (asyncCommandCallback == nullptr) {
-
+ // Don't check with asyncIsBusy() since this would also check if a sequence or setConfigDataAsync is active
+ if (!sendCommandAsyncCommandPending) {
- //Register data for callback
- asyncCommandCallback = callback;
- asyncCommandCallbackUserData = userData;
- asyncCommandStartMs = millis();
- asyncCommandCommandCode = command[2];
+ // Register data for callback
+ sendCommandAsyncStoreDataForCallback(command, retries, callback);
xTaskResumeAll();
+ sendCommandAsyncStartMs = millis();
sendCommand(command);
DEBUG_PRINT_MILLIS;
DEBUG_PRINT("Async command ");
- DEBUG_PRINT(byte2hex(command[2]));
+ DEBUG_PRINT(byte2hex(sendCommandAsyncCommandCode));
DEBUG_PRINTLN(" sent");
return true;
@@ -559,7 +595,7 @@ bool LD2410Async::sendCommandAsync(const byte* command, AsyncCommandCallback cal
else {
xTaskResumeAll();
DEBUG_PRINT_MILLIS;
- DEBUG_PRINT("Error! Did not send async command ");
+ DEBUG_PRINT("Error! Async command is pending. Did not send async command ");
DEBUG_PRINTLN(byte2hex(command[2]));
return false;
@@ -572,7 +608,7 @@ bool LD2410Async::sendCommandAsync(const byte* command, AsyncCommandCallback cal
***********************************************************************************/
bool LD2410Async::asyncIsBusy() {
- return asyncCommandCallback != nullptr || sendAsyncSequenceCallback != nullptr;
+ return sendCommandAsyncCommandPending || executeCommandSequencePending || configureAllConfigSettingsAsyncConfigActive || rebootAsyncPending;
}
/**********************************************************************************
@@ -580,8 +616,9 @@ bool LD2410Async::asyncIsBusy() {
***********************************************************************************/
-bool LD2410Async::sendConfigCommandAsync(const byte* command, AsyncCommandCallback callback, byte userData) {
- if (asyncIsBusy()) return false;
+bool LD2410Async::sendConfigCommandAsync(const byte* command, AsyncCommandCallback callback) {
+ // Don't check with asyncIsBusy() since this would also check if a sequence or setConfigDataAsync is active
+ if (sendCommandAsyncCommandPending || executeCommandSequencePending) return false;
// Reset sequence buffer
if (!resetCommandSequence()) return false;;
@@ -590,138 +627,141 @@ bool LD2410Async::sendConfigCommandAsync(const byte* command, AsyncCommandCallba
if (!addCommandToSequence(command)) return false;
// Execute as a sequence (with just one command)
- return sendCommandSequenceAsync(callback, userData);
+ return executeCommandSequenceAsync(callback);
}
/**********************************************************************************
* Async command sequence methods
***********************************************************************************/
-void LD2410Async::executeAsyncSequenceCallback(AsyncCommandResult result) {
- if (sendAsyncSequenceCallback != nullptr) {
+void LD2410Async::executeCommandSequenceAsyncExecuteCallback(LD2410Async::AsyncCommandResult result) {
+ if (executeCommandSequencePending) {
DEBUG_PRINT_MILLIS;
DEBUG_PRINT("Command sequence duration ms: ");
- DEBUG_PRINTLN(millis() - sendAsyncSequenceStartMs);
+ DEBUG_PRINTLN(millis() - executeCommandSequenceStartMs);
vTaskSuspendAll();
- AsyncCommandCallback cb = sendAsyncSequenceCallback;
- byte userData = sendAsyncSequenceUserData;
- sendAsyncSequenceCallback = nullptr;
- sendAsyncSequenceUserData = 0;
+ AsyncCommandCallback cb = executeCommandSequenceCallback;
+ executeCommandSequenceCallback = nullptr;
+ executeCommandSequencePending = false;
xTaskResumeAll();
if (cb != nullptr) {
- cb(this, result, userData);
+ cb(this, result);
}
}
}
// Callback after disabling config mode at the end of sequence
-void LD2410Async::sendCommandSequenceAsyncDisableConfigModeCallback(LD2410Async* sender, AsyncCommandResult result, byte userData) {
- // Ignore disable result, sequence result is determined by command execution
- AsyncCommandResult sequenceResult = static_cast(userData);
- sender->executeAsyncSequenceCallback(sequenceResult);
+void LD2410Async::executeCommandSequenceAsyncDisableConfigModeCallback(LD2410Async* sender, LD2410Async::AsyncCommandResult result) {
+
+ LD2410Async::AsyncCommandResult sequenceResult = sender->executeCommandSequenceResultToReport;
+
+ if (result != LD2410Async::AsyncCommandResult::SUCCESS) {
+ DEBUG_PRINT_MILLIS;
+ DEBUG_PRINTLN("Warning: Disabling config mode after command sequence failed. Result: ");
+ DEBUG_PRINTLN((byte)result);
+ sequenceResult = result; // report disable failure if it happened
+ }
+ sender->executeCommandSequenceAsyncExecuteCallback(sequenceResult);
+}
+
+void LD2410Async::executeCommandSequenceAsyncFinalize(LD2410Async::AsyncCommandResult result) {
+ if (!executeCommandSequenceInitialConfigModeState) {
+ executeCommandSequenceResultToReport = result;
+ if (!disableConfigModeInternalAsync(executeCommandSequenceAsyncDisableConfigModeCallback)) {
+ executeCommandSequenceAsyncExecuteCallback(LD2410Async::AsyncCommandResult::FAILED);
+ }
+ }
+ else {
+ executeCommandSequenceAsyncExecuteCallback(result);
+ }
}
// Called when a single command in the sequence completes
-void LD2410Async::sendCommandSequenceAsyncCommandCallback(LD2410Async* sender, AsyncCommandResult result, byte userData) {
- if (result != AsyncCommandResult::SUCCESS) {
+void LD2410Async::executeCommandSequenceAsyncCommandCallback(LD2410Async* sender, LD2410Async::AsyncCommandResult result) {
+ if (result != LD2410Async::AsyncCommandResult::SUCCESS) {
// Abort sequence if a command fails
- if (!sender->sendAsyncSequenceInitialConfigModeState) {
- sender->disableConfigModeAsync(sendCommandSequenceAsyncDisableConfigModeCallback, static_cast(result));
- }
- else {
- sender->executeAsyncSequenceCallback(result);
- }
+ DEBUG_PRINT_MILLIS
+ DEBUG_PRINT("Error: Command sequence aborted due to command failure. Result: ");
+ DEBUG_PRINTLN((byte)result);
+ sender->executeCommandSequenceAsyncFinalize(result);
+
return;
- }
+ };
// Move to next command
- sender->sendAsyncSequenceIndex++;
+ sender->executeCommandSequenceIndex++;
- if (sender->sendAsyncSequenceIndex < sender->commandSequenceBufferCount) {
+ if (sender->executeCommandSequenceIndex < sender->commandSequenceBufferCount) {
// Send next command
- sender->sendCommandAsync(
- sender->commandSequenceBuffer[sender->sendAsyncSequenceIndex],
- sendCommandSequenceAsyncCommandCallback,
- 0
- );
+ sender->sendCommandAsync(sender->commandSequenceBuffer[sender->executeCommandSequenceIndex], executeCommandSequenceAsyncCommandCallback);
}
else {
// Sequence finished successfully
- if (!sender->sendAsyncSequenceInitialConfigModeState) {
- sender->disableConfigModeAsync(sendCommandSequenceAsyncDisableConfigModeCallback, static_cast(AsyncCommandResult::SUCCESS));
- }
- else {
- sender->executeAsyncSequenceCallback(AsyncCommandResult::SUCCESS);
- }
- }
-}
-
-// After enabling config mode at the beginning
-void LD2410Async::sendCommandSequenceAsyncEnableConfigModeCallback(LD2410Async* sender, AsyncCommandResult result, byte userData) {
- if (result == AsyncCommandResult::SUCCESS) {
- // Start with first command
- sender->sendAsyncSequenceIndex = 0;
- sender->sendCommandAsync(
- sender->commandSequenceBuffer[sender->sendAsyncSequenceIndex],
- sendCommandSequenceAsyncCommandCallback,
- 0
- );
- }
- else {
- // Failed to enable config mode
- sender->executeAsyncSequenceCallback(result);
+ sender->executeCommandSequenceAsyncFinalize(LD2410Async::AsyncCommandResult::SUCCESS);
}
}
-
-bool LD2410Async::sendCommandSequenceAsync(AsyncCommandCallback callback, byte userData) {
- if (asyncIsBusy()) return false;
+bool LD2410Async::executeCommandSequenceAsync(AsyncCommandCallback callback) {
+ if (sendCommandAsyncCommandPending || executeCommandSequencePending) return false;
if (commandSequenceBufferCount == 0) return true; // nothing to send
DEBUG_PRINT_MILLIS;
DEBUG_PRINT("Starting command sequence execution. Number of commands: ");
DEBUG_PRINTLN(commandSequenceBufferCount);
+ executeCommandSequencePending = true;
+ executeCommandSequenceCallback = callback;
+ executeCommandSequenceInitialConfigModeState = configModeEnabled;
+ executeCommandSequenceStartMs = millis();
+
+ if (commandSequenceBufferCount == 0) {
+ // Wait 1 ms to ensure that the callback is not executed before the caller of this method has finished its work
+ executeCommandSequenceOnceTicker.once_ms(1, [this]() {
+ this->executeCommandSequenceAsyncExecuteCallback(LD2410Async::AsyncCommandResult::SUCCESS);
+ });
+ return true;
- sendAsyncSequenceCallback = callback;
- sendAsyncSequenceUserData = userData;
- sendAsyncSequenceInitialConfigModeState = configModeEnabled;
- sendAsyncSequenceIndex = 0;
- sendAsyncSequenceStartMs = millis();
+ }
if (!configModeEnabled) {
- enableConfigModeAsync(sendCommandSequenceAsyncEnableConfigModeCallback, 0);
+ // Need to enable config mode first
+ // So set the sequence index to -1 to ensure the first command in the sequence (at index 0) is executed when the callback fires.
+ executeCommandSequenceIndex = -1;
+ return sendCommandAsync(LD2410Defs::configEnableCommandData, 1, executeCommandSequenceAsyncCommandCallback); // Retry once because this command sometimes fails or may not send an ACK.
}
else {
- // Already in config mode, start directly
- sendCommandAsync(
- commandSequenceBuffer[sendAsyncSequenceIndex],
- sendCommandSequenceAsyncCommandCallback,
- 0
- );
+ // Already in config mode, start directly.
+ // We start the first command in the sequence directly and set the sequence index 0, so the second command (if any) is executed when the callback fires.
+ executeCommandSequenceIndex = 0;
+ return sendCommandAsync(commandSequenceBuffer[executeCommandSequenceIndex], executeCommandSequenceAsyncCommandCallback);
}
- return true;
}
bool LD2410Async::addCommandToSequence(const byte* command) {
- if (asyncIsBusy()) return false;
-
- if (commandSequenceBufferCount >= MAX_COMMAND_SEQUENCE) return false; // buffer full
+ if (sendCommandAsyncCommandPending || executeCommandSequencePending) return false;
+ if (commandSequenceBufferCount >= MAX_COMMAND_SEQUENCE_LENGTH) {
+ DEBUG_PRINT_MILLIS;
+ DEBUG_PRINTLN("Error: Command sequence buffer full.");
+ return false;
+ };
// First byte of the command is the payload length
uint8_t len = command[0];
uint8_t totalLen = len + 2; // payload + 2 length bytes
- if (totalLen > LD2410_BUFFER_SIZE) return false; // safety
-
+ if (totalLen > LD2410Defs::LD2410_Buffer_Size) {
+ DEBUG_PRINT_MILLIS;
+ DEBUG_PRINTLN("Error: Command too long for command sequence buffer.");
+ return false; // safety
+ }
memcpy(commandSequenceBuffer[commandSequenceBufferCount],
command,
totalLen);
@@ -736,419 +776,808 @@ bool LD2410Async::addCommandToSequence(const byte* command) {
bool LD2410Async::resetCommandSequence() {
- if (asyncIsBusy()) return false;
+ if (sendCommandAsyncCommandPending || executeCommandSequencePending) return false;
+
+ commandSequenceBufferCount = 0;
DEBUG_PRINT_MILLIS;
DEBUG_PRINTLN("Command sequence reset done.");
- commandSequenceBufferCount = 0;
return true;
}
/**********************************************************************************
-* Async commands methods
+* Config mode commands
***********************************************************************************/
-bool LD2410Async::enableConfigModeAsync(AsyncCommandCallback callback, byte userData) {
+// Config mode commands have an internal version which does not check for busy and can therefore be used within other command implementations.
+
+
+
+
+bool LD2410Async::enableConfigModeInternalAsync(bool force, AsyncCommandCallback callback) {
DEBUG_PRINT_MILLIS;
DEBUG_PRINTLN("Enable Config Mode");
- return sendCommandAsync(LD2410Defs::configEnableCommandData, callback, userData);
+
+ if (!configModeEnabled || force) {
+ // Need to send the enable command
+ return sendCommandAsync(LD2410Defs::configEnableCommandData, 1, callback); // We retry once if necessary because this command sometimes fails or might not send an ACK.
+ }
+ else {
+ // Already in config mode -> just trigger the callback after a tiny delay
+ sendCommandAsyncStoreDataForCallback(LD2410Defs::configEnableCommandData, 0, callback);
+ configModeOnceTicker.once_ms(1, [this]() {
+ sendCommandAsyncExecuteCallback(LD2410Defs::configEnableCommand, LD2410Async::AsyncCommandResult::SUCCESS);
+ });
+ return true;
+ }
+}
+
+bool LD2410Async::enableConfigModeAsync(bool force, AsyncCommandCallback callback) {
+ if (asyncIsBusy()) return false;
+ return enableConfigModeInternalAsync(force, callback);
}
-bool LD2410Async::disableConfigModeAsync(AsyncCommandCallback callback, byte userData) {
+bool LD2410Async::disableConfigModeInternalAsync(bool force, AsyncCommandCallback callback) {
DEBUG_PRINT_MILLIS;
DEBUG_PRINTLN("Disable Config Mode");
- return sendCommandAsync(LD2410Defs::configDisableCommandData, callback, userData);
+
+ if (configModeEnabled || force) {
+ return sendCommandAsync(LD2410Defs::configDisableCommandData, callback);
+ }
+ else {
+ // If config mode doesn't have to be disabled, the callback gets triggered after a minimal delay
+ // to ensure the disableConfigMode method can complete before the callback fires.
+ sendCommandAsyncStoreDataForCallback(LD2410Defs::configDisableCommandData, 0, callback);
+ configModeOnceTicker.once_ms(1, [this]() {
+ sendCommandAsyncExecuteCallback(LD2410Defs::configDisableCommand, LD2410Async::AsyncCommandResult::SUCCESS);
+ });
+ return true;
+ }
}
+bool LD2410Async::disableConfigModeAsync(bool force, AsyncCommandCallback callback) {
+ if (asyncIsBusy()) return false;
+ return disableConfigModeInternalAsync(force, callback);
+}
-bool LD2410Async::setMaxGateAndNoOneTimeoutAsync(byte maxMovingGate, byte maxStationaryGate,
+/**********************************************************************************
+* Native LD2410 commands to control, configure and query the sensor
+***********************************************************************************/
+// All these commands need to be executed in config mode.
+// The code takes care of that: enabling or disabling config mode if necessary,
+// while keeping config mode active if it was already active before the command.
+bool LD2410Async::configureMaxGateAndNoOneTimeoutAsync(byte maxMovingGate, byte maxStationaryGate,
unsigned short noOneTimeout,
- AsyncCommandCallback callback, byte userData)
+ AsyncCommandCallback callback)
{
DEBUG_PRINT_MILLIS;
+ DEBUG_PRINT(noOneTimeout);
DEBUG_PRINTLN("Set Max Gate");
+ if (asyncIsBusy()) return false;
byte cmd[sizeof(LD2410Defs::maxGateCommandData)];
- LD2410CommandBuilder::buildMaxGateCommand(cmd, maxMovingGate, maxStationaryGate, noOneTimeout);
-
- return sendConfigCommandAsync(cmd, callback, userData);
+ if (LD2410CommandBuilder::buildMaxGateCommand(cmd, maxMovingGate, maxStationaryGate, noOneTimeout)) {
+ return sendConfigCommandAsync(cmd, callback);
+ }
+ return false;
}
-bool LD2410Async::requestGateParametersAsync(AsyncCommandCallback callback, byte userData) {
+bool LD2410Async::requestGateParametersAsync(AsyncCommandCallback callback) {
DEBUG_PRINT_MILLIS;
DEBUG_PRINTLN("Request Gate Parameters");
- return sendConfigCommandAsync(LD2410Defs::requestParamsCommandData, callback, userData);
+ if (asyncIsBusy()) return false;
+ return sendConfigCommandAsync(LD2410Defs::requestParamsCommandData, callback);
}
-bool LD2410Async::enableEngineeringModeAsync(AsyncCommandCallback callback, byte userData) {
+bool LD2410Async::enableEngineeringModeAsync(AsyncCommandCallback callback) {
DEBUG_PRINT_MILLIS;
DEBUG_PRINTLN("Enable EngineeringMode");
- return sendConfigCommandAsync(LD2410Defs::engineeringModeEnableCommandData, callback, userData);
+ if (asyncIsBusy()) return false;
+ return sendConfigCommandAsync(LD2410Defs::engineeringModeEnableCommandData, callback);
}
-bool LD2410Async::disableEngineeringModeAsync(AsyncCommandCallback callback, byte userData) {
+bool LD2410Async::disableEngineeringModeAsync(AsyncCommandCallback callback) {
DEBUG_PRINT_MILLIS;
DEBUG_PRINTLN("Disable EngineeringMode");
- return sendConfigCommandAsync(LD2410Defs::engineeringModeDisableCommandData, callback, userData);
+ if (asyncIsBusy()) return false;
+ return sendConfigCommandAsync(LD2410Defs::engineeringModeDisableCommandData, callback);
}
-bool LD2410Async::setDistanceGateSensitivityAsync(const byte movingThresholds[9],
+bool LD2410Async::configureDistanceGateSensitivityAsync(const byte movingThresholds[9],
const byte stationaryThresholds[9],
- AsyncCommandCallback callback, byte userData)
+ AsyncCommandCallback callback)
{
DEBUG_PRINT_MILLIS;
DEBUG_PRINTLN("Set Distance Gate Sensitivities (all gates)");
+ if (asyncIsBusy()) return false;
+
if (!resetCommandSequence()) return false;
for (byte gate = 0; gate < 9; gate++) {
byte cmd[sizeof(LD2410Defs::distanceGateSensitivityConfigCommandData)];
- LD2410CommandBuilder::buildGateSensitivityCommand(cmd,
- gate,
- movingThresholds[gate],
- stationaryThresholds[gate]);
+ if (!LD2410CommandBuilder::buildGateSensitivityCommand(cmd, gate, movingThresholds[gate], stationaryThresholds[gate])) return false;
if (!addCommandToSequence(cmd)) return false;
}
- return sendCommandSequenceAsync(callback, userData);
+ return executeCommandSequenceAsync(callback);
}
-bool LD2410Async::setDistanceGateSensitivityAsync(byte gate, byte movingThreshold,
+bool LD2410Async::configureDistanceGateSensitivityAsync(byte gate, byte movingThreshold,
byte stationaryThreshold,
- AsyncCommandCallback callback, byte userData)
+ AsyncCommandCallback callback)
{
DEBUG_PRINT_MILLIS;
DEBUG_PRINTLN("Set Distance Gate Sensitivity");
+ if (asyncIsBusy()) return false;
+
byte cmd[sizeof(LD2410Defs::distanceGateSensitivityConfigCommandData)];
- LD2410CommandBuilder::buildGateSensitivityCommand(cmd, gate, movingThreshold, stationaryThreshold);
+ if (!LD2410CommandBuilder::buildGateSensitivityCommand(cmd, gate, movingThreshold, stationaryThreshold)) return false;
- return sendConfigCommandAsync(cmd, callback, userData);
+ return sendConfigCommandAsync(cmd, callback);
}
-bool LD2410Async::requestFirmwareAsync(AsyncCommandCallback callback, byte userData) {
+bool LD2410Async::requestFirmwareAsync(AsyncCommandCallback callback) {
DEBUG_PRINT_MILLIS;
DEBUG_PRINTLN("Request Firmware");
- return sendConfigCommandAsync(LD2410Defs::requestFirmwareCommandData, callback, userData);
+
+ if (asyncIsBusy()) return false;
+
+ return sendConfigCommandAsync(LD2410Defs::requestFirmwareCommandData, callback);
}
-bool LD2410Async::setBaudRateAsync(byte baudRateSetting,
- AsyncCommandCallback callback, byte userData)
+bool LD2410Async::configureBaudRateAsync(byte baudRateSetting,
+ AsyncCommandCallback callback)
{
DEBUG_PRINT_MILLIS;
DEBUG_PRINTLN("Set Baud Rate");
+ if (asyncIsBusy()) return false;
+
if ((baudRateSetting < 1) || (baudRateSetting > 8))
return false;
byte cmd[sizeof(LD2410Defs::setBaudRateCommandData)];
- LD2410CommandBuilder::buildBaudRateCommand(cmd, baudRateSetting);
+ if (!LD2410CommandBuilder::buildBaudRateCommand(cmd, baudRateSetting)) return false;
- return sendConfigCommandAsync(cmd, callback, userData);
+ return sendConfigCommandAsync(cmd, callback);
}
-bool LD2410Async::setBaudRateAsync(Baudrate baudRate, AsyncCommandCallback callback, byte userData) {
- return setBaudRateAsync((byte)baudRate, callback, userData);
+bool LD2410Async::configureBaudRateAsync(LD2410Types::Baudrate baudRate, AsyncCommandCallback callback) {
+
+ if (asyncIsBusy()) return false;
+
+ return configureBaudRateAsync((byte)baudRate, callback);
}
-bool LD2410Async::restoreFactorySettingsAsync(AsyncCommandCallback callback, byte userData) {
+bool LD2410Async::restoreFactorySettingsAsync(AsyncCommandCallback callback) {
DEBUG_PRINT_MILLIS;
DEBUG_PRINTLN("Restore Factory Settings");
- return sendConfigCommandAsync(LD2410Defs::restoreFactorSettingsCommandData, callback, userData);
+
+ if (asyncIsBusy()) return false;
+ return sendConfigCommandAsync(LD2410Defs::restoreFactorSettingsCommandData, callback);
}
-bool LD2410Async::enableBluetoothAsync(AsyncCommandCallback callback, byte userData) {
+bool LD2410Async::enableBluetoothAsync(AsyncCommandCallback callback) {
DEBUG_PRINT_MILLIS;
DEBUG_PRINTLN("Enable Bluetooth");
- return sendConfigCommandAsync(LD2410Defs::bluetoothSettingsOnCommandData, callback, userData);
+ if (asyncIsBusy()) return false;
+
+ return sendConfigCommandAsync(LD2410Defs::bluetoothSettingsOnCommandData, callback);
}
-bool LD2410Async::disableBluetoothAsync(AsyncCommandCallback callback, byte userData) {
+bool LD2410Async::disableBluetoothAsync(AsyncCommandCallback callback) {
DEBUG_PRINT_MILLIS;
DEBUG_PRINTLN("Disable Bluetooth");
- return sendConfigCommandAsync(LD2410Defs::bluetoothSettingsOnCommandData, callback, userData);
+ if (asyncIsBusy()) return false;
+ return sendConfigCommandAsync(LD2410Defs::bluetoothSettingsOnCommandData, callback);
}
-bool LD2410Async::requestBluetoothMacAddressAsync(AsyncCommandCallback callback, byte userData) {
+bool LD2410Async::requestBluetoothMacAddressAsync(AsyncCommandCallback callback) {
DEBUG_PRINT_MILLIS;
DEBUG_PRINTLN("Request Mac Address");
- return sendConfigCommandAsync(LD2410Defs::requestMacAddressCommandData, callback, userData);
+ if (asyncIsBusy()) return false;
+ return sendConfigCommandAsync(LD2410Defs::requestMacAddressCommandData, callback);
}
-bool LD2410Async::setBluetoothpasswordAsync(const char* password,
- AsyncCommandCallback callback, byte userData)
+bool LD2410Async::configureBluetoothPasswordAsync(const char* password,
+ AsyncCommandCallback callback)
{
DEBUG_PRINT_MILLIS;
DEBUG_PRINTLN("Set Bluetooth Password");
+ if (asyncIsBusy()) return false;
byte cmd[sizeof(LD2410Defs::setBluetoothPasswordCommandData)];
- LD2410CommandBuilder::buildBluetoothPasswordCommand(cmd, password);
+ if (!LD2410CommandBuilder::buildBluetoothPasswordCommand(cmd, password)) return false;
- return sendConfigCommandAsync(cmd, callback, userData);
+ return sendConfigCommandAsync(cmd, callback);
}
-bool LD2410Async::setBluetoothpasswordAsync(const String& password, AsyncCommandCallback callback, byte userData) {
+bool LD2410Async::configureBluetoothPasswordAsync(const String& password, AsyncCommandCallback callback) {
- return setBluetoothpasswordAsync(password.c_str(), callback, userData);
+ return configureBluetoothPasswordAsync(password.c_str(), callback);
}
-bool LD2410Async::resetBluetoothpasswordAsync(AsyncCommandCallback callback, byte userData) {
+bool LD2410Async::configureDefaultBluetoothPasswordAsync(AsyncCommandCallback callback) {
DEBUG_PRINT_MILLIS;
DEBUG_PRINTLN("Reset Bluetooth Password");
- return sendConfigCommandAsync(LD2410Defs::setBluetoothPasswordCommandData, callback, userData);
+ if (asyncIsBusy()) return false;
+ return sendConfigCommandAsync(LD2410Defs::setBluetoothPasswordCommandData, callback);
}
-bool LD2410Async::setDistanceResolution75cmAsync(AsyncCommandCallback callback, byte userData) {
+bool LD2410Async::configureDistanceResolution75cmAsync(AsyncCommandCallback callback) {
DEBUG_PRINT_MILLIS;
DEBUG_PRINTLN("Set Distance Resolution 75cm");
- return sendConfigCommandAsync(LD2410Defs::setDistanceResolution75cmCommandData, callback, userData);
+ if (asyncIsBusy()) return false;
+ return sendConfigCommandAsync(LD2410Defs::setDistanceResolution75cmCommandData, callback);
};
-bool LD2410Async::setDistanceResolutionAsync(DistanceResolution distanceResolution,
- AsyncCommandCallback callback, byte userData)
+bool LD2410Async::configureDistanceResolutionAsync(LD2410Types::DistanceResolution distanceResolution,
+ AsyncCommandCallback callback)
{
DEBUG_PRINT_MILLIS;
DEBUG_PRINTLN("Set Distance Resolution");
-
- if (distanceResolution == DistanceResolution::NOT_SET)
- return false;
+ if (asyncIsBusy()) return false;
byte cmd[6];
- LD2410CommandBuilder::buildDistanceResolutionCommand(cmd, distanceResolution);
+ if (!LD2410CommandBuilder::buildDistanceResolutionCommand(cmd, distanceResolution)) return false;
- return sendConfigCommandAsync(cmd, callback, userData);
+ return sendConfigCommandAsync(cmd, callback);
}
-bool LD2410Async::setDistanceResolution20cmAsync(AsyncCommandCallback callback, byte userData) {
+bool LD2410Async::configuresDistanceResolution20cmAsync(AsyncCommandCallback callback) {
DEBUG_PRINT_MILLIS;
DEBUG_PRINTLN("Set Distance Resolution 20cm");
- return sendConfigCommandAsync(LD2410Defs::setDistanceResolution20cmCommandData, callback, userData);
+ if (asyncIsBusy()) return false;
+ return sendConfigCommandAsync(LD2410Defs::setDistanceResolution20cmCommandData, callback);
};
-bool LD2410Async::requestDistanceResolutioncmAsync(AsyncCommandCallback callback, byte userData) {
+bool LD2410Async::requestDistanceResolutionAsync(AsyncCommandCallback callback) {
DEBUG_PRINT_MILLIS;
DEBUG_PRINTLN("Request Distance Resolution cm");
- return sendConfigCommandAsync(LD2410Defs::requestDistanceResolutionCommandData, callback, userData);
+ if (asyncIsBusy()) return false;
+ return sendConfigCommandAsync(LD2410Defs::requestDistanceResolutionCommandData, callback);
}
-bool LD2410Async::setAuxControlSettingsAsync(LightControl lightControl, byte lightThreshold,
- OutputControl outputControl,
- AsyncCommandCallback callback, byte userData)
+bool LD2410Async::configureAuxControlSettingsAsync(LD2410Types::LightControl lightControl, byte lightThreshold,
+ LD2410Types::OutputControl outputControl,
+ AsyncCommandCallback callback)
{
DEBUG_PRINT_MILLIS;
DEBUG_PRINTLN("Set Aux Control Settings");
-
- if (lightControl == LightControl::NOT_SET || outputControl == OutputControl::NOT_SET) {
- DEBUG_PRINT_MILLIS;
- DEBUG_PRINTLN("Invalid paras. Enum set to NOT_SET. Can't execute command");
- return false;
- }
+ if (asyncIsBusy()) return false;
byte cmd[sizeof(LD2410Defs::setAuxControlSettingCommandData)];
- LD2410CommandBuilder::buildAuxControlCommand(cmd, lightControl, lightThreshold, outputControl);
+ if (!LD2410CommandBuilder::buildAuxControlCommand(cmd, lightControl, lightThreshold, outputControl)) return false;
- return sendConfigCommandAsync(cmd, callback, userData);
+ return sendConfigCommandAsync(cmd, callback);
}
-bool LD2410Async::requestAuxControlSettingsAsync(AsyncCommandCallback callback, byte userData) {
+bool LD2410Async::requestAuxControlSettingsAsync(AsyncCommandCallback callback) {
DEBUG_PRINT_MILLIS;
DEBUG_PRINTLN("Request Aux Control Settings");
- return sendConfigCommandAsync(LD2410Defs::requestAuxControlSettingsCommandData, callback, userData);
+
+ if (asyncIsBusy()) return false;
+
+ return sendConfigCommandAsync(LD2410Defs::requestAuxControlSettingsCommandData, callback);
}
-bool LD2410Async::beginAutoConfigAsync(AsyncCommandCallback callback, byte userData ) {
+bool LD2410Async::beginAutoConfigAsync(AsyncCommandCallback callback) {
DEBUG_PRINT_MILLIS;
DEBUG_PRINTLN("Begin auto config");
- return sendConfigCommandAsync(LD2410Defs::beginAutoConfigCommandData, callback, userData);
+
+ if (asyncIsBusy()) return false;
+
+ return sendConfigCommandAsync(LD2410Defs::beginAutoConfigCommandData, callback);
};
-bool LD2410Async::requestAutoConfigStatusAsync(AsyncCommandCallback callback, byte userData ) {
+bool LD2410Async::requestAutoConfigStatusAsync(AsyncCommandCallback callback) {
DEBUG_PRINT_MILLIS;
DEBUG_PRINTLN("Reqtest auto config status");
- return sendConfigCommandAsync(LD2410Defs::requestAutoConfigStatusCommandData, callback, userData);
+
+ if (asyncIsBusy()) return false;
+
+ return sendConfigCommandAsync(LD2410Defs::requestAutoConfigStatusCommandData, callback);
}
+/**********************************************************************************
+* High level commands that combine several native commands
+***********************************************************************************/
+// It is recommend to always use these commands if feasible,
+// since they streamline the inconsistencies in the native requesr and config commands
+// and reduce the total number of commands that have to be called.
-bool LD2410Async::requestAllStaticData(AsyncCommandCallback callback, byte userData) {
- if (asyncIsBusy()) return false;
+bool LD2410Async::requestAllStaticDataAsync(AsyncCommandCallback callback) {
DEBUG_PRINT_MILLIS;
DEBUG_PRINTLN("Request all static data");
+ if (asyncIsBusy()) return false;
+
+
if (!resetCommandSequence()) return false;
if (!addCommandToSequence(LD2410Defs::requestFirmwareCommandData)) return false;
if (!addCommandToSequence(LD2410Defs::requestMacAddressCommandData)) return false;
- return sendCommandSequenceAsync(callback, userData);
+ return executeCommandSequenceAsync(callback);
}
-bool LD2410Async::requestAllConfigData(AsyncCommandCallback callback, byte userData) {
- if (asyncIsBusy()) return false;
+bool LD2410Async::requestAllConfigSettingsAsync(AsyncCommandCallback callback) {
DEBUG_PRINT_MILLIS;
DEBUG_PRINTLN("Request all config data");
+ if (asyncIsBusy()) return false;
+
+
+
if (!resetCommandSequence()) return false;
if (!addCommandToSequence(LD2410Defs::requestDistanceResolutionCommandData)) return false;
if (!addCommandToSequence(LD2410Defs::requestParamsCommandData)) return false;
if (!addCommandToSequence(LD2410Defs::requestAuxControlSettingsCommandData)) return false;
- return sendCommandSequenceAsync(callback, userData);
+ resetConfigData();
+ return executeCommandSequenceAsync(callback);
}
+// ----------------------------------------------------------------------------------
+// - Set config data async methods
+// ----------------------------------------------------------------------------------
+// The command to set all config values on the sensor is the most complex command internally.
+// It uses a first command sequences to get the current sensor config, then checks what
+// actually needs to be changed and then creates a second command sequence to do the needed changes.
-bool LD2410Async::setConfigDataAsync(const ConfigData& config, AsyncCommandCallback callback, byte userData)
-{
- if (asyncIsBusy()) return false;
+void LD2410Async::configureAllConfigSettingsAsyncExecuteCallback(LD2410Async::AsyncCommandResult result) {
+ AsyncCommandCallback cb = configureAllConfigSettingsAsyncConfigCallback;
+ configureAllConfigSettingsAsyncConfigCallback = nullptr;
+ configureAllConfigSettingsAsyncConfigActive = false;
- // === Check enums first ===
- if (config.distanceResolution == DistanceResolution::NOT_SET) {
+ DEBUG_PRINT_MILLIS
+ DEBUG_PRINT("configureAllConfigSettingsAsync complete. Result: ");
+ DEBUG_PRINTLN((byte)result);
+
+ if (cb) {
+ cb(this, result);
+ }
+
+}
+
+void LD2410Async::configureAllConfigSettingsAsyncConfigModeDisabledCallback(LD2410Async* sender, LD2410Async::AsyncCommandResult result) {
+ if (result != LD2410Async::AsyncCommandResult::SUCCESS) {
DEBUG_PRINT_MILLIS;
- DEBUG_PRINTLN("ConfigData invalid: distanceResolution is NOT_SET");
- return false;
+ DEBUG_PRINTLN("Warning: Disabling config mode after configureAllConfigSettingsAsync failed. Result: ");
+ DEBUG_PRINTLN((byte)result);
+ sender->configureAllConfigSettingsAsyncExecuteCallback(result);
}
- if (config.lightControl == LightControl::NOT_SET) {
+ else {
+ // Config mode disabled successfully
DEBUG_PRINT_MILLIS;
- DEBUG_PRINTLN("ConfigData invalid: lightControl is NOT_SET");
- return false;
+ DEBUG_PRINTLN("Config mode disabled, call the callback");
+
+ sender->configureAllConfigSettingsAsyncExecuteCallback(sender->configureAllConfigSettingsAsyncResultToReport);
}
- if (config.outputControl == OutputControl::NOT_SET) {
+}
+
+void LD2410Async::configureAllConfigSettingsAsyncFinalize(LD2410Async::AsyncCommandResult resultToReport) {
+
+ if (!configureAllConfigSettingsAsyncConfigInitialConfigMode) {
DEBUG_PRINT_MILLIS;
- DEBUG_PRINTLN("ConfigData invalid: outputControl is NOT_SET");
- return false;
+ DEBUG_PRINTLN("Config mode was not enabled initially, disable it.");
+ configureAllConfigSettingsAsyncResultToReport = resultToReport;
+
+ if (!disableConfigModeInternalAsync(configureAllConfigSettingsAsyncConfigModeDisabledCallback)) {
+ DEBUG_PRINT_MILLIS;
+ DEBUG_PRINTLN("Error: Disabling config mode after configureAllConfigSettingsAsync failed.");
+ configureAllConfigSettingsAsyncExecuteCallback(LD2410Async::AsyncCommandResult::FAILED);
+ }
}
+ else {
+ DEBUG_PRINT_MILLIS;
+ DEBUG_PRINTLN("Config mode was enabled initially, no need to disable it, just execute the callback.");
- DEBUG_PRINT_MILLIS;
- DEBUG_PRINTLN("Set full ConfigData");
+ configureAllConfigSettingsAsyncExecuteCallback(resultToReport);
+ }
+}
+
+
+void LD2410Async::configureAllConfigSettingsAsyncWriteConfigCallback(LD2410Async* sender, LD2410Async::AsyncCommandResult result) {
+ if (AsyncCommandResult::SUCCESS != result) {
+ DEBUG_PRINT_MILLIS;
+ DEBUG_PRINTLN("Error: Writing config data to sensor failed.");
+ };
+ // Pass result to finalize method, which will also disable config mode if needed
+ sender->configureAllConfigSettingsAsyncFinalize(result);
+}
- if (!resetCommandSequence()) return false;
+bool LD2410Async::configureAllConfigSettingsAsyncBuildSaveChangesCommandSequence() {
+ // Get a clone of the current config, so it does not get changed while we are working
+ LD2410Types::ConfigData currentConfig = LD2410Async::getConfigData();
+
+ if (!resetCommandSequence()) {
+ DEBUG_PRINT_MILLIS;
+ DEBUG_PRINTLN("Error: Could not reset command sequence.");
+ return false;
+ };
// 1. Max gate + no one timeout
- {
+ if (configureAllConfigSettingsAsyncWriteFullConfig
+ || currentConfig.maxMotionDistanceGate != configureAllConfigSettingsAsyncConfigDataToWrite.maxMotionDistanceGate
+ || currentConfig.maxStationaryDistanceGate != configureAllConfigSettingsAsyncConfigDataToWrite.maxStationaryDistanceGate
+ || currentConfig.noOneTimeout != configureAllConfigSettingsAsyncConfigDataToWrite.noOneTimeout) {
byte cmd[sizeof(LD2410Defs::maxGateCommandData)];
- LD2410CommandBuilder::buildMaxGateCommand(cmd,
- config.maxMotionDistanceGate,
- config.maxStationaryDistanceGate,
- config.noOneTimeout);
- if (!addCommandToSequence(cmd)) return false;
+ if (!LD2410CommandBuilder::buildMaxGateCommand(cmd,
+ configureAllConfigSettingsAsyncConfigDataToWrite.maxMotionDistanceGate,
+ configureAllConfigSettingsAsyncConfigDataToWrite.maxStationaryDistanceGate,
+ configureAllConfigSettingsAsyncConfigDataToWrite.noOneTimeout)) {
+ DEBUG_PRINT_MILLIS;
+ DEBUG_PRINTLN("Error: Building max gate command failed.");
+ return false;
+ };
+ if (!addCommandToSequence(cmd)) {
+ DEBUG_PRINT_MILLIS;
+ DEBUG_PRINTLN("Error: Adding max gate command to sequence failed.");
+ return false;
+ };
+ DEBUG_PRINT_MILLIS;
+ DEBUG_PRINTLN("Max gate command added to sequence.");
}
-
// 2. Gate sensitivities (sequence of commands)
for (byte gate = 0; gate < 9; gate++) {
- byte cmd[sizeof(LD2410Defs::distanceGateSensitivityConfigCommandData)];
- LD2410CommandBuilder::buildGateSensitivityCommand(cmd,
- gate,
- config.distanceGateMotionSensitivity[gate],
- config.distanceGateStationarySensitivity[gate]);
- if (!addCommandToSequence(cmd)) return false;
+ if (configureAllConfigSettingsAsyncWriteFullConfig
+ || currentConfig.distanceGateMotionSensitivity[gate] != configureAllConfigSettingsAsyncConfigDataToWrite.distanceGateMotionSensitivity[gate]
+ || currentConfig.distanceGateStationarySensitivity[gate] != configureAllConfigSettingsAsyncConfigDataToWrite.distanceGateStationarySensitivity[gate]) {
+ byte cmd[sizeof(LD2410Defs::distanceGateSensitivityConfigCommandData)];
+ if (!LD2410CommandBuilder::buildGateSensitivityCommand(cmd, gate,
+ configureAllConfigSettingsAsyncConfigDataToWrite.distanceGateMotionSensitivity[gate],
+ configureAllConfigSettingsAsyncConfigDataToWrite.distanceGateStationarySensitivity[gate])) {
+ DEBUG_PRINT_MILLIS;
+ DEBUG_PRINT("Error: Error building gate sensitivity command for gate ");
+ DEBUG_PRINTLN(gate);
+
+ return false;
+ };
+ if (!addCommandToSequence(cmd)) {
+ DEBUG_PRINT_MILLIS;
+ DEBUG_PRINT("Error: Adding gate sensitivity command for gate ");
+ DEBUG_PRINT(gate);
+ DEBUG_PRINTLN(" to sequence failed.");
+
+ return false;
+ }
+ DEBUG_PRINT_MILLIS
+ DEBUG_PRINT("Gate sensitivity command for gate ");
+ DEBUG_PRINT(gate);
+ DEBUG_PRINTLN(" added to sequence.");
+ }
}
- // 3. Distance resolution
- {
+ //3. Distance resolution
+ if (configureAllConfigSettingsAsyncWriteFullConfig
+ || currentConfig.distanceResolution != configureAllConfigSettingsAsyncConfigDataToWrite.distanceResolution) {
byte cmd[6]; // resolution commands are 6 bytes long
- LD2410CommandBuilder::buildDistanceResolutionCommand(cmd, config.distanceResolution);
- if (!addCommandToSequence(cmd)) return false;
+ if (!LD2410CommandBuilder::buildDistanceResolutionCommand(cmd, configureAllConfigSettingsAsyncConfigDataToWrite.distanceResolution)) {
+ DEBUG_PRINT_MILLIS;
+ DEBUG_PRINTLN("Error: Building distance resolution command failed.");
+ return false;
+ };
+ if (!addCommandToSequence(cmd)) {
+ DEBUG_PRINT_MILLIS;
+ DEBUG_PRINTLN("Error: Adding distance resolution command to sequence failed.");
+ return false;
+ };
+ DEBUG_PRINT_MILLIS;
+ DEBUG_PRINTLN("Distance resolution command added to sequence.");
+ };
+
+ //4. Aux control settings
+ if (configureAllConfigSettingsAsyncWriteFullConfig
+ || currentConfig.lightControl != configureAllConfigSettingsAsyncConfigDataToWrite.lightControl
+ || currentConfig.lightThreshold != configureAllConfigSettingsAsyncConfigDataToWrite.lightThreshold
+ || currentConfig.outputControl != configureAllConfigSettingsAsyncConfigDataToWrite.outputControl) {
+ byte cmd[sizeof(LD2410Defs::setAuxControlSettingCommandData)];
+ if (!LD2410CommandBuilder::buildAuxControlCommand(cmd,
+ configureAllConfigSettingsAsyncConfigDataToWrite.lightControl,
+ configureAllConfigSettingsAsyncConfigDataToWrite.lightThreshold,
+ configureAllConfigSettingsAsyncConfigDataToWrite.outputControl)) {
+ DEBUG_PRINT_MILLIS;
+ DEBUG_PRINTLN("Error: Building aux control command failed.");
+ return false;
+ };
+ if (!addCommandToSequence(cmd)) {
+ DEBUG_PRINT_MILLIS;
+ DEBUG_PRINTLN("Error: Adding aux control command to sequence failed.");
+ return false;
+ };
+ DEBUG_PRINT_MILLIS;
+ DEBUG_PRINTLN("Aux control command added to sequence.");
+ };
+ return true;
+};
+
+bool LD2410Async::configureAllConfigSettingsAsyncWriteConfig() {
+
+ if (!configureAllConfigSettingsAsyncBuildSaveChangesCommandSequence()) {
+ // Could not build command sequence
+ DEBUG_PRINT_MILLIS;
+ DEBUG_PRINTLN("Could not build the command sequence to save the config data");
+ return false;
+ }
+
+ if (commandSequenceBufferCount == 0) {
+ DEBUG_PRINT_MILLIS
+ DEBUG_PRINTLN("No config changes detected, no need to write anything");
+ }
+
+ if (!executeCommandSequenceAsync(configureAllConfigSettingsAsyncWriteConfigCallback)) {
+ DEBUG_PRINT_MILLIS;
+ DEBUG_PRINTLN("Error: Starting command sequence to write config data failed.");
+
+ return false;
+ }
+ return true;
+
+};
+
+void LD2410Async::configureAllConfigSettingsAsyncRequestAllConfigDataCallback(LD2410Async* sender, LD2410Async::AsyncCommandResult result) {
+ if (result != LD2410Async::AsyncCommandResult::SUCCESS) {
+ DEBUG_PRINT_MILLIS;
+ DEBUG_PRINTLN("Error: Requesting current config data failed. Result: ");
+ DEBUG_PRINTLN((byte)result);
+ sender->configureAllConfigSettingsAsyncFinalize(result);
+ return;
}
+ // Received current config data, now write the changed values
+ DEBUG_PRINT_MILLIS;
+ DEBUG_PRINTLN("Current config data received.");
+
+ if (!sender->configureAllConfigSettingsAsyncWriteConfig()) {
+ DEBUG_PRINT_MILLIS;
+ DEBUG_PRINTLN("Error: Starting to save config data changes failed.");
+ sender->configureAllConfigSettingsAsyncFinalize(AsyncCommandResult::FAILED);
+ }
+}
- // 4. Aux control settings
+bool LD2410Async::configureAllConfigSettingsAsyncRequestAllConfigData() {
+ if (resetCommandSequence()
+ && addCommandToSequence(LD2410Defs::requestDistanceResolutionCommandData)
+ && addCommandToSequence(LD2410Defs::requestParamsCommandData)
+ && addCommandToSequence(LD2410Defs::requestAuxControlSettingsCommandData))
{
- byte cmd[sizeof(LD2410Defs::setAuxControlSettingCommandData)];
- LD2410CommandBuilder::buildAuxControlCommand(cmd,
- config.lightControl,
- config.lightThreshold,
- config.outputControl);
- if (!addCommandToSequence(cmd)) return false;
+ if (executeCommandSequenceAsync(configureAllConfigSettingsAsyncRequestAllConfigDataCallback)) {
+ DEBUG_PRINT_MILLIS;
+ DEBUG_PRINTLN("Requesting current config data");
+ return true;
+ }
+ else {
+ DEBUG_PRINT_MILLIS;
+ DEBUG_PRINTLN("Error: Starting command sequence to request current config data failed.");
+ }
+ }
+ return false;
+}
+
+void LD2410Async::configureAllConfigSettingsAsyncConfigModeEnabledCallback(LD2410Async* sender, LD2410Async::AsyncCommandResult result) {
+ if (result != AsyncCommandResult::SUCCESS) {
+
+ DEBUG_PRINT_MILLIS;
+ DEBUG_PRINTLN("Error: Enabling config mode failed. Result: ");
+ DEBUG_PRINTLN((byte)result);
+ sender->configureAllConfigSettingsAsyncFinalize(result);
+ return;
+ };
+
+ // Received ACK for enable config mode command
+ DEBUG_PRINT_MILLIS;
+ DEBUG_PRINTLN("Config mode enabled.");
+
+ bool ret = false;
+ if (sender->configureAllConfigSettingsAsyncWriteFullConfig) {
+ // If we save all changes anyway, no need to request current config data first
+ DEBUG_PRINT_MILLIS;
+ DEBUG_PRINTLN("Saving all data is forced, save directly");
+ ret = sender->configureAllConfigSettingsAsyncWriteConfig();
+
}
+ else {
+ DEBUG_PRINT_MILLIS;
+ DEBUG_PRINTLN("Requesting current config data");
+ ret = sender->configureAllConfigSettingsAsyncRequestAllConfigData();
+ }
+ if (!ret) {
+ DEBUG_PRINT_MILLIS;
+ DEBUG_PRINTLN("Error: Starting config data write or request of current config data failed.");
+ sender->configureAllConfigSettingsAsyncFinalize(AsyncCommandResult::FAILED);
+ }
+
- // Execute the sequence
- return sendCommandSequenceAsync(callback, userData);
}
+bool LD2410Async::configureAllConfigSettingsAsync(const LD2410Types::ConfigData& configToWrite, bool writeAllConfigData, AsyncCommandCallback callback)
+{
+ DEBUG_PRINT_MILLIS;
+ DEBUG_PRINTLN("Writing config data to the LD2410");
+ if (asyncIsBusy()) return false;
+
+
+ if (!configToWrite.isValid()) {
+ DEBUG_PRINT_MILLIS;
+ DEBUG_PRINTLN("configToWrite is invalid.");
+ return false;
+ }
+
+ configureAllConfigSettingsAsyncConfigActive = true;
+ configureAllConfigSettingsAsyncConfigDataToWrite = configToWrite;
+ configureAllConfigSettingsAsyncWriteFullConfig = writeAllConfigData;
+ configureAllConfigSettingsAsyncConfigCallback = callback;
+ configureAllConfigSettingsAsyncConfigInitialConfigMode = isConfigModeEnabled();
+
+ if (!configureAllConfigSettingsAsyncConfigInitialConfigMode) {
+ DEBUG_PRINT_MILLIS;
+ DEBUG_PRINTLN("Enable the config mode");
+ return enableConfigModeInternalAsync(configureAllConfigSettingsAsyncConfigModeEnabledCallback);
+ }
+ else {
+ if (configureAllConfigSettingsAsyncWriteFullConfig) {
+ // If we save all changes anyway, no need to request current config data first
+ DEBUG_PRINT_MILLIS;
+ DEBUG_PRINTLN("Saving all data is forced and config mode is enabled -> save directly");
+ return configureAllConfigSettingsAsyncWriteConfig();
+ }
+ else {
+ DEBUG_PRINT_MILLIS;
+ DEBUG_PRINTLN("Config mode is already enabled, just request all config data");
+ return configureAllConfigSettingsAsyncRequestAllConfigData();
+ }
+ }
+
+}
/*--------------------------------------------------------------------
- Reboot command
---------------------------------------------------------------------*/
+// The reboot command is special, since it needs to be sent in config mode,
+// but doesn't have to disable config mode, since the sensor goes into normal
+// detection mode after the reboot anyway.
+
+void LD2410Async::rebootAsyncFinialize(LD2410Async::AsyncCommandResult result) {
+ if (rebootAsyncPending) {
+ rebootAsyncPending = false;
+ executeCommandSequenceOnceTicker.detach();
+ DEBUG_PRINT_MILLIS;
+ DEBUG_PRINTLN("Reboot completed");
+ executeCommandSequenceAsyncExecuteCallback(result);
+ }
+}
-void LD2410Async::rebootRebootCallback(LD2410Async* sender, AsyncCommandResult result, byte userData) {
- if (result == AsyncCommandResult::SUCCESS) {
- sender->configModeEnabled = false;
- sender->engineeringModeEnabled = false;
+void LD2410Async::rebootAsyncRebootCallback(LD2410Async* sender, LD2410Async::AsyncCommandResult result) {
+ if (result == LD2410Async::AsyncCommandResult::SUCCESS) {
+
+ // Not necessary, since the first data frame we receive after the reboot will set these variables back at the right point in time.
+ // If this does not happen, we are in trouble or likely stuck in config mode anyway.
+ // sender->configModeEnabled = false;
+ // sender->engineeringModeEnabled = false;
DEBUG_PRINT_MILLIS;
DEBUG_PRINTLN("Reboot initiated");
+ if (sender->rebootAsyncDontWaitForNormalOperationAfterReboot || sender->rebootAsyncWaitTimeoutMs == 0) {
+ DEBUG_PRINT_MILLIS;
+ DEBUG_PRINTLN("Triggering the reboot callback directly since the parameters of the method want us not to wait until normal operation resumes.");
+ sender->rebootAsyncFinialize(result);
+ }
+ else {
+ if (sender->rebootAsyncPending) {
+ DEBUG_PRINT_MILLIS;
+ DEBUG_PRINT("Will be waiting ");
+ DEBUG_PRINT(sender->rebootAsyncWaitTimeoutMs);
+ DEBUG_PRINTLN(" ms for normal operation to resume.");
+ sender->executeCommandSequenceOnceTicker.once_ms(sender->rebootAsyncWaitTimeoutMs, [sender]() {
+ DEBUG_PRINT_MILLIS;
+ DEBUG_PRINTLN("Timeout period while waiting for normal operation to resume has elapsed.");
+ sender->rebootAsyncFinialize(LD2410Async::AsyncCommandResult::TIMEOUT);
+ });
+ }
+ else {
+ DEBUG_PRINT_MILLIS;
+ DEBUG_PRINTLN("It seems that reboot has already been finalized (maybe the callback from processData has already been triggered)");
+ }
+ }
+ }
+ else {
+ DEBUG_PRINT_MILLIS;
+ DEBUG_PRINT("Error! Could not initiate reboot. Result: ");
+ DEBUG_PRINTLN((int)result);
+ sender->rebootAsyncFinialize(result);
}
- sender->executeAsyncSequenceCallback(result);
}
-void LD2410Async::rebootEnableConfigModeCallback(LD2410Async* sender, AsyncCommandResult result, byte userData) {
- if (result == AsyncCommandResult::SUCCESS) {
- //Got ack of enable config mode command
+void LD2410Async::rebootAsyncEnableConfigModeCallback(LD2410Async* sender, LD2410Async::AsyncCommandResult result) {
+ if (result == LD2410Async::AsyncCommandResult::SUCCESS) {
+ // Received ACK for enable config mode command
DEBUG_PRINT_MILLIS;
DEBUG_PRINTLN("Config mode enabled before reboot");
- sender->sendCommandAsync(LD2410Defs::rebootCommandData, rebootRebootCallback, 0);
+ sender->sendCommandAsync(LD2410Defs::rebootCommandData, rebootAsyncRebootCallback);
}
else {
- //Config mode command timeout or canceled
- //Just execute the callback
+ // Config mode command timeout or canceled
+ // Just execute the callback
DEBUG_PRINT_MILLIS;
DEBUG_PRINTLN("Error! Could not enabled config mode before reboot");
- sender->executeAsyncSequenceCallback(result);
+ sender->rebootAsyncFinialize(result);
}
}
-bool LD2410Async::rebootAsync(AsyncCommandCallback callback, byte userData) {
- if (asyncIsBusy()) return false;
+bool LD2410Async::rebootAsync(bool dontWaitForNormalOperationAfterReboot, AsyncCommandCallback callback) {
+
DEBUG_PRINT_MILLIS;
DEBUG_PRINTLN("Reboot");
- sendAsyncSequenceCallback = callback;
- sendAsyncSequenceUserData = userData;
+ if (asyncIsBusy()) return false;
+
+ //"Missusing" the variables for the command sequence
+ executeCommandSequencePending = true;
+ executeCommandSequenceCallback = callback;
+ executeCommandSequenceInitialConfigModeState = configModeEnabled;
+ executeCommandSequenceStartMs = millis();
+ rebootAsyncDontWaitForNormalOperationAfterReboot = dontWaitForNormalOperationAfterReboot;
+ // Force config mode (just to be sure)
+ rebootAsyncPending = enableConfigModeInternalAsync(true, rebootAsyncEnableConfigModeCallback);
+
+ return rebootAsyncPending;
- return enableConfigModeAsync(rebootEnableConfigModeCallback, 0);
}
/**********************************************************************************
* Data access
***********************************************************************************/
-LD2410Async::DetectionData LD2410Async::getDetectionData() const {
+LD2410Types::DetectionData LD2410Async::getDetectionData() const {
return detectionData;
}
-LD2410Async::ConfigData LD2410Async::getConfigData() const {
+LD2410Types::ConfigData LD2410Async::getConfigData() const {
return configData;
}
+/**********************************************************************************
+* Rest config data
+***********************************************************************************/
+// Resets the values of configdata to their initial value
+void LD2410Async::resetConfigData() {
+ configData = LD2410Types::ConfigData();
+}
+
+
/**********************************************************************************
* Inactivity handling
***********************************************************************************/
-void LD2410Async::handleInactivityRebootCallback(LD2410Async* sender, AsyncCommandResult result, byte userData) {
+void LD2410Async::handleInactivityRebootCallback(LD2410Async* sender, LD2410Async::AsyncCommandResult result) {
sender->configModeEnabled = false;
sender->engineeringModeEnabled = false;
-#ifdef ENABLE_DEBUG
+#if (LD2410ASYNC_DEBUG_LEVEL > 0)
if (result == AsyncCommandResult::SUCCESS) {
DEBUG_PRINT_MILLIS;
DEBUG_PRINTLN("LD2410 reboot due to inactivity initiated");
@@ -1163,10 +1592,10 @@ void LD2410Async::handleInactivityRebootCallback(LD2410Async* sender, AsyncComma
}
-void LD2410Async::handleInactivityDisableConfigmodeCallback(LD2410Async* sender, AsyncCommandResult result, byte userData) {
+void LD2410Async::handleInactivityDisableConfigmodeCallback(LD2410Async* sender, LD2410Async::AsyncCommandResult result) {
-#ifdef ENABLE_DEBUG
+#if (LD2410ASYNC_DEBUG_LEVEL > 0)
if (result == AsyncCommandResult::SUCCESS) {
DEBUG_PRINT_MILLIS;
DEBUG_PRINTLN("Config mode disabled due to inactivity");
@@ -1182,29 +1611,57 @@ void LD2410Async::handleInactivityDisableConfigmodeCallback(LD2410Async* sender,
}
+
void LD2410Async::handleInactivity() {
- if (inactivityHandlingEnabled) {
+ if (inactivityHandlingEnabled && inactivityHandlingTimeoutMs > 0) {
+ unsigned long timeoutToUse = inactivityHandlingTimeoutMs;
+ if (timeoutToUse < asyncCommandTimeoutMs + 1000) {
+ timeoutToUse = asyncCommandTimeoutMs + 1000;
+ }
unsigned long currentTime = millis();
- unsigned long inactiveDurationMs = currentTime - lastActivityMs;
- if (lastActivityMs != 0 && inactiveDurationMs > activityTimeoutMs) {
- if (!handleInactivityExitConfigModeDone) {
- handleInactivityExitConfigModeDone = true;
- disableConfigModeAsync(handleInactivityDisableConfigmodeCallback, 0);
- }
- else if (inactiveDurationMs > activityTimeoutMs + 5000) {
- rebootAsync(handleInactivityRebootCallback, 0);
- lastActivityMs = currentTime;
+ unsigned long inactiveDurationMs = currentTime - lastSensorActivityTimestamp;
+ if (lastSensorActivityTimestamp != 0 && inactiveDurationMs > timeoutToUse) {
+ if (inactivityHandlingStep == 0 || currentTime - lastInactivityHandlingTimestamp > asyncCommandTimeoutMs + 1000) {
+ lastInactivityHandlingTimestamp = currentTime;
+
+ switch (inactivityHandlingStep++) {
+ case 0:
+ DEBUG_PRINT_MILLIS;
+ DEBUG_PRINTLN("Inactivity handling cancels pending async operations");
+ asyncCancel();
+ break;
+ case 1:
+ DEBUG_PRINT_MILLIS;
+ DEBUG_PRINTLN("Inactivity handling tries to force disable config mode");
+ // We don't care about the result or a callback, if the command has the desired effect, fine; otherwise we will try to reboot the sensor anyway
+ disableConfigModeInternalAsync(true, nullptr);
+ break;
+ case 3:
+ DEBUG_PRINT_MILLIS;
+ DEBUG_PRINTLN("Inactivity handling reboots the sensor");
+ // We don't care about the result, if the command has the desired effect, fine; otherwise we will try to reboot the sensor anyway
+ rebootAsync(true, nullptr);
+
+ break;
+ default:
+ DEBUG_PRINT_MILLIS;
+ DEBUG_PRINTLN("Inactivity handling could not revert sensor to normal operation. Reset the inactivity timeout and try again after the configured inactivity period.");
+ // Inactivity handling has tried everything it can do, so reset the inactivity handling steps and call heartbeat. This will ensure inactivity handling will get called again after the inactivity handling period
+ inactivityHandlingStep = 0;
+ heartbeat();
+ break;
+ }
}
}
else {
- handleInactivityExitConfigModeDone = false;
+ inactivityHandlingStep = 0;
}
}
}
void LD2410Async::heartbeat() {
- lastActivityMs = millis();
+ lastSensorActivityTimestamp = millis();
}
void LD2410Async::setInactivityHandling(bool enable) {
@@ -1242,7 +1699,7 @@ void LD2410Async::taskLoop() {
while (!taskStop) {
processReceivedData();
- handleAsyncCommandCallbackTimeout();
+ sendCommandAsyncHandleTimeout();
handleInactivity();
vTaskDelay(10 / portTICK_PERIOD_MS);
diff --git a/src/LD2410Async.h b/src/LD2410Async.h
index ea198a7..c652489 100644
--- a/src/LD2410Async.h
+++ b/src/LD2410Async.h
@@ -1,218 +1,48 @@
-#pragma once
+#pragma once
-#define ENABLE_DEBUG
-
-//#define ENABLE_DEBUG_DATA
-
-#ifndef ARDUINO_ARCH_ESP32
-#error "The LD2410Async library is only supported on ESP32 platforms."
-#endif
-
-
-#ifdef ENABLE_DEBUG
-
-#define DEBUG_PRINT_MILLIS \
- { \
- Serial.print(millis()); \
- Serial.print(" "); \
- }
-
-
-#define DEBUG_PRINT(...) \
- { \
- Serial.print(__VA_ARGS__); \
- }
-
-
-#define DEBUG_PRINTLN(...) \
- { \
- Serial.println(__VA_ARGS__); \
- }
-
-#define DEBUG_PRINTBUF(...) \
- { \
- printBuf(__VA_ARGS__); \
- }
-
-#else
-
-#define DEBUG_PRINT(...)
-#define DEBUG_PRINTLN(...)
-#define DEBUG_PRINTBUF(...)
-#endif
-
-#ifdef ENABLE_DEBUG_DATA
-
-#define DEBUG_PRINT_DATA(...) \
- { \
- Serial.print(__VA_ARGS__); \
- }
-
-
-#define DEBUG_PRINTLN_DATA(...) \
- { \
- Serial.println(__VA_ARGS__); \
- }
+/**
+ * @brief Use the following defines to set the debug level for the library.
+ *
+ * @details
+ * LD2410ASYNC_DEBUG_LEVEL controls debug messages for commands and command responses/ACKs from the sensor.
+ * LD2410ASYNC_DEBUG_DATA_LEVEL controls debug messages for received detection data.
+ *
+ * For both defines, 0 turns off all debug messages, 1 enables the messages, and 2 enables additional output of the received data.
+ *
+ * @note
+ * Don't enable debug messages in production. They will clutter the compiled build with numerous print commands, increasing the build size
+ * and negatively impacting performance.
+ */
+#define LD2410ASYNC_DEBUG_LEVEL 0
+#define LD2410ASYNC_DEBUG_DATA_LEVEL 0
-#define DEBUG_PRINTBUF_DATA(...) \
- { \
- printBuf(__VA_ARGS__); \
- }
-#else
-#define DEBUG_PRINT_DATA(...)
-#define DEBUG_PRINTLN_DATA(...)
-#define DEBUG_PRINTBUF_DATA(...)
-#endif
#include "Arduino.h"
-#define LD2410_BUFFER_SIZE 0x40
+#include "Ticker.h"
+#include "LD2410Debug.h"
+#include "LD2410Types.h"
+#include "LD2410Defs.h"
/**
- * @brief Asynchronous driver for the LD2410 human presence radar sensor.
- *
- * The LD2410 is a mmWave radar sensor capable of detecting both moving and
- * stationary targets, reporting presence, distance, and per-gate signal strength.
- * This class implements a non-blocking, asynchronous interface for communicating
- * with the sensor over a UART stream (HardwareSerial, SoftwareSerial, etc.).
- *
- * ## Features
- * - Continuous background task that parses incoming frames and updates data.
- * - Access to latest detection results via getDetectionData() or getDetectionDataRef().
- * - Access to current configuration via getConfigData() or getConfigDataRef().
- * - Asynchronous commands for configuration (with callbacks).
- * - Support for engineering mode (per-gate signal values).
- * - Automatic inactivity handling (optional recovery and reboot).
- * - Utility methods for safe enum conversion and debugging output.
+ * @brief Asynchronous driver class for the LD2410 human presence radar sensor.
*
- * ## Accessing data
- * You can either clone the structs (safe to modify) or access them by reference (efficient read-only):
+ * @details This class provides a non-blocking interface for communicating with the LD2410 sensor,
+ * allowing for efficient data retrieval and configuration without halting the main program flow.
+ * It supports multiple instances and features automatic handling of communication timeouts and errors.
+ * It supports all native commands of the LD2410 and has several additional high level commands for more consistent access to the sensor.
*
- * ### Example: Access detection data without cloning
- * @code
- * const DetectionData& data = radar.getDetectionDataRef(); // no copy
- * Serial.print("Target state: ");
- * Serial.println(static_cast(data.targetState));
- * @endcode
- *
- * ### Example: Clone config data, modify, and write back
- * @code
- * ConfigData cfg = radar.getConfigData(); // clone
- * cfg.noOneTimeout = 60;
- * radar.setConfigDataAsync(cfg, [](LD2410Async* sender,
- * AsyncCommandResult result,
- * byte) {
- * if (result == AsyncCommandResult::SUCCESS) {
- * Serial.println("Config updated successfully!");
- * }
- * });
- * @endcode
- *
- * ## Usage
- * Typical workflow:
- * 1. Construct with a reference to a Stream object connected to the sensor.
- * 2. Call begin() to start the background task.
- * 3. Register callbacks for detection data and/or config updates.
- * 4. Use async commands to adjust sensor configuration as needed.
- * 5. Call end() to stop background processing if no longer required.
- *
- * Example:
- * @code
- * HardwareSerial radarSerial(2);
- * LD2410Async radar(radarSerial);
- *
- * void setup() {
- * Serial.begin(115200);
- * radar.begin();
- *
- * // Register callback for detection updates
- * radar.registerDetectionDataReceivedCallback([](LD2410Async* sender, bool presenceDetetced, byte userData) {
- * sender->getDetectionDataRef().print(); // direct access, no copy
- * });
- * }
- *
- * void loop() {
- * // Other application logic
- * }
- * @endcode
*/
-
class LD2410Async {
public:
- /**
- * @brief Represents the current target detection state reported by the radar.
- *
- * Values can be combined internally by the sensor depending on its
- * signal evaluation logic. The AUTO-CONFIG states are special values
- * that are only reported while the sensor is running its
- * self-calibration routine.
- */
-
- enum class TargetState {
- NO_TARGET = 0, ///< No moving or stationary target detected.
- MOVING_TARGET = 1, ///< A moving target has been detected.
- STATIONARY_TARGET = 2, ///< A stationary target has been detected.
- MOVING_AND_STATIONARY_TARGET = 3, ///< Both moving and stationary targets detected.
- AUTOCONFIG_IN_PROGRESS = 4, ///< Auto-configuration routine is running.
- AUTOCONFIG_SUCCESS = 5, ///< Auto-configuration completed successfully.
- AUTOCONFIG_FAILED = 6 ///< Auto-configuration failed.
- };
-
- /**
- * @brief Safely converts a numeric value to a TargetState enum.
- *
- * @param value Raw numeric value (0–6 expected).
- * - 0 → NO_TARGET
- * - 1 → MOVING_TARGET
- * - 2 → STATIONARY_TARGET
- * - 3 → MOVING_AND_STATIONARY_TARGET
- * - 4 → AUTOCONFIG_IN_PROGRESS
- * - 5 → AUTOCONFIG_SUCCESS
- * - 6 → AUTOCONFIG_FAILED
- * @returns The matching TargetState value, or NO_TARGET if invalid.
- */
- static TargetState toTargetState(int value) {
- switch (value) {
- case 0: return TargetState::NO_TARGET;
- case 1: return TargetState::MOVING_TARGET;
- case 2: return TargetState::STATIONARY_TARGET;
- case 3: return TargetState::MOVING_AND_STATIONARY_TARGET;
- case 4: return TargetState::AUTOCONFIG_IN_PROGRESS;
- case 5: return TargetState::AUTOCONFIG_SUCCESS;
- case 6: return TargetState::AUTOCONFIG_FAILED;
- default: return TargetState::NO_TARGET; // safe fallback
- }
- }
-
- /**
- * @brief Converts a TargetState enum value to a human-readable String.
- *
- * Useful for printing detection results in logs or Serial Monitor.
- *
- * @param state TargetState enum value.
- * @returns Human-readable string such as "No target", "Moving target", etc.
- */
- static String targetStateToString(TargetState state) {
- switch (state) {
- case TargetState::NO_TARGET: return "No target";
- case TargetState::MOVING_TARGET: return "Moving target";
- case TargetState::STATIONARY_TARGET: return "Stationary target";
- case TargetState::MOVING_AND_STATIONARY_TARGET: return "Moving and stationary target";
- case TargetState::AUTOCONFIG_IN_PROGRESS: return "Auto-config in progress";
- case TargetState::AUTOCONFIG_SUCCESS: return "Auto-config success";
- case TargetState::AUTOCONFIG_FAILED: return "Auto-config failed";
- default: return "Unknown";
- }
- }
-
-
/**
* @brief Result of an asynchronous command execution.
*
* Every async command reports back its outcome via the callback.
+ *
*/
enum class AsyncCommandResult : byte {
SUCCESS, ///< Command completed successfully and ACK was received.
@@ -223,352 +53,24 @@ class LD2410Async {
- /**
- * @brief Light-dependent control status of the auxiliary output.
- *
- * The radar sensor can control an external output based on ambient
- * light level in combination with presence detection.
- *
- * Use NOT_SET only as a placeholder; it is not a valid configuration value.
- */
- enum class LightControl {
- NOT_SET = -1, ///< Placeholder resp. inital value. Do not use as a config value (will result in abortion of the config command).
- NO_LIGHT_CONTROL = 0, ///< Output is not influenced by light levels.
- LIGHT_BELOW_THRESHOLD = 1, ///< Condition: light < threshold.
- LIGHT_ABOVE_THRESHOLD = 2 ///< Condition: light ≥ threshold.
- };
- /**
- * @brief Safely converts a numeric value to a LightControl enum.
- *
- * @param value Raw numeric value (0–2 expected).
- * - 0 → NO_LIGHT_CONTROL
- * - 1 → LIGHT_BELOW_THRESHOLD
- * - 2 → LIGHT_ABOVE_THRESHOLD
- * @returns The matching LightControl value, or NOT_SET if invalid.
- */
- static LightControl toLightControl(int value) {
- switch (value) {
- case 0: return LightControl::NO_LIGHT_CONTROL;
- case 1: return LightControl::LIGHT_BELOW_THRESHOLD;
- case 2: return LightControl::LIGHT_ABOVE_THRESHOLD;
- default: return LightControl::NOT_SET;
- }
- }
-
-
- /**
- * @brief Logic level behavior of the auxiliary output pin.
- *
- * Determines whether the output pin is active-high or active-low
- * in relation to presence detection.
- *
- * Use NOT_SET only as a placeholder; it is not a valid configuration value.
- */
- enum class OutputControl {
- NOT_SET = -1, ///< Placeholder resp. inital value. Do not use as a config value (will result in abortion of the config command).
- DEFAULT_LOW_DETECTED_HIGH = 0, ///< Default low, goes HIGH when detection occurs.
- DEFAULT_HIGH_DETECTED_LOW = 1 ///< Default high, goes LOW when detection occurs.
- };
-
- /**
- * @brief Safely converts a numeric value to an OutputControl enum.
- *
- * @param value Raw numeric value (0–1 expected).
- * - 0 → DEFAULT_LOW_DETECTED_HIGH
- * - 1 → DEFAULT_HIGH_DETECTED_LOW
- * @returns The matching OutputControl value, or NOT_SET if invalid.
- */
- static OutputControl toOutputControl(int value) {
- switch (value) {
- case 0: return OutputControl::DEFAULT_LOW_DETECTED_HIGH;
- case 1: return OutputControl::DEFAULT_HIGH_DETECTED_LOW;
- default: return OutputControl::NOT_SET;
- }
- }
-
- /**
- * @brief State of the automatic threshold configuration routine.
- *
- * Auto-config adjusts the sensitivity thresholds for optimal detection
- * in the current environment. This process may take several seconds.
- *
- * Use NOT_SET only as a placeholder; it is not a valid configuration value.
- */
- enum class AutoConfigStatus {
- NOT_SET = -1, ///< Status not yet retrieved.
- NOT_IN_PROGRESS, ///< Auto-configuration not running.
- IN_PROGRESS, ///< Auto-configuration is currently running.
- COMPLETED ///< Auto-configuration finished (success or failure).
- };
-
- /**
- * @brief Safely converts a numeric value to an AutoConfigStatus enum.
- *
- * @param value Raw numeric value (0–2 expected).
- * - 0 → NOT_IN_PROGRESS
- * - 1 → IN_PROGRESS
- * - 2 → COMPLETED
- * @returns The matching AutoConfigStatus value, or NOT_SET if invalid.
- */
- static AutoConfigStatus toAutoConfigStatus(int value) {
- switch (value) {
- case 0: return AutoConfigStatus::NOT_IN_PROGRESS;
- case 1: return AutoConfigStatus::IN_PROGRESS;
- case 2: return AutoConfigStatus::COMPLETED;
- default: return AutoConfigStatus::NOT_SET;
- }
- }
-
-
- /**
- * @brief Supported baud rates for the sensor’s UART interface.
- *
- * These values correspond to the configuration commands accepted
- * by the LD2410. After changing the baud rate, a sensor reboot
- * is required, and the ESP32’s UART must be reconfigured to match.
- */
- enum class Baudrate {
- BAUDRATE_9600 = 1, ///< 9600 baud.
- BAUDRATE_19200 = 2, ///< 19200 baud.
- BAUDRATE_38400 = 3, ///< 38400 baud.
- BAUDRATE_57600 = 4, ///< 57600 baud.
- BAUDRATE_115200 = 5, ///< 115200 baud.
- BAUDRATE_230500 = 6, ///< 230400 baud.
- BAUDRATE_256000 = 7, ///< 256000 baud (factory default).
- BAUDRATE_460800 = 8 ///< 460800 baud (high-speed).
- };
-
- /**
- * @brief Distance resolution per gate for detection.
- *
- * The resolution defines how fine-grained the distance measurement is:
- * - RESOLUTION_75CM: Coarser, maximum range up to ~6 m, each gate ≈ 0.75 m wide.
- * - RESOLUTION_20CM: Finer, maximum range up to ~1.8 m, each gate ≈ 0.20 m wide.
- *
- * Use NOT_SET only as a placeholder; it is not a valid configuration value.
- */
- enum class DistanceResolution {
- NOT_SET = -1, ///< Placeholder resp. inital value. Do not use as a config value (will result in abortion of the config command).
- RESOLUTION_75CM = 0, ///< Each gate ~0.75 m, max range ~6 m.
- RESOLUTION_20CM = 1 ///< Each gate ~0.20 m, max range ~1.8 m.
- };
- /**
- * @brief Safely converts a numeric value to a DistanceResolution enum.
- *
- * @param value Raw numeric value (typically from a sensor response).
- * Expected values:
- * - 0 → RESOLUTION_75CM
- * - 1 → RESOLUTION_20CM
- * @returns The matching DistanceResolution value, or NOT_SET if invalid.
- */
- static DistanceResolution toDistanceResolution(int value) {
- switch (value) {
- case 0: return DistanceResolution::RESOLUTION_75CM;
- case 1: return DistanceResolution::RESOLUTION_20CM;
- default: return DistanceResolution::NOT_SET;
- }
- }
-
- /**
- * @brief Holds the most recent detection data reported by the radar.
- *
- * This structure is continuously updated as new frames arrive.
- * Values reflect either the basic presence information or, if
- * engineering mode is enabled, per-gate signal details.
- */
- struct DetectionData
- {
- unsigned long timestamp = 0; ///< Timestamp (ms since boot) when this data was received.
-
- // === Basic detection results ===
-
- bool engineeringMode = false; ///< True if engineering mode data was received.
-
- bool presenceDetected = false; ///< True if any target is detected.
- bool movingPresenceDetected = false; ///< True if a moving target is detected.
- bool stationaryPresenceDetected = false; ///< True if a stationary target is detected.
-
- TargetState targetState = TargetState::NO_TARGET; ///< Current detection state.
- unsigned int movingTargetDistance = 0; ///< Distance (cm) to the nearest moving target.
- byte movingTargetSignal = 0; ///< Signal strength (0–100) of the moving target.
- unsigned int stationaryTargetDistance = 0; ///< Distance (cm) to the nearest stationary target.
- byte stationaryTargetSignal = 0; ///< Signal strength (0–100) of the stationary target.
- unsigned int detectedDistance = 0; ///< General detection distance (cm).
-
- // === Engineering mode data ===
- byte movingTargetGateSignalCount = 0; ///< Number of gates with moving target signals.
- byte movingTargetGateSignals[9] = { 0 }; ///< Per-gate signal strengths for moving targets.
-
- byte stationaryTargetGateSignalCount = 0; ///< Number of gates with stationary target signals.
- byte stationaryTargetGateSignals[9] = { 0 }; ///< Per-gate signal strengths for stationary targets.
-
- byte lightLevel = 0; ///< Reported ambient light level (0–255).
- bool outPinStatus = 0; ///< Current status of the OUT pin (true = high, false = low).
-
-
- /**
- * @brief Debug helper: print detection data contents to Serial.
- */
- void print() const {
- Serial.println("=== DetectionData ===");
-
- Serial.print(" Timestamp: ");
- Serial.println(timestamp);
-
- Serial.print(" Engineering Mode: ");
- Serial.println(engineeringMode ? "Yes" : "No");
-
- Serial.print(" Target State: ");
- Serial.println(static_cast(targetState));
-
- Serial.print(" Moving Target Distance (cm): ");
- Serial.println(movingTargetDistance);
-
- Serial.print(" Moving Target Signal: ");
- Serial.println(movingTargetSignal);
-
- Serial.print(" Stationary Target Distance (cm): ");
- Serial.println(stationaryTargetDistance);
-
- Serial.print(" Stationary Target Signal: ");
- Serial.println(stationaryTargetSignal);
-
- Serial.print(" Detected Distance (cm): ");
- Serial.println(detectedDistance);
-
- Serial.print(" Light Level: ");
- Serial.println(lightLevel);
-
- Serial.print(" OUT Pin Status: ");
- Serial.println(outPinStatus ? "High" : "Low");
-
- // --- Engineering mode fields ---
- if (engineeringMode) {
- Serial.println(" --- Engineering Mode Data ---");
-
- Serial.print(" Moving Target Gate Signal Count: ");
- Serial.println(movingTargetGateSignalCount);
-
- Serial.print(" Moving Target Gate Signals: ");
- for (int i = 0; i < movingTargetGateSignalCount; i++) {
- Serial.print(movingTargetGateSignals[i]);
- if (i < movingTargetGateSignalCount - 1) Serial.print(",");
- }
- Serial.println();
-
- Serial.print(" Stationary Target Gate Signal Count: ");
- Serial.println(stationaryTargetGateSignalCount);
-
- Serial.print(" Stationary Target Gate Signals: ");
- for (int i = 0; i < stationaryTargetGateSignalCount; i++) {
- Serial.print(stationaryTargetGateSignals[i]);
- if (i < stationaryTargetGateSignalCount - 1) Serial.print(",");
- }
- Serial.println();
- }
-
- Serial.println("======================");
- }
-
- };
-
- /**
- * @brief Stores the sensor’s configuration parameters.
- *
- * This structure represents both static capabilities
- * (e.g. number of gates) and configurable settings
- * (e.g. sensitivities, timeouts, resolution).
- *
- * The values are typically filled by request commands
- * such as requestAllConfigData() or requestGateParametersAsync() or
- * requestAuxControlSettingsAsync() or requestDistanceResolutioncmAsync().
- */
- struct ConfigData {
- // === Radar capabilities ===
- byte numberOfGates = 0; ///< Number of distance gates (2–8). This member is readonly resp. changing its value will not influence the radar sentting when setConfigDataAsync() is called.
-
- byte maxMotionDistanceGate = 0; ///< Furthest gate used for motion detection.
- byte maxStationaryDistanceGate = 0; ///< Furthest gate used for stationary detection.
-
- // === Per-gate sensitivity settings ===
- byte distanceGateMotionSensitivity[9] = { 0 }; ///< Motion sensitivity values per gate (0–100).
- byte distanceGateStationarySensitivity[9] = { 0 }; ///< Stationary sensitivity values per gate (0–100).
-
- // === General detection parameters ===
- unsigned short noOneTimeout = 0; ///< Timeout (seconds) until "no presence" is declared.
-
- // === Resolution and auxiliary controls ===
- DistanceResolution distanceResolution = DistanceResolution::NOT_SET; ///< Current distance resolution. A reboot is required to activate changed setting after calling setConfigDataAsync() is called.
- byte lightThreshold = 0; ///< Threshold for auxiliary light control (0–255).
- LightControl lightControl = LightControl::NOT_SET; ///< Light-dependent auxiliary control mode.
- OutputControl outputControl = OutputControl::NOT_SET; ///< Logic configuration of the OUT pin.
-
- /**
- * @brief Debug helper: print configuration contents to Serial.
- */
- void print() const {
- Serial.println("=== ConfigData ===");
-
- Serial.print(" Number of Gates: ");
- Serial.println(numberOfGates);
-
- Serial.print(" Max Motion Distance Gate: ");
- Serial.println(maxMotionDistanceGate);
-
- Serial.print(" Max Stationary Distance Gate: ");
- Serial.println(maxStationaryDistanceGate);
-
- Serial.print(" Motion Sensitivity: ");
- for (int i = 0; i < 9; i++) {
- Serial.print(distanceGateMotionSensitivity[i]);
- if (i < 8) Serial.print(",");
- }
- Serial.println();
-
- Serial.print(" Stationary Sensitivity: ");
- for (int i = 0; i < 9; i++) {
- Serial.print(distanceGateStationarySensitivity[i]);
- if (i < 8) Serial.print(",");
- }
- Serial.println();
-
- Serial.print(" No One Timeout: ");
- Serial.println(noOneTimeout);
-
- Serial.print(" Distance Resolution: ");
- Serial.println(static_cast(distanceResolution));
-
- Serial.print(" Light Threshold: ");
- Serial.println(lightThreshold);
-
- Serial.print(" Light Control: ");
- Serial.println(static_cast(lightControl));
-
- Serial.print(" Output Control: ");
- Serial.println(static_cast(outputControl));
-
- Serial.println("===================");
- }
- };
-
-
/**
* @brief Callback signature for asynchronous command completion.
*
* @param sender Pointer to the LD2410Async instance that triggered the callback.
* @param result Outcome of the async operation (SUCCESS, FAILED, TIMEOUT, CANCELED).
- * @param userData User-specified value passed when registering the callback.
+ *
*/
- typedef void (*AsyncCommandCallback)(LD2410Async* sender, AsyncCommandResult result, byte userData);
+ typedef void (*AsyncCommandCallback)(LD2410Async* sender, AsyncCommandResult result);
/**
* @brief Generic callback signature used for simple notifications.
*
* @param sender Pointer to the LD2410Async instance that triggered the callback.
- * @param userData User-specified value passed when registering the callback.
+ *
+ *
*/
- typedef void (*GenericCallback)(LD2410Async* sender, byte userData);
+ typedef void (*GenericCallback)(LD2410Async* sender);
/**
* @brief Callback type for receiving detection data events.
@@ -580,174 +82,60 @@ class LD2410Async {
*
* @param sender Pointer to the LD2410Async instance that triggered the callback.
* @param presenceDetected True if the radar currently detects presence (moving or stationary), false otherwise.
- * @param userData User-defined value passed when registering the callback.
+ *
*/
- typedef void(*DetectionDataCallback)(LD2410Async* sender, bool presenceDetected, byte userData);
-
-
-private:
- //Pointer to the Serial of the LD2410
- Stream* sensor;
-
- //Read frame enums, members and methods
- enum ReadFrameState {
- WAITING_FOR_HEADER,
- ACK_HEADER,
- DATA_HEADER,
- READ_ACK_SIZE,
- READ_DATA_SIZE,
- READ_ACK_PAYLOAD,
- READ_DATA_PAYLOAD
- };
- enum FrameReadResponse
- {
- FAIL = 0,
- ACK,
- DATA
- };
- int readFrameHeaderIndex = 0;
- int payloadSize = 0;
- ReadFrameState readFrameState = ReadFrameState::WAITING_FOR_HEADER;
-
- bool readFramePayloadSize(byte b, ReadFrameState nextReadFrameState);
- FrameReadResponse readFramePayload(byte b, const byte* tailPattern, LD2410Async::FrameReadResponse succesResponseType);
- FrameReadResponse readFrame();
-
-
- //Buffer for received data
- byte receiveBuffer[LD2410_BUFFER_SIZE];
- byte receiveBufferIndex = 0;
-
- //Vars for async command sequences
- static constexpr size_t MAX_COMMAND_SEQUENCE = 15;
- byte commandSequenceBuffer[MAX_COMMAND_SEQUENCE][LD2410_BUFFER_SIZE];
- byte commandSequenceBufferCount = 0;
-
- unsigned long sendAsyncSequenceStartMs = 0;
- AsyncCommandCallback sendAsyncSequenceCallback = nullptr;
- byte sendAsyncSequenceUserData = 0;
- int sendAsyncSequenceIndex = 0;
- bool sendAsyncSequenceInitialConfigModeState = false;
-
- void executeAsyncSequenceCallback(AsyncCommandResult result);
- static void sendCommandSequenceAsyncDisableConfigModeCallback(LD2410Async* sender, AsyncCommandResult result, byte userData = 0);
- static void sendCommandSequenceAsyncCommandCallback(LD2410Async* sender, AsyncCommandResult result, byte userData = 0);
- static void sendCommandSequenceAsyncEnableConfigModeCallback(LD2410Async* sender, AsyncCommandResult result, byte userData = 0);
- bool sendCommandSequenceAsync(AsyncCommandCallback callback, byte userData = 0);
- bool addCommandToSequence(const byte* command);
- bool resetCommandSequence();
-
-
- //Inactivity handling
-
- void heartbeat();
-
- bool inactivityHandlingEnabled = true;
- unsigned long lastActivityMs = 0;
- bool handleInactivityExitConfigModeDone = false;
- void handleInactivity();
- static void handleInactivityRebootCallback(LD2410Async* sender, AsyncCommandResult result, byte userData);
- static void handleInactivityDisableConfigmodeCallback(LD2410Async* sender, AsyncCommandResult result, byte userData);
- const unsigned long activityTimeoutMs = 60000;
-
-
- //Reboot
- static void rebootEnableConfigModeCallback(LD2410Async* sender, AsyncCommandResult result, byte userData = 0);
- static void rebootRebootCallback(LD2410Async* sender, AsyncCommandResult result, byte userData = 0);
-
-
-
-
-
-
- bool processAck();
- bool processData();
-
-
- GenericCallback configUpdateReceivedReceivedCallback = nullptr;
- byte configUpdateReceivedReceivedCallbackUserData = 0;
- void executeConfigUpdateReceivedCallback();
-
- GenericCallback configChangedCallback = nullptr;
- byte configChangedCallbackUserData = 0;
- void executeConfigChangedCallback();
-
-
-
- DetectionDataCallback detectionDataCallback = nullptr;
- byte detectionDataCallbackUserData = 0;
-
- void sendCommand(const byte* command);
-
- TaskHandle_t taskHandle = NULL;
- bool taskStop = false;
- void taskLoop();
-
-
- //Private async variables and methods
- const unsigned long asyncCommandTimeoutMs = 5000;
-
- bool sendCommandAsync(const byte* command, AsyncCommandCallback callback, byte userData = 0);
- void executeAsyncCommandCallback(byte commandCode, AsyncCommandResult result);
- void handleAsyncCommandCallbackTimeout();
-
- bool sendConfigCommandAsync(const byte* command, AsyncCommandCallback callback, byte userData = 0);
+ typedef void(*DetectionDataCallback)(LD2410Async* sender, bool presenceDetected);
- AsyncCommandCallback asyncCommandCallback = nullptr;
- byte asyncCommandCallbackUserData = 0;
- unsigned long asyncCommandStartMs = 0;
- byte asyncCommandCommandCode = 0;
- void processReceivedData();
+public:
-public:
/**
- * @brief Latest detection results from the radar.
- *
- * Updated automatically whenever new data frames are received.
- * Use registerDetectionDataReceivedCallback() to be notified
- * whenever this struct changes.
- */
- DetectionData detectionData;
+ * @brief Latest detection results from the radar.
+ *
+ * @details Updated automatically whenever new data frames are received.
+ * Use onDetectionDataReceived() to be notified
+ * whenever this struct changes.
+ * Use getDetectionData() or getDetectionDataRef() to access the current values, rather than accessing the struct directly.
+ *
+ *
+ */
+ LD2410Types::DetectionData detectionData;
/**
* @brief Current configuration parameters of the radar.
*
- * Filled when configuration query commands are issued
- * (e.g. requestAllConfigData() or requestGateParametersAsync() ect). Can be modified and
- * sent back using setConfigDataAsync().
+ * @details Filled when configuration query commands are issued
+ * (e.g. requestAllConfigSettingsAsync() or requestGateParametersAsync(), etc.).
+ * Use onConfigDataReceived() to be notified when data in this struct changes.
+ * Use getConfigData() or getConfigDataRef() to access the current values, rather than accessing the struct directly.
+ *
+ * The structure contains only uninitialized data if config data is not queried explicitly.
*
- * Structure will contain only uninitilaized data if config data is not queried explicitly.
*/
- ConfigData configData;
+ LD2410Types::ConfigData configData;
/**
- * @brief Protocol version reported by the radar.
- *
- * This value is set when entering config mode. It can be useful
- * for compatibility checks between firmware and library.
- */
- unsigned long protocolVersion = 0;
+ * @brief Static data of the radar
+ *
+ * @details Filled when config mode is enabled (protocol version and buffer size)
+ * and when issuing query commands for the static data (requestAllStaticDataAsync(), requestFirmwareAsync(), requestBluetoothMacAddressAsync()).
+ */
+ LD2410Types::StaticData staticData;
+
- /**
- * @brief Buffer size reported by the radar protocol.
- *
- * Set when entering config mode. Typically not required by users
- * unless debugging low-level protocol behavior.
- */
- unsigned long bufferSize = 0;
/**
* @brief True if the sensor is currently in config mode.
*
* Config mode must be enabled using enableConfigModeAsync() before sending configuration commands.
* After sending config commands, always disable the config mode using disableConfigModeAsync(),
- * otherwiese the radar will not send any detection data.
+ * otherwise the radar will not send any detection data.
+ *
*/
bool configModeEnabled = false;
@@ -759,64 +147,49 @@ class LD2410Async {
* signal data in addition to basic detection data.
*
* Use enableEngineeringModeAsync() and disableEngineeringModeAsync() to control the engineering mode.
+ *
*/
bool engineeringModeEnabled = false;
- /**
- * @brief Firmware version string of the radar.
- *
- * Populated by requestFirmwareAsync(). Format is usually
- * "major.minor.build".
- */
- String firmware = "";
+
+
/**
- * @brief MAC address of the radar’s Bluetooth module (if available).
+ * @brief Current status of the auto-configuration routine.
*
- * Populated by requestBluetoothMacAddressAsync().
- */
- byte mac[6];
- /**
- * @brief MAC address as a human-readable string (e.g. "AA:BB:CC:DD:EE:FF").
+ * Updated by requestAutoConfigStatusAsync().
*
- * Populated by requestBluetoothMacAddressAsync().
*/
- String macString = "";
+ LD2410Types::AutoConfigStatus autoConfigStatus = LD2410Types::AutoConfigStatus::NOT_SET;
- /**
- * @brief Current status of the auto-configuration routine.
- *
- * Updated by requestAutoConfigStatusAsync().
- */
- AutoConfigStatus autoConfigStatus = AutoConfigStatus::NOT_SET;
+ /**********************************************************************************
+ * Constructor
+ ***********************************************************************************/
- /**********************************************************************************
- * Constrcutor
- ***********************************************************************************/
-
- /**
- * @brief Constructs a new LD2410Async instance bound to a given serial stream.
- *
- * The sensor communicates over a UART interface. Pass the corresponding
- * Stream object (e.g. HardwareSerial, SoftwareSerial, or another compatible
- * implementation) that is connected to the LD2410 sensor.
- *
- * Example:
- * @code
- * HardwareSerial radarSerial(2);
- * LD2410Async radar(radarSerial);
- * @endcode
- *
- * @param serial Reference to a Stream object used to exchange data with the sensor.
- */
+ /**
+ * @brief Constructs a new LD2410Async instance bound to a given serial stream.
+ *
+ * The sensor communicates over a UART interface. Pass the corresponding
+ * Stream object (e.g. HardwareSerial, SoftwareSerial, or another compatible
+ * implementation) that is connected to the LD2410 sensor.
+ *
+ * Example:
+ * @code{.cpp}
+ * HardwareSerial radarSerial(2);
+ * LD2410Async radar(radarSerial);
+ * @endcode
+ *
+ * @param serial Reference to a Stream object used to exchange data with the sensor.
+ *
+ */
LD2410Async(Stream& serial);
/**********************************************************************************
- * begin, end
- ***********************************************************************************/
+ * begin, end
+ ***********************************************************************************/
/**
* @brief Starts the background task that continuously reads data from the sensor.
*
@@ -825,37 +198,45 @@ class LD2410Async {
* sensor cannot deliver detection results asynchronously.
*
* @returns true if the task was successfully started, false if already running.
+ *
*/
bool begin();
/**
- * @brief Stops the background task started by begin().
- *
- * After calling end(), no more data will be processed until begin() is called again.
- * This is useful to temporarily suspend radar processing without rebooting.
- *
- * @returns true if the task was stopped, false if it was not active.
- */
+ * @brief Stops the background task started by begin().
+ *
+ * After calling end(), no more data will be processed until begin() is called again.
+ * This is useful to temporarily suspend radar processing without rebooting.
+ *
+ * @returns true if the task was stopped, false if it was not active.
+ *
+ */
bool end();
+
+
/**********************************************************************************
- * Inactivity handling
- ***********************************************************************************/
+ * Inactivity handling
+ ***********************************************************************************/
+
+
+
/**
* @brief Enables or disables automatic inactivity handling of the sensor.
*
- * When inactivity handling is enabled, the library continuously monitors the time
- * since the last activity (received data or command ACK). If no activity is detected
- * for a longer period (defined by activityTimeoutMs), the library will attempt to
- * recover the sensor automatically:
- * 1. It first tries to exit config mode (even if configModeEnabled is false).
- * 2. If no activity is restored within 5 seconds after leaving config mode,
- * the library reboots the sensor.
+ * When inactivity handling is enabled, the library continuously monitors the time
+ * since the last activity of the sensor (received data or ACK). If no activity is detected
+ * for a longer period, the library will attempt to return the sensor to normal detection operation.
*
- * This helps recover the sensor from rare cases where it gets "stuck"
- * in config mode or stops sending data.
+ * It will attempt the following steps (each separated by the timeout period for async commands):
+ * 1. Cancel pending async commands, in case the user's code is still waiting for a callback.
+ * 2. If the cancel did not help, it will try to disable config mode, even if the library thinks config mode is disabled.
+ * 3. As a last step, it will try to reboot the sensor.
+ *
+ * If all those recovery steps don't work, the library will try again to recover the sensor after the inactivity timeout period has elapsed.
*
* @param enable Pass true to enable inactivity handling, false to disable it.
+ *
*/
void setInactivityHandling(bool enable);
@@ -863,6 +244,9 @@ class LD2410Async {
* @brief Convenience method: enables inactivity handling.
*
* Equivalent to calling setInactivityHandling(true).
+ *
+ * Check setInactivityHandling() for details.
+ *
*/
void enableInactivityHandling() { setInactivityHandling(true); };
@@ -870,53 +254,189 @@ class LD2410Async {
* @brief Convenience method: disables inactivity handling.
*
* Equivalent to calling setInactivityHandling(false).
+ *
+ * Check setInactivityHandling() for details.
*/
void disableInactivityHandling() { setInactivityHandling(false); };
-
- /**********************************************************************************
- * Data received methods
- ***********************************************************************************/
-
/**
- * @brief Registers a callback for new detection data.
- *
- * The callback is invoked whenever a valid data frame is received
- * from the radar, after detectionData has been updated.
- *
- * @param callback Function pointer with signature
- * void methodName(LD2410Async* sender, bool presenceDetected, byte userData).
- * @param userData Optional value that will be passed to the callback.
- */
- void registerDetectionDataReceivedCallback(DetectionDataCallback callback, byte userData=0);
+ * @brief Returns whether inactivity handling is currently enabled.
+ *
+ * @returns true if inactivity handling is enabled, false otherwise.
+ *
+ */
+ bool isInactivityHandlingEnabled() const { return inactivityHandlingEnabled; };
/**
- * @brief Registers a callback for configuration changes.
+ * @brief Sets the timeout period for inactivity handling.
*
- * The callback is invoked whenever the sensor’s configuration
- * has been successfully updated (e.g. after setting sensitivity).
+ * If no data or command ACK is received within this period,
+ * the library will attempt to recover the sensor as described
+ * in setInactivityHandling().
+ *
+ * Default is 60000 ms (1 minute).
+ *
+ * @note
+ * Make sure to set a value that is larger than the timeout for async commands (see getAsyncCommandTimeoutMs() and setAsyncCommandTimeoutMs()).
+ * Otherwise inactivity handling could kick in while commands are still pending (usually waiting for an ACK), which will result in the cancellation of the pending async command.
+ * To ensure this does not happen, inactivity handling will not use the configured timeout if it is shorter than the command timeout and will instead use the command timeout plus 1000 ms.
+ *
+ * @param timeoutMs Timeout in milliseconds (minimum 10000 ms recommended). 0 will disable inactivity checking and handling.
*
- * @param callback Function pointer with signature
- * void methodName(LD2410Async* sender, byte userData).
- * @param userData Optional value that will be passed to the callback.
*/
- void registerConfigChangedCallback(GenericCallback callback, byte userData = 0);
+ void setInactivityTimeoutMs(unsigned long timeoutMs = 60000) { inactivityHandlingTimeoutMs = timeoutMs; };
/**
- * @brief Registers a callback for configuration data updates.
+ * @brief Returns the current inactivity handling timeout period.
+ *
+ * @returns Timeout in milliseconds.
+ *
+ */
+ unsigned long getInactivityTimeoutMs() const { return inactivityHandlingTimeoutMs; };
+
+
+
+ /**********************************************************************************
+ * Callback registration methods
+ ***********************************************************************************/
+
+ /**
+ * @brief Registers a callback for new detection data.
+ *
+ * The callback is invoked whenever a valid detection data frame is received
+ * from the radar. This happens for **every frame**, not only when the
+ * `presenceDetected` flag changes.
+ *
+ * The library updates its internal
+ * @ref LD2410Types::DetectionData "DetectionData" structure with each frame
+ * before the callback is executed. This provides both a quick boolean
+ * `presenceDetected` for simple use cases and full access to detailed
+ * per-gate information if needed.
+ *
+ * @param callback Function pointer with signature
+ * void methodName(LD2410Async* sender, bool presenceDetected).
+ *
+ * @note
+ * Within the callback you can use
+ * @ref LD2410Async::getDetectionData "getDetectionData()" or
+ * @ref LD2410Async::getDetectionDataRef "getDetectionDataRef()" to access
+ * the full @ref LD2410Types::DetectionData "DetectionData" struct, including
+ * distances, energies, and gate values.
+ *
+ * ## Examples
+ *
+ * ### Example: Registering the detection data callback
+ * @code{.cpp}
+ * void myDetectionCallback(LD2410Async* sender, bool presence) {
+ * if (presence) {
+ * Serial.println("Presence detected!");
+ * } else {
+ * Serial.println("No presence.");
+ * }
+ * }
+ *
+ * // Somewhere in setup():
+ * radar.onDetectionDataReceived(myDetectionCallback);
+ * @endcode
+ *
+ * ### Example: Access detection data (cloned copy)
+ * @code{.cpp}
+ * LD2410Types::DetectionData data = radar.getDetectionData(); // clone
+ * Serial.print("Moving distance: ");
+ * Serial.println(data.movingTargetDistance);
+ * @endcode
+ *
+ * ### Example: Access detection data (by reference, no copy)
+ * @code{.cpp}
+ * const LD2410Types::DetectionData& data = radar.getDetectionDataRef(); // no copy
+ * Serial.print("Stationary energy: ");
+ * Serial.println(data.stationaryTargetEnergy);
+ * @endcode
+ */
+ void onDetectionDataReceived(DetectionDataCallback callback);
+
+ /**
+ * @brief Registers a callback for configuration data updates.
*
* The callback is invoked whenever new configuration information
- * has been received from the sensor (e.g. after requestGateParametersAsync()).
+ * has been received from the sensor (e.g. after calling
+ * @ref LD2410Async::requestAllConfigSettingsAsync "requestAllConfigSettingsAsync()").
+ *
+ * @note
+ * Configuration data is **not sent automatically** by the sensor and
+ * is **not updated automatically** when it changes internally. It must
+ * be explicitly requested by the application. Use
+ * @ref LD2410Async::requestAllConfigSettingsAsync() whenever you need
+ * to refresh the local configuration data structure.
+ *
+ * ## Examples
+ *
+ * ### Example: Registering a config data received callback
+ * @code{.cpp}
+ * void onConfigDataReceived(LD2410Async* sender) {
+ * Serial.println("Config data received from sensor.");
+ * }
+ *
+ * // Somewhere in setup():
+ * radar.onConfigDataReceived(onConfigDataReceived);
+ * @endcode
+ *
+ * ### Example: Access configuration data (cloned copy)
+ * @code{.cpp}
+ * LD2410Types::ConfigData cfg = radar.getConfigData(); // clone
+ * Serial.print("No one timeout: ");
+ * Serial.println(cfg.noOneTimeout);
+ * @endcode
+ *
+ * ### Example: Access configuration data (by reference, no copy)
+ * @code{.cpp}
+ * const LD2410Types::ConfigData& cfg = radar.getConfigDataRef(); // no copy
+ * Serial.print("Resolution: ");
+ * Serial.println(static_cast(cfg.distanceResolution));
+ * @endcode
*
* @param callback Function pointer with signature
- * void methodName(LD2410Async* sender, byte userData).
- * @param userData Optional value that will be passed to the callback.
+ * void methodName(LD2410Async* sender).
+ */
+ void onConfigDataReceived(GenericCallback callback);
+
+ /**
+ * @brief Registers a callback that is invoked whenever the sensor's configuration
+ * has been changed successfully.
+ *
+ * This event is triggered after a configuration command (e.g.
+ * @ref configureAllConfigSettingsAsync "configureAllConfigSettingsAsync()" or
+ * @ref configureDistanceResolutionAsync "configureDistanceResolutionAsync()")
+ * has been executed by the sensor.
+ *
+ * @note
+ * - This callback only signals that the sensor acknowledged and applied a change.
+ * - The local @ref LD2410Types::ConfigData "ConfigData" structure is **not**
+ * automatically updated when this event occurs. To refresh the struct, explicitly
+ * request it using @ref requestAllConfigSettingsAsync "requestAllConfigSettingsAsync()".
+ *
+ * @param callback Function pointer with the signature:
+ * `void myCallback(LD2410Async* sender)`
+ *
+ * Example:
+ * @code
+ * void myConfigChangedCallback(LD2410Async* sender) {
+ * Serial.println("Sensor configuration updated.");
+ * // Optionally request fresh config data:
+ * sender->requestAllConfigSettingsAsync(onConfigReceived);
+ * }
+ *
+ * // In setup():
+ * radar.onConfigChanged(myConfigChangedCallback);
+ * @endcode
*/
- void registerConfigUpdateReceivedCallback(GenericCallback callback, byte userData = 0);
+ void onConfigChanged(GenericCallback callback);
/**********************************************************************************
- * Special async commands
- ***********************************************************************************/
+ * Detection and config data access commands
+ ***********************************************************************************/
+ // It is recommended to use the data access commands instead of accessing the detectionData and the configData structs in the class directly.
+
/**
* @brief Returns a clone of the latest detection data from the radar.
*
@@ -931,7 +451,7 @@ class LD2410Async {
* the data that has already been received from the sensor.
*
* ## Example: Access values from a clone
- * @code
+ * @code{.cpp}
* DetectionData data = radar.getDetectionData(); // makes a copy
* if (data.targetState == TargetState::MOVING_TARGET) {
* Serial.print("Moving target at distance: ");
@@ -943,13 +463,47 @@ class LD2410Async {
* - Use when you want a snapshot of the latest detection data.
* - Modify the returned struct freely without affecting the internal state.
*
- * ## Don’t:
+ * ## Don't:
* - Expect this to fetch new data from the sensor (it only returns what was already received).
*
* @returns A copy of the current DetectionData.
+ *
*/
- DetectionData getDetectionData() const;
+ LD2410Types::DetectionData getDetectionData() const;
+
+ /**
+ * @brief Access the current detection data without making a copy.
+ *
+ * This returns a const reference to the internal struct. It is efficient,
+ * but the data must not be modified directly. Use this if you only want
+ * to read values.
+ *
+ * @note Since this returns a reference to the internal data, the values
+ * may change as new frames arrive. Do not store the reference for
+ * long-term use.
+ * @note This function will not query the sensor for data. It just returns
+ * the data that has already been received from the sensor.
+ *
+ * ## Example: Efficient read access without cloning
+ * @code{.cpp}
+ * const DetectionData& data = radar.getDetectionDataRef(); // no copy
+ * Serial.print("Stationary signal: ");
+ * Serial.println(data.stationaryTargetSignal);
+ * @endcode
+ *
+ * ## Do:
+ * - Use when you only need to read values quickly and efficiently.
+ * - Use when printing or inspecting live data without keeping it.
+ *
+ * ## Don't:
+ * - Try to modify the returned struct (it's const).
+ * - Store the reference long-term (it may be updated at any time).
+ *
+ * @returns Const reference to the current DetectionData.
+ *
+ */
+ const LD2410Types::DetectionData& getDetectionDataRef() const { return detectionData; }
/**
@@ -966,7 +520,7 @@ class LD2410Async {
* the data that has already been received from the sensor.
*
* ## Example: Clone, modify, and write back
- * @code
+ * @code{.cpp}
* // Clone current config
* ConfigData cfg = radar.getConfigData();
*
@@ -975,7 +529,7 @@ class LD2410Async {
* cfg.distanceGateMotionSensitivity[3] = 80; // adjust sensitivity
*
* // Send modified config back to sensor
- * radar.setConfigDataAsync(cfg, [](LD2410Async* sender,
+ * radar.configureAllConfigSettingsAsync(cfg, [](LD2410Async* sender,
* AsyncCommandResult result,
* byte) {
* if (result == AsyncCommandResult::SUCCESS) {
@@ -988,47 +542,13 @@ class LD2410Async {
* - Use when you want a clone of the current config to adjust and send back.
* - Safely modify the struct without risking internal state corruption.
*
- * ## Don’t:
- * - Assume this always reflects the live sensor config (it’s only as fresh as the last received config data).
+ * ## Don't:
+ * - Assume this always reflects the live sensor config (it's only as fresh as the last received config data).
*
* @returns A copy of the current ConfigData.
- */
- ConfigData getConfigData() const;
-
-
-
- /**
- * @brief Access the current detection data without making a copy.
- *
- * This returns a const reference to the internal struct. It is efficient,
- * but the data must not be modified directly. Use this if you only want
- * to read values.
- *
- * @note Since this returns a reference to the internal data, the values
- * may change as new frames arrive. Do not store the reference for
- * long-term use.
- * @note This function will not query the sensor for data. It just returns
- * the data that has already been received from the sensor.
- *
- * ## Example: Efficient read access without cloning
- * @code
- * const DetectionData& data = radar.getDetectionDataRef(); // no copy
- * Serial.print("Stationary signal: ");
- * Serial.println(data.stationaryTargetSignal);
- * @endcode
- *
- * ## Do:
- * - Use when you only need to read values quickly and efficiently.
- * - Use when printing or inspecting live data without keeping it.
- *
- * ## Don’t:
- * - Try to modify the returned struct (it’s const).
- * - Store the reference long-term (it may be updated at any time).
*
- * @returns Const reference to the current DetectionData.
*/
- const DetectionData& getDetectionDataRef() const { return detectionData; }
-
+ LD2410Types::ConfigData getConfigData() const;
/**
@@ -1045,7 +565,7 @@ class LD2410Async {
* the data that has already been received from the sensor.
*
* ## Example: Efficient read access without cloning
- * @code
+ * @code{.cpp}
* const ConfigData& cfg = radar.getConfigDataRef(); // no copy
* Serial.print("Resolution: ");
* Serial.println(static_cast(cfg.distanceResolution));
@@ -1055,114 +575,168 @@ class LD2410Async {
* - Use when you only want to inspect configuration quickly.
* - Use for efficient read-only access.
*
- * ## Don’t:
- * - Try to modify the returned struct (it’s const).
+ * ## Don't:
+ * - Try to modify the returned struct (it's const).
* - Keep the reference and assume it will remain valid forever.
*
* @returns Const reference to the current ConfigData.
+ *
*/
- const ConfigData& getConfigDataRef() const { return configData; }
+ const LD2410Types::ConfigData& getConfigDataRef() const { return configData; }
+ /**
+ * Resets the config data to the initial values
+ */
+ void resetConfigData();
+
/**********************************************************************************
- * Special async commands
- ***********************************************************************************/
+ * Special async commands
+ ***********************************************************************************/
/**
* @brief Checks if an asynchronous command is currently pending.
*
* @returns true if there is an active command awaiting an ACK,
* false if the library is idle.
+ *
*/
bool asyncIsBusy();
/**
* @brief Cancels any pending asynchronous command or sequence.
*
- * If canceled, the callback of the running command is invoked
- * with result type CANCELED. After canceling, the sensor may
- * remain in config mode — consider disabling config mode or
- * rebooting to return to detection operation.
+ * @note
+ * Since this command can lead to incomplete operations (in particular with high level commands),
+ * it is not advised to use this command unless really, really necessary. Better wait for the callback
+ * of the current operation.
+ * If canceled, the callback of the running command or command sequence is invoked
+ * with result type CANCELED. After canceling, the sensor may remain in config mode,
+ * consider disabling config mode or rebooting to resume normal detection operation. *
*/
void asyncCancel();
+ /**
+ * @brief Sets the timeout for async command callbacks.
+ *
+ * @note
+ * Make sure the timeout is long enough to allow for the execution of long running commands.
+ * In particular, enabling config mode can take up to 4 seconds; therefore, using a value below 4000 is not recommended.
+ *
+ * @param timeoutMs Timeout in milliseconds (default 4000 ms).
+ *
+ */
+ void setAsyncCommandTimeoutMs(unsigned long timeoutMs) { asyncCommandTimeoutMs = timeoutMs; }
+
+ /**
+ * @brief Returns the current async command timeout.
+ *
+ * @return Timeout in milliseconds.
+ *
+ */
+ unsigned long getAsyncCommandTimeoutMs() const { return asyncCommandTimeoutMs; }
+
+
/**********************************************************************************
- * Config commands
- ***********************************************************************************/
+ * Commands
+ ***********************************************************************************/
+ /*---------------------------------------------------------------------------------
+ - Config mode commands
+ ---------------------------------------------------------------------------------*/
/**
* @brief Enables config mode on the radar.
*
- * Config mode must be enabled before issuing most configuration commands.
- * This command itself is asynchronous — the callback fires once the
+ * Config mode must be enabled before sending any commands (apart from this command) to the sensor.
+ * This command itself is asynchronous - the callback fires once the
* sensor acknowledges the mode switch.
*
* @note If asyncIsBusy() is true, this command will not be sent.
* @note Normal detection data is suspended while config mode is active.
*
* @param callback Callback with signature
- * void(LD2410Async* sender, AsyncCommandResult result, byte userData).
- * @param userData Optional value that will be passed to the callback.
+ * void(LD2410Async* sender, AsyncCommandResult result).
+ *
+ *
+ * @returns true if the command was accepted, false if blocked (typically because another async command is pending).
*
- * @returns true if the command was sent, false if blocked.
*/
- bool enableConfigModeAsync(AsyncCommandCallback callback, byte userData = 0);
+ bool enableConfigModeAsync(AsyncCommandCallback callback) {
+ return enableConfigModeAsync(false, callback);
+ }
/**
- * @brief Disables config mode on the radar.
- *
- * This should be called after finishing configuration, to return
- * the sensor to normal detection operation.
- *
- * @note If an async command is already pending (asyncIsBusy() == true),
- * this command will not be sent.
- *
- * @param callback Callback with signature
- * void(LD2410Async* sender, AsyncCommandResult result, byte userData).
- * @param userData Optional value passed to the callback.
- *
- * @returns true if the command was sent, false otherwise.
- */
- bool disableConfigModeAsync(AsyncCommandCallback callback, byte userData = 0);
-
+ * @brief Enables config mode on the radar.
+ *
+ * Config mode must be enabled before sending any commands (apart from this command) to the sensor.
+ * This command itself is asynchronous - the callback fires once the
+ * sensor acknowledges the mode switch.
+ *
+ * @note If asyncIsBusy() is true, this command will not be sent.
+ * @note Normal detection data is suspended while config mode is active.
+ *
+ * @param callback Callback with signature
+ * void(LD2410Async* sender, AsyncCommandResult result).
+ * @param force Forces the command to send the config mode enable command, even if the current status of the library indicates that config mode is already active. The callback of the method will be called anyway, either after the sent command has sent an ACK or after a minimal delay (if the command is not sent).
+ *
+ * @returns true if the command was accepted, false if blocked (typically because another async command is pending).
+ *
+ */
+ bool enableConfigModeAsync(bool force, AsyncCommandCallback callback);
/**
- * @brief Sets the maximum detection gates and "no-one" timeout.
+ * @brief Disables config mode on the radar.
*
- * This command updates:
- * - Maximum motion detection distance gate (2–8).
- * - Maximum stationary detection distance gate (2–8).
- * - Timeout duration (0–65535 seconds) until "no presence" is declared.
+ * This must be called after finishing sending commands to the sensor, to return
+ * the sensor to normal detection operation.
*
- * @note Requires config mode to be enabled. The method will internally
- * enable/disable config mode if necessary.
- * @note Values outside the allowed ranges are clamped to valid limits.
+ * @note If an async command is already pending (asyncIsBusy() == true),
+ * this command will not be sent.
+ *
+ * @param callback Callback with signature
+ * void(LD2410Async* sender, AsyncCommandResult result).
*
- * @param maxMovingGate Furthest gate used for motion detection (2–8).
- * @param maxStationaryGate Furthest gate used for stationary detection (2–8).
- * @param noOneTimeout Timeout in seconds until "no one" is reported (0–65535).
- * @param callback Callback fired when ACK is received or on failure/timeout.
- * @param userData Optional value passed to the callback.
*
- * @returns true if the command was sent, false otherwise (busy state).
+ * @returns true if the command was accepted, false otherwise (typically because another async command is pending).
+ *
*/
- bool setMaxGateAndNoOneTimeoutAsync(byte maxMovingGate, byte maxStationaryGate, unsigned short noOneTimeout, AsyncCommandCallback callback, byte userData = 0);
+ bool disableConfigModeAsync(AsyncCommandCallback callback) {
+ return disableConfigModeAsync(false, callback);
+ }
/**
- * @brief Requests the current gate parameters from the sensor.
+ * @brief Disables config mode on the radar.
*
- * Retrieves sensitivities, max gates, and timeout settings,
- * which will be written into configData.
+ * This should be called after finishing sending commands to the sensor, to return
+ * the sensor to normal detection operation.
*
- * @note Requires config mode. The method will manage mode switching if needed.
- * @note If an async command is already pending, the request is rejected.
+ * @note If an async command is already pending (asyncIsBusy() == true), this command will not be sent, but will still trigger the callback.
+ * @note If config mode is already disabled and sending the command is forced using the force parameter, the command will timeout, since the sensor will not send an ACK for the command.
*
- * @param callback Callback fired when data is received or on failure.
- * @param userData Optional value passed to the callback.
+ * @param callback Callback with signature
+ * void(LD2410Async* sender, AsyncCommandResult result).
+ *
+ * @param force Forces sending the command to disable config mode, even if local data indicates that config mode is not enabled. If config mode is not enabled, this command will time out when force is true. The callback of the method will be called anyway, either after the sent command has sent an ACK or after a minimal delay (if the command is not sent).
+ *
+ * @returns true if the command was accepted, false otherwise (typically because another async command is pending).
*
- * @returns true if the command was sent, false otherwise.
*/
- bool requestGateParametersAsync(AsyncCommandCallback callback, byte userData = 0);
+ bool disableConfigModeAsync(bool force, AsyncCommandCallback callback);
+
+
+ /**
+ * @brief Detects if config mode is enabled
+ *
+ * @returns true if config mode is enabled, false if config mode is disabled.
+ *
+ */
+ bool isConfigModeEnabled() const {
+ return configModeEnabled;
+ };
+
+ /*---------------------------------------------------------------------------------
+ - Engineering mode commands
+ ---------------------------------------------------------------------------------*/
/**
* @brief Enables engineering mode.
*
@@ -1173,11 +747,12 @@ class LD2410Async {
* @note Requires config mode. Will be enabled automatically if not active.
*
* @param callback Callback fired when ACK is received or on failure.
- * @param userData Optional value passed to the callback.
+ *
*
* @returns true if the command was sent, false otherwise.
+ *
*/
- bool enableEngineeringModeAsync(AsyncCommandCallback callback, byte userData = 0);
+ bool enableEngineeringModeAsync(AsyncCommandCallback callback);
/**
* @brief Disables engineering mode.
@@ -1187,51 +762,110 @@ class LD2410Async {
* @note Requires config mode. Will be enabled automatically if not active.
*
* @param callback Callback fired when ACK is received or on failure.
- * @param userData Optional value passed to the callback.
+ *
+ *
+ * @returns true if the command was sent, false otherwise.
+ *
+ */
+ bool disableEngineeringModeAsync(AsyncCommandCallback callback);
+
+ /**
+ * @brief Detects if engineering mode is enabled
+ *
+ * @returns true if engineering mode is enabled, false if engineering mode is disabled
+ *
+ */
+ bool isEngineeringModeEnabled() const {
+ return engineeringModeEnabled;
+ };
+
+ /*---------------------------------------------------------------------------------
+ - Native sensor commands
+ ---------------------------------------------------------------------------------*/
+ /**
+ * @brief Requests the current gate parameters from the sensor.
+ *
+ * Retrieves sensitivities, max gates, and timeout settings,
+ * which will be written into configData.
+ *
+ * @note Requires config mode. The method will manage mode switching if needed.
+ * @note If an async command is already pending, the request is rejected.
+ *
+ * @param callback Callback fired when data is received or on failure.
+ *
*
* @returns true if the command was sent, false otherwise.
+ *
+ */
+ bool requestGateParametersAsync(AsyncCommandCallback callback);
+
+
+
+ /**
+ * @brief Configures the maximum detection gates and "no-one" timeout on the sensor.
+ *
+ * This command updates:
+ * - Maximum motion detection distance gate (2-8).
+ * - Maximum stationary detection distance gate (2-8).
+ * - Timeout duration (0-65535 seconds) until "no presence" is declared.
+ *
+ * @note Requires config mode to be enabled. The method will internally
+ * enable/disable config mode if necessary.
+ * @note If another async command is pending, this call fails.
+ *
+ * @param maxMovingGate Furthest gate used for motion detection (2-8).
+ * @param maxStationaryGate Furthest gate used for stationary detection (2-8).
+ * @param noOneTimeout Timeout in seconds until "no one" is reported (0-65535).
+ * @param callback Callback fired when ACK is received or on failure/timeout.
+ *
+ *
+ * @returns true if the command was sent, false otherwise (busy state or invalid values).
+ *
*/
- bool disableEngineeringModeAsync(AsyncCommandCallback callback, byte userData = 0);
+ bool configureMaxGateAndNoOneTimeoutAsync(byte maxMovingGate, byte maxStationaryGate, unsigned short noOneTimeout, AsyncCommandCallback callback);
/**
- * @brief Sets sensitivity thresholds for all gates at once.
+ * @brief Configures sensitivity thresholds for all gates at once.
*
* A sequence of commands will be sent, one for each gate.
- * Threshold values are automatically clamped to 0–100.
+ * Threshold values are automatically clamped to 0-100.
*
* @note Requires config mode. Will be managed automatically.
* @note If another async command is pending, this call fails.
*
- * @param movingThresholds Array of 9 sensitivity values for moving targets (0–100).
- * @param stationaryThresholds Array of 9 sensitivity values for stationary targets (0–100).
+ * @param movingThresholds Array of 9 sensitivity values for moving targets (0-100).
+ * @param stationaryThresholds Array of 9 sensitivity values for stationary targets (0-100).
* @param callback Callback fired when all updates are acknowledged or on failure.
- * @param userData Optional value passed to the callback.
+ *
*
* @returns true if the sequence was started, false otherwise.
+ *
*/
- bool setDistanceGateSensitivityAsync(const byte movingThresholds[9], const byte stationaryThresholds[9], AsyncCommandCallback callback, byte userData = 0);
+ bool configureDistanceGateSensitivityAsync(const byte movingThresholds[9], const byte stationaryThresholds[9], AsyncCommandCallback callback);
/**
- * @brief Sets sensitivity thresholds for a single gate.
+ * @brief Configures sensitivity thresholds for a single gate.
*
* Updates both moving and stationary thresholds for the given gate index.
* If the gate index is greater than 8, all gates are updated instead.
*
- * @note Threshold values are automatically clamped to 0–100.
* @note Requires config mode. Will be managed automatically.
+ * @note If another async command is pending, this call fails.
*
- * @param gate Index of the gate (0–8). Values >8 apply to all gates.
- * @param movingThreshold Sensitivity for moving targets (0–100).
- * @param stationaryThreshold Sensitivity for stationary targets (0–100).
+ * @param gate Index of the gate (0-8). Values >8 apply to all gates.
+ * @param movingThreshold Sensitivity for moving targets (0-100).
+ * @param stationaryThreshold Sensitivity for stationary targets (0-100).
* @param callback Callback fired when ACK is received or on failure.
- * @param userData Optional value passed to the callback.
+ *
*
* @returns true if the command was sent, false otherwise.
+ *
+ *
*/
- bool setDistanceGateSensitivityAsync(byte gate, byte movingThreshold, byte stationaryThreshold, AsyncCommandCallback callback, byte userData = 0);
+ bool configureDistanceGateSensitivityAsync(byte gate, byte movingThreshold, byte stationaryThreshold, AsyncCommandCallback callback);
/**
* @brief Requests the firmware version of the sensor.
@@ -1241,44 +875,50 @@ class LD2410Async {
* @note Requires config mode. Will be managed automatically.
*
* @param callback Callback fired when firmware info is received.
- * @param userData Optional value passed to the callback.
+ *
*
* @returns true if the command was sent, false otherwise.
+ *
*/
- bool requestFirmwareAsync(AsyncCommandCallback callback, byte userData = 0);
+ bool requestFirmwareAsync(AsyncCommandCallback callback);
/**
- * @brief Sets the UART baud rate of the sensor.
+ * @brief Configures the UART baud rate of the sensor.
*
* The new baud rate becomes active only after reboot.
- * The ESP32’s Serial interface must also be reconfigured
+ * The ESP32's Serial interface must also be reconfigured
* to the new baud rate after reboot.
*
- * @note Valid values are 1–8. Values outside range are rejected resp. method will fail.
+ * @note Valid values are 1-8. Values outside the range are rejected, and the method will fail.
* @note Requires config mode. Will be managed automatically.
+ * @note If another async command is pending, this call fails.
+ * @note After execution, call rebootAsync() to activate changes.
*
* @param baudRateSetting Numeric setting: 1=9600, 2=19200, 3=38400, 4=57600, 5=115200, 6=230400, 7=25600 (factory default), 8=460800.
* @param callback Callback fired when ACK is received or on failure.
- * @param userData Optional value passed to the callback.
+ *
*
* @returns true if the command was sent, false otherwise.
+ *
*/
- bool setBaudRateAsync(byte baudRateSetting, AsyncCommandCallback callback, byte userData = 0);
-
- /**
- * @brief Sets the baudrate of the serial port of the sensor.
- *
- * The new baudrate will only become active after a reboot of the sensor.
- * If changing the baud rate, remember that you also need to addjust the baud rate of the ESP32 serial that is associated with then sensor.
- *
- * @param baudrate A valid baud rate from the Baudrate enum.
- *
- * @param callback Callback method with void methodName(LD2410Async* sender, AsyncCommandResult result, byte userData) signature. Will be called after the Ack for the command has been received (success=true) or after the command timeout (success=false) or after the command has been canceld (sucess=false).
- * @param userData Optional value that will be passed to the callback function.
- *
- * @returns true if the command has been sent, false if the command cant be sent (typically because another async command is pending).
- */
- bool setBaudRateAsync(Baudrate baudRate, AsyncCommandCallback callback, byte userData = 0);
+ bool configureBaudRateAsync(byte baudRateSetting, AsyncCommandCallback callback);
+
+ /**
+ * @brief Configures the baud rate of the sensor's serial port.
+ *
+ * The new baud rate becomes active only after the sensor reboots.
+ * If you change the baud rate, also adjust the baud rate of the ESP32 serial interface connected to the sensor.
+ *
+ * @note If another async command is pending, this call fails.
+ * @note After execution, call rebootAsync() to activate changes.
+ *
+ * @param baudrate A valid baud rate from the Baudrate enum.
+ * @param callback Callback method with the signature void methodName(LD2410Async* sender, AsyncCommandResult result). The callback is invoked after the ACK for the command has been received (success=true), after the command times out (success=false), or after the command has been canceled (success=false).
+ *
+ * @returns true if the command has been sent, false if the command can't be sent (typically because another async command is pending).
+ *
+ */
+ bool configureBaudRateAsync(LD2410Types::Baudrate baudRate, AsyncCommandCallback callback);
/**
@@ -1290,176 +930,219 @@ class LD2410Async {
* @note After execution, call rebootAsync() to activate changes.
*
* @param callback Callback fired when ACK is received or on failure.
- * @param userData Optional value passed to the callback.
+ *
*
* @returns true if the command was sent, false otherwise.
+ *
*/
- bool restoreFactorySettingsAsync(AsyncCommandCallback callback, byte userData = 0);
+ bool restoreFactorySettingsAsync(AsyncCommandCallback callback);
/**
* @brief Reboots the sensor.
*
* After reboot, the sensor stops responding for a few seconds.
- * Config and engineering mode are reset.
+ * The callback if this method will be triggered as soon as normal operation of the sensor has been detected.
+ * Config and engineering mode are reset once the sensor is back to normal operation.
*
- * @note The reboot of the sensor takes place after the ACK has been sent.
*
* @param callback Callback fired when ACK is received or on failure.
- * @param userData Optional value passed to the callback.
+ *
*
* @returns true if the command was sent, false otherwise.
+ *
*/
- bool rebootAsync(AsyncCommandCallback callback, byte userData = 0);
-
- /**
- * @brief Enables bluetooth
- *
- * @param callback Callback method with void methodName(LD2410Async* sender, AsyncCommandResult result, byte userData) signature. Will be called after the Ack for the command has been received (success=true) or after the command timeout (success=false) or after the command has been canceld (sucess=false).
- * @param userData Optional value that will be passed to the callback function.
- *
- * @returns true if the command has been sent, false if the command cant be sent (typically because another async command is pending).
- */
- bool enableBluetoothAsync(AsyncCommandCallback callback, byte userData = 0);
-
- /**
- * @brief Disables bluetooth
- *
- * @param callback Callback method with void methodName(LD2410Async* sender, AsyncCommandResult result, byte userData) signature. Will be called after the Ack for the command has been received (success=true) or after the command timeout (success=false) or after the command has been canceld (sucess=false).
- * @param userData Optional value that will be passed to the callback function.
- *
- * @returns true if the command has been sent, false if the command cant be sent (typically because another async command is pending).
- */
- bool disableBluetoothAsync(AsyncCommandCallback callback, byte userData = 0);
-
- /**
- * @brief Requests the bluetooth mac address
- *
- * @note The callback fires when the mac address has been received from the sensor (is sent with the ACK).
- *
- * @param callback Callback method with void methodName(LD2410Async* sender, AsyncCommandResult result, byte userData) signature. Will be called after the Ack for the command has been received (success=true) or after the command timeout (success=false) or after the command has been canceld (sucess=false).
- * @param userData Optional value that will be passed to the callback function.
- *
- * @returns true if the command has been sent, false if the command cant be sent (typically because another async command is pending).
- */
- bool requestBluetoothMacAddressAsync(AsyncCommandCallback callback, byte userData = 0);
-
- /**
- * @brief Sets the password for bluetooth access to the sensor.
- *
- * @param password New bluetooth password. Max 6. chars.
- * @param callback Callback method with void methodName(LD2410Async* sender, AsyncCommandResult result, byte userData) signature. Will be called after the Ack for the command has been received (success=true) or after the command timeout (success=false) or after the command has been canceld (sucess=false).
- * @param userData Optional value that will be passed to the callback function.
- *
- * @returns true if the command has been sent, false if the command cant be sent (typically because another async command is pending).
- */
- bool setBluetoothpasswordAsync(const char* password, AsyncCommandCallback callback, byte userData = 0);
-
- /**
- * @brief Sets the password for bluetooth access to the sensor.
- *
- * @param password New bluetooth password. Max 6. chars.
- * @param callback Callback method with void methodName(LD2410Async* sender, AsyncCommandResult result, byte userData) signature. Will be called after the Ack for the command has been received (success=true) or after the command timeout (success=false) or after the command has been canceld (sucess=false).
- * @param userData Optional value that will be passed to the callback function.
- *
- * @returns true if the command has been sent, false if the command cant be sent (typically because another async command is pending).
- */
- bool setBluetoothpasswordAsync(const String& password, AsyncCommandCallback callback, byte userData = 0);
-
- /**
- * @brief Resets the password for bluetooth access to the default value (HiLink)
- * @param callback Callback method with void methodName(LD2410Async* sender, AsyncCommandResult result, byte userData) signature. Will be called after the Ack for the command has been received (success=true) or after the command timeout (success=false) or after the command has been canceld (sucess=false).
- * @param userData Optional value that will be passed to the callback function.
- *
- * @returns true if the command has been sent, false if the command cant be sent (typically because another async command is pending).
- */
- bool resetBluetoothpasswordAsync(AsyncCommandCallback callback, byte userData = 0);
-
- /**
- * @brief Sets the distance resolution of the radar.
+ bool rebootAsync(AsyncCommandCallback callback) {
+ return rebootAsync(false, callback);
+ }
+
+ /**
+ * @brief Reboots the sensor.
*
- * The distance resolution defines the size of each distance gate
- * and the maximum detection range:
- * - RESOLUTION_75CM → longer range, coarser detail.
- * - RESOLUTION_20CM → shorter range, finer detail.
+ * After reboot, the sensor stops responding for a few seconds.
+ * Depending on the value of the dontWaitForNormalOperationAfterReboot parameter, the callback will be triggered once the sensor has sent an ACK for the reboot command (when false) or when normal operation of the sensor resumes.
+ * Config and engineering mode are reset once normal operation of the sensor resumes.
*
- * @note The new resolution takes effect immediately, but some
- * configuration-dependent values may require re-query.
- * @note Requires config mode. Will be managed automatically.
+ * @note If dontWaitForNormalOperationAfterReboot is true, the sensor reboots only after the ACK has been sent. Wait a short period before sending commands to ensure the reboot is complete and the sensor is responsive.
*
- * @param distanceResolution Value from the DistanceResolution enum.
- * Must not be NOT_SET.
- * @param callback Function pointer with signature:
- * void(LD2410Async* sender, AsyncCommandResult result, byte userData).
- * Fired when the ACK is received or on failure/timeout.
- * @param userData Optional value passed to the callback.
+ * @param dontWaitForNormalOperationAfterReboot Controls whether the callback will be triggered once the sensor has sent an ACK for the reboot command (when false) or when normal operation of the sensor resumes (when true).
+ * @param callback Callback fired when ACK is received or on failure.
+ *
+ *
+ * @returns true if the command was sent, false otherwise.
*
- * @returns true if the command was sent, false if invalid parameters
- * or the library is busy.
*/
- bool setDistanceResolutionAsync(DistanceResolution distanceResolution, AsyncCommandCallback callback, byte userData = 0);
+ bool rebootAsync(bool dontWaitForNormalOperationAfterReboot, AsyncCommandCallback callback);
+
/**
- * @brief Sets the distance resolution explicitly to 75 cm per gate.
- *
- * Equivalent to setDistanceResolutionAsync(DistanceResolution::RESOLUTION_75CM).
- *
- * @param callback Function pointer with signature:
- * void(LD2410Async* sender, AsyncCommandResult result, byte userData).
- * @param userData Optional value passed to the callback.
- *
- * @returns true if the command was sent, false otherwise.
- */
- bool setDistanceResolution75cmAsync(AsyncCommandCallback callback, byte userData = 0);
+ * @brief Enables Bluetooth
+ *
+ * @param callback Callback method with void methodName(LD2410Async* sender, AsyncCommandResult result) signature. Will be called after the ACK for the command has been received (success=true) or after the command timeout (success=false) or after the command has been canceled (success=false).
+ *
+ *
+ * @returns true if the command has been sent, false if the command can't be sent (typically because another async command is pending).
+ *
+ */
+ bool enableBluetoothAsync(AsyncCommandCallback callback);
/**
- * @brief Sets the distance resolution explicitly to 20 cm per gate.
- *
- * Equivalent to setDistanceResolutionAsync(DistanceResolution::RESOLUTION_20CM).
- *
- * @param callback Function pointer with signature:
- * void(LD2410Async* sender, AsyncCommandResult result, byte userData).
- * @param userData Optional value passed to the callback.
- *
- * @returns true if the command was sent, false otherwise.
- */
- bool setDistanceResolution20cmAsync(AsyncCommandCallback callback, byte userData = 0);
+ * @brief Disables Bluetooth
+ *
+ * @param callback Callback method with void methodName(LD2410Async* sender, AsyncCommandResult result) signature. Will be called after the ACK for the command has been received (success=true) or after the command timeout (success=false) or after the command has been canceled (success=false).
+ *
+ *
+ * @returns true if the command has been sent, false if the command can't be sent (typically because another async command is pending).
+ *
+ */
+ bool disableBluetoothAsync(AsyncCommandCallback callback);
/**
- * @brief Requests the current distance resolution setting from the sensor.
+ * @brief Requests the Bluetooth MAC address
*
- * The result is written into configData.distanceResolution.
+ * @note The callback fires when the MAC address has been received from the sensor (is sent with the ACK).
*
- * @note Requires config mode. Will be managed automatically.
+ * @param callback Callback method with void methodName(LD2410Async* sender, AsyncCommandResult result) signature. Will be called after the ACK for the command has been received (success=true) or after the command timeout (success=false) or after the command has been canceled (success=false).
+ *
*
- * @param callback Function pointer with signature:
- * void(LD2410Async* sender, AsyncCommandResult result, byte userData).
- * @param userData Optional value passed to the callback.
+ * @returns true if the command has been sent, false if the command can't be sent (typically because another async command is pending).
*
- * @returns true if the command was sent, false otherwise.
*/
- bool requestDistanceResolutioncmAsync(AsyncCommandCallback callback, byte userData = 0);
+ bool requestBluetoothMacAddressAsync(AsyncCommandCallback callback);
/**
- * @brief Sets the auxiliary control parameters (light and output pin).
+ * @brief Sets the password for Bluetooth access to the sensor.
*
- * This configures how the OUT pin behaves depending on light levels
+ * @param password New Bluetooth password. Max 6. chars.
+ * @param callback Callback method with void methodName(LD2410Async* sender, AsyncCommandResult result) signature. Will be called after the ACK for the command has been received (success=true) or after the command timeout (success=false) or after the command has been canceled (success=false).
+ *
+ *
+ * @returns true if the command has been sent, false if the command can't be sent (typically because another async command is pending).
+ *
+ */
+ bool configureBluetoothPasswordAsync(const char* password, AsyncCommandCallback callback);
+
+ /**
+ * @brief Sets the password for Bluetooth access to the sensor.
+ *
+ * @param password New Bluetooth password. Max 6. chars.
+ * @param callback Callback method with void methodName(LD2410Async* sender, AsyncCommandResult result) signature. Will be called after the ACK for the command has been received (success=true) or after the command timeout (success=false) or after the command has been canceled (success=false).
+ *
+ *
+ * @returns true if the command has been sent, false if the command can't be sent (typically because another async command is pending).
+ *
+ */
+ bool configureBluetoothPasswordAsync(const String& password, AsyncCommandCallback callback);
+
+ /**
+ * @brief Resets the password for Bluetooth access to the default value (HiLink)
+ * @param callback Callback method with void methodName(LD2410Async* sender, AsyncCommandResult result) signature. Will be called after the ACK for the command has been received (success=true) or after the command timeout (success=false) or after the command has been canceled (success=false).
+ *
+ *
+ * @returns true if the command has been sent, false if the command can't be sent (typically because another async command is pending).
+ *
+ */
+ bool configureDefaultBluetoothPasswordAsync(AsyncCommandCallback callback);
+
+ /**
+ * @brief Configures the distance resolution of the radar.
+ *
+ * The distance resolution defines the size of each distance gate
+ * and the maximum detection range:
+ * - RESOLUTION_75CM - longer range, coarser detail.
+ * - RESOLUTION_20CM - shorter range, finer detail.
+ *
+ * @note Requires config mode. Will be managed automatically.
+ * @note Requires a reboot to activate value changes. Call rebootAsync() after setting.
+ * @note Fails if another async command is pending.
+ *
+ * @param distanceResolution Value from the DistanceResolution enum.
+ * Must not be NOT_SET.
+ * @param callback Function pointer with signature:
+ * void(LD2410Async* sender, AsyncCommandResult result).
+ * Fired when the ACK is received or on failure/timeout.
+ *
+ *
+ * @returns true if the command was sent, false if invalid parameters
+ * or the library is busy.
+ *
+ */
+ bool configureDistanceResolutionAsync(LD2410Types::DistanceResolution distanceResolution, AsyncCommandCallback callback);
+
+ /**
+ * @brief Configures the distance resolution explicitly to 75 cm per gate.
+ *
+ * Equivalent to configureDistanceResolutionAsync(DistanceResolution::RESOLUTION_75CM).
+ *
+ * @note Requires config mode. Will be managed automatically.
+ * @note Requires a reboot to activate value changes. Call rebootAsync() after setting.
+ * @note Fails if another async command is pending.
+ *
+ * @param callback Function pointer with signature:
+ * void(LD2410Async* sender, AsyncCommandResult result).
+ *
+ *
+ * @returns true if the command was sent, false otherwise.
+ *
+ */
+ bool configureDistanceResolution75cmAsync(AsyncCommandCallback callback);
+
+ /**
+ * @brief Configures the distance resolution explicitly to 20 cm per gate.
+ *
+ * Equivalent to configureDistanceResolutionAsync(DistanceResolution::RESOLUTION_20CM).
+ *
+ * @note Requires config mode. Will be managed automatically.
+ * @note Requires a reboot to activate value changes. Call rebootAsync() after setting.
+ * @note Fails if another async command is pending.
+ *
+ * @param callback Function pointer with signature:
+ * void(LD2410Async* sender, AsyncCommandResult result).
+ *
+ *
+ * @returns true if the command was sent, false otherwise.
+ *
+ */
+ bool configuresDistanceResolution20cmAsync(AsyncCommandCallback callback);
+
+ /**
+ * @brief Requests the current distance resolution setting from the sensor.
+ *
+ * The result is written into configData.distanceResolution.
+ *
+ * @note Requires config mode. Will be managed automatically.
+ *
+ * @param callback Function pointer with signature:
+ * void(LD2410Async* sender, AsyncCommandResult result).
+ *
+ *
+ * @returns true if the command was sent, false otherwise.
+ *
+ */
+ bool requestDistanceResolutionAsync(AsyncCommandCallback callback);
+
+ /**
+ * @brief Configures the auxiliary control parameters (light and output pin).
+ *
+ * This configures how the OUT pin behaves depending on light levels
* and presence detection. Typical use cases include controlling
* an external lamp or relay.
*
* @note Requires config mode. Will be managed automatically.
* @note Both enums must be set to valid values (not NOT_SET).
+ * @note Fails if another async command is pending.
*
* @param lightControl Light control behavior (see LightControl enum).
- * @param lightThreshold Threshold (0–255) used for light-based switching.
+ * @param lightThreshold Threshold (0-255) used for light-based switching.
* @param outputControl Output pin logic configuration (see OutputControl enum).
* @param callback Function pointer with signature:
- * void(LD2410Async* sender, AsyncCommandResult result, byte userData).
+ * void(LD2410Async* sender, AsyncCommandResult result).
* Fired when ACK is received or on failure/timeout.
- * @param userData Optional value passed to the callback.
+ *
*
* @returns true if the command was sent, false otherwise.
+ *
*/
- bool setAuxControlSettingsAsync(LightControl light_control, byte light_threshold, OutputControl output_control, AsyncCommandCallback callback, byte userData = 0);
+ bool configureAuxControlSettingsAsync(LD2410Types::LightControl light_control, byte light_threshold, LD2410Types::OutputControl output_control, AsyncCommandCallback callback);
/**
* @brief Requests the current auxiliary control settings.
@@ -1470,16 +1153,126 @@ class LD2410Async {
* @note Requires config mode. Will be managed automatically.
*
* @param callback Function pointer with signature:
- * void(LD2410Async* sender, AsyncCommandResult result, byte userData).
+ * void(LD2410Async* sender, AsyncCommandResult result).
* Fired when ACK is received or on failure/timeout.
- * @param userData Optional value passed to the callback.
+ *
+ *
+ * @returns true if the command was sent, false otherwise.
+ *
+ */
+ bool requestAuxControlSettingsAsync(AsyncCommandCallback callback);
+
+
+ /**
+ * @brief Starts the automatic configuration (auto-config) routine on the sensor.
+ *
+ * Auto-config lets the radar adjust its internal thresholds and
+ * sensitivities for the current environment. This can take several
+ * seconds to complete and results in updated sensitivity values.
+ *
+ * The progress and result can be checked with requestAutoConfigStatusAsync().
+ *
+ * @note Requires config mode. This method will manage entering and
+ * exiting config mode automatically.
+ * @note Auto-config temporarily suspends normal detection reporting.
+ *
+ * ## Example: Run auto-config
+ * @code{.cpp}
+ * radar.beginAutoConfigAsync([](LD2410Async* sender,
+ * AsyncCommandResult result,
+ * byte) {
+ * if (result == AsyncCommandResult::SUCCESS) {
+ * Serial.println("Auto-config started.");
+ * } else {
+ * Serial.println("Failed to start auto-config.");
+ * }
+ * });
+ * @endcode
+ *
+ * ## Do:
+ * - Use in new environments to optimize detection performance.
+ * - Query status afterwards with requestAutoConfigStatusAsync().
+ *
+ * ## Don't:
+ * - Expect instant results - the sensor needs time to complete the process.
+ *
+ * @param callback Callback with signature:
+ * void(LD2410Async* sender, AsyncCommandResult result).
+ * Fired when the command is acknowledged or on failure/timeout.
+ *
+ *
+ * @returns true if the command was sent, false otherwise.
+ *
+ */
+ bool beginAutoConfigAsync(AsyncCommandCallback callback);
+
+
+ /**
+ * @brief Requests the current status of the auto-config routine.
+ *
+ * The status is written into the member variable autoConfigStatus:
+ * - NOT_IN_PROGRESS - no auto-config running.
+ * - IN_PROGRESS - auto-config is currently running.
+ * - COMPLETED - auto-config finished (success or failure).
+ *
+ * @note Requires config mode. This method will manage mode switching automatically.
+ * @note If another async command is already pending, this call fails.
+ *
+ * ## Example: Check auto-config status
+ * @code{.cpp}
+ * radar.requestAutoConfigStatusAsync([](LD2410Async* sender,
+ * AsyncCommandResult result,
+ * byte) {
+ * if (result == AsyncCommandResult::SUCCESS) {
+ * switch (sender->autoConfigStatus) {
+ * case AutoConfigStatus::NOT_IN_PROGRESS:
+ * Serial.println("Auto-config not running.");
+ * break;
+ * case AutoConfigStatus::IN_PROGRESS:
+ * Serial.println("Auto-config in progress...");
+ * break;
+ * case AutoConfigStatus::COMPLETED:
+ * Serial.println("Auto-config completed.");
+ * break;
+ * default:
+ * Serial.println("Unknown auto-config status.");
+ * }
+ * } else {
+ * Serial.println("Failed to request auto-config status.");
+ * }
+ * });
+ * @endcode
+ *
+ * ## Do:
+ * - Use this after beginAutoConfigAsync() to track progress.
+ * - Use autoConfigStatus for decision-making in your logic.
+ *
+ * ## Don't:
+ * - Assume COMPLETED means success - thresholds should still be verified.
+ *
+ * @param callback Callback with signature:
+ * void(LD2410Async* sender, AsyncCommandResult result).
+ * Fired when the sensor replies or on failure/timeout.
+ *
*
* @returns true if the command was sent, false otherwise.
+ *
*/
- bool requestAuxControlSettingsAsync(AsyncCommandCallback callback, byte userData = 0);
+ bool requestAutoConfigStatusAsync(AsyncCommandCallback callback);
+
+
+
+ /*---------------------------------------------------------------------------------
+ - High level commands
+ ---------------------------------------------------------------------------------*/
+ // High level commands typically encapsulate several native commands.
+ // They provide a more consistent access to the sensors configuration,
+ // e.g. for reading all config data 3 native commands are necessary,
+ // to update the same data up to 12 native commands may be required.
+ // The high-level commands encapsulate both situations into a single command
/**
- * @brief Requests all configuration parameters from the sensor.
+ * @brief Requests all configuration settings from the sensor.
*
* This triggers a sequence of queries that retrieves and updates:
* - Gate parameters (sensitivities, max gates, timeout).
@@ -1487,14 +1280,15 @@ class LD2410Async {
* - Auxiliary light/output control settings.
*
* The results are stored in configData, and the
- * registerConfigUpdateReceivedCallback() is invoked after completion.
+ * onConfigDataReceived() is invoked after completion.
*
+ * @note This is a high-level method that involves multiple commands.
* @note Requires config mode. This method will manage mode switching automatically.
* @note If another async command is already pending, the request fails.
*
* ## Example: Refresh config data
- * @code
- * radar.requestAllConfigData([](LD2410Async* sender,
+ * @code{.cpp}
+ * radar.requestAllConfigSettingsAsync([](LD2410Async* sender,
* AsyncCommandResult result,
* byte) {
* if (result == AsyncCommandResult::SUCCESS) {
@@ -1506,19 +1300,20 @@ class LD2410Async {
*
* ## Do:
* - Use this after connecting to ensure configData is fully populated.
- * - Call before modifying config if you’re unsure of current values.
+ * - Call before modifying config if you're unsure of current values.
*
- * ## Don’t:
+ * ## Don't:
* - Expect it to succeed if another async command is still running.
*
* @param callback Callback with signature:
- * void(LD2410Async* sender, AsyncCommandResult result, byte userData).
+ * void(LD2410Async* sender, AsyncCommandResult result).
* Fired when all config data has been received or on failure.
- * @param userData Optional value passed to the callback.
+ *
*
* @returns true if the command was sent, false otherwise.
+ *
*/
- bool requestAllConfigData(AsyncCommandCallback callback, byte userData = 0);
+ bool requestAllConfigSettingsAsync(AsyncCommandCallback callback);
/**
@@ -1531,12 +1326,13 @@ class LD2410Async {
* The values are written into the public members `firmware`, `mac`,
* and `macString`.
*
+ * @note This is a high-level method that involves multiple commands.
* @note Requires config mode. Managed automatically by this method.
* @note If another async command is already pending, the request fails.
*
* ## Example: Retrieve firmware and MAC
- * @code
- * radar.requestAllStaticData([](LD2410Async* sender,
+ * @code{.cpp}
+ * radar.requestAllStaticDataAsync([](LD2410Async* sender,
* AsyncCommandResult result,
* byte) {
* if (result == AsyncCommandResult::SUCCESS) {
@@ -1553,40 +1349,44 @@ class LD2410Async {
* - Use after initialization to log firmware version and MAC.
* - Useful for debugging or inventory identification.
*
- * ## Don’t:
- * - Expect frequently changing data — this is static information.
+ * ## Don't:
+ * - Expect frequently changing data - this is static information.
*
* @param callback Callback with signature:
- * void(LD2410Async* sender, AsyncCommandResult result, byte userData).
+ * void(LD2410Async* sender, AsyncCommandResult result).
* Fired when static data is received or on failure.
- * @param userData Optional value passed to the callback.
+ *
*
* @returns true if the command was sent, false otherwise.
+ *
*/
- bool requestAllStaticData(AsyncCommandCallback callback, byte userData = 0);
+ bool requestAllStaticDataAsync(AsyncCommandCallback callback);
/**
- * @brief Applies a full ConfigData struct to the sensor.
+ * @brief Applies a full ConfigData struct to the LD2410.
*
- * The provided ConfigData is converted into the appropriate sequence
- * of configuration commands and sent asynchronously. This allows
- * bulk updating of multiple settings (gate sensitivities, timeouts,
- * resolution, auxiliary controls) in one call.
+ * If writeAllConfigData is true, the method will first fetch the current config,
+ * compare it with the provide Config data and then create a command sequence that
+ * will only update the changes config values.
+ * If writeAllConfigData is false, the method will write all values in the provided
+ * ConfigData to the sensor, regardless of whether they differ from the current config.
*
+ * @note This is a high-level method that involves multiple commands (up to 18).
* @note Requires config mode. This method will manage entering and
- * exiting config mode automatically.
+ * exiting config mode automatically (if config mode is not already active).
+ * @note If another async command is already pending, the command fails.
* @note Any members of ConfigData that are left at invalid values
* (e.g. enums set to NOT_SET) will cause the sequence to fail.
*
* ## Example: Clone, modify, and apply config
- * @code
+ * @code{.cpp}
* ConfigData cfg = radar.getConfigData(); // clone current config
* cfg.noOneTimeout = 120; // change timeout
* cfg.distanceGateMotionSensitivity[2] = 75;
*
- * radar.setConfigDataAsync(cfg, [](LD2410Async* sender,
+ * radar.configureAllConfigSettingsAsync(cfg, [](LD2410Async* sender,
* AsyncCommandResult result,
* byte) {
* if (result == AsyncCommandResult::SUCCESS) {
@@ -1596,118 +1396,299 @@ class LD2410Async {
* @endcode
*
* ## Do:
- * - Use this for bulk updates of sensor configuration.
- * - Always start from a clone of getConfigData() to avoid missing fields.
+ * - Always start from a clone of getConfigData() to avoid setting unwanted values for settings that do not need to change.
+ * - If the method's callback does not report SUCCESS, any portion of the config might have been written to the sensor or the sensor might have remained in config mode.
+ * Make sure to take appropriate measures to return the sensor to normal operation mode if required (reboot usually does the trick) and check the config values, if they are what you need.
*
- * ## Don’t:
- * - Pass uninitialized or partially filled ConfigData (may fail).
- * - Expect changes to persist after power cycle without reapplying.
+ * ## Don't:
+ * - Don't write all config data (writeAllConfigData=true) if not really necessary.
+ * This generates unnecessary wear on the sensor's memory.
+ * - Pass uninitialized or partially filled ConfigData (may fail or result in unwanted settings)
+
*
- * @param config The configuration data to be applied.
+ * @param configToWrite The configuration data to be applied.
+ * @param writeAllConfigData If true, all fields in configToWrite are applied. If false, changed values are written.
* @param callback Function with signature:
- * void(LD2410Async* sender, AsyncCommandResult result, byte userData),
+ * void(LD2410Async* sender, AsyncCommandResult result),
* executed when the sequence finishes (success/fail/timeout/cancel).
- * @param userData Optional value passed to the callback.
+ *
*
* @returns true if the command sequence has been started, false otherwise.
+ *
*/
- bool setConfigDataAsync(const ConfigData& config, AsyncCommandCallback callback, byte userData = 0);
+ bool configureAllConfigSettingsAsync(const LD2410Types::ConfigData& configToWrite, bool writeAllConfigData, AsyncCommandCallback callback);
- /**
- * @brief Starts the automatic configuration (auto-config) routine on the sensor.
- *
- * Auto-config lets the radar adjust its internal thresholds and
- * sensitivities for the current environment. This can take several
- * seconds to complete and results in updated sensitivity values.
- *
- * The progress and result can be checked with requestAutoConfigStatusAsync().
- *
- * @note Requires config mode. This method will manage entering and
- * exiting config mode automatically.
- * @note Auto-config temporarily suspends normal detection reporting.
- *
- * ## Example: Run auto-config
- * @code
- * radar.beginAutoConfigAsync([](LD2410Async* sender,
- * AsyncCommandResult result,
- * byte) {
- * if (result == AsyncCommandResult::SUCCESS) {
- * Serial.println("Auto-config started.");
- * } else {
- * Serial.println("Failed to start auto-config.");
- * }
- * });
- * @endcode
- *
- * ## Do:
- * - Use in new environments to optimize detection performance.
- * - Query status afterwards with requestAutoConfigStatusAsync().
- *
- * ## Don’t:
- * - Expect instant results — the sensor needs time to complete the process.
- *
- * @param callback Callback with signature:
- * void(LD2410Async* sender, AsyncCommandResult result, byte userData).
- * Fired when the command is acknowledged or on failure/timeout.
- * @param userData Optional value passed to the callback.
- *
- * @returns true if the command was sent, false otherwise.
- */
- bool beginAutoConfigAsync(AsyncCommandCallback callback, byte userData = 0);
+private:
+ // ============================================================================
+ // Low-level serial interface
+ // ============================================================================
- /**
- * @brief Requests the current status of the auto-config routine.
- *
- * The status is written into the member variable autoConfigStatus:
- * - NOT_IN_PROGRESS → no auto-config running.
- * - IN_PROGRESS → auto-config is currently running.
- * - COMPLETED → auto-config finished (success or failure).
- *
- * @note Requires config mode. This method will manage mode switching automatically.
- * @note If another async command is already pending, this call fails.
- *
- * ## Example: Check auto-config status
- * @code
- * radar.requestAutoConfigStatusAsync([](LD2410Async* sender,
- * AsyncCommandResult result,
- * byte) {
- * if (result == AsyncCommandResult::SUCCESS) {
- * switch (sender->autoConfigStatus) {
- * case AutoConfigStatus::NOT_IN_PROGRESS:
- * Serial.println("Auto-config not running.");
- * break;
- * case AutoConfigStatus::IN_PROGRESS:
- * Serial.println("Auto-config in progress...");
- * break;
- * case AutoConfigStatus::COMPLETED:
- * Serial.println("Auto-config completed.");
- * break;
- * default:
- * Serial.println("Unknown auto-config status.");
- * }
- * } else {
- * Serial.println("Failed to request auto-config status.");
- * }
- * });
- * @endcode
- *
- * ## Do:
- * - Use this after beginAutoConfigAsync() to track progress.
- * - Use autoConfigStatus for decision-making in your logic.
- *
- * ## Don’t:
- * - Assume COMPLETED means success — thresholds should still be verified.
- *
- * @param callback Callback with signature:
- * void(LD2410Async* sender, AsyncCommandResult result, byte userData).
- * Fired when the sensor replies or on failure/timeout.
- * @param userData Optional value passed to the callback.
- *
- * @returns true if the command was sent, false otherwise.
- */
- bool requestAutoConfigStatusAsync(AsyncCommandCallback callback, byte userData = 0);
+ /// Pointer to the Serial/Stream object connected to the LD2410 sensor
+ Stream* sensor;
+
+
+ // ============================================================================
+ // Frame parsing state machine
+ // ============================================================================
+
+ /// States used when parsing incoming frames from the sensor
+ enum ReadFrameState {
+ WAITING_FOR_HEADER, ///< Waiting for frame header sequence
+ ACK_HEADER, ///< Parsing header of an ACK frame
+ DATA_HEADER, ///< Parsing header of a DATA frame
+ READ_ACK_SIZE, ///< Reading payload size field of an ACK frame
+ READ_DATA_SIZE, ///< Reading payload size field of a DATA frame
+ READ_ACK_PAYLOAD, ///< Reading payload content of an ACK frame
+ READ_DATA_PAYLOAD ///< Reading payload content of a DATA frame
+ };
+
+ /// Result type when trying to read a frame
+ enum FrameReadResponse {
+ FAIL = 0, ///< Frame was invalid or incomplete
+ ACK, ///< A valid ACK frame was received
+ DATA ///< A valid DATA frame was received
+ };
+
+ int readFrameHeaderIndex = 0; ///< Current index while matching the frame header
+ int payloadSize = 0; ///< Expected payload size of current frame
+ ReadFrameState readFrameState = ReadFrameState::WAITING_FOR_HEADER; ///< Current frame parsing state
+
+ /// Extract payload size from the current byte and update state machine
+ bool readFramePayloadSize(byte b, ReadFrameState nextReadFrameState);
+
+ /// Read payload bytes until full ACK/DATA frame is assembled
+ FrameReadResponse readFramePayload(byte b, const byte* tailPattern, LD2410Async::FrameReadResponse successResponseType);
+
+ /// State machine entry: read incoming frame and return read response
+ FrameReadResponse readFrame();
+
+
+ // ============================================================================
+ // Receive buffer
+ // ============================================================================
+
+ /// Raw buffer for storing incoming bytes
+ byte receiveBuffer[LD2410Defs::LD2410_Buffer_Size];
+
+ /// Current index into receiveBuffer
+ byte receiveBufferIndex = 0;
+
+
+ // ============================================================================
+ // Asynchronous command sequence handling
+ // ============================================================================
+
+ /// Maximum number of commands in one async sequence
+ static constexpr size_t MAX_COMMAND_SEQUENCE_LENGTH = 15;
+
+ /// Buffer holding queued commands for sequence execution
+ byte commandSequenceBuffer[MAX_COMMAND_SEQUENCE_LENGTH][LD2410Defs::LD2410_Buffer_Size];
+
+ /// Number of commands currently queued in the sequence buffer
+ byte commandSequenceBufferCount = 0;
+
+ /// Timestamp when the current async sequence started
+ unsigned long executeCommandSequenceStartMs = 0;
+
+ /// Callback for current async sequence
+ AsyncCommandCallback executeCommandSequenceCallback = nullptr;
+
+ AsyncCommandResult executeCommandSequenceResultToReport = AsyncCommandResult::SUCCESS;
+
+ /// True if an async sequence is currently pending.
+ bool executeCommandSequencePending = false;
+
+ /// Ticker used for small delay before firing the commandsequence callback when the command sequence is empty.
+ /// This ensures that the callback is always called asynchronously and never directly from the calling context.
+ Ticker executeCommandSequenceOnceTicker;
+
+ /// Index of currently active command in the sequence buffer
+ int executeCommandSequenceIndex = 0;
+
+ /// Stores config mode state before sequence started (to restore later)
+ bool executeCommandSequenceInitialConfigModeState = false;
+
+ /// Finalize an async sequence and invoke its callback
+ void executeCommandSequenceAsyncExecuteCallback(LD2410Async::AsyncCommandResult result);
+
+ /// Final step of an async sequence: restore config mode if needed and call callback
+ void executeCommandSequenceAsyncFinalize(LD2410Async::AsyncCommandResult resultToReport);
+
+ /// Internal callbacks for sequence steps
+ static void executeCommandSequenceAsyncDisableConfigModeCallback(LD2410Async* sender, LD2410Async::AsyncCommandResult result);
+ static void executeCommandSequenceAsyncCommandCallback(LD2410Async* sender, LD2410Async::AsyncCommandResult result);
+
+ /// Start executing an async sequence
+ bool executeCommandSequenceAsync(AsyncCommandCallback callback);
+
+ /// Add one command to the sequence buffer
+ bool addCommandToSequence(const byte* command);
+
+ /// Reset sequence buffer to empty
+ bool resetCommandSequence();
+
+
+ // ============================================================================
+ // Inactivity handling
+ // ============================================================================
+
+ /// Update last-activity timestamp ("I am alive" signal)
+ void heartbeat();
+
+ bool inactivityHandlingEnabled = true; ///< Whether automatic inactivity handling is active
+ unsigned long inactivityHandlingTimeoutMs = 60000; ///< Timeout until recovery action is triggered (ms)
+ unsigned long lastSensorActivityTimestamp = 0; ///< Timestamp of the last sensor activity, i.e., when data from the sensor was last received
+
+ byte inactivityHandlingStep = 0;
+ unsigned long lastInactivityHandlingTimestamp = 0;
+
+ bool handleInactivityExitConfigModeDone = false; ///< Flag to avoid repeating config-mode exit
+
+ /// Main inactivity handler: exit config mode or reboot if stuck
+ void handleInactivity();
+
+ /// Callback for reboot triggered by inactivity handler
+ static void handleInactivityRebootCallback(LD2410Async* sender, LD2410Async::AsyncCommandResult result);
+
+ /// Callback for disabling config mode during inactivity recovery
+ static void handleInactivityDisableConfigmodeCallback(LD2410Async* sender, LD2410Async::AsyncCommandResult result);
+
+
+ // ============================================================================
+ // Reboot handling
+ // ============================================================================
+
+ /// Step 1: Enter config mode before reboot
+ static void rebootAsyncEnableConfigModeCallback(LD2410Async* sender, LD2410Async::AsyncCommandResult result);
+
+ /// Step 2: Issue reboot command
+ static void rebootAsyncRebootCallback(LD2410Async* sender, LD2410Async::AsyncCommandResult result);
+
+ void rebootAsyncFinialize(LD2410Async::AsyncCommandResult result);
+
+ unsigned long rebootAsyncWaitTimeoutMs = 5000;
+ bool rebootAsyncDontWaitForNormalOperationAfterReboot = false;
+ bool rebootAsyncPending = false;
+
+
+ // ============================================================================
+ // ACK/DATA processing
+ // ============================================================================
+
+ /// Process a received ACK frame
+ bool processAck();
+
+ /// Process a received DATA frame
+ bool processData();
+
+
+ // ============================================================================
+ // Callbacks
+ // ============================================================================
+
+ GenericCallback configUpdateReceivedReceivedCallback = nullptr; ///< Callback for new config data received
+ void executeConfigUpdateReceivedCallback(); ///< Execute config-update callback
+
+ GenericCallback configChangedCallback = nullptr; ///< Callback for successful config change
+ void executeConfigChangedCallback(); ///< Execute config-changed callback
+
+ DetectionDataCallback detectionDataCallback = nullptr; ///< Callback for new detection data
+
+ // ============================================================================
+ // Command sending
+ // ============================================================================
+
+ /// Send raw command bytes to the sensor
+ void sendCommand(const byte* command);
+
+
+ // ============================================================================
+ // FreeRTOS task management
+ // ============================================================================
+
+ TaskHandle_t taskHandle = NULL; ///< Handle to FreeRTOS background task
+ bool taskStop = false; ///< Stop flag for task loop
+ void taskLoop(); ///< Background task loop for reading data
+
+
+ // ============================================================================
+ // Async command handling
+ // ============================================================================
+
+ ///< Timeout for async commands in ms (default 4000).
+ unsigned long asyncCommandTimeoutMs = 4000;
+
+ /// Send a generic async command
+ bool sendCommandAsync(const byte* command, byte retries, AsyncCommandCallback callback);
+
+ // If the number of retries is not defined, there will be no retries
+ bool sendCommandAsync(const byte* command, AsyncCommandCallback callback) {
+ return sendCommandAsync(command, 0, callback);
+ };
+
+
+ /// Invoke async command callback with result
+ void sendCommandAsyncExecuteCallback(byte commandCode, LD2410Async::AsyncCommandResult result);
+
+ /// Stores the data that is needed for the execution of the callback later on
+ void sendCommandAsyncStoreDataForCallback(const byte* command, byte retries, AsyncCommandCallback callback);
+
+ /// Handle async command timeout
+ void sendCommandAsyncHandleTimeout();
+
+ /// Send async command that modifies configuration
+ bool sendConfigCommandAsync(const byte* command, AsyncCommandCallback callback);
+
+ AsyncCommandCallback sendCommandAsyncCallback = nullptr; ///< Current async command callback
+ unsigned long sendCommandAsyncStartMs = 0; ///< Timestamp when async command started
+ byte sendCommandAsyncCommandCode = 0; ///< Last command code issued
+ bool sendCommandAsyncCommandPending = false; ///< True if an async command is currently pending.
+ byte sendCommandAsyncCommandBuffer[LD2410Defs::LD2410_Buffer_Size];
+ byte sendCommandAsyncRetriesLeft = 0;
+ // ============================================================================
+ // Data processing
+ // ============================================================================
+
+ /// Main dispatcher: process incoming bytes into frames, update state, trigger callbacks
+ void processReceivedData();
+
+ // ============================================================================
+ // Config mode
+ // ============================================================================
+ bool enableConfigModeInternalAsync(bool force, AsyncCommandCallback callback);
+ bool enableConfigModeInternalAsync(AsyncCommandCallback callback) {
+ return enableConfigModeInternalAsync(false, callback);
+ }
+
+ bool disableConfigModeInternalAsync(bool force, AsyncCommandCallback callback);
+
+ bool disableConfigModeInternalAsync(AsyncCommandCallback callback) {
+ return disableConfigModeInternalAsync(false, callback);
+ };
+ Ticker configModeOnceTicker; // Used for a short delay when config mode just has to execute the callback
+
+ // ============================================================================
+ // Config writing
+ // ============================================================================
+ LD2410Types::ConfigData configureAllConfigSettingsAsyncConfigDataToWrite;
+ bool configureAllConfigSettingsAsyncWriteFullConfig = false;
+ AsyncCommandCallback configureAllConfigSettingsAsyncConfigCallback = nullptr;
+ bool configureAllConfigSettingsAsyncConfigActive = false;
+ bool configureAllConfigSettingsAsyncConfigInitialConfigMode = false;
+ AsyncCommandResult configureAllConfigSettingsAsyncResultToReport = LD2410Async::AsyncCommandResult::SUCCESS;
+
+ void configureAllConfigSettingsAsyncExecuteCallback(LD2410Async::AsyncCommandResult result);
+ static void configureAllConfigSettingsAsyncConfigModeDisabledCallback(LD2410Async* sender, LD2410Async::AsyncCommandResult result);
+ void configureAllConfigSettingsAsyncFinalize(LD2410Async::AsyncCommandResult resultToReport);
+ static void configureAllConfigSettingsAsyncWriteConfigCallback(LD2410Async* sender, LD2410Async::AsyncCommandResult result);
+ bool configureAllConfigSettingsAsyncWriteConfig();
+ static void configureAllConfigSettingsAsyncRequestAllConfigDataCallback(LD2410Async* sender, LD2410Async::AsyncCommandResult result);
+ bool configureAllConfigSettingsAsyncRequestAllConfigData();
+ static void configureAllConfigSettingsAsyncConfigModeEnabledCallback(LD2410Async* sender, LD2410Async::AsyncCommandResult result);
+
+ bool configureAllConfigSettingsAsyncBuildSaveChangesCommandSequence();
};
diff --git a/src/LD2410CommandBuilder.h b/src/LD2410CommandBuilder.h
index 75e5507..6871636 100644
--- a/src/LD2410CommandBuilder.h
+++ b/src/LD2410CommandBuilder.h
@@ -2,25 +2,30 @@
#include "Arduino.h"
#include "LD2410Async.h"
#include "LD2410Defs.h"
+#include "LD2410Types.h"
namespace LD2410CommandBuilder {
- inline void buildMaxGateCommand(byte* out, byte maxMotionGate, byte maxStationaryGate, unsigned short noOneTimeout) {
- memcpy(out, LD2410Defs::maxGateCommandData, sizeof(LD2410Defs::maxGateCommandData));
+ inline bool buildMaxGateCommand(byte* out, byte maxMotionGate, byte maxStationaryGate, unsigned short noOneTimeout) {
- if (maxMotionGate > 8) maxMotionGate = 8;
- if (maxMotionGate < 2) maxMotionGate = 2;
+ if (maxMotionGate < 2 || maxMotionGate > 8) return false;
+ if (maxStationaryGate < 2 || maxStationaryGate > 8) return false;
- if (maxStationaryGate > 8) maxStationaryGate = 8;
- if (maxStationaryGate < 2) maxStationaryGate = 2;
+ memcpy(out, LD2410Defs::maxGateCommandData, sizeof(LD2410Defs::maxGateCommandData));
out[6] = maxMotionGate;
out[12] = maxStationaryGate;
memcpy(&out[18], &noOneTimeout, 2);
+
+ return true;
+
}
- inline void buildGateSensitivityCommand(byte* out, byte gate, byte movingThreshold, byte stationaryThreshold) {
+ inline bool buildGateSensitivityCommand(byte* out, byte gate, byte movingThreshold, byte stationaryThreshold) {
+
+ if (gate > 8 && gate != 0xFF) return false; // 0-8 allowed, or 0xFF (all gates)
+
memcpy(out, LD2410Defs::distanceGateSensitivityConfigCommandData,
sizeof(LD2410Defs::distanceGateSensitivityConfigCommandData));
@@ -38,34 +43,49 @@ namespace LD2410CommandBuilder {
out[12] = movingThreshold;
out[18] = stationaryThreshold;
+ return true;
}
- inline void buildDistanceResolutionCommand(byte* out, LD2410Async::DistanceResolution resolution) {
- if (resolution == LD2410Async::DistanceResolution::RESOLUTION_75CM) {
+ inline bool buildDistanceResolutionCommand(byte* out, LD2410Types::DistanceResolution resolution) {
+ if (resolution == LD2410Types::DistanceResolution::RESOLUTION_75CM) {
memcpy(out, LD2410Defs::setDistanceResolution75cmCommandData,
sizeof(LD2410Defs::setDistanceResolution75cmCommandData));
}
- else if (resolution == LD2410Async::DistanceResolution::RESOLUTION_20CM) {
+ else if (resolution == LD2410Types::DistanceResolution::RESOLUTION_20CM) {
memcpy(out, LD2410Defs::setDistanceResolution20cmCommandData,
sizeof(LD2410Defs::setDistanceResolution20cmCommandData));
}
+ else {
+ return false;
+ }
+ return true;
}
- inline void buildAuxControlCommand(byte* out, LD2410Async::LightControl lightControl, byte lightThreshold, LD2410Async::OutputControl outputControl) {
+ inline bool buildAuxControlCommand(byte* out, LD2410Types::LightControl lightControl, byte lightThreshold, LD2410Types::OutputControl outputControl) {
memcpy(out, LD2410Defs::setAuxControlSettingCommandData,
sizeof(LD2410Defs::setAuxControlSettingCommandData));
+ if (lightControl == LD2410Types::LightControl::NOT_SET) return false;
+ if (outputControl == LD2410Types::OutputControl::NOT_SET) return false;
+
out[4] = byte(lightControl);
out[5] = lightThreshold;
out[6] = byte(outputControl);
+ return true;
}
- inline void buildBaudRateCommand(byte* out, byte baudRateSetting) {
+ inline bool buildBaudRateCommand(byte* out, byte baudRateSetting) {
memcpy(out, LD2410Defs::setBaudRateCommandData, sizeof(LD2410Defs::setBaudRateCommandData));
+ if (baudRateSetting < 1 || baudRateSetting > 8) return false;
out[4] = baudRateSetting;
+ return true;
}
- inline void buildBluetoothPasswordCommand(byte* out, const char* password) {
+ inline bool buildBluetoothPasswordCommand(byte* out, const char* password) {
+ if (!password) return false;
+ size_t len = strlen(password);
+ if (len > 6) return false;
+
memcpy(out, LD2410Defs::setBluetoothPasswordCommandData,
sizeof(LD2410Defs::setBluetoothPasswordCommandData));
@@ -75,6 +95,7 @@ namespace LD2410CommandBuilder {
else
out[4 + i] = byte(' '); // pad with spaces
}
+ return true;
}
}
diff --git a/src/LD2410Debug.h b/src/LD2410Debug.h
new file mode 100644
index 0000000..fa3e57e
--- /dev/null
+++ b/src/LD2410Debug.h
@@ -0,0 +1,71 @@
+
+
+#pragma once
+#include "Arduino.h"
+
+// =======================================================================================
+// Debug level configuration
+// =======================================================================================
+// If not defined by the user/project, initialize to 0 (disabled).
+
+#ifndef LD2410ASYNC_DEBUG_LEVEL
+#define LD2410ASYNC_DEBUG_LEVEL 0
+#endif
+
+#ifndef LD2410ASYNC_DEBUG_DATA_LEVEL
+#define LD2410ASYNC_DEBUG_DATA_LEVEL 0
+#endif
+
+
+// =======================================================================================
+// Helper: Hex buffer print (only compiled if needed)
+// =======================================================================================
+#if (LD2410ASYNC_DEBUG_LEVEL > 1) || (LD2410ASYNC_DEBUG_DATA_LEVEL > 1)
+static inline void printBuf(const byte* buf, byte size)
+{
+ for (byte i = 0; i < size; i++) {
+ String bStr(buf[i], HEX);
+ bStr.toUpperCase();
+ if (bStr.length() == 1) bStr = "0" + bStr;
+ Serial.print(bStr);
+ Serial.print(' ');
+ }
+ Serial.println();
+}
+#endif
+
+// =======================================================================================
+// Normal debug macros
+// =======================================================================================
+#if LD2410ASYNC_DEBUG_LEVEL > 0
+#define DEBUG_PRINT_MILLIS { Serial.print(millis()); Serial.print(" "); }
+#define DEBUG_PRINT(...) { Serial.print(__VA_ARGS__); }
+#define DEBUG_PRINTLN(...) { Serial.println(__VA_ARGS__); }
+#if LD2410ASYNC_DEBUG_LEVEL > 1
+#define DEBUG_PRINTBUF(...) { printBuf(__VA_ARGS__); }
+#else
+#define DEBUG_PRINTBUF(...)
+#endif
+#else
+#define DEBUG_PRINT_MILLIS
+#define DEBUG_PRINT(...)
+#define DEBUG_PRINTLN(...)
+#define DEBUG_PRINTBUF(...)
+#endif
+
+// =======================================================================================
+// Data debug macros
+// =======================================================================================
+#if LD2410ASYNC_DEBUG_DATA_LEVEL > 0
+#define DEBUG_PRINT_DATA(...) { Serial.print(__VA_ARGS__); }
+#define DEBUG_PRINTLN_DATA(...) { Serial.println(__VA_ARGS__); }
+#if LD2410ASYNC_DEBUG_DATA_LEVEL > 1
+#define DEBUG_PRINTBUF_DATA(...) { printBuf(__VA_ARGS__); }
+#else
+#define DEBUG_PRINTBUF_DATA(...)
+#endif
+#else
+#define DEBUG_PRINT_DATA(...)
+#define DEBUG_PRINTLN_DATA(...)
+#define DEBUG_PRINTBUF_DATA(...)
+#endif
diff --git a/src/LD2410Defs.h b/src/LD2410Defs.h
index c973592..b206066 100644
--- a/src/LD2410Defs.h
+++ b/src/LD2410Defs.h
@@ -4,10 +4,12 @@
// LD2410Defs.h
// Internal protocol constants and command templates.
-// Not part of the public API – subject to change.
+// Not part of the public API - subject to change.
namespace LD2410Defs
{
+ constexpr size_t LD2410_Buffer_Size = 0x40;
+
constexpr byte headData[4]{ 0xF4, 0xF3, 0xF2, 0xF1 };
constexpr byte tailData[4]{ 0xF8, 0xF7, 0xF6, 0xF5 };
constexpr byte headConfig[4]{ 0xFD, 0xFC, 0xFB, 0xFA };
diff --git a/src/LD2410Types.h b/src/LD2410Types.h
new file mode 100644
index 0000000..66732bb
--- /dev/null
+++ b/src/LD2410Types.h
@@ -0,0 +1,526 @@
+#pragma once
+
+
+#include
+
+/**
+ * @brief All enums, structs, and type helpers for LD2410Async.
+ */
+namespace LD2410Types {
+ /**
+ * @brief Represents the current target detection state reported by the radar.
+ *
+ * Values can be combined internally by the sensor depending on its
+ * signal evaluation logic. The AUTO-CONFIG states are special values
+ * that are only reported while the sensor is running its
+ * self-calibration routine.
+ *
+ */
+
+ enum class TargetState {
+ NO_TARGET = 0, ///< No moving or stationary target detected.
+ MOVING_TARGET = 1, ///< A moving target has been detected.
+ STATIONARY_TARGET = 2, ///< A stationary target has been detected.
+ MOVING_AND_STATIONARY_TARGET = 3, ///< Both moving and stationary targets detected.
+ AUTOCONFIG_IN_PROGRESS = 4, ///< Auto-configuration routine is running.
+ AUTOCONFIG_SUCCESS = 5, ///< Auto-configuration completed successfully.
+ AUTOCONFIG_FAILED = 6 ///< Auto-configuration failed.
+ };
+
+ /**
+ * @brief Safely converts a numeric value to a TargetState enum.
+ *
+ * @param value Raw numeric value (0-6 expected).
+ * - 0 - NO_TARGET
+ * - 1 - MOVING_TARGET
+ * - 2 - STATIONARY_TARGET
+ * - 3 - MOVING_AND_STATIONARY_TARGET
+ * - 4 - AUTOCONFIG_IN_PROGRESS
+ * - 5 - AUTOCONFIG_SUCCESS
+ * - 6 - AUTOCONFIG_FAILED
+ * @returns The matching TargetState value, or NO_TARGET if invalid.
+ *
+ */
+ static TargetState toTargetState(int value) {
+ switch (value) {
+ case 0: return TargetState::NO_TARGET;
+ case 1: return TargetState::MOVING_TARGET;
+ case 2: return TargetState::STATIONARY_TARGET;
+ case 3: return TargetState::MOVING_AND_STATIONARY_TARGET;
+ case 4: return TargetState::AUTOCONFIG_IN_PROGRESS;
+ case 5: return TargetState::AUTOCONFIG_SUCCESS;
+ case 6: return TargetState::AUTOCONFIG_FAILED;
+ default: return TargetState::NO_TARGET; // safe fallback
+ }
+ }
+
+ /**
+ * @brief Converts a TargetState enum value to a human-readable String.
+ *
+ * Useful for printing detection results in logs or Serial Monitor.
+ *
+ * @param state TargetState enum value.
+ * @returns Human-readable string such as "No target", "Moving target", etc.
+ *
+ */
+ static String targetStateToString(TargetState state) {
+ switch (state) {
+ case TargetState::NO_TARGET: return "No target";
+ case TargetState::MOVING_TARGET: return "Moving target";
+ case TargetState::STATIONARY_TARGET: return "Stationary target";
+ case TargetState::MOVING_AND_STATIONARY_TARGET: return "Moving and stationary target";
+ case TargetState::AUTOCONFIG_IN_PROGRESS: return "Auto-config in progress";
+ case TargetState::AUTOCONFIG_SUCCESS: return "Auto-config success";
+ case TargetState::AUTOCONFIG_FAILED: return "Auto-config failed";
+ default: return "Unknown";
+ }
+ }
+
+
+
+
+
+
+ /**
+ * @brief Light-dependent control status of the auxiliary output.
+ *
+ * The radar sensor can control an external output based on ambient
+ * light level in combination with presence detection.
+ *
+ * Use NOT_SET only as a placeholder; it is not a valid configuration value.
+ *
+ */
+ enum class LightControl {
+ NOT_SET = -1, ///< Placeholder for the initial value. Do not use as a config value (it will cause the configuration command to abort).
+ NO_LIGHT_CONTROL = 0, ///< Output is not influenced by light levels.
+ LIGHT_BELOW_THRESHOLD = 1, ///< Condition: light < threshold.
+ LIGHT_ABOVE_THRESHOLD = 2 ///< Condition: light >= threshold.
+ };
+ /**
+ * @brief Safely converts a numeric value to a LightControl enum.
+ *
+ * @param value Raw numeric value (0-2 expected).
+ * - 0 - NO_LIGHT_CONTROL
+ * - 1 - LIGHT_BELOW_THRESHOLD
+ * - 2 - LIGHT_ABOVE_THRESHOLD
+ * @returns The matching LightControl value, or NOT_SET if invalid.
+ *
+ */
+ static LightControl toLightControl(int value) {
+ switch (value) {
+ case 0: return LightControl::NO_LIGHT_CONTROL;
+ case 1: return LightControl::LIGHT_BELOW_THRESHOLD;
+ case 2: return LightControl::LIGHT_ABOVE_THRESHOLD;
+ default: return LightControl::NOT_SET;
+ }
+ }
+
+
+ /**
+ * @brief Logic level behavior of the auxiliary output pin.
+ *
+ * Determines whether the output pin is active-high or active-low
+ * in relation to presence detection.
+ *
+ * Use NOT_SET only as a placeholder; it is not a valid configuration value.
+ *
+ */
+ enum class OutputControl {
+ NOT_SET = -1, ///< Placeholder for the initial value. Do not use as a config value (it will cause the configuration command to abort).
+ DEFAULT_LOW_DETECTED_HIGH = 0, ///< Default low, goes HIGH when detection occurs.
+ DEFAULT_HIGH_DETECTED_LOW = 1 ///< Default high, goes LOW when detection occurs.
+ };
+
+ /**
+ * @brief Safely converts a numeric value to an OutputControl enum.
+ *
+ * @param value Raw numeric value (0-1 expected).
+ * - 0 - DEFAULT_LOW_DETECTED_HIGH
+ * - 1 - DEFAULT_HIGH_DETECTED_LOW
+ * @returns The matching OutputControl value, or NOT_SET if invalid.
+ *
+ */
+ static OutputControl toOutputControl(int value) {
+ switch (value) {
+ case 0: return OutputControl::DEFAULT_LOW_DETECTED_HIGH;
+ case 1: return OutputControl::DEFAULT_HIGH_DETECTED_LOW;
+ default: return OutputControl::NOT_SET;
+ }
+ }
+
+ /**
+ * @brief State of the automatic threshold configuration routine.
+ *
+ * Auto-config adjusts the sensitivity thresholds for optimal detection
+ * in the current environment. This process may take several seconds.
+ *
+ * Use NOT_SET only as a placeholder; it is not a valid configuration value.
+ *
+ */
+ enum class AutoConfigStatus {
+ NOT_SET = -1, ///< Status not yet retrieved.
+ NOT_IN_PROGRESS, ///< Auto-configuration not running.
+ IN_PROGRESS, ///< Auto-configuration is currently running.
+ COMPLETED ///< Auto-configuration finished (success or failure).
+ };
+
+ /**
+ * @brief Safely converts a numeric value to an AutoConfigStatus enum.
+ *
+ * @param value Raw numeric value (0-2 expected).
+ * - 0 - NOT_IN_PROGRESS
+ * - 1 - IN_PROGRESS
+ * - 2 - COMPLETED
+ * @returns The matching AutoConfigStatus value, or NOT_SET if invalid.
+ *
+ */
+ static AutoConfigStatus toAutoConfigStatus(int value) {
+ switch (value) {
+ case 0: return AutoConfigStatus::NOT_IN_PROGRESS;
+ case 1: return AutoConfigStatus::IN_PROGRESS;
+ case 2: return AutoConfigStatus::COMPLETED;
+ default: return AutoConfigStatus::NOT_SET;
+ }
+ }
+
+
+ /**
+ * @brief Supported baud rates for the sensor's UART interface.
+ *
+ * These values correspond to the configuration commands accepted
+ * by the LD2410. After changing the baud rate, a sensor reboot
+ * is required, and the ESP32's UART must be reconfigured to match.
+ *
+ */
+ enum class Baudrate {
+ BAUDRATE_9600 = 1, ///< 9600 baud.
+ BAUDRATE_19200 = 2, ///< 19200 baud.
+ BAUDRATE_38400 = 3, ///< 38400 baud.
+ BAUDRATE_57600 = 4, ///< 57600 baud.
+ BAUDRATE_115200 = 5, ///< 115200 baud.
+ BAUDRATE_230500 = 6, ///< 230400 baud.
+ BAUDRATE_256000 = 7, ///< 256000 baud (factory default).
+ BAUDRATE_460800 = 8 ///< 460800 baud (high-speed).
+ };
+
+ /**
+ * @brief Distance resolution per gate for detection.
+ *
+ * The resolution defines how fine-grained the distance measurement is:
+ * - RESOLUTION_75CM: Coarser, maximum range up to about 6 m, each gate approximately 0.75 m wide.
+ * - RESOLUTION_20CM: Finer, maximum range up to about 1.8 m, each gate approximately 0.20 m wide.
+ *
+ * Use NOT_SET only as a placeholder; it is not a valid configuration value.
+ *
+ */
+ enum class DistanceResolution {
+ NOT_SET = -1, ///< Placeholder for the initial value. Do not use as a config value (it will cause the configuration command to abort).
+ RESOLUTION_75CM = 0, ///< Each gate is about 0.75 m, max range about 6 m.
+ RESOLUTION_20CM = 1 ///< Each gate is about 0.20 m, max range about 1.8 m.
+ };
+ /**
+ * @brief Safely converts a numeric value to a DistanceResolution enum.
+ *
+ * @param value Raw numeric value (typically from a sensor response).
+ * Expected values:
+ * - 0 - RESOLUTION_75CM
+ * - 1 - RESOLUTION_20CM
+ * @returns The matching DistanceResolution value, or NOT_SET if invalid.
+ *
+ */
+ static DistanceResolution toDistanceResolution(int value) {
+ switch (value) {
+ case 0: return DistanceResolution::RESOLUTION_75CM;
+ case 1: return DistanceResolution::RESOLUTION_20CM;
+ default: return DistanceResolution::NOT_SET;
+ }
+ }
+
+ /**
+ * @brief Holds the most recent detection data reported by the radar.
+ *
+ * This structure is continuously updated as new frames arrive.
+ * Values reflect either the basic presence information or, if
+ * engineering mode is enabled, per-gate signal details.
+ *
+ */
+ struct DetectionData
+ {
+ unsigned long timestamp = 0; ///< Timestamp (ms since boot) when this data was received.
+
+ // === Basic detection results ===
+
+ bool engineeringMode = false; ///< True if engineering mode data was received.
+
+ bool presenceDetected = false; ///< True if any target is detected.
+ bool movingPresenceDetected = false; ///< True if a moving target is detected.
+ bool stationaryPresenceDetected = false; ///< True if a stationary target is detected.
+
+ TargetState targetState = TargetState::NO_TARGET; ///< Current detection state.
+ unsigned int movingTargetDistance = 0; ///< Distance (cm) to the nearest moving target.
+ byte movingTargetSignal = 0; ///< Signal strength (0-100) of the moving target.
+ unsigned int stationaryTargetDistance = 0; ///< Distance (cm) to the nearest stationary target.
+ byte stationaryTargetSignal = 0; ///< Signal strength (0-100) of the stationary target.
+ unsigned int detectedDistance = 0; ///< General detection distance (cm).
+
+ // === Engineering mode data ===
+ byte movingTargetGateSignalCount = 0; ///< Number of gates with moving target signals.
+ byte movingTargetGateSignals[9] = { 0 }; ///< Per-gate signal strengths for moving targets.
+
+ byte stationaryTargetGateSignalCount = 0; ///< Number of gates with stationary target signals.
+ byte stationaryTargetGateSignals[9] = { 0 }; ///< Per-gate signal strengths for stationary targets.
+
+ byte lightLevel = 0; ///< Reported ambient light level (0-255).
+ bool outPinStatus = 0; ///< Current status of the OUT pin (true = high, false = low).
+
+
+ /**
+ * @brief Debug helper: print detection data contents to Serial.
+ */
+ void print() const {
+ Serial.println("=== DetectionData ===");
+
+ Serial.print(" Timestamp: ");
+ Serial.println(timestamp);
+
+ Serial.print(" Engineering Mode: ");
+ Serial.println(engineeringMode ? "Yes" : "No");
+
+ Serial.print(" Target State: ");
+ Serial.println(static_cast(targetState));
+
+ Serial.print(" Moving Target Distance (cm): ");
+ Serial.println(movingTargetDistance);
+
+ Serial.print(" Moving Target Signal: ");
+ Serial.println(movingTargetSignal);
+
+ Serial.print(" Stationary Target Distance (cm): ");
+ Serial.println(stationaryTargetDistance);
+
+ Serial.print(" Stationary Target Signal: ");
+ Serial.println(stationaryTargetSignal);
+
+ Serial.print(" Detected Distance (cm): ");
+ Serial.println(detectedDistance);
+
+ Serial.print(" Light Level: ");
+ Serial.println(lightLevel);
+
+ Serial.print(" OUT Pin Status: ");
+ Serial.println(outPinStatus ? "High" : "Low");
+
+ // --- Engineering mode fields ---
+ if (engineeringMode) {
+ Serial.println(" --- Engineering Mode Data ---");
+
+ Serial.print(" Moving Target Gate Signal Count: ");
+ Serial.println(movingTargetGateSignalCount);
+
+ Serial.print(" Moving Target Gate Signals: ");
+ for (int i = 0; i < movingTargetGateSignalCount; i++) {
+ Serial.print(movingTargetGateSignals[i]);
+ if (i < movingTargetGateSignalCount - 1) Serial.print(",");
+ }
+ Serial.println();
+
+ Serial.print(" Stationary Target Gate Signal Count: ");
+ Serial.println(stationaryTargetGateSignalCount);
+
+ Serial.print(" Stationary Target Gate Signals: ");
+ for (int i = 0; i < stationaryTargetGateSignalCount; i++) {
+ Serial.print(stationaryTargetGateSignals[i]);
+ if (i < stationaryTargetGateSignalCount - 1) Serial.print(",");
+ }
+ Serial.println();
+ }
+
+ Serial.println("======================");
+ }
+
+ };
+
+ /**
+ * @brief Stores the sensor's configuration parameters.
+ *
+ * This structure represents both static capabilities
+ * (e.g. number of gates) and configurable settings
+ * (e.g. sensitivities, timeouts, resolution).
+ *
+ * The values are typically filled by request commands
+ * such as requestAllConfigSettingsAsync() or requestGateParametersAsync() or
+ * requestAuxControlSettingsAsync() or requestDistanceResolutioncmAsync().
+ *
+ */
+ struct ConfigData {
+ // === Radar capabilities ===
+ byte numberOfGates = 0; ///< Number of distance gates (2-8). This member is read-only; changing its value will not influence the radar setting when configureAllConfigSettingsAsync() is called. It is not entirely clear what this value represents, but it seems to indicate the number of gates on the sensor.
+
+ // === Max distance gate settings ===
+ byte maxMotionDistanceGate = 0; ///< Furthest gate used for motion detection.
+ byte maxStationaryDistanceGate = 0; ///< Furthest gate used for stationary detection.
+
+ // === Per-gate sensitivity settings ===
+ byte distanceGateMotionSensitivity[9] = { 0 }; ///< Motion sensitivity values per gate (0-100).
+ byte distanceGateStationarySensitivity[9] = { 0 }; ///< Stationary sensitivity values per gate (0-100).
+
+ // === Timeout parameters ===
+ unsigned short noOneTimeout = 0; ///< Timeout (seconds) until "no presence" is declared.
+
+ // === Distance resolution ===
+ DistanceResolution distanceResolution = DistanceResolution::NOT_SET; ///< Current distance resolution. A reboot is required to activate changes after configureAllConfigSettingsAsync() is called.
+
+ // === Auxiliary controls ===
+ byte lightThreshold = 0; ///< Threshold for auxiliary light control (0-255).
+ LightControl lightControl = LightControl::NOT_SET; ///< Light-dependent auxiliary control mode.
+ OutputControl outputControl = OutputControl::NOT_SET; ///< Logic configuration of the OUT pin.
+
+
+
+ /**
+ * @brief Validates the configuration data for correctness.
+ *
+ * Ensures that enum values are set and values are within valid ranges.
+ * This method is called internally before applying a config
+ * via configureAllConfigSettingsAsync().
+ *
+ * @returns True if the configuration is valid, false otherwise.
+ */
+ bool isValid() const {
+ // Validate enum settings
+ if (distanceResolution == DistanceResolution::NOT_SET) return false;
+ if (lightControl == LightControl::NOT_SET) return false;
+ if (outputControl == OutputControl::NOT_SET) return false;
+
+ // Validate max distance gates
+ if (maxMotionDistanceGate < 2 || maxMotionDistanceGate > numberOfGates) return false;
+ if (maxStationaryDistanceGate < 1 || maxStationaryDistanceGate > numberOfGates) return false;
+
+ // Validate sensitivities
+ for (int i = 0; i < 9; i++) {
+ if (distanceGateMotionSensitivity[i] > 100) return false;
+ if (distanceGateStationarySensitivity[i] > 100) return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * @brief Compares this ConfigData with another for equality.
+ *
+ * @param other The other ConfigData instance to compare against.
+ * @returns True if all fields are equal, false otherwise.
+ */
+ bool equals(const ConfigData& other) const {
+ if (numberOfGates != other.numberOfGates) return false;
+ if (maxMotionDistanceGate != other.maxMotionDistanceGate) return false;
+ if (maxStationaryDistanceGate != other.maxStationaryDistanceGate) return false;
+ if (noOneTimeout != other.noOneTimeout) return false;
+ if (distanceResolution != other.distanceResolution) return false;
+ if (lightThreshold != other.lightThreshold) return false;
+ if (lightControl != other.lightControl) return false;
+ if (outputControl != other.outputControl) return false;
+
+ for (int i = 0; i < 9; i++) {
+ if (distanceGateMotionSensitivity[i] != other.distanceGateMotionSensitivity[i]) return false;
+ if (distanceGateStationarySensitivity[i] != other.distanceGateStationarySensitivity[i]) return false;
+ }
+ return true;
+ }
+
+ /**
+ * @brief Debug helper: print configuration contents to Serial.
+ */
+ void print() const {
+ Serial.println("=== ConfigData ===");
+
+ Serial.print(" Number of Gates: ");
+ Serial.println(numberOfGates);
+
+ Serial.print(" Max Motion Distance Gate: ");
+ Serial.println(maxMotionDistanceGate);
+
+ Serial.print(" Max Stationary Distance Gate: ");
+ Serial.println(maxStationaryDistanceGate);
+
+ Serial.print(" Motion Sensitivity: ");
+ for (int i = 0; i < 9; i++) {
+ Serial.print(distanceGateMotionSensitivity[i]);
+ if (i < 8) Serial.print(",");
+ }
+ Serial.println();
+
+ Serial.print(" Stationary Sensitivity: ");
+ for (int i = 0; i < 9; i++) {
+ Serial.print(distanceGateStationarySensitivity[i]);
+ if (i < 8) Serial.print(",");
+ }
+ Serial.println();
+
+ Serial.print(" No One Timeout: ");
+ Serial.println(noOneTimeout);
+
+ Serial.print(" Distance Resolution: ");
+ Serial.println(static_cast(distanceResolution));
+
+ Serial.print(" Light Threshold: ");
+ Serial.println(lightThreshold);
+
+ Serial.print(" Light Control: ");
+ Serial.println(static_cast(lightControl));
+
+ Serial.print(" Output Control: ");
+ Serial.println(static_cast(outputControl));
+
+ Serial.println("===================");
+ }
+ };
+
+ struct StaticData {
+ /**
+ * @brief Protocol version reported by the radar.
+ *
+ * This value is set when entering config mode. It can be useful
+ * for compatibility checks between firmware and library.
+ *
+ */
+ uint16_t protocolVersion = 0;
+
+ /**
+ * @brief Buffer size reported by the radar protocol.
+ *
+ * Set when entering config mode. Typically not required by users
+ * unless debugging low-level protocol behavior.
+ *
+ */
+ uint16_t bufferSize = 0;
+
+
+ /**
+ * @brief Firmware version string of the radar.
+ *
+ * Populated by requestFirmwareAsync(). Format is usually
+ * "major.minor.build".
+ *
+ */
+ char firmwareText[16] = { 0 };
+
+ /**
+ * @brief MAC address of the radar's Bluetooth module (if available).
+ *
+ * Populated by requestBluetoothMacAddressAsync().
+ *
+ */
+ byte bluetoothMac[6];
+
+ /**
+ * @brief MAC address as a human-readable string (e.g. "AA:BB:CC:DD:EE:FF").
+ *
+ * Populated by requestBluetoothMacAddressAsync().
+ *
+ */
+ char bluetoothMacText[18] = { 0 };
+
+ };
+
+}
\ No newline at end of file