Initialisation depot
This commit is contained in:
309
ESP32/DCC-Loco/src/ConfigServer.cpp
Normal file
309
ESP32/DCC-Loco/src/ConfigServer.cpp
Normal file
@@ -0,0 +1,309 @@
|
||||
/**
|
||||
* @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(
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>DCC Loco Decoder Config</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style>
|
||||
body { font-family: Arial; margin: 20px; }
|
||||
.container { max-width: 600px; margin: 0 auto; }
|
||||
button { padding: 10px 20px; margin: 5px; }
|
||||
input { padding: 5px; margin: 5px; }
|
||||
.status { background: #f0f0f0; padding: 10px; margin: 10px 0; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>DCC Locomotive Decoder</h1>
|
||||
<div class="status" id="status">Connecting...</div>
|
||||
|
||||
<h2>Configuration Variables</h2>
|
||||
<div>
|
||||
<label>CV Number: <input type="number" id="cvNum" min="1" max="1024" value="1"></label>
|
||||
<button onclick="readCV()">Read CV</button>
|
||||
</div>
|
||||
<div>
|
||||
<label>CV Value: <input type="number" id="cvVal" min="0" max="255" value="0"></label>
|
||||
<button onclick="writeCV()">Write CV</button>
|
||||
</div>
|
||||
<div id="cvResult"></div>
|
||||
|
||||
<h2>Actions</h2>
|
||||
<button onclick="resetDecoder()">Reset to Defaults</button>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const ws = new WebSocket('ws://' + location.host + '/ws');
|
||||
|
||||
ws.onmessage = (event) => {
|
||||
const msg = JSON.parse(event.data);
|
||||
console.log('Received:', msg);
|
||||
|
||||
if (msg.type === 'status') {
|
||||
document.getElementById('status').innerHTML =
|
||||
'Address: ' + msg.address + '<br>' +
|
||||
'Speed: ' + msg.speed + '<br>' +
|
||||
'Direction: ' + (msg.direction ? 'Forward' : 'Reverse');
|
||||
} else if (msg.type === 'cv_read') {
|
||||
document.getElementById('cvResult').innerHTML =
|
||||
'CV' + msg.cv + ' = ' + msg.value;
|
||||
} else if (msg.type === 'cv_write') {
|
||||
document.getElementById('cvResult').innerHTML =
|
||||
msg.success ? 'CV written successfully' : 'Write failed';
|
||||
}
|
||||
};
|
||||
|
||||
function readCV() {
|
||||
const cv = parseInt(document.getElementById('cvNum').value);
|
||||
ws.send(JSON.stringify({command: 'read_cv', cv: cv}));
|
||||
}
|
||||
|
||||
function writeCV() {
|
||||
const cv = parseInt(document.getElementById('cvNum').value);
|
||||
const value = parseInt(document.getElementById('cvVal').value);
|
||||
ws.send(JSON.stringify({command: 'write_cv', cv: cv, value: value}));
|
||||
}
|
||||
|
||||
function resetDecoder() {
|
||||
if (confirm('Reset all CVs to defaults?')) {
|
||||
ws.send(JSON.stringify({command: 'reset'}));
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
)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<JsonObject>());
|
||||
} else if (strcmp(command, "write_cv") == 0) {
|
||||
handleWriteCV(client, doc.as<JsonObject>());
|
||||
} 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<JsonObject>();
|
||||
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<JsonObject>();
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user