vehicle-monitor/ESP32/src/WebServer.cpp

301 lines
8.6 KiB
C++

#include "WebServer.h"
#include "DebugLog.h"
#include "DataLogger.h"
#include "Info.h"
#include "utils.h"
#include <Arduino.h>
#include <SPIFFS.h>
#include <string>
#include <cstring>
#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_);
ResourceNode* nodeGetLogList = new ResourceNode("/api/log/list", "GET", &WebServer::HandleLogList_);
ResourceNode* nodeGetLog = new ResourceNode("/api/log/*", "GET", &WebServer::HandleLog_);
httpServer.registerNode(nodeGetStatus);
httpServer.registerNode(nodePostInfo);
httpServer.registerNode(nodeGetLogList);
httpServer.registerNode(nodeGetLog);
// 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::HandleLogList_(HTTPRequest * request, HTTPResponse * response)
{
String json;
json = "{\"files\":[";
auto logFolder = SPIFFS.open("/log");
auto file = logFolder.openNextFile();
bool first = true;
while(file)
{
if(!first) json += ",";
json += "{\"n\":\"/api/log/";
//::DebugLog.println(file.name());
json += file.name();
json += "\",\"s\":";
json += file.size();
json += "}";
first = false;
file = logFolder.openNextFile();
}
json += "]}";
response->setHeader("Content-Type", "text/json");
response->print(json.c_str());
}
void WebServer::HandleLog_(HTTPRequest * request, HTTPResponse * response)
{
auto* params = request->getParams();
std::string path = params->getPathParameter(0);
std::string fullPath = std::string("/log/") + path;
File file = SPIFFS.open(fullPath.c_str());
SendContent_(file, response);
file.close();
}
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("<!DOCTYPE html>");
response->println("<html>");
response->println("<head><title>Not Found</title></head>");
response->println("<body><h1>404 Not Found</h1><p>The requested resource was not found on this server.</p></body>");
response->println("</html>");
}
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);
}
}
}