Monitoring system for electric vehicles (log various sensors, such as consumed power, solar production, speed, slope, apparent wind, etc.)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

258 lines
7.4 KiB

#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_);
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("<!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);
}
}
}