#include "WebServer.h" #include "DebugLog.h" #include "DataLogger.h" #include "Info.h" #include "utils.h" #include #include #include #include #include "HTTPSServer.hpp" #include "SSLCert.hpp" #include "HTTPRequest.hpp" #include "HTTPResponse.hpp" // generated certificate data #include "cert.h" #include "private_key.h" /** Check if we have multiple cores */ #if CONFIG_FREERTOS_UNICORE #define WEBSERVER_RUNNING_CORE 0 #else #define WEBSERVER_RUNNING_CORE 1 #endif using namespace httpsserver; detail::WebServer WebServer; namespace detail { // Create an SSL certificate object from the files included above SSLCert cert = SSLCert( crt_DER, crt_DER_len, private_key_DER, private_key_DER_len ); HTTPSServer httpServer = HTTPSServer(&cert); WebServer::WebServer() { } WebServer::~WebServer() { } void WebServer::begin() { ::DebugLog.println("Creating server task... "); xTaskCreatePinnedToCore(WebServer::ServerTask_, "webserver", 6144, nullptr, 1, nullptr, WEBSERVER_RUNNING_CORE); } void WebServer::setStatus(const Status& status) { status_ = status; } void WebServer::ServerTask_(void* params) { // Main HTML page ResourceNode* nodeRoot = new ResourceNode("/", "GET", &WebServer::HandleIndex_); ResourceNode* nodeIndex = new ResourceNode("/index.html", "GET", &WebServer::HandleIndex_); httpServer.registerNode(nodeRoot); httpServer.registerNode(nodeIndex); // API ResourceNode* nodeGetStatus = new ResourceNode("/api/status", "GET", &WebServer::HandleGetStatus_); ResourceNode* nodePostInfo = new ResourceNode("/api/info", "POST", &WebServer::HandlePostInfo_); httpServer.registerNode(nodeGetStatus); httpServer.registerNode(nodePostInfo); // Default node (either a static file, if found, or a 404 error) ResourceNode* nodeDefault = new ResourceNode("", "GET", &WebServer::HandleDefault_); httpServer.setDefaultNode(nodeDefault); ::DebugLog.println("Starting server... "); httpServer.start(); if (httpServer.isRunning()) { ::DebugLog.println("Server ready."); while(true) { httpServer.loop(); delay(1); } } } void WebServer::HandleIndex_(HTTPRequest * request, HTTPResponse * response) { // Status code is 200 OK by default. response->setHeader("Content-Type", "text/html"); File file = SPIFFS.open("/www/index.html"); SendContent_(file, response); file.close(); } void WebServer::HandleGetStatus_(httpsserver::HTTPRequest * request, httpsserver::HTTPResponse * response) { const Status& status = ::WebServer.status_; int v = status.batteryVoltage; int c = status.batteryOutputCurrent; int s = (int)(status.speed * 1000.0f + 0.5f); int temp = status.temperature; int alt = status.altitude; int td = status.tripDistance; int ttt = status.tripTotalTime; int tmt = status.tripMovingTime; int tae = status.tripAscendingElevation / 100; // convert mm to dm int tme = status.tripMotorEnergy / 360; // convert Joules to dWh (tenth of Wh) float latitude = -1000.0f; float longitude = -1000.0f; char realtime[64] = {0}; const char* logFileName = DataLogger::get().currentLogFileName(); if(String(logFileName).startsWith("/log/")) logFileName += 5; int totalSize = (int)(SPIFFS.totalBytes() / 1000); int usedSize = (int)(SPIFFS.usedBytes() / 1000); char json[256]; sprintf(json, "{\"v\":%d,\"c\":%d,\"s\":%d,\"td\":%d,\"ttt\":%d,\"tmt\":%d,\"tae\":%d,\"tme\":%d,\"temp\":%d,\"alt\":%d,\"log\":\"%s\",\"tot\":%d,\"used\":%d,\"lat\":%.5f,\"lng\":%.5f,\"d\":\"%s\"}", v, c, s, td, ttt, tmt, tae, tme, temp, alt, logFileName, totalSize, usedSize, latitude, longitude, realtime); response->setHeader("Content-Type", "text/json"); response->print(json); } void WebServer::HandlePostInfo_(httpsserver::HTTPRequest * request, httpsserver::HTTPResponse * response) { char body[128]; // in current implementation, we expect the whole body to fit here size_t bodySize = request->readChars(body, sizeof(body) - 1); body[bodySize] = 0; request->discardRequestBody(); //::DebugLog.println(body.c_str()); Info receivedInfo; char* nextParam = body; while (true) { char* sepPos = strstr(nextParam, "&"); if(sepPos == nullptr) sepPos = &body[bodySize]; if(nextParam == sepPos) break; // should happen only if body is an empty string or ends with an "&" character *sepPos = 0; // split the string in-place, this overrides the separating character which is fine //::DebugLog.println(nextParam); char* eqPos = strstr(nextParam, "="); if(eqPos != nullptr && eqPos != nextParam && *(eqPos+1) != 0) { *eqPos = 0; // split the string in-place, overriding the equal sign char* paramName = nextParam; char* paramValue = eqPos + 1; utils::replaceString(paramValue, "%3A", ":"); //::DebugLog.print(paramName); ::DebugLog.print(" = "); ::DebugLog.println(paramValue); if(strcmp(paramName, "lat") == 0) { char *ending = nullptr; receivedInfo.latitude = strtof(paramValue, &ending); if (*ending != 0) receivedInfo.latitude = -1000.0f; } else if(strcmp(paramName, "lng") == 0) { char *ending = nullptr; receivedInfo.longitude = strtof(paramValue, &ending); if (*ending != 0) receivedInfo.longitude = -1000.0f; } else if(strcmp(paramName, "alt") == 0) { char *ending = nullptr; receivedInfo.gpsAltitude = strtof(paramValue, &ending); if (*ending != 0) receivedInfo.gpsAltitude = -1000.0f; } else if(strcmp(paramName, "time") == 0) { strncpy(receivedInfo.realtime, paramValue, sizeof(receivedInfo.realtime)); } } if(sepPos == &body[bodySize]) break; nextParam = sepPos + 1; } if(::WebServer.infoReceived_ != nullptr) { ::WebServer.infoReceived_(receivedInfo); } } void WebServer::HandleDefault_(HTTPRequest * request, HTTPResponse * response) { // Discard request body, if we received any // We do this, as this is the default node and may also server POST/PUT requests request->discardRequestBody(); std::string filePath = "/www"; std::string fullURL = request->getRequestString(); filePath = filePath + fullURL.substr(0, fullURL.find("?")); //::DebugLog.println(filePath.c_str()); File file = SPIFFS.open(filePath.c_str()); if(!file || file.isDirectory()) { // We've not found any matching file, so this is a 404 error response->setStatusCode(404); response->setStatusText("Not Found"); response->setHeader("Content-Type", "text/html"); response->println(""); response->println(""); response->println("Not Found"); response->println("

404 Not Found

The requested resource was not found on this server.

"); response->println(""); } else { // We've found a file, send the content response->setHeader("Cache-Control", "max-age=5184000"); SendContent_(file, response); } file.close(); } void WebServer::SendContent_(Stream& stream, httpsserver::HTTPResponse * response) { uint8_t buffer[128]; while(stream.available()) { size_t numBytes = stream.readBytes(buffer, sizeof(buffer)); response->write(buffer, numBytes); } } }