cập nhật lại README

This commit is contained in:
admin committed 2026-09-11 17:28:32 +07:00
1 parent 649edec6ca
commit 87be72b52b
2 files changed
+451 -127

No files matched your search

+271
View File
@@ -0,0 +1,271 @@
# ESP32-S3 + HLK-LD2453 — Zone Presence Detection System
> **Vietnamese version** available at: [README.md](README.md)
This project is ported from [thanhtantran/ESP32-LD2450](https://github.com/thanhtantran/ESP32-LD2450), targeting the following hardware:
- **MCU:** ESP32-S3 (Arduino framework, plain Arduino IDE — no PlatformIO)
- **Radar:** Hi-Link **HLK-LD2453** (24 GHz, 1TX+2RX, up to 3 targets simultaneously)
---
## ⚠️ Current status: Radar parser is a stub
Byte-level frame decoding in `LD2453Parser.cpp` is **not implemented yet**.
A verifiable source for the official HLK-LD2453 "Communication Protocol" section (frame header/tail, per-target byte layout, sign convention, speed encoding) could not be found 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 from the original
- Removed LD2450's `rbegamer/HLK-LD2450` library dependency; parsing is now handled by our own `LD2453Parser`, because the byte format is different for this sensor.
- From `Serial2` with fixed pins → `HardwareSerial` on **configurable** pins (`RADAR_RX_PIN` / `RADAR_TX_PIN` in `config.h`).
- Removed the blocking `delay()`-based sensor "reopen on failure" hack; radar acquisition now runs as 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 `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.
- From PlatformIO `.cpp`/`lib_deps` project → standard Arduino IDE sketch folder.
---
## Wiring
### LD2453 Radar
```
LD2453 ESP32-S3
V -> 3V3
G -> GND
T/TX -> RADAR_RX_PIN (default GPIO18)
R/RX -> RADAR_TX_PIN (default GPIO17)
```
### OLED Display I2C 128x64
```
OLED ESP32-S3
VCC -> 3V3
GND -> GND
SCL -> GPIO7 (OLED_SCL_PIN)
SDA -> GPIO6 (OLED_SDA_PIN)
```
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 (search for these exact names):
| Library | Used for | Notes |
|---|---|---|
| `ArduinoJson` (by Benoit Blanchon) | REST API + WebSocket JSON | v7.x — code uses the current `JsonDocument` API (not the deprecated `StaticJsonDocument`) |
| `ESP Async WebServer` (ESP32Async fork) | HTTP + WebSocket server | Only needed for stage 2 (`DIAGNOSTIC_MODE 0`) |
| `AsyncTCP` | dependency of ESP Async WebServer | matching fork/version to the WebServer library above |
| `Adafruit GFX Library` | Graphics library for OLED | Used with Adafruit SSD1306 |
| `Adafruit SSD1306` | Driver for OLED 128x64 I2C display | Required if `OLED_ENABLED 1` |
Board package: **esp32 by Espressif Systems**, board = your specific ESP32-S3 dev board.
Confirmed building clean (no warnings) with: esp32 core 3.3.11, ArduinoJson (JsonDocument API), ESP_Async_WebServer + Async_TCP.
---
## Project layout
```
esp32s3-ld2453/
├── esp32s3-ld2453/
│ ├── esp32s3-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
│ ├── OLEDDisplay.h/.cpp — OLED I2C 128x64 display manager
│ ├── config.example.h — Copy to config.h and edit
│ └── config.h — Configuration file (not committed to git)
├── orangepi-server/
│ ├── ORANGE_PI_GUIDE.md — Detailed guide for Orange Pi
│ ├── orange_pi_visualizer.py — Python script to visualize radar data
│ └── orange_pi_requirements.txt
└── README-en.md
```
---
## OLED Display Features
The OLED 128x64 display shows the following information:
### When DIAGNOSTIC_MODE = 1 (Serial Debug Mode)
- **Boot screen**: Displays logo and Node ID
- **Diagnostic mode notification**: Indicates Serial-only mode is active
- **Radar statistics every 5 seconds**:
- Number of bytes received from LD2453
- Number of parsed frames
- Number of parse errors
- Packets per second (PPS)
- Number of detected targets
### When DIAGNOSTIC_MODE = 0 (Full WiFi Mode)
- **Connecting WiFi**: Shows SSID being connected
- **WiFi connected**: Shows IP address
- **Realtime detection**:
- Number of detected targets (T:0-3)
- Status of 3 zones (YES/NO)
### OLED configuration in config.h
```cpp
#define OLED_ENABLED 1 // 1 = enable OLED, 0 = disable
#define OLED_SDA_PIN 6 // SDA pin
#define OLED_SCL_PIN 7 // SCL pin
#define OLED_I2C_ADDR 0x3C // I2C address (typically 0x3C or 0x3D)
```
**Note**: If the display does not work, try changing `OLED_I2C_ADDR` from `0x3C` to `0x3D`.
---
## 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://<ESP32_IP>/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 }
]
}
```
Field explanations:
- `sensor`: Sensor type name
- `node_id`: Radar node ID (from `NODE_ID` in `config.h`)
- `sequence`: Frame sequence number, incrementing per frame
- `timestamp`: Time in millis() at frame completion
- `targets[]`: Array of detected targets (max 3):
- `id`: The radar's target *slot* (1–3), **NOT** a persistent person identity — a target disappearing and reappearing may land in a different slot
- `valid`: true if this target has valid data
- `x`, `y`: Coordinates in millimeters (radar coordinate system)
- `speed`: Speed in cm/s (+/− sign indicates direction)
### REST API
| Endpoint | Method | Description |
|---|---|---|
| `/zones` | GET | Returns current zone rectangles + live occupancy status |
| `/updateZones` | POST | Update one or more zones (same format as `GET /zones`, partial updates supported |
| `/status` | GET | Basic health check (uptime) |
Example zone update with `curl`:
```bash
curl -X POST http://192.168.1.100/updateZones \
-H "Content-Type: application/json" \
-d '[
{"id":1,"x1":-3000,"y1":500,"x2":-1,"y2":3000},
{"id":2,"x1":1,"y1":500,"x2":3000,"y2":3000},
{"id":3,"x1":-3000,"y1":3001,"x2":3000,"y2":5000}
]'
```
---
## Orange Pi Visualizer
The `orangepi-server/` folder contains a Python script to receive data from the ESP32 via WebSocket and display it in realtime on an Orange Pi (or any computer with Python 3.8+).
### Key features
- Connect to ESP32 via WebSocket, receive realtime radar frames
- Draw zones (3 rectangles) and target positions (max 3)
- Display coordinates, speed, distance, and angle for each target
- Supports 2 modes:
- **GUI (Tkinter)**: Graphics display, requires a monitor
- **Headless (Console)**: No monitor required, runs over SSH
### Quick setup
```bash
sudo apt update
sudo apt install -y python3-tk python3-pip python3-venv
cd ~/orangepi-server
python3 -m venv venv
source venv/bin/activate
pip install -r orange_pi_requirements.txt
```
### Running the program
GUI mode (with monitor):
```bash
source venv/bin/activate
python3 orange_pi_visualizer.py <ESP32_IP>
```
Headless mode (SSH, no monitor needed):
```bash
python3 orange_pi_visualizer.py <ESP32_IP> --headless
```
See detailed guide at: [ORANGE_PI_GUIDE.md](orangepi-server/ORANGE_PI_GUIDE.md)
---
## 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 originally.
---
## 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.