Files
esp32s3-ld2453/WebServerManager.cpp
T
2026-09-11 15:19:14 +07:00

127 lines
4.6 KiB
C++

// =====================================================================
// 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) {
JsonDocument 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
JsonDocument doc;
doc["sensor"] = "LD2453";
doc["node_id"] = frame.node_id;
doc["sequence"] = frame.sequence_number;
doc["timestamp"] = frame.timestamp_ms;
doc["target_count"] = frame.targetCount;
JsonArray targets = doc["targets"].to<JsonArray>();
// Loop qua TẤT CẢ 3 slot, không chỉ dựa vào targetCount
// Đảm bảo target ở slot 2 vẫn được gửi ngay cả khi slot 1 trống
for (uint8_t i = 0; i < 3; i++) {
const RadarTarget &t = frame.targets[i];
JsonObject jt = targets.add<JsonObject>();
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;
if (t.valid) {
jt["distance"] = (int)t.distance_mm;
jt["angle"] = (int)t.angle_deg;
}
}
String out;
serializeJson(doc, out);
_ws.textAll(out);
}