Files
canFD_esp32_glm-oh-my-open-/web_server.cpp
2026-02-20 17:50:40 +00:00

688 lines
18 KiB
C++

// web_server.cpp - Web Server Implementation with WebServer and WebSocketsServer
#include <Arduino.h>
#include <WiFi.h>
#include <WebServer.h>
#include <WebSocketsServer.h>
#include <ESPmDNS.h>
#include <SD_MMC.h>
#include <ArduinoJson.h>
#include "web_server.h"
#include "task_config.h"
#include "can_handler.h"
#include "sd_logger.h"
#include "rtc_manager.h"
#include "signal_manager.h"
#include "dbc_parser.h"
#include "auto_trigger.h"
#include "psram_buffer.h"
#include "test_handler.h"
#include "data/web_index.h"
#include "data/web_settings.h"
#include "data/web_files.h"
#include "data/web_can.h"
#include "data/web_graph.h"
#include "data/web_test.h"
WebServer server(WEB_SERVER_PORT);
WebSocketsServer webSocket(81);
bool wifiInitialized = false;
bool apModeActive = false;
bool staModeActive = false;
WiFiConfig wifiConfig;
static char wsBuffer[2048];
bool initWiFi() {
Serial.println("Initializing WiFi...");
loadWiFiConfig();
if (!startAPMode()) {
Serial.println("Failed to start AP mode!");
return false;
}
if (wifiConfig.useSTA && strlen(wifiConfig.staSSID) > 0) {
startSTAMode(wifiConfig.staSSID, wifiConfig.staPassword);
}
wifiInitialized = true;
return true;
}
bool startAPMode() {
Serial.println("Starting WiFi AP mode...");
WiFi.mode(WIFI_AP_STA);
bool result = WiFi.softAP(WIFI_AP_SSID, WIFI_AP_PASSWORD, WIFI_AP_CHANNEL, 0, WIFI_AP_MAX_CLIENTS);
if (result) {
apModeActive = true;
Serial.printf("AP Started: %s\n", WIFI_AP_SSID);
Serial.printf("AP IP: %s\n", WiFi.softAPIP().toString().c_str());
initMDNS();
return true;
}
return false;
}
bool startSTAMode(const char* ssid, const char* password) {
Serial.printf("Connecting to WiFi: %s\n", ssid);
WiFi.begin(ssid, password);
int attempts = 0;
while (WiFi.status() != WL_CONNECTED && attempts < 20) {
delay(500);
Serial.print(".");
attempts++;
}
if (WiFi.status() == WL_CONNECTED) {
staModeActive = true;
Serial.println("\nWiFi Connected!");
Serial.printf("STA IP: %s\n", WiFi.localIP().toString().c_str());
return true;
} else {
Serial.println("\nWiFi connection failed!");
return false;
}
}
void stopWiFi() {
WiFi.disconnect(true);
WiFi.mode(WIFI_OFF);
apModeActive = false;
staModeActive = false;
}
bool initMDNS() {
if (!MDNS.begin("esp32-can")) {
Serial.println("mDNS failed to start!");
return false;
}
MDNS.addService("http", "tcp", WEB_SERVER_PORT);
MDNS.addService("ws", "tcp", 81);
Serial.println("mDNS started: esp32-can.local");
return true;
}
void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) {
switch(type) {
case WStype_DISCONNECTED:
Serial.printf("[%u] Disconnected!\n", num);
break;
case WStype_CONNECTED:
Serial.printf("[%u] Connected from %s\n", num, webSocket.remoteIP(num).toString().c_str());
break;
case WStype_TEXT:
Serial.printf("[%u] Message: %s\n", num, payload);
break;
case WStype_BIN:
case WStype_ERROR:
case WStype_FRAGMENT_TEXT_START:
case WStype_FRAGMENT_BIN_START:
case WStype_FRAGMENT:
case WStype_FRAGMENT_FIN:
break;
}
}
void broadcastToClients(const char* message) {
webSocket.broadcastTXT(message);
}
void broadcastSignalData(const GraphSignal* signals, uint8_t count) {
StaticJsonDocument<2048> doc;
doc["type"] = "signal";
doc["timestamp"] = millis();
JsonArray sigArray = doc.createNestedArray("signals");
for (uint8_t i = 0; i < count; i++) {
JsonObject sig = sigArray.createNestedObject();
sig["id"] = signals[i].signal_id;
sig["value"] = signals[i].value;
}
serializeJson(doc, wsBuffer, sizeof(wsBuffer));
webSocket.broadcastTXT(wsBuffer);
}
void handleRoot() {
server.send(200, "text/html", HTML_INDEX);
}
void handleSettings() {
server.send(200, "text/html", HTML_SETTINGS);
}
void handleFiles() {
server.send(200, "text/html", HTML_FILES);
}
void handleCAN() {
server.send(200, "text/html", HTML_CAN);
}
void handleGraph() {
server.send(200, "text/html", HTML_GRAPH);
}
void handleTest() {
server.send(200, "text/html", HTML_TEST);
}
void handleAPIStatus() {
StaticJsonDocument<1024> doc;
doc["wifi"] = wifiInitialized;
doc["ap"] = apModeActive;
doc["sta"] = staModeActive;
doc["ap_ip"] = WiFi.softAPIP().toString();
if (staModeActive) {
doc["sta_ip"] = WiFi.localIP().toString();
}
doc["can"]["initialized"] = canInitialized;
uint32_t rx, tx, err;
getCANStats(rx, tx, err);
doc["can"]["rx_count"] = rx;
doc["can"]["tx_count"] = tx;
doc["can"]["error_count"] = err;
doc["can"]["mode"] = getCANMode();
doc["can"]["buffer_used"] = canFrameBuffer.available();
doc["can"]["buffer_capacity"] = canFrameBuffer.capacity();
doc["sd"]["initialized"] = sdInitialized;
doc["sd"]["total_mb"] = getSDCardSize() / (1024 * 1024);
doc["sd"]["free_mb"] = getFreeSpace() / (1024 * 1024);
doc["rtc"]["initialized"] = rtcInitialized;
doc["log"]["filename"] = getCurrentLogFilename();
doc["memory"]["heap_free"] = ESP.getFreeHeap();
doc["memory"]["heap_total"] = ESP.getHeapSize();
if (psramFound()) {
doc["memory"]["psram_free"] = ESP.getFreePsram();
doc["memory"]["psram_total"] = ESP.getPsramSize();
doc["memory"]["psram_used_mb"] = (ESP.getPsramSize() - ESP.getFreePsram()) / (1024 * 1024);
}
String output;
serializeJson(doc, output);
server.send(200, "application/json", output);
}
void handleAPIMemory() {
StaticJsonDocument<512> doc;
doc["heap"]["free"] = ESP.getFreeHeap();
doc["heap"]["total"] = ESP.getHeapSize();
doc["heap"]["used"] = ESP.getHeapSize() - ESP.getFreeHeap();
if (psramFound()) {
doc["psram"]["found"] = true;
doc["psram"]["free"] = ESP.getFreePsram();
doc["psram"]["total"] = ESP.getPsramSize();
doc["psram"]["used"] = ESP.getPsramSize() - ESP.getFreePsram();
doc["psram"]["free_mb"] = ESP.getFreePsram() / (1024 * 1024);
doc["psram"]["total_mb"] = ESP.getPsramSize() / (1024 * 1024);
} else {
doc["psram"]["found"] = false;
}
doc["can_buffer"]["used"] = canFrameBuffer.available();
doc["can_buffer"]["capacity"] = canFrameBuffer.capacity();
doc["can_buffer"]["free"] = canFrameBuffer.freeSpace();
String output;
serializeJson(doc, output);
server.send(200, "application/json", output);
}
void handleAPIFileList() {
StaticJsonDocument<4096> doc;
JsonArray files = doc.to<JsonArray>();
if (sdInitialized) {
File root = SD_MMC.open(LOGS_DIR);
if (root && root.isDirectory()) {
File file = root.openNextFile();
while (file) {
if (!file.isDirectory() && String(file.name()).endsWith(".pcap")) {
JsonObject f = files.createNestedObject();
f["name"] = file.name();
f["size"] = file.size();
f["time"] = file.getLastWrite();
}
file = root.openNextFile();
}
}
}
String output;
serializeJson(doc, output);
server.send(200, "application/json", output);
}
void handleAPIFileDownload() {
if (!server.hasArg("name")) {
server.send(400, "application/json", "{\"error\":\"Missing name parameter\"}");
return;
}
String filename = server.arg("name");
String path = String(LOGS_DIR) + "/" + filename;
if (!SD_MMC.exists(path)) {
server.send(404, "application/json", "{\"error\":\"File not found\"}");
return;
}
File file = SD_MMC.open(path, "r");
if (!file) {
server.send(500, "application/json", "{\"error\":\"Cannot open file\"}");
return;
}
server.streamFile(file, "application/octet-stream");
file.close();
}
void handleAPIFileDelete() {
if (!server.hasArg("name")) {
server.send(400, "application/json", "{\"error\":\"Missing name parameter\"}");
return;
}
String filename = server.arg("name");
if (deleteLogFile(filename.c_str())) {
server.send(200, "application/json", "{\"status\":\"deleted\"}");
} else {
server.send(500, "application/json", "{\"error\":\"Delete failed\"}");
}
}
void handleAPICANSend() {
if (!server.hasArg("plain")) {
server.send(400, "application/json", "{\"error\":\"Missing body\"}");
return;
}
String body = server.arg("plain");
StaticJsonDocument<512> doc;
DeserializationError error = deserializeJson(doc, body);
if (error) {
server.send(400, "application/json", "{\"error\":\"Invalid JSON\"}");
return;
}
String idStr = doc["id"] | "0x100";
uint32_t id = 0;
if (idStr.startsWith("0x") || idStr.startsWith("0X")) {
id = strtol(idStr.c_str(), NULL, 16);
} else {
id = idStr.toInt();
}
String frameType = doc["type"] | "standard";
bool ext = (frameType == "extended");
bool fd = (frameType == "fd") || (doc["isFD"] | false);
JsonArray dataArr = doc["data"];
uint8_t data[64] = {0};
uint8_t len = doc["length"] | 8;
int i = 0;
for (JsonVariant v : dataArr) {
if (i < 64) {
data[i++] = v.as<uint8_t>();
}
}
if (ext) id |= 0x80000000;
if (sendCANFrame(id, data, len, fd)) {
server.send(200, "application/json", "{\"status\":\"sent\"}");
} else {
server.send(500, "application/json", "{\"error\":\"Send failed\"}");
}
}
void handleAPIWiFiConfig() {
if (!server.hasArg("plain")) {
server.send(400, "application/json", "{\"error\":\"Missing body\"}");
return;
}
String body = server.arg("plain");
StaticJsonDocument<256> doc;
DeserializationError error = deserializeJson(doc, body);
if (error) {
server.send(400, "application/json", "{\"error\":\"Invalid JSON\"}");
return;
}
if (doc.containsKey("ssid") && doc.containsKey("password")) {
strlcpy(wifiConfig.staSSID, doc["ssid"], sizeof(wifiConfig.staSSID));
strlcpy(wifiConfig.staPassword, doc["password"], sizeof(wifiConfig.staPassword));
wifiConfig.useSTA = true;
saveWiFiConfig();
server.send(200, "application/json", "{\"status\":\"saved\",\"reconnect\":true}");
} else {
server.send(400, "application/json", "{\"error\":\"Missing ssid or password\"}");
}
}
void handleAPILoggingStart() {
if (startLogFile()) {
server.send(200, "application/json", "{\"status\":\"started\"}");
} else {
server.send(500, "application/json", "{\"error\":\"Start failed\"}");
}
}
void handleAPILoggingStop() {
closeLogFile();
server.send(200, "application/json", "{\"status\":\"stopped\"}");
}
void handleAPIDBCUpload() {
if (!server.hasArg("plain")) {
server.send(400, "application/json", "{\"error\":\"Missing body\"}");
return;
}
String content = server.arg("plain");
if (parseDBC(content.c_str())) {
server.send(200, "application/json", "{\"status\":\"loaded\"}");
} else {
server.send(400, "application/json", "{\"error\":\"Parse failed\"}");
}
}
void handleAPITimeSync() {
if (!server.hasArg("plain")) {
server.send(400, "application/json", "{\"error\":\"Missing body\"}");
return;
}
String body = server.arg("plain");
StaticJsonDocument<128> doc;
DeserializationError error = deserializeJson(doc, body);
if (error) {
server.send(400, "application/json", "{\"error\":\"Invalid JSON\"}");
return;
}
if (doc.containsKey("timestamp")) {
uint32_t timestamp = doc["timestamp"];
setRTCTime(timestamp);
server.send(200, "application/json", "{\"status\":\"synced\"}");
} else {
server.send(400, "application/json", "{\"error\":\"Missing timestamp\"}");
}
}
void handleAPIRestart() {
server.send(200, "application/json", "{\"status\":\"restarting\"}");
delay(100);
ESP.restart();
}
void handleAPICANConfig() {
if (!server.hasArg("plain")) {
server.send(400, "application/json", "{\"error\":\"Missing body\"}");
return;
}
String body = server.arg("plain");
StaticJsonDocument<256> doc;
DeserializationError error = deserializeJson(doc, body);
if (error) {
server.send(400, "application/json", "{\"error\":\"Invalid JSON\"}");
return;
}
uint32_t arbBaud = doc["arbBaud"] | CAN_DEFAULT_ARBITRATION_BAUDRATE;
uint32_t dataBaud = doc["dataBaud"] | CAN_DEFAULT_DATA_BAUDRATE;
uint8_t mode = doc["mode"] | 0;
bool enableFD = doc["enableFD"] | true;
if (setCANBaudrateAndMode(arbBaud, dataBaud, mode, enableFD)) {
server.send(200, "application/json", "{\"status\":\"configured\"}");
} else {
server.send(500, "application/json", "{\"error\":\"Configuration failed\"}");
}
}
void handleAPITriggerConfig() {
if (!server.hasArg("plain")) {
server.send(200, "application/json", "{\"error\":\"Missing body\"}");
return;
}
String body = server.arg("plain");
StaticJsonDocument<512> doc;
deserializeJson(doc, body);
if (doc.containsKey("enabled")) {
enableTrigger(doc["enabled"]);
}
if (doc.containsKey("logic")) {
setLogicalOperator(doc["logic"] == "AND" ? LOGIC_AND : LOGIC_OR);
}
char buffer[256];
getTriggerStatusJSON(buffer, sizeof(buffer));
server.send(200, "application/json", buffer);
}
void handleAPISignalAdd() {
if (!server.hasArg("plain")) {
server.send(400, "application/json", "{\"error\":\"Missing body\"}");
return;
}
String body = server.arg("plain");
StaticJsonDocument<256> doc;
deserializeJson(doc, body);
const char* name = doc["name"];
uint32_t canId = doc["canId"];
uint32_t startBit = doc["startBit"];
uint32_t length = doc["length"];
bool littleEndian = doc["littleEndian"] | true;
bool isSigned = doc["signed"] | false;
float factor = doc["factor"] | 1.0;
float offset = doc["offset"] | 0.0;
if (addManualSignal(name, canId, startBit, length, littleEndian, isSigned, factor, offset)) {
server.send(200, "application/json", "{\"status\":\"added\"}");
} else {
server.send(500, "application/json", "{\"error\":\"Failed to add signal\"}");
}
}
void handleAPISignalList() {
char buffer[2048];
getSignalsJSON(buffer, sizeof(buffer));
server.send(200, "application/json", buffer);
}
void handleAPITestStart() {
if (isTestRunning()) {
server.send(400, "application/json", "{\"error\":\"Test already running\"}");
return;
}
if (!server.hasArg("plain")) {
server.send(400, "application/json", "{\"error\":\"Missing body\"}");
return;
}
String body = server.arg("plain");
StaticJsonDocument<256> doc;
deserializeJson(doc, body);
String testType = doc["type"] | "loopback";
uint32_t frameCount = doc["frames"] | 1000;
uint32_t interval = doc["interval"] | 1000;
uint8_t dataLen = doc["dataLen"] | 64;
bool useFD = doc["useFD"] | true;
uint32_t canId = doc["canId"] | 0x100;
bool started = false;
if (testType == "loopback") {
started = startLoopbackTest(frameCount, interval);
} else if (testType == "stress") {
started = startStressTest(frameCount, dataLen, useFD);
} else if (testType == "sequence") {
started = startSequenceTest(frameCount, canId);
}
if (started) {
server.send(200, "application/json", "{\"status\":\"started\"}");
} else {
server.send(500, "application/json", "{\"error\":\"Failed to start test\"}");
}
}
void handleAPITestStop() {
stopTest();
server.send(200, "application/json", "{\"status\":\"stopped\"}");
}
void handleAPITestStatus() {
char buffer[512];
getTestResultJSON(buffer, sizeof(buffer));
server.send(200, "application/json", buffer);
}
void handleNotFound() {
server.send(404, "text/plain", "Not Found");
}
bool initWebServer() {
Serial.println("Initializing Web Server...");
server.on("/", handleRoot);
server.on("/settings", handleSettings);
server.on("/files", handleFiles);
server.on("/can", handleCAN);
server.on("/graph", handleGraph);
server.on("/test", handleTest);
server.on("/api/status", handleAPIStatus);
server.on("/api/memory", handleAPIMemory);
server.on("/api/files", handleAPIFileList);
server.on("/api/files/download", handleAPIFileDownload);
server.on("/api/files/delete", handleAPIFileDelete);
server.on("/api/can/send", HTTP_POST, handleAPICANSend);
server.on("/api/wifi", HTTP_POST, handleAPIWiFiConfig);
server.on("/api/logging/start", handleAPILoggingStart);
server.on("/api/logging/stop", handleAPILoggingStop);
server.on("/api/dbc/upload", HTTP_POST, handleAPIDBCUpload);
server.on("/api/time", HTTP_POST, handleAPITimeSync);
server.on("/api/restart", HTTP_POST, handleAPIRestart);
server.on("/api/can/config", HTTP_POST, handleAPICANConfig);
server.on("/api/trigger", handleAPITriggerConfig);
server.on("/api/signal/add", HTTP_POST, handleAPISignalAdd);
server.on("/api/signal/list", handleAPISignalList);
server.on("/api/test/start", HTTP_POST, handleAPITestStart);
server.on("/api/test/stop", handleAPITestStop);
server.on("/api/test/status", handleAPITestStatus);
server.onNotFound(handleNotFound);
server.begin();
webSocket.begin();
webSocket.onEvent(webSocketEvent);
Serial.println("Web Server started on port 80");
Serial.println("WebSocket started on port 81");
return true;
}
void webServerTask(void *pvParameters) {
Serial.println("Web Server Task started on Core 1");
if (!initWiFi()) {
Serial.println("WiFi initialization failed!");
}
if (!initWebServer()) {
Serial.println("Web Server initialization failed!");
}
while (1) {
server.handleClient();
webSocket.loop();
vTaskDelay(pdMS_TO_TICKS(10));
}
}
void wsTxTask(void *pvParameters) {
Serial.println("WebSocket TX Task started on Core 1");
CanFrame frame;
SignalValue signals[10];
uint16_t signalCount = 0;
uint32_t lastUpdate = 0;
while (1) {
if (xQueueReceive(graphQueue, &frame, pdMS_TO_TICKS(50)) == pdTRUE) {
updateAllSignals(&frame);
}
uint32_t now = millis();
if (now - lastUpdate >= 100) {
signalCount = getEnabledSignals(signals, 10);
if (signalCount > 0) {
GraphSignal graphSignals[10];
for (uint16_t i = 0; i < signalCount; i++) {
strncpy(graphSignals[i].signal_id, signals[i].name, 32);
graphSignals[i].value = signals[i].value;
graphSignals[i].timestamp = signals[i].timestamp;
}
broadcastSignalData(graphSignals, signalCount);
}
lastUpdate = now;
}
updateTrigger();
}
}
bool loadWiFiConfig() {
wifiConfig.useSTA = false;
wifiConfig.staSSID[0] = '\0';
wifiConfig.staPassword[0] = '\0';
return true;
}
bool saveWiFiConfig() {
return true;
}