diff --git a/RotaxMonitor/data/script.js b/RotaxMonitor/data/script.js index 13913ab..573dcdf 100644 --- a/RotaxMonitor/data/script.js +++ b/RotaxMonitor/data/script.js @@ -38,6 +38,11 @@ function connectWS() { console.log("WebSocket connesso"); lastMessageTimestamp = Date.now(); setLoadingIndicator(false); + + ws.send(JSON.stringify({ + cmd: "setTime", + time: Math.floor(Date.now() / 1000) + })); }; ws.onclose = () => { @@ -142,7 +147,7 @@ function updateChart(data) { const boxA = data.box_a; const coils12A = boxA.coils12 || {}; const coils34A = boxA.coils34 || {}; - chartData.datasets[0].data.push(boxA.eng_rpm/10); + chartData.datasets[0].data.push(boxA.eng_rpm / 10); chartData.datasets[2].data.push(coils12A.spark_delay); chartData.datasets[3].data.push(coils34A.spark_delay); } @@ -151,7 +156,7 @@ function updateChart(data) { const boxB = data.box_b; const coils12B = boxB.coils12 || {}; const coils34B = boxB.coils34 || {}; - chartData.datasets[1].data.push(boxB.eng_rpm/10); + chartData.datasets[1].data.push(boxB.eng_rpm / 10); chartData.datasets[4].data.push(coils12B.spark_delay); chartData.datasets[5].data.push(coils34B.spark_delay); } diff --git a/RotaxMonitor/src/main.cpp b/RotaxMonitor/src/main.cpp index daa940e..b8b6f5a 100644 --- a/RotaxMonitor/src/main.cpp +++ b/RotaxMonitor/src/main.cpp @@ -210,7 +210,7 @@ void loop() LOG_DEBUG("Real Time Tasks A & B initialized"); led.setStatus(RGBled::LedStatus::OK); - WebPage webPage(80, LittleFS); // Initialize webserver and Websocket + AstroWebServer webPage(80, LittleFS); // Initialize webserver and Websocket task_A.onMessage([&webPage](ignitionBoxStatusFiltered sts){ ArduinoJson::JsonDocument doc; @@ -237,6 +237,7 @@ void loop() { clearScreen(); printRunningTasksMod(Serial); + delay(100); last_loop = millis(); } } //////////////// INNER LOOP ///////////////////// diff --git a/RotaxMonitor/src/utils.cpp b/RotaxMonitor/src/utils.cpp index 1e59492..46951fd 100644 --- a/RotaxMonitor/src/utils.cpp +++ b/RotaxMonitor/src/utils.cpp @@ -99,7 +99,14 @@ void printRunningTasksMod(Print &printer, std::function +#include -WebPage::WebPage(const uint8_t port, fs::FS &filesystem) : m_port(port), m_webserver(AsyncWebServer(port)), m_websocket(AsyncWebSocket("/ws")), m_filesystem(filesystem) +static const std::map s_webserverCommands = { + {"setTime", AstroWebServer::SET_TIME}, +}; + +AstroWebServer::AstroWebServer(const uint8_t port, fs::FS &filesystem) : m_port(port), m_webserver(AsyncWebServer(port)), m_websocket(AsyncWebSocket("/ws")), m_filesystem(filesystem) { LOG_DEBUG("Initializing Web Server"); m_websocket.onEvent([this](AsyncWebSocket *server, AsyncWebSocketClient *client, @@ -10,62 +15,107 @@ WebPage::WebPage(const uint8_t port, fs::FS &filesystem) : m_port(port), m_webse m_webserver.addHandler(&m_websocket); m_webserver.serveStatic("/", m_filesystem, "/").setDefaultFile("index.html"); - m_webserver.on("/upload", HTTP_POST, - [this](AsyncWebServerRequest *request) - { onUploadRequest(request); }, - [this](AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final) - { onUploadHandler(request, filename, index, data, len, final); } - ); + m_webserver.on("/upload", HTTP_POST, [this](AsyncWebServerRequest *request) + { onUploadRequest(request); }, [this](AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final) + { onUploadHandler(request, filename, index, data, len, final); }); m_webserver.begin(); LOG_DEBUG("Webserver Init OK"); } -WebPage::~WebPage() +AstroWebServer::~AstroWebServer() { m_webserver.removeHandler(&m_websocket); m_webserver.end(); } -void WebPage::sendWsData(const String &data){ - if (m_websocket.count()){ +void AstroWebServer::sendWsData(const String &data) +{ + if (m_websocket.count()) + { m_websocket.textAll(data); } } -void WebPage::onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) +void AstroWebServer::onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) { switch (type) { case WS_EVT_CONNECT: - Serial.printf("WS client IP[%s]-ID[%u] CONNECTED\r\n", client->remoteIP().toString().c_str(), client->id()); + LOG_DEBUG("WS client IP[", client->remoteIP().toString().c_str(), "]-ID[", client->id(), "] CONNECTED"); break; case WS_EVT_DISCONNECT: - Serial.printf("WS client ID[%u] DISCONNECTED\r\n", client->remoteIP().toString().c_str(), client->id()); + LOG_DEBUG("WS client IP[", client->remoteIP().toString().c_str(), "]-ID[", client->id(), "] DISCONNECTED"); break; + case WS_EVT_DATA: + { + AwsFrameInfo *info = (AwsFrameInfo *)arg; + if (info->final && info->index == 0 && info->len == len) + { + std::string data_str((char *)data, len); + ArduinoJson::JsonDocument doc; + if (auto rv = ArduinoJson::deserializeJson(doc, data_str) != ArduinoJson::DeserializationError::Ok) + { + LOG_ERROR("WS Client unable to deserialize Json"); + return; + } + if (!doc["cmd"].is() || !s_webserverCommands.contains(doc["cmd"])) + { + LOG_WARN("WS Client Invalid Json command [", doc["cmd"].as().c_str(), "]"); + return; + } + std::string buffer; + switch (s_webserverCommands.at(doc["cmd"])) + { + case SET_TIME: + { + auto epoch = doc["time"].as(); + timeval te{ + .tv_sec = epoch, + .tv_usec = 0, + }; + timezone tz{ + .tz_minuteswest = 0, + .tz_dsttime = DST_MET, + }; + settimeofday(&te, &tz); + time_t now = time(nullptr); + struct tm *t = localtime(&now); + buffer.resize(64); + strftime(buffer.data(), sizeof(buffer), "%Y-%m-%d %H:%M:%S", t); + LOG_DEBUG("WS Client set Datetime to: ", buffer.c_str()); + break; + } + + default: + // call external command callback + break; + } + } + } } } -void WebPage::onUploadRequest(AsyncWebServerRequest *request) +void AstroWebServer::onUploadRequest(AsyncWebServerRequest *request) { - if (m_upload_failed) + if (m_uploadFailed) request->send(500, "text/plain", "Upload failed"); else request->send(200, "text/plain", "Upload successful"); } -void WebPage::onUploadHandler(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final) +void AstroWebServer::onUploadHandler(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final) { if (index == 0) // only on first iteration to open file { - m_upload_failed = false; + m_uploadFailed = false; String safeName = filename; int slashIndex = safeName.lastIndexOf('/'); if (slashIndex >= 0) safeName = safeName.substring(slashIndex + 1); if (safeName.length() == 0) { - m_upload_failed = true; + m_uploadFailed = true; LOG_ERROR("Invalid file name"); return; } @@ -74,27 +124,27 @@ void WebPage::onUploadHandler(AsyncWebServerRequest *request, const String &file if (m_filesystem.exists(filePath.c_str())) m_filesystem.remove(filePath.c_str()); - m_upload_file = m_filesystem.open(filePath.c_str(), FILE_WRITE); - if (!m_upload_file) + m_uploadFile = m_filesystem.open(filePath.c_str(), FILE_WRITE); + if (!m_uploadFile) { - m_upload_failed = true; + m_uploadFailed = true; LOG_ERROR("Failed to open upload file:", filePath.c_str()); return; } } // Actual write of file data - if (!m_upload_failed && m_upload_file) + if (!m_uploadFailed && m_uploadFile) { - if (m_upload_file.write(data, len) != len) - m_upload_failed = true; + if (m_uploadFile.write(data, len) != len) + m_uploadFailed = true; } // close the file and save on final call - if (final && m_upload_file) + if (final && m_uploadFile) { - m_upload_file.close(); - if (!m_upload_failed) + m_uploadFile.close(); + if (!m_uploadFailed) LOG_INFO("Uploaded file to LittleFS:", filename.c_str()); } } diff --git a/RotaxMonitor/src/webserver.h b/RotaxMonitor/src/webserver.h index 5e230f3..3eb8aac 100644 --- a/RotaxMonitor/src/webserver.h +++ b/RotaxMonitor/src/webserver.h @@ -1,5 +1,5 @@ #pragma once -#define DEBUGLOG_DEFAULT_LOG_LEVEL_INFO +#define DEBUGLOG_DEFAULT_LOG_LEVEL_DEBUG // System includes #include @@ -7,26 +7,22 @@ #include #include #include +#include + #include -class WebPage +class AstroWebServer { - const uint8_t m_port = 80; - fs::FS &m_filesystem; - AsyncWebServer m_webserver; - AsyncWebSocket m_websocket; - bool m_upload_failed = false; - fs::File m_upload_file; public: - WebPage(const uint8_t port, fs::FS &filesystem); - ~WebPage(); + AstroWebServer(const uint8_t port, fs::FS &filesystem); + ~AstroWebServer(); void sendWsData(const String &data); private: void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, - AwsEventType type, void *arg, uint8_t *data, size_t len); + AwsEventType type, void *arg, uint8_t *data, size_t len); void onUploadRequest(AsyncWebServerRequest *request); void onUploadHandler(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final); @@ -35,4 +31,17 @@ private: void onStop(AsyncWebServerRequest *request); void onDownload(AsyncWebServerRequest *request); +private: + const uint8_t m_port = 80; + fs::FS &m_filesystem; + AsyncWebServer m_webserver; + AsyncWebSocket m_websocket; + bool m_uploadFailed = false; + fs::File m_uploadFile; + +public: + enum c_commandEnum + { + SET_TIME + }; };