#include #include static std::map s_webserverCommands = { {"setTime", AstroWebServer::SET_TIME}, }; void on_ping(TimerHandle_t xTimer) { if (!xTimer) return; auto ws = (AsyncWebSocket *)pvTimerGetTimerID(xTimer); ws->pingAll(); ws->cleanupClients(); } 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, AwsEventType type, void *arg, uint8_t *data, size_t len) { onWsEvent(server, client, type, arg, data, len); }); 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.begin(); m_websocket.enable(true); m_pingTimer = xTimerCreate("wsPingTimer", pdMS_TO_TICKS(2000), pdTRUE, (void *)&m_websocket, on_ping); LOG_DEBUG("Webserver Init OK"); } AstroWebServer::~AstroWebServer() { xTimerDelete(m_pingTimer, pdMS_TO_TICKS(10)); m_webserver.removeHandler(&m_websocket); m_webserver.end(); } void AstroWebServer::sendWsData(const String &data) { if (m_websocket.count()) { m_websocket.textAll(data); } } void AstroWebServer::onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) { switch (type) { case WS_EVT_CONNECT: LOG_DEBUG("WS client IP[", client->remoteIP().toString().c_str(), "]-ID[", client->id(), "] CONNECTED"); break; case WS_EVT_DISCONNECT: LOG_DEBUG("WS client IP[", client->remoteIP().toString().c_str(), "]-ID[", client->id(), "] DISCONNECTED"); break; case WS_EVT_PONG: LOG_DEBUG("WS client IP[", client->remoteIP().toString().c_str(), "]-ID[", client->id(), "] PONG"); 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 AstroWebServer::onUploadRequest(AsyncWebServerRequest *request) { if (m_uploadFailed) request->send(500, "text/plain", "Upload failed"); else request->send(200, "text/plain", "Upload successful"); } 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_uploadFailed = false; String safeName = filename; int slashIndex = safeName.lastIndexOf('/'); if (slashIndex >= 0) safeName = safeName.substring(slashIndex + 1); if (safeName.length() == 0) { m_uploadFailed = true; LOG_ERROR("Invalid file name"); return; } const std::filesystem::path filePath = std::filesystem::path(m_filesystem.mountpoint()) / safeName.c_str(); if (m_filesystem.exists(filePath.c_str())) m_filesystem.remove(filePath.c_str()); m_uploadFile = m_filesystem.open(filePath.c_str(), FILE_WRITE); if (!m_uploadFile) { m_uploadFailed = true; LOG_ERROR("Failed to open upload file:", filePath.c_str()); return; } } // Actual write of file data if (!m_uploadFailed && m_uploadFile) { if (m_uploadFile.write(data, len) != len) m_uploadFailed = true; } // close the file and save on final call if (final && m_uploadFile) { m_uploadFile.close(); if (!m_uploadFailed) LOG_INFO("Uploaded file to LittleFS:", filename.c_str()); } }