ESP32-S3 + HLK-LD2453 — Zone Presence Detection (port of ESP32-LD2450)
Ported from thanhtantran/ESP32-LD2450, targeting:
- MCU: ESP32-S3 (Arduino framework, plain Arduino IDE — no PlatformIO)
- Radar: Hi-Link HLK-LD2453 (24 GHz, 1TX+2RX, up to 3 targets)
⚠️ Current status: radar parser is a stub
LD2453Parser.cpp's byte-level frame decoding is not implemented yet.
The official HLK-LD2453 "Communication Protocol" manual section (frame
header/tail, per-target byte layout, sign convention, speed encoding)
could not be sourced from a verifiable source at the time this was
written — see "Confirming the protocol" below. Everything else (Wi-Fi,
REST API, WebSocket, zone logic, non-blocking UART acquisition, raw hex
capture) is fully implemented and ready to go.
What's reused from the original ESP32-LD2450 project
- The zone model: up to 3 rectangles in millimeters,
GET /zones/POST /updateZonesREST endpoints,ArduinoJsonfor (de)serialization — seeZoneManager.cpp. - The WebSocket broadcast pattern (
AsyncWebSocketon/ws,ws.textAll()per frame) — seeWebServerManager.cpp. - The overall setup()/loop() shape and Wi-Fi reconnect handling.
What changed
- LD2450's
rbegamer/HLK-LD2450library dependency is gone; parsing is now our ownLD2453Parser, because the byte format is different for this sensor. Serial2with fixed pins →HardwareSerialon configurable pins (RADAR_RX_PIN/RADAR_TX_PINinconfig.h).- Blocking
delay()-based sensor "reopen on failure" hack removed; radar acquisition is now a non-blocking ring-buffer state machine that tolerates partial/corrupted frames without ever callingdelay()in the acquisition path. - Every frame now carries
node_idand asequence_number, so this node can later feed a multi-radar fusion setup (RADAR_A/B/C→ UDP → Orange Pi) without touching the parser again. - Plain PlatformIO
.cpp/lib_deps project → standard Arduino IDE sketch folder.
Wiring
LD2453 ESP32-S3
V -> 3V3
G -> GND
T/TX -> RADAR_RX_PIN (default GPIO18)
R/RX -> RADAR_TX_PIN (default GPIO17)
Change RADAR_RX_PIN / RADAR_TX_PIN in config.h to match your
wiring — any two free GPIOs that support UART on your ESP32-S3 board
will work, since the ESP32 Arduino core remaps UART pins in software.
Do not use SoftwareSerial — HardwareSerial is required at
256000 baud.
Required Arduino libraries
Install via Library Manager (exact names to search for):
| Library | Used for | Notes |
|---|---|---|
ArduinoJson (by Benoit Blanchon) |
REST API + WebSocket JSON | v7.x |
ESP Async WebServer (by me-no-dev / ESP32Async fork) |
HTTP + WebSocket server | Only needed for stage 2 (DIAGNOSTIC_MODE 0). Check current ESP32-S3/Arduino-core-3.x compatibility for whichever fork you install — this ecosystem has had breaking forks; if it doesn't build, the ESP32Async/ESPAsyncWebServer fork is the actively maintained one as of this writing. |
AsyncTCP |
dependency of ESP Async WebServer | matching fork/version to the WebServer library above |
Board package: esp32 by Espressif Systems, board = your specific ESP32-S3 dev board.
Project layout
ESP32_LD2453/
ESP32_LD2453.ino - setup()/loop(), diagnostic printing
LD2453Parser.h/.cpp - UART acquisition + frame parsing (STUBBED, see above)
ZoneManager.h/.cpp - rectangular zone occupancy logic
WebServerManager.h/.cpp - Wi-Fi, REST API, WebSocket broadcast
config.example.h - copy to config.h and edit
README.md
Stage 1: diagnostic mode (do this first)
- Copy
config.example.htoconfig.h. - Leave
DIAGNOSTIC_MODE 1(the default). This disables Wi-Fi/web entirely — Serial-only. - Flash, open Serial Monitor at 115200.
- You should see:
Target lines will currently always print
LD2453 connected UART: 256000 ...INVALID— expected, since the parser is stubbed. This stage exists so you can validate wiring/baud/board bring-up independent of the parser.
Confirming the protocol
Until LD2453Parser's real frame format is filled in, use it as a
raw-capture tool:
- In
config.h, setDEBUG_RADAR_RAW 1. - Flash, open Serial Monitor at 115200, and let the LD2453 see some
motion. You'll get a hex dump of every raw byte the module sends,
e.g.:
[RAW] AA FF 03 00 4B 01 36 08 0C 00 ... (len=30) - Share that capture (and, if you can get it, the actual "5.
Communication Protocol" section of the official HLK-LD2453 user
manual — Hi-Link's own download link for it is currently
mislabeled and points at the LD2450 manual instead) so the header,
tail, per-target byte layout, and sign/speed encoding can be
confirmed and dropped into
tryParseFrame()inLD2453Parser.cpp. The exact insertion point is marked with a comment block.
Stage 2: full firmware (once the parser is confirmed)
- Set
DIAGNOSTIC_MODE 0inconfig.h. - Fill in
WIFI_SSID/WIFI_PASSWORD. - Flash. The board connects to Wi-Fi, starts the HTTP server, and
broadcasts one JSON message per radar frame over
/ws.
WebSocket data format (ws://<ESP32_IP>/ws)
{
"sensor": "LD2453",
"node_id": "RADAR_A",
"sequence": 12345,
"timestamp": 12345678,
"targets": [
{ "id": 1, "valid": true, "x": -820, "y": 2840, "speed": 12 },
{ "id": 2, "valid": true, "x": 1350, "y": 4120, "speed": -4 }
]
}
id is the radar's target slot (1–3), not a persistent person
identity — a target disappearing and reappearing may land in a
different slot.
REST API
GET /zones— current zone rectangles + live occupancyPOST /updateZones— update one or more zones (same shape asGET /zones, partial updates supported)GET /status— basic health check (uptime)
Multi-radar future work (not implemented yet)
Every RadarFrame already carries node_id and sequence_number so
that, later, this same parser output can be forwarded over UDP to an
Orange Pi doing sensor fusion across RADAR_A/B/C, without touching
LD2453Parser again. No UDP/MQTT/cloud code has been added — this
firmware only does local Wi-Fi + WebSocket, as requested.
Code quality notes
- No
delay()in the radar acquisition path (LD2453Parser::update(),feedByte()) — only the one-time Wi-Fi connect retry inWebServerManager::begin(), which runs once duringsetup()before acquisition starts. - No dynamic allocation in the parser (fixed-size ring buffer).
- Radar acquisition, zone logic, and networking are in separate
classes/files and don't call into each other directly — the
.inowires them together.