esp32-sk6812/src/WebServer.cpp
Thomas Kolb 8e9124c884 Avoid LED update glitches caused by flash access
Now any access to the flash, either through update writing or SPIFFS
access, is blocked while the LED stripes are written. This is
accomplished using a FreeRTOS semaphore.
2019-12-15 18:03:23 +01:00

335 lines
9.7 KiB
C++

/* HTTP Server setup and handler functions */
#include <SPIFFS.h>
#include <mbedtls/sha256.h>
#include "WebServer.h"
#include "Config.h"
#include "Fader.h"
#include "Animation/AnimationController.h"
#include "coreids.h"
WebServer::WebServer(void)
: m_cr(Config::instance().getCRPassword()), m_fader(NULL)
{
m_server = new httpsserver::HTTPServer();
}
bool WebServer::serveFile(String filename, httpsserver::HTTPResponse *res)
{
uint8_t buf[1024];
xSemaphoreTake(*instance().m_ledLockoutMutex, portMAX_DELAY);
bool exists = SPIFFS.exists(filename);
xSemaphoreGive(*instance().m_ledLockoutMutex);
if(!exists) {
return false;
}
xSemaphoreTake(*instance().m_ledLockoutMutex, portMAX_DELAY);
File f = SPIFFS.open(filename.c_str(), "r");
xSemaphoreGive(*instance().m_ledLockoutMutex);
if(!f) {
return false;
}
res->setHeader("Content-Length", httpsserver::intToString(f.size()));
size_t nread = 1;
while(nread > 0) {
xSemaphoreTake(*instance().m_ledLockoutMutex, portMAX_DELAY);
nread = f.readBytes(reinterpret_cast<char*>(buf), 1024);
xSemaphoreGive(*instance().m_ledLockoutMutex);
if(nread <= 0) {
break;
}
res->write(buf, nread);
}
xSemaphoreTake(*instance().m_ledLockoutMutex, portMAX_DELAY);
f.close();
xSemaphoreGive(*instance().m_ledLockoutMutex);
return true;
}
void WebServer::handleRoot(httpsserver::HTTPRequest *req, httpsserver::HTTPResponse *res)
{
serveFile("/htdocs/index.html", res);
res->setHeader("Content-Type", "text/html");
}
static void error400(httpsserver::HTTPResponse *res, const std::string &reason)
{
res->println("Error 400: Bad Request");
res->println(reason.c_str());
res->setStatusCode(400);
}
void WebServer::handleColor(httpsserver::HTTPRequest *req, httpsserver::HTTPResponse *res)
{
res->setHeader("Content-Type", "text/plain");
httpsserver::ResourceParameters * params = req->getParams();
if(!params->isRequestParameterSet("color")) {
error400(res, "Parameter 'color' not set.");
return;
}
std::string colorstr = params->getRequestParameter("color");
if(colorstr.length() != 8) {
error400(res, "Wrong length of color string (expected 8 hex digits).");
return;
}
uint32_t color = 0;
for(size_t i = 0; i < 8; i++) {
color <<= 4;
char c = colorstr[i];
if(c >= 'A' && c <= 'F') {
color |= c - 'A' + 10;
} else if(c >= 'a' && c <= 'f') {
color |= c - 'a' + 10;
} else if(c >= '0' && c <= '9') {
color |= c - '0';
} else {
error400(res, "Invalid character in color string (allowed: 0-9, a-f, A-F)");
return;
}
}
uint8_t r = (color & 0xFF000000) >> 24;
uint8_t g = (color & 0x00FF0000) >> 16;
uint8_t b = (color & 0x0000FF00) >> 8;
uint8_t w = (color & 0x000000FF) >> 0;
res->print("Color changed to r:");
res->print(r);
res->print(" g:");
res->print(g);
res->print(" b:");
res->print(b);
res->print(" w:");
res->println(w);
WebServer::instance().m_fader->fade_color({r,g,b,w});
}
void WebServer::handleSetAnim(httpsserver::HTTPRequest *req, httpsserver::HTTPResponse *res)
{
res->setHeader("Content-Type", "text/plain");
httpsserver::ResourceParameters * params = req->getParams();
if(!params->isRequestParameterSet("anim")) {
error400(res, "Parameter 'anim' not set.");
return;
}
std::istringstream animstr(params->getRequestParameter("anim"));
uint32_t anim_id;
animstr >> anim_id;
if(animstr.bad() || animstr.fail()) {
error400(res, "Parameter 'anim' could not be parsed as number.");
return;
}
if(anim_id >= AnimationController::NUM_DEFAULT_ANIMATIONS) {
error400(res, "Animation ID out of range.");
return;
}
WebServer::instance().m_animController->changeAnimation(
static_cast<AnimationController::DefaultAnimation>(anim_id),
true);
}
void WebServer::handleListAnim(httpsserver::HTTPRequest *req, httpsserver::HTTPResponse *res)
{
res->setHeader("Content-Type", "application/json");
res->print("[");
for(size_t i = 0; i < AnimationController::AnimationNames.size(); i++) {
res->print("{\"id\":");
res->print(i);
res->print(",\"name\":\"");
res->print(AnimationController::AnimationNames[i]);
res->print("\"}");
if(i < AnimationController::AnimationNames.size()-1) {
res->print(",");
}
}
res->print("]");
}
void WebServer::handleUpdate(httpsserver::HTTPRequest *req, httpsserver::HTTPResponse *res)
{
res->setHeader("Content-Type", "text/plain");
httpsserver::ResourceParameters * params = req->getParams();
if(!params->isRequestParameterSet("content")) {
error400(res, "Send update image as 'content'.");
}
res->print("Content length: ");
res->println(req->getContentLength());
res->println("Content:");
res->println("--------------------------------------------------------------------------------");
uint8_t buf[128];
while(!req->requestComplete()) {
size_t bytes_processed = req->readBytes(buf, 128);
res->write(buf, bytes_processed);
}
res->println("--------------------------------------------------------------------------------");
std::vector<uint8_t> d;
d.push_back(1);
d.push_back(2);
d.push_back(3);
uint8_t sha256sum[32];
mbedtls_sha256_ret(d.data(), d.size(), sha256sum, 0);
for(size_t i = 0; i < 32; i++) {
static const char *conv = "0123456789abcdef";
uint8_t b = sha256sum[i];
res->print(conv[(b >> 4)]);
res->print(conv[(b &0x0F)]);
}
res->println();
}
void WebServer::handleChallenge(httpsserver::HTTPRequest *req, httpsserver::HTTPResponse *res)
{
res->setHeader("Content-Type", "application/json");
std::ostringstream responseBuilder;
responseBuilder << "{ \"nonce\": " << WebServer::instance().m_cr.nonce() << "}";
res->println(responseBuilder.str().c_str());
}
void WebServer::handleAuthTest(httpsserver::HTTPRequest *req, httpsserver::HTTPResponse *res)
{
res->setHeader("Content-Type", "text/plain");
httpsserver::ResourceParameters * params = req->getParams();
if(!params->isRequestParameterSet("response")) {
error400(res, "Parameter 'response' not set.");
return;
}
std::string response = params->getRequestParameter("response");
bool result = WebServer::instance().m_cr.verify(response);
if(result) {
res->println("Authentication test successful!");
} else {
res->println("Authentication test failed (invalid response)");
}
}
void WebServer::handleStatic(httpsserver::HTTPRequest *req, httpsserver::HTTPResponse *res)
{
std::string filename = "/htdocs" + req->getRequestString();
if(*(filename.end()-1) == '/') {
// looks like a directory name, so append 'index.html'
filename.append("index.html");
}
if(!serveFile(filename.c_str(), res)) {
req->discardRequestBody();
res->setStatusCode(404);
res->setHeader("Content-Type", "text/plain");
res->println("Error 404: Not found");
}
}
void WebServer::serverTask(void *arg)
{
httpsserver::ResourceNode *nodeRoot =
new httpsserver::ResourceNode("/", "GET", WebServer::handleRoot);
httpsserver::ResourceNode *nodeAPIColor =
new httpsserver::ResourceNode("/api/color", "GET", WebServer::handleColor);
httpsserver::ResourceNode *nodeAPISetAnim =
new httpsserver::ResourceNode("/api/setanim", "GET", WebServer::handleSetAnim);
httpsserver::ResourceNode *nodeAPIListAnim =
new httpsserver::ResourceNode("/api/listanim", "GET", WebServer::handleListAnim);
httpsserver::ResourceNode *nodeAPIUpdate =
new httpsserver::ResourceNode("/api/update", "POST", WebServer::handleUpdate);
httpsserver::ResourceNode *nodeAPIChallenge =
new httpsserver::ResourceNode("/api/challenge", "GET", WebServer::handleChallenge);
httpsserver::ResourceNode *nodeAPIAuthTest =
new httpsserver::ResourceNode("/api/authtest", "GET", WebServer::handleAuthTest);
// handle all remaining requests by trying to serve static files. If no file is found, 404 is generated.
httpsserver::ResourceNode *nodeStatic =
new httpsserver::ResourceNode("", "GET", WebServer::handleStatic);
WebServer &server = WebServer::instance();
server.m_server->registerNode(nodeRoot);
server.m_server->registerNode(nodeAPIColor);
server.m_server->registerNode(nodeAPISetAnim);
server.m_server->registerNode(nodeAPIListAnim);
server.m_server->registerNode(nodeAPIUpdate);
server.m_server->registerNode(nodeAPIChallenge);
server.m_server->registerNode(nodeAPIAuthTest);
server.m_server->setDefaultNode(nodeStatic);
Serial.println("[server] Starting HTTP Server...");
server.m_server->start();
if (server.m_server->isRunning())
{
Serial.println("[server] Server ready.");
while (true)
{
server.m_server->loop();
delay(1);
}
}
Serial.println("[server] Server died?! This should never happen.");
}
bool WebServer::start(void)
{
xTaskCreatePinnedToCore(
WebServer::serverTask, /* Task function. */
"HTTP Server Task", /* name of task. */
6144, /* Stack size of task */
NULL, /* parameter of the task */
1, /* priority of the task */
NULL, /* Task handle to keep track of created task */
CORE_ID_WEBSERVER);
return true;
}