add files

This commit is contained in:
admin committed 2026-09-11 12:04:46 +07:00
1 parent 3eaff3a647
commit e76a9c1e9b
9 files changed
+752

No files matched your search

+133
View File
@@ -0,0 +1,133 @@
// =====================================================================
// LD2453Parser.cpp
//
// See the big comment at the top of LD2453Parser.h before touching
// this file. The acquisition/debug layer below is fully functional;
// tryParseFrame() is intentionally stubbed pending the official
// HLK-LD2453 protocol.
// =====================================================================
#include "LD2453Parser.h"
#if !defined(ARDUINO_ARCH_ESP32)
#warning "LD2453Parser was written/tested against the ESP32 Arduino core. Verify HardwareSerial pin-remap behavior on other cores."
#endif
// ---------------------------------------------------------------
// PLACEHOLDER PROTOCOL CONSTANTS
//
// None of the values below are confirmed. They exist only so the
// state machine in tryParseFrame() has something concrete to compare
// against once you fill them in from the real manual or from a
// DEBUG_RADAR_RAW capture. Do not rely on these for real parsing.
// ---------------------------------------------------------------
// static const uint8_t FRAME_HEADER[] = { 0x00, 0x00, 0x00, 0x00 }; // TODO: confirm
// static const uint8_t FRAME_TAIL[] = { 0x00, 0x00 }; // TODO: confirm
// static const size_t TARGET_RECORD_SIZE = 0; // bytes per target, TODO: confirm
// static const size_t MAX_TARGETS = 3;
void LD2453Parser::begin(HardwareSerial &serial, int rxPin, int txPin, uint32_t baud,
const char *nodeId, bool rawDebug) {
_serial = &serial;
_rawDebug = rawDebug;
_nodeId = nodeId;
_bufferLen = 0;
_sequenceCounter = 0;
_stats = LD2453Stats{};
_ppsWindowStart = millis();
_ppsWindowCount = 0;
// HardwareSerial::begin(baud, config, rxPin, txPin) — ESP32 Arduino
// core supports remapping RX/TX to any capable GPIO. 8N1 assumed;
// confirm against the official manual.
_serial->begin(baud, SERIAL_8N1, rxPin, txPin);
}
void LD2453Parser::dumpRawHex(const uint8_t *data, size_t len) {
Serial.print(F("[RAW] "));
for (size_t i = 0; i < len; i++) {
if (data[i] < 0x10) Serial.print('0');
Serial.print(data[i], HEX);
Serial.print(' ');
}
Serial.print(F(" (len="));
Serial.print(len);
Serial.println(')');
}
void LD2453Parser::feedByte(uint8_t b) {
if (_bufferLen >= RING_BUFFER_SIZE) {
// Buffer overrun with no recognized frame boundary — drop the
// oldest half so we can keep tolerating a stuck/garbage stream
// without ever blocking or allocating.
memmove(_ringBuffer, _ringBuffer + RING_BUFFER_SIZE / 2, RING_BUFFER_SIZE / 2);
_bufferLen = RING_BUFFER_SIZE / 2;
_stats.parseErrors++;
}
_ringBuffer[_bufferLen++] = b;
}
bool LD2453Parser::update() {
if (!_serial) return false;
bool gotFrame = false;
// Drain whatever is available right now — non-blocking, no delay().
// While rawDebug is on, dump exactly the newly-arrived bytes as
// hex so you can capture real traffic to reverse-engineer the
// frame format, independent of whether tryParseFrame() (still a
// stub) recognizes anything.
size_t newBytesStart = _bufferLen;
while (_serial->available() > 0) {
uint8_t b = (uint8_t)_serial->read();
feedByte(b);
_stats.bytesReceived++;
}
if (_rawDebug && _bufferLen > newBytesStart) {
dumpRawHex(_ringBuffer + newBytesStart, _bufferLen - newBytesStart);
}
// Try to pull as many complete frames out of the buffer as
// possible in this call (in case several arrived since the last
// loop() iteration).
while (tryParseFrame()) {
gotFrame = true;
_stats.framesParsed++;
_ppsWindowCount++;
}
// Update packets-per-second once a second, non-blocking.
uint32_t now = millis();
if (now - _ppsWindowStart >= 1000) {
_stats.packetsPerSecond = _ppsWindowCount * 1000.0f / (now - _ppsWindowStart);
_ppsWindowStart = now;
_ppsWindowCount = 0;
}
return gotFrame;
}
bool LD2453Parser::tryParseFrame() {
// -----------------------------------------------------------
// STUB: real frame sync/parse goes here once the official
// HLK-LD2453 protocol is confirmed. Suggested shape, mirroring
// the LD2410/LD2450 family's approach (adapt once verified):
//
// 1. Scan _ringBuffer for FRAME_HEADER.
// 2. If found but fewer than (header + declared length + tail)
// bytes are available yet, return false and wait for more
// bytes next call (do NOT block).
// 3. Verify FRAME_TAIL at the expected offset.
// 4. If rawDebug, dumpRawHex() the whole candidate frame.
// 5. Decode each target record into RadarTarget (x_mm, y_mm,
// speed, and distance/angle only if the protocol actually
// carries them).
// 6. Fill _lastFrame (timestamp_ms = millis(), sequence_number
// = ++_sequenceCounter, node_id = _nodeId, targetCount).
// 7. memmove() the consumed bytes out of _ringBuffer.
// 8. Return true.
//
// Until then, this always returns false so callers never receive
// fabricated coordinates.
// -----------------------------------------------------------
return false;
}
+110
View File
@@ -0,0 +1,110 @@
// =====================================================================
// LD2453Parser.h
//
// UART frame acquisition + parsing for the Hi-Link HLK-LD2453 radar.
//
// *** IMPORTANT / READ THIS FIRST ***
// The byte-level frame format implemented in LD2453Parser.cpp is a
// PLACEHOLDER. As of this writing, the official HLK-LD2453 "5.
// Communication Protocol" manual section (frame header, frame tail,
// per-target record layout, byte order, sign convention, speed
// encoding) could not be sourced from a verifiable primary source.
//
// Rather than guess and silently emit plausible-but-wrong X/Y/speed
// numbers, tryParseFrame() is stubbed to always fail until the real
// constants below are filled in from the official manual or from a
// reverse-engineered capture (see DEBUG_RADAR_RAW in config.h and the
// README "Confirming the protocol" section).
//
// What IS implemented and fully working right now:
// - Non-blocking UART byte acquisition (ring buffer, no delay())
// - Raw hex frame dumping (DEBUG_RADAR_RAW) for protocol capture
// - Packets-per-second / parse-error counters
// - The public data structures your application code should use
// (RadarTarget, RadarFrame) so ZoneManager / WebServerManager /
// your .ino never need to change once the real parser is dropped
// in below.
// =====================================================================
#pragma once
#include <Arduino.h>
// ---------------------------------------------------------------
// Public data structures
// ---------------------------------------------------------------
struct RadarTarget {
bool valid;
int16_t x_mm;
int16_t y_mm;
int16_t speed; // cm/s, sign = direction (towards/away), per LD2450-family convention — UNCONFIRMED for LD2453
float distance_mm; // only populated if the confirmed protocol provides it directly; else derived from x/y
float angle_deg; // only populated if the confirmed protocol provides it directly
};
struct RadarFrame {
uint32_t timestamp_ms; // millis() at time of frame completion
uint32_t sequence_number; // increments per frame, for multi-radar fusion later
const char *node_id; // NODE_ID from config.h
RadarTarget targets[3];
uint8_t targetCount; // number of *valid* targets in this frame (0-3)
};
// Simple acquisition/parse statistics, useful for the diagnostic
// serial output requested for stage 1.
struct LD2453Stats {
uint32_t framesParsed = 0;
uint32_t parseErrors = 0;
uint32_t bytesReceived = 0;
uint32_t lastFrameLength = 0;
float packetsPerSecond = 0.0f;
};
// ---------------------------------------------------------------
// Parser class
// ---------------------------------------------------------------
class LD2453Parser {
public:
// rawDebug: when true, every candidate frame's raw bytes are
// printed as hex to Serial. Drive this from DEBUG_RADAR_RAW.
void begin(HardwareSerial &serial, int rxPin, int txPin, uint32_t baud,
const char *nodeId, bool rawDebug = false);
// Call every loop() iteration. Non-blocking: reads whatever bytes
// are available, feeds the internal state machine, and returns
// true exactly once when a *new, fully parsed* frame is ready
// (retrieve it with getLastFrame()).
//
// NOTE: with tryParseFrame() stubbed out (see .cpp), this will
// currently never return true — see the header comment above.
bool update();
const RadarFrame &getLastFrame() const { return _lastFrame; }
const LD2453Stats &getStats() const { return _stats; }
private:
HardwareSerial *_serial = nullptr;
bool _rawDebug = false;
const char *_nodeId = "RADAR_A";
static const size_t RING_BUFFER_SIZE = 256;
uint8_t _ringBuffer[RING_BUFFER_SIZE];
size_t _bufferLen = 0;
RadarFrame _lastFrame{};
LD2453Stats _stats{};
uint32_t _sequenceCounter = 0;
uint32_t _ppsWindowStart = 0;
uint32_t _ppsWindowCount = 0;
void feedByte(uint8_t b);
void dumpRawHex(const uint8_t *data, size_t len);
// Returns true and consumes `len` bytes from _ringBuffer if a
// valid frame was found and parsed into _lastFrame. Returns
// false (and consumes 0 bytes) otherwise.
//
// *** STUBBED — see LD2453Parser.cpp ***
bool tryParseFrame();
};
+119
View File
@@ -0,0 +1,119 @@
// =====================================================================
// WebServerManager.cpp
// =====================================================================
#include "WebServerManager.h"
#include <ArduinoJson.h>
WebServerManager::WebServerManager(ZoneManager &zoneManager)
: _zoneManager(zoneManager), _server(80), _ws("/ws") {
}
void WebServerManager::onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client,
AwsEventType type, void *arg, uint8_t *data, size_t len) {
if (type == WS_EVT_CONNECT) {
Serial.printf("[WS] client #%u connected from %s\n", client->id(),
client->remoteIP().toString().c_str());
} else if (type == WS_EVT_DISCONNECT) {
Serial.printf("[WS] client #%u disconnected\n", client->id());
}
}
void WebServerManager::setupRoutes() {
_ws.onEvent(onWsEvent);
_server.addHandler(&_ws);
// GET /zones -> current zone definitions + occupancy
_server.on("/zones", HTTP_GET, [this](AsyncWebServerRequest *request) {
request->send(200, "application/json", _zoneManager.zonesToJson());
});
// POST /updateZones -> replace zone definitions (partial updates
// allowed; see ZoneManager::zonesFromJson)
_server.on(
"/updateZones", HTTP_POST, [](AsyncWebServerRequest *request) {},
nullptr,
[this](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {
if (_zoneManager.zonesFromJson(data, len)) {
request->send(200, "application/json",
"{\"status\":\"success\",\"message\":\"Zones updated\"}");
} else {
request->send(400, "application/json",
"{\"status\":\"error\",\"message\":\"Invalid JSON\"}");
}
});
// Basic health check, handy when bringing up a new node in the
// future multi-radar setup.
_server.on("/status", HTTP_GET, [](AsyncWebServerRequest *request) {
StaticJsonDocument<128> doc;
doc["status"] = "ok";
doc["uptime_ms"] = millis();
String out;
serializeJson(doc, out);
request->send(200, "application/json", out);
});
}
void WebServerManager::begin(const char *ssid, const char *password) {
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
// Matches the original project's blocking connect-on-boot
// behavior. This only runs once at startup, not in the main
// loop, so it doesn't violate the "no long delay() in the
// acquisition path" requirement — radar acquisition hasn't
// started yet at this point in setup().
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println();
Serial.print("WiFi connected, IP: ");
Serial.println(WiFi.localIP());
setupRoutes();
_server.begin();
Serial.println("HTTP server started.");
}
void WebServerManager::loop() {
if (WiFi.status() != WL_CONNECTED) {
// Non-blocking reconnect kick. We don't retry-loop with
// delay() here (unlike begin()) so radar acquisition in the
// main loop is never stalled by a flaky network.
static uint32_t lastAttempt = 0;
uint32_t now = millis();
if (now - lastAttempt > 5000) {
Serial.println("WiFi disconnected, retrying...");
WiFi.reconnect();
lastAttempt = now;
}
}
_ws.cleanupClients();
}
void WebServerManager::broadcastFrame(const RadarFrame &frame) {
if (_ws.count() == 0) return; // skip serialization work if nobody's listening
StaticJsonDocument<512> doc;
doc["sensor"] = "LD2453";
doc["node_id"] = frame.node_id;
doc["sequence"] = frame.sequence_number;
doc["timestamp"] = frame.timestamp_ms;
JsonArray targets = doc.createNestedArray("targets");
for (uint8_t i = 0; i < frame.targetCount && i < 3; i++) {
const RadarTarget &t = frame.targets[i];
JsonObject jt = targets.createNestedObject();
jt["id"] = i + 1; // radar slot index, NOT a persistent person ID
jt["valid"] = t.valid;
jt["x"] = t.x_mm;
jt["y"] = t.y_mm;
jt["speed"] = t.speed;
}
String out;
serializeJson(doc, out);
_ws.textAll(out);
}
+45
View File
@@ -0,0 +1,45 @@
// =====================================================================
// WebServerManager.h
//
// Wi-Fi + HTTP REST API + WebSocket live feed, ported from the
// ESP32-LD2450 project's AsyncWebServer/AsyncWebSocket setup.
// Protocol-agnostic: only ever touches RadarFrame/RadarTarget and
// ZoneManager, never raw radar bytes.
// =====================================================================
#pragma once
#include <Arduino.h>
#include <WiFi.h>
#include <ESPAsyncWebServer.h>
#include "LD2453Parser.h"
#include "ZoneManager.h"
class WebServerManager {
public:
WebServerManager(ZoneManager &zoneManager);
// Blocks briefly (via delay in the WiFi connect retry loop only,
// as in the original project) until Wi-Fi is up, then starts the
// HTTP + WebSocket server. Call once from setup().
void begin(const char *ssid, const char *password);
// Non-blocking. Call every loop() iteration: reconnects Wi-Fi if
// dropped, and prunes disconnected WebSocket clients.
void loop();
// Broadcasts one normalized JSON payload (see README "Web Data
// Format") to all connected WebSocket clients for this frame.
// No-op if no clients are connected.
void broadcastFrame(const RadarFrame &frame);
bool isWifiConnected() const { return WiFi.status() == WL_CONNECTED; }
private:
ZoneManager &_zoneManager;
AsyncWebServer _server;
AsyncWebSocket _ws;
void setupRoutes();
static void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client,
AwsEventType type, void *arg, uint8_t *data, size_t len);
};
+82
View File
@@ -0,0 +1,82 @@
// =====================================================================
// ZoneManager.cpp
// =====================================================================
#include "ZoneManager.h"
#include <ArduinoJson.h>
ZoneManager::ZoneManager() {
// Same default layout as the original ESP32-LD2450 project
// (three thirds of the room, roughly), in millimeters. Adjust to
// taste or override via POST /updateZones once the web UI is up.
_zones[0] = { -4000, 1, -1, 4000 };
_zones[1] = { 1, 1, 4000, 4000 };
_zones[2] = { -4001, 4001, 4001, 6000 };
for (uint8_t i = 0; i < MAX_ZONES; i++) _occupied[i] = false;
}
bool ZoneManager::pointInZone(int x, int y, const Zone &z) {
return x >= z.x1 && x <= z.x2 && y >= z.y1 && y <= z.y2;
}
void ZoneManager::update(const RadarFrame &frame) {
bool temp[MAX_ZONES] = { false, false, false };
for (uint8_t t = 0; t < frame.targetCount && t < 3; t++) {
const RadarTarget &target = frame.targets[t];
if (!target.valid) continue;
for (uint8_t z = 0; z < MAX_ZONES; z++) {
if (pointInZone(target.x_mm, target.y_mm, _zones[z])) {
temp[z] = true;
}
}
}
for (uint8_t z = 0; z < MAX_ZONES; z++) _occupied[z] = temp[z];
}
bool ZoneManager::isZoneOccupied(uint8_t zoneIndex) const {
if (zoneIndex >= MAX_ZONES) return false;
return _occupied[zoneIndex];
}
Zone ZoneManager::getZone(uint8_t zoneIndex) const {
if (zoneIndex >= MAX_ZONES) return Zone{0, 0, 0, 0};
return _zones[zoneIndex];
}
void ZoneManager::setZone(uint8_t zoneIndex, const Zone &zone) {
if (zoneIndex >= MAX_ZONES) return;
_zones[zoneIndex] = zone;
}
String ZoneManager::zonesToJson() const {
StaticJsonDocument<512> doc;
JsonArray arr = doc.to<JsonArray>();
for (uint8_t i = 0; i < MAX_ZONES; i++) {
JsonObject z = arr.createNestedObject();
z["id"] = i + 1;
z["x1"] = _zones[i].x1;
z["y1"] = _zones[i].y1;
z["x2"] = _zones[i].x2;
z["y2"] = _zones[i].y2;
z["occupied"] = _occupied[i];
}
String out;
serializeJson(doc, out);
return out;
}
bool ZoneManager::zonesFromJson(const uint8_t *data, size_t len) {
StaticJsonDocument<512> doc;
DeserializationError err = deserializeJson(doc, data, len);
if (err) return false;
for (uint8_t i = 0; i < MAX_ZONES; i++) {
_zones[i].x1 = doc[i]["x1"] | _zones[i].x1;
_zones[i].y1 = doc[i]["y1"] | _zones[i].y1;
_zones[i].x2 = doc[i]["x2"] | _zones[i].x2;
_zones[i].y2 = doc[i]["y2"] | _zones[i].y2;
}
return true;
}
+46
View File
@@ -0,0 +1,46 @@
// =====================================================================
// ZoneManager.h
//
// Rectangular detection-zone logic, ported from the ESP32-LD2450
// project's main_zone.cpp. Works entirely on RadarTarget X/Y values,
// so it is unaffected by however LD2453Parser ends up decoding raw
// frames — nothing here needs to change once the parser is confirmed.
// =====================================================================
#pragma once
#include <Arduino.h>
#include "LD2453Parser.h"
static const uint8_t MAX_ZONES = 3;
struct Zone {
int x1, y1, x2, y2; // millimeters, same coordinate system as RadarTarget
};
class ZoneManager {
public:
ZoneManager();
// Re-evaluate occupancy for all zones against the targets in
// `frame`. Call this once per new radar frame.
void update(const RadarFrame &frame);
bool isZoneOccupied(uint8_t zoneIndex) const;
Zone getZone(uint8_t zoneIndex) const;
void setZone(uint8_t zoneIndex, const Zone &zone);
// Serialize current zone definitions to JSON, e.g. for GET /zones.
// Returns the JSON string.
String zonesToJson() const;
// Parse a JSON body (as sent by POST /updateZones) and update
// zones in place. Missing fields keep their previous value.
// Returns false on JSON parse failure.
bool zonesFromJson(const uint8_t *data, size_t len);
private:
Zone _zones[MAX_ZONES];
bool _occupied[MAX_ZONES];
static bool pointInZone(int x, int y, const Zone &z);
};
+51
View File
@@ -0,0 +1,51 @@
// =====================================================================
// config.example.h
//
// Copy this file to "config.h" (same folder) and edit the values
// below. "config.h" is meant to be gitignored so you don't commit
// your Wi-Fi password.
// =====================================================================
#pragma once
// ---------------------------------------------------------------
// Wi-Fi credentials
// ---------------------------------------------------------------
#define WIFI_SSID "your_ssid"
#define WIFI_PASSWORD "your_password"
// ---------------------------------------------------------------
// Radar UART pins (ESP32-S3, HardwareSerial — NOT SoftwareSerial)
// Change these to match your wiring. Any two free GPIOs that
// support UART RX/TX on your ESP32-S3 board will work.
// ---------------------------------------------------------------
#define RADAR_RX_PIN 18 // ESP32-S3 RX <- LD2453 TX
#define RADAR_TX_PIN 17 // ESP32-S3 TX -> LD2453 RX
#define RADAR_UART_PORT 1 // which HardwareSerial to use (Serial1 or Serial2)
// Default LD2453 UART settings. 8N1 is assumed by analogy with the
// rest of the Hi-Link 24xx radar family; this has NOT been confirmed
// against the official LD2453 manual. Verify with DEBUG_RADAR_RAW
// before trusting any parsed output.
#define RADAR_BAUD_RATE 256000
// ---------------------------------------------------------------
// Node identity — for the future multi-radar (LD2453 A/B/C -> UDP
// -> Orange Pi) architecture. Not used for networking yet, but every
// frame is tagged with this so the plumbing is ready later.
// ---------------------------------------------------------------
#define NODE_ID "RADAR_A"
// ---------------------------------------------------------------
// Debug / diagnostic switches
// ---------------------------------------------------------------
// 1 = print every raw UART frame candidate as hex on the Serial
// Monitor (115200). Use this to capture real traffic from the
// module so the frame format can be confirmed/reverse-engineered.
#define DEBUG_RADAR_RAW 0
// 1 = diagnostic mode only: Serial-only target printout, no Wi-Fi,
// no web server, no WebSocket. This is the recommended first
// stage — get the parser confirmed against real hardware before
// bringing up networking.
// 0 = full stage-2 firmware: Wi-Fi + REST API + WebSocket + zones.
#define DIAGNOSTIC_MODE 1
+51
View File
@@ -0,0 +1,51 @@
// =====================================================================
// config.example.h
//
// Copy this file to "config.h" (same folder) and edit the values
// below. "config.h" is meant to be gitignored so you don't commit
// your Wi-Fi password.
// =====================================================================
#pragma once
// ---------------------------------------------------------------
// Wi-Fi credentials
// ---------------------------------------------------------------
#define WIFI_SSID "OrangePiVietnam"
#define WIFI_PASSWORD "orangepi.vn"
// ---------------------------------------------------------------
// Radar UART pins (ESP32-S3, HardwareSerial — NOT SoftwareSerial)
// Change these to match your wiring. Any two free GPIOs that
// support UART RX/TX on your ESP32-S3 board will work.
// ---------------------------------------------------------------
#define RADAR_RX_PIN 18 // ESP32-S3 RX <- LD2453 TX
#define RADAR_TX_PIN 17 // ESP32-S3 TX -> LD2453 RX
#define RADAR_UART_PORT 1 // which HardwareSerial to use (Serial1 or Serial2)
// Default LD2453 UART settings. 8N1 is assumed by analogy with the
// rest of the Hi-Link 24xx radar family; this has NOT been confirmed
// against the official LD2453 manual. Verify with DEBUG_RADAR_RAW
// before trusting any parsed output.
#define RADAR_BAUD_RATE 256000
// ---------------------------------------------------------------
// Node identity — for the future multi-radar (LD2453 A/B/C -> UDP
// -> Orange Pi) architecture. Not used for networking yet, but every
// frame is tagged with this so the plumbing is ready later.
// ---------------------------------------------------------------
#define NODE_ID "RADAR_A"
// ---------------------------------------------------------------
// Debug / diagnostic switches
// ---------------------------------------------------------------
// 1 = print every raw UART frame candidate as hex on the Serial
// Monitor (115200). Use this to capture real traffic from the
// module so the frame format can be confirmed/reverse-engineered.
#define DEBUG_RADAR_RAW 0
// 1 = diagnostic mode only: Serial-only target printout, no Wi-Fi,
// no web server, no WebSocket. This is the recommended first
// stage — get the parser confirmed against real hardware before
// bringing up networking.
// 0 = full stage-2 firmware: Wi-Fi + REST API + WebSocket + zones.
#define DIAGNOSTIC_MODE 1
+115
View File
@@ -0,0 +1,115 @@
// =====================================================================
// ESP32_LD2453.ino
//
// ESP32-S3 + Hi-Link HLK-LD2453 radar firmware.
// Ported from https://github.com/thanhtantran/ESP32-LD2450
//
// *** LD2453Parser's byte-level decoding is currently a STUB ***
// See the comment block at the top of LD2453Parser.h for why, and
// the README for how to help confirm the real protocol. Everything
// else in this sketch (Wi-Fi, REST API, WebSocket, zones, diagnostic
// printing) is fully functional today.
//
// Copy config.example.h to config.h and edit it before building.
// =====================================================================
#include <Arduino.h>
#include "config.h"
#include "LD2453Parser.h"
#include "ZoneManager.h"
#if !DIAGNOSTIC_MODE
#include "WebServerManager.h"
#endif
// ---------------------------------------------------------------
// Pick the HardwareSerial instance for the radar based on
// RADAR_UART_PORT in config.h. Serial0/USB is left free for the
// Serial Monitor.
// ---------------------------------------------------------------
#if RADAR_UART_PORT == 1
HardwareSerial &RadarSerial = Serial1;
#else
HardwareSerial &RadarSerial = Serial2;
#endif
LD2453Parser radar;
ZoneManager zoneManager;
#if !DIAGNOSTIC_MODE
WebServerManager webServer(zoneManager);
#endif
void printDiagnostics(const RadarFrame &frame, const LD2453Stats &stats) {
Serial.println();
Serial.println(F("LD2453 connected"));
Serial.print(F("UART: "));
Serial.println(RADAR_BAUD_RATE);
Serial.print(F("Frame length (last): "));
Serial.println(stats.lastFrameLength);
Serial.print(F("Parse errors: "));
Serial.println(stats.parseErrors);
Serial.print(F("Packets/sec: "));
Serial.println(stats.packetsPerSecond, 1);
Serial.print(F("Target count: "));
Serial.println(frame.targetCount);
for (uint8_t i = 0; i < 3; i++) {
Serial.println();
Serial.print(F("Target "));
Serial.print(i + 1);
Serial.println(':');
const RadarTarget &t = frame.targets[i];
if (!t.valid) {
Serial.println(F("INVALID"));
continue;
}
Serial.print(F("X: "));
Serial.print(t.x_mm);
Serial.println(F(" mm"));
Serial.print(F("Y: "));
Serial.print(t.y_mm);
Serial.println(F(" mm"));
Serial.print(F("Speed: "));
Serial.println(t.speed);
}
}
void setup() {
Serial.begin(115200);
delay(1500); // lets the Serial Monitor catch up without blocking the app loop later
Serial.println(F("=== ESP32_LD2453 booting ==="));
Serial.print(F("Node ID: "));
Serial.println(NODE_ID);
radar.begin(RadarSerial, RADAR_RX_PIN, RADAR_TX_PIN, RADAR_BAUD_RATE,
NODE_ID, DEBUG_RADAR_RAW);
#if !DIAGNOSTIC_MODE
webServer.begin(WIFI_SSID, WIFI_PASSWORD);
#else
Serial.println(F("DIAGNOSTIC_MODE=1: Wi-Fi/web server disabled. Serial-only output."));
#endif
}
void loop() {
// Radar acquisition/parsing is non-blocking and runs every
// iteration regardless of mode, independent of Wi-Fi state.
bool gotFrame = radar.update();
if (gotFrame) {
const RadarFrame &frame = radar.getLastFrame();
zoneManager.update(frame);
#if DIAGNOSTIC_MODE
printDiagnostics(frame, radar.getStats());
#else
webServer.broadcastFrame(frame);
#endif
}
#if !DIAGNOSTIC_MODE
webServer.loop();
#endif
}