/**
* @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
)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);
}