commit 3eaff3a647a04b7d1514ef6e648b72ef91de171b Author: Tony Tran Date: Fri Sep 11 12:02:40 2026 +0700 first commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..53dbbfa --- /dev/null +++ b/README.md @@ -0,0 +1,173 @@ +# ESP32-S3 + HLK-LD2453 — Zone Presence Detection (port of ESP32-LD2450) + +Ported from [thanhtantran/ESP32-LD2450](https://github.com/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 /updateZones` REST endpoints, `ArduinoJson` for + (de)serialization — see `ZoneManager.cpp`. +- The WebSocket broadcast pattern (`AsyncWebSocket` on `/ws`, + `ws.textAll()` per frame) — see `WebServerManager.cpp`. +- The overall setup()/loop() shape and Wi-Fi reconnect handling. + +## What changed + +- LD2450's `rbegamer/HLK-LD2450` library dependency is gone; parsing is + now our own `LD2453Parser`, because the byte format is different for + this sensor. +- `Serial2` with fixed pins → `HardwareSerial` on **configurable** + pins (`RADAR_RX_PIN` / `RADAR_TX_PIN` in `config.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 calling + `delay()` in the acquisition path. +- Every frame now carries `node_id` and a `sequence_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) + +1. Copy `config.example.h` to `config.h`. +2. Leave `DIAGNOSTIC_MODE 1` (the default). This disables Wi-Fi/web + entirely — Serial-only. +3. Flash, open Serial Monitor at 115200. +4. You should see: + ``` + LD2453 connected + UART: 256000 + ... + ``` + Target lines will currently always print `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: + +1. In `config.h`, set `DEBUG_RADAR_RAW 1`. +2. 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) + ``` +3. 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()` in `LD2453Parser.cpp`. + The exact insertion point is marked with a comment block. + +## Stage 2: full firmware (once the parser is confirmed) + +1. Set `DIAGNOSTIC_MODE 0` in `config.h`. +2. Fill in `WIFI_SSID` / `WIFI_PASSWORD`. +3. 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:///ws`) + +```json +{ + "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 occupancy +- `POST /updateZones` — update one or more zones (same shape as + `GET /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 in + `WebServerManager::begin()`, which runs once during `setup()` 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 `.ino` + wires them together.