/** * @file ConfigServer.cpp * @brief WiFi/Bluetooth Configuration Server Implementation */ #include "ConfigServer.h" ConfigServer::ConfigServer(CVManager& cvManager) : cvMgr(cvManager), server(nullptr), ws(nullptr), active(false), statusCallback(nullptr), lastStatusUpdate(0) {} bool ConfigServer::begin(const char* ssid, const char* password, bool useAP) { // Initialize WiFi if (useAP) { // Access Point mode String apName = ssid ? String(ssid) : getDefaultAPName(); String apPass = password ? String(password) : "dcc12345"; WiFi.softAP(apName.c_str(), apPass.c_str()); Serial.println("WiFi AP started"); Serial.print("AP Name: "); Serial.println(apName); Serial.print("IP Address: "); Serial.println(WiFi.softAPIP()); } else { // Station mode if (!ssid) return false; WiFi.begin(ssid, password); int attempts = 0; while (WiFi.status() != WL_CONNECTED && attempts < 20) { delay(500); Serial.print("."); attempts++; } if (WiFi.status() != WL_CONNECTED) { Serial.println("\nWiFi connection failed"); return false; } Serial.println("\nWiFi connected"); Serial.print("IP Address: "); Serial.println(WiFi.localIP()); } // Create web server server = new AsyncWebServer(80); ws = new AsyncWebSocket("/ws"); setupWebSocket(); setupHTTPRoutes(); server->begin(); active = true; return true; } void ConfigServer::stop() { if (server) { server->end(); delete server; server = nullptr; } if (ws) { delete ws; ws = nullptr; } WiFi.disconnect(); active = false; } void ConfigServer::update() { if (!active) return; // Send periodic status updates unsigned long currentTime = millis(); if (currentTime - lastStatusUpdate >= 1000) { // Every second lastStatusUpdate = currentTime; broadcastStatus(); } } void ConfigServer::setupWebSocket() { ws->onEvent([this](AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len) { handleWebSocketEvent(server, client, type, arg, data, len); }); server->addHandler(ws); } void ConfigServer::setupHTTPRoutes() { // Serve basic HTML page server->on("/", HTTP_GET, [](AsyncWebServerRequest* request) { String html = R"html( DCC Loco Decoder Config

DCC Locomotive Decoder

Connecting...

Configuration Variables

Actions

)html"; request->send(200, "text/html", html); }); } void ConfigServer::handleWebSocketEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len) { if (type == WS_EVT_CONNECT) { Serial.printf("WebSocket client #%u connected\n", client->id()); handleGetStatus(client); } else if (type == WS_EVT_DISCONNECT) { Serial.printf("WebSocket client #%u disconnected\n", client->id()); } else if (type == WS_EVT_DATA) { AwsFrameInfo* info = (AwsFrameInfo*)arg; if (info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT) { data[len] = 0; handleWebSocketMessage(client, data, len); } } } void ConfigServer::handleWebSocketMessage(void* clientPtr, uint8_t* data, size_t len) { AsyncWebSocketClient* client = (AsyncWebSocketClient*)clientPtr; StaticJsonDocument<256> doc; DeserializationError error = deserializeJson(doc, data, len); if (error) { Serial.println("JSON parse error"); return; } const char* command = doc["command"]; if (strcmp(command, "read_cv") == 0) { handleReadCV(client, doc.as()); } else if (strcmp(command, "write_cv") == 0) { handleWriteCV(client, doc.as()); } else if (strcmp(command, "get_status") == 0) { handleGetStatus(client); } else if (strcmp(command, "reset") == 0) { handleReset(client); } } void ConfigServer::handleReadCV(AsyncWebSocketClient* client, JsonObject& json) { uint16_t cvNum = json["cv"]; uint8_t value = cvMgr.readCV(cvNum, 0); StaticJsonDocument<128> response; response["type"] = "cv_read"; response["cv"] = cvNum; response["value"] = value; String output; serializeJson(response, output); client->text(output); } void ConfigServer::handleWriteCV(AsyncWebSocketClient* client, JsonObject& json) { uint16_t cvNum = json["cv"]; uint8_t value = json["value"]; bool success = cvMgr.writeCV(cvNum, value); StaticJsonDocument<128> response; response["type"] = "cv_write"; response["success"] = success; String output; serializeJson(response, output); client->text(output); } void ConfigServer::handleGetStatus(AsyncWebSocketClient* client) { StaticJsonDocument<256> response; JsonObject status = response.to(); status["type"] = "status"; if (statusCallback) { statusCallback(status); } else { status["address"] = cvMgr.getLocoAddress(); status["speed"] = 0; status["direction"] = true; } String output; serializeJson(response, output); client->text(output); } void ConfigServer::handleReset(AsyncWebSocketClient* client) { cvMgr.resetToDefaults(); sendResponse(client, "reset", true, "Decoder reset to defaults"); } void ConfigServer::sendResponse(AsyncWebSocketClient* client, const char* type, bool success, const char* message) { StaticJsonDocument<128> response; response["type"] = type; response["success"] = success; if (message) { response["message"] = message; } String output; serializeJson(response, output); client->text(output); } void ConfigServer::broadcastStatus() { if (!ws || ws->count() == 0) return; StaticJsonDocument<256> response; JsonObject status = response.to(); status["type"] = "status"; if (statusCallback) { statusCallback(status); } else { status["address"] = cvMgr.getLocoAddress(); } String output; serializeJson(response, output); ws->textAll(output); } void ConfigServer::setStatusCallback(StatusCallback callback) { statusCallback = callback; } String ConfigServer::getDefaultAPName() { uint64_t chipid = ESP.getEfuseMac(); return "DCC-Loco-" + String((uint32_t)(chipid >> 32), HEX); }