From a1f818f83c5df0abdf0466843331820ad1bf355f Mon Sep 17 00:00:00 2001 From: fvanroie <15969459+fvanroie@users.noreply.github.com> Date: Tue, 23 Feb 2021 00:04:44 +0100 Subject: [PATCH] Set correct webHandleConfig callback --- src/svc/hasp_http.cpp | 2121 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2121 insertions(+) create mode 100644 src/svc/hasp_http.cpp diff --git a/src/svc/hasp_http.cpp b/src/svc/hasp_http.cpp new file mode 100644 index 00000000..11b66f44 --- /dev/null +++ b/src/svc/hasp_http.cpp @@ -0,0 +1,2121 @@ +/* MIT License - Copyright (c) 2020 Francis Van Roie + For full license information read the LICENSE file in the project folder */ + +//#include "webServer.h" +#include "ArduinoJson.h" +#include "ArduinoLog.h" +#include "lvgl.h" + +#if defined(ARDUINO_ARCH_ESP32) +#include "Update.h" +#endif + +#include "hasp_conf.h" + +#include "hasp_gui.h" +#include "hasp_hal.h" +#include "hasp_debug.h" +#include "hasp_config.h" + +#include "hasp/hasp_utilities.h" +#include "hasp/hasp_dispatch.h" +#include "hasp/hasp.h" + +#if HASP_USE_HTTP > 0 + +#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) +File fsUploadFile; +#endif + +//////////////////////////////////////////////////////////////////////////////////////////////////// +bool webServerStarted = false; + +// bool httpEnable = true; +// uint16_t httpPort = 80; +// char httpUser[32] = ""; +// char httpPassword[32] = ""; +hasp_http_config_t http_config; + +#define HTTP_PAGE_SIZE (6 * 256) + +#if defined(STM32F4xx) && HASP_USE_ETHERNET > 0 +#include +EthernetWebServer webServer(80); +#endif + +#if defined(STM32F4xx) && HASP_USE_WIFI > 0 +#include +// #include +EthernetWebServer webServer(80); +#endif + +#if defined(ARDUINO_ARCH_ESP8266) +#include "StringStream.h" +#include +#include +ESP8266WebServer webServer(80); +#endif + +#if defined(ARDUINO_ARCH_ESP32) +#include +#include +WebServer webServer(80); +#endif // ESP32 + +HTTPUpload* upload; + +static const char HTTP_MENU_BUTTON[] PROGMEM = + "

"; + +const char MAIN_MENU_BUTTON[] PROGMEM = + "

"; +const char MIT_LICENSE[] PROGMEM = "
MIT License

"; + +const char HTTP_DOCTYPE[] PROGMEM = + ""; +const char HTTP_META_GO_BACK[] PROGMEM = ""; +const char HTTP_HEADER[] PROGMEM = "%s"; +const char HTTP_STYLE[] PROGMEM = + ""; +const char HTTP_SCRIPT[] PROGMEM = ""; +const char HTTP_HEADER_END[] PROGMEM = + "
"; +const char HTTP_END[] PROGMEM = ""; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// URL for auto-update "version.json" +// const char UPDATE_URL[] PROGMEM = "http://haswitchplate.com/update/version.json"; +// // Default link to compiled Arduino firmware image +// String espFirmwareUrl = "http://haswitchplate.com/update/HASwitchPlate.ino.d1_mini.bin"; +// // Default link to compiled Nextion firmware images +// String lcdFirmwareUrl = "http://haswitchplate.com/update/HASwitchPlate.tft"; + +#if HASP_USE_MQTT > 0 +extern char mqttNodeName[16]; +#else +char mqttNodeName[3] = "na"; +#endif + +//////////////////////////////////////////////////////////////////////////////////////////////////// +String getOption(int value, String label, bool selected) +{ + char buffer[128]; + snprintf_P(buffer, sizeof(buffer), PSTR(""), value, + (selected ? PSTR(" selected") : ""), label.c_str()); + return buffer; +} + +String getOption(String value, String label, bool selected) +{ + char buffer[128]; + snprintf_P(buffer, sizeof(buffer), PSTR(""), value.c_str(), + (selected ? PSTR(" selected") : ""), label.c_str()); + return buffer; +} + +static void add_gpio_select_option(String& str, uint8_t gpio, uint8_t bcklpin) +{ + char buffer[10]; + snprintf_P(buffer, sizeof(buffer), PSTR("GPIO %d"), gpio); + str += getOption(gpio, buffer, bcklpin == gpio); +} + +static void add_button(String& str, const __FlashStringHelper* label, const __FlashStringHelper* extra) +{ + str += F(""); +} + +static void close_form(String& str) +{ + str += F("

"); +} + +static void add_form_button(String& str, const __FlashStringHelper* label, const __FlashStringHelper* action, + const __FlashStringHelper* extra) +{ + str += F("

"); + add_button(str, label, extra); + close_form(str); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +void webHandleHaspConfig(); + +static inline char* httpGetNodename() +{ + return mqttNodeName; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +bool httpIsAuthenticated(const __FlashStringHelper* fstr_page) +{ + if(http_config.password[0] != '\0') { // Request HTTP auth if httpPassword is set + if(!webServer.authenticate(http_config.user, http_config.password)) { + webServer.requestAuthentication(); + return false; + } + } + +#if defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_ESP8266) + LOG_TRACE(TAG_HTTP, F("Sending %S page to client connected from: %s"), fstr_page, + webServer.client().remoteIP().toString().c_str()); +#else + // LOG_INFO(TAG_HTTP,F("Sending %s page to client connected from: %s"), page, + // String(webServer.client().remoteIP()).c_str()); +#endif + + return true; +} + +void webSendFooter() +{ + char buffer[16]; + haspGetVersion(buffer, sizeof(buffer)); + +#if defined(STM32F4xx) + webServer.sendContent(HTTP_END); + webServer.sendContent(buffer); + webServer.sendContent(HTTP_FOOTER); +#else + webServer.sendContent_P(HTTP_END); + webServer.sendContent(buffer); + webServer.sendContent_P(HTTP_FOOTER); +#endif +} + +void webSendPage(char* nodename, uint32_t httpdatalength, bool gohome = false) +{ + { + char buffer[64]; + haspGetVersion(buffer, sizeof(buffer)); + + /* Calculate Content Length upfront */ + uint16_t contentLength = strlen(buffer); // verion length + contentLength += sizeof(HTTP_DOCTYPE) - 1; + contentLength += sizeof(HTTP_HEADER) - 1 - 2 + strlen(nodename); // -2 for %s + contentLength += sizeof(HTTP_SCRIPT) - 1; + contentLength += sizeof(HTTP_STYLE) - 1; + // contentLength += sizeof(HASP_STYLE) - 1; + if(gohome) contentLength += sizeof(HTTP_META_GO_BACK) - 1; + contentLength += sizeof(HTTP_HEADER_END) - 1; + contentLength += sizeof(HTTP_END) - 1; + contentLength += sizeof(HTTP_FOOTER) - 1; + + if(httpdatalength > HTTP_PAGE_SIZE) { + LOG_WARNING(TAG_HTTP, F("Sending page with %u static and %u dynamic bytes"), contentLength, httpdatalength); + } + + webServer.setContentLength(contentLength + httpdatalength); +#if defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_ESP8266) + webServer.send_P(200, PSTR("text/html"), HTTP_DOCTYPE); // 122 +#else + webServer.send(200, ("text/html"), HTTP_DOCTYPE); // 122 +#endif + + snprintf_P(buffer, sizeof(buffer), HTTP_HEADER, nodename); + webServer.sendContent(buffer); // 17-2+len + } + +#if defined(STM32F4xx) + webServer.sendContent(HTTP_SCRIPT); // 131 + webServer.sendContent(HTTP_STYLE); // 487 + // webServer.sendContent(HASP_STYLE); // 145 + if(gohome) webServer.sendContent(HTTP_META_GO_BACK); // 47 + webServer.sendContent(HTTP_HEADER_END); // 80 +#else + webServer.sendContent_P(HTTP_SCRIPT); // 131 + webServer.sendContent_P(HTTP_STYLE); // 487 + // webServer.sendContent_P(HASP_STYLE); // 145 + if(gohome) webServer.sendContent_P(HTTP_META_GO_BACK); // 47 + webServer.sendContent_P(HTTP_HEADER_END); // 80 +#endif +} + +void saveConfig() +{ + if(webServer.method() == HTTP_POST) { + if(webServer.hasArg(PSTR("save"))) { + String save = webServer.arg(PSTR("save")); + + StaticJsonDocument<256> settings; + for(int i = 0; i < webServer.args(); i++) settings[webServer.argName(i)] = webServer.arg(i); + + if(save == String(PSTR("hasp"))) { + haspSetConfig(settings.as()); + +#if HASP_USE_MQTT > 0 + } else if(save == String(PSTR("mqtt"))) { + mqttSetConfig(settings.as()); +#endif + + } else if(save == String(PSTR("gui"))) { + settings[FPSTR(FP_GUI_POINTER)] = webServer.hasArg(PSTR("cur")); + settings[FPSTR(FP_GUI_INVERT)] = webServer.hasArg(PSTR("inv")); + guiSetConfig(settings.as()); + + } else if(save == String(PSTR("debug"))) { + debugSetConfig(settings.as()); + + } else if(save == String(PSTR("http"))) { + httpSetConfig(settings.as()); + + // Password might have changed + if(!httpIsAuthenticated(F("config"))) return; + +#if HASP_USE_WIFI > 0 + } else if(save == String(PSTR("wifi"))) { + wifiSetConfig(settings.as()); +#endif + } + } + } +} + +void webHandleRoot() +{ + if(!httpIsAuthenticated(F("root"))) return; + + saveConfig(); + { + String httpMessage((char*)0); + httpMessage.reserve(HTTP_PAGE_SIZE); + httpMessage += F("

"); + httpMessage += httpGetNodename(); + httpMessage += F("


"); + + httpMessage += F("

"); + + httpMessage += F("

"); + httpMessage += + F("

"); + add_form_button(httpMessage, F(D_HTTP_CONFIGURATION), F("/config"), F("")); + // httpMessage += F("

"); + + httpMessage += F("

"); + +#if HASP_USE_SPIFFS > 0 || HASP_USE_LITTLEFS > 0 + if(HASP_FS.exists(F("/edit.htm.gz"))) { + httpMessage += + F("

"); + } +#endif + + httpMessage += F("

"); + + webSendPage(httpGetNodename(), httpMessage.length(), false); + webServer.sendContent(httpMessage); + } + // httpMessage.clear(); + webSendFooter(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +void httpHandleReboot() +{ // http://plate01/reboot + if(!httpIsAuthenticated(F("reboot"))) return; + + { + String httpMessage((char*)0); + httpMessage.reserve(HTTP_PAGE_SIZE); + httpMessage += F("

"); + httpMessage += httpGetNodename(); + httpMessage += F("


"); + httpMessage = F(D_DISPATCH_REBOOT); + + webSendPage(httpGetNodename(), httpMessage.length(), true); + webServer.sendContent(httpMessage); + } + // httpMessage.clear(); + webSendFooter(); + + delay(200); + dispatch_reboot(true); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +void webHandleScreenshot() +{ // http://plate01/screenshot + if(!httpIsAuthenticated(F("screenshot"))) return; + + if(webServer.hasArg(F("a"))) { + if(webServer.arg(F("a")) == F("next")) { + dispatch_page_next(); + } else if(webServer.arg(F("a")) == F("prev")) { + dispatch_page_prev(); + } + } + + if(webServer.hasArg(F("q"))) { + lv_disp_t* disp = lv_disp_get_default(); + webServer.setContentLength(122 + disp->driver.hor_res * disp->driver.ver_res * sizeof(lv_color_t)); + webServer.send_P(200, PSTR("image/bmp"), ""); + guiTakeScreenshot(); + webServer.client().stop(); + + } else { + { + String httpMessage((char*)0); + httpMessage.reserve(HTTP_PAGE_SIZE); + httpMessage += F("

"); + httpMessage += httpGetNodename(); + httpMessage += F("


"); + + httpMessage += + F(""); + httpMessage += F("

"); + + httpMessage += F("

"); + httpMessage += + F("

"); + httpMessage += + F("

"); + httpMessage += FPSTR(MAIN_MENU_BUTTON); + + webSendPage(httpGetNodename(), httpMessage.length(), false); + webServer.sendContent(httpMessage); + } + // httpMessage.clear(); + webSendFooter(); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void webHandleAbout() +{ // http://plate01/about + if(!httpIsAuthenticated(F("about"))) return; + + { + String httpMessage((char*)0); + httpMessage.reserve(HTTP_PAGE_SIZE); + + httpMessage += F("

HASP OpenHardware edition

Copyright© 2020 Francis Van Roie "); + httpMessage += FPSTR(MIT_LICENSE); + httpMessage += F("

Based on the previous work of the following open source developers.


"); + httpMessage += F("

HASwitchPlate

Copyright© 2019 Allen Derusha allen@derusha.org"); + httpMessage += FPSTR(MIT_LICENSE); + httpMessage += + F("

LittlevGL

Copyright© 2016 Gábor Kiss-Vámosi
Copyright© 2019 " + "LittlevGL"); + httpMessage += FPSTR(MIT_LICENSE); + httpMessage += F("

zi Font Engine

Copyright© 2020 Francis Van Roie"); + httpMessage += FPSTR(MIT_LICENSE); + httpMessage += F("

TFT_eSPI Library

Copyright© 2020 Bodmer (https://github.com/Bodmer) All " + "rights reserved.
FreeBSD License

"); + httpMessage += + F("

includes parts from the Adafruit_GFX library
Copyright© 2012 Adafruit Industries. " + "All rights reserved
BSD License

"); + httpMessage += F("

ArduinoJson

Copyright© 2014-2020 Benoit BLANCHON"); + httpMessage += FPSTR(MIT_LICENSE); + httpMessage += F("

PubSubClient

Copyright© 2008-2015 Nicholas O'Leary"); + httpMessage += FPSTR(MIT_LICENSE); + httpMessage += + F("

ArduinoLog

Copyright© 2017,2018 Thijs Elenbaas, MrRobot62, rahuldeo2047, NOX73, " + "dhylands, Josha blemasle, mfalkvidd"); + httpMessage += FPSTR(MIT_LICENSE); +#if HASP_USE_SYSLOG > 0 + // Replaced with WiFiUDP client + // httpMessage += F("

Syslog

Copyright© 2016 Martin Sloup"); + // httpMessage += FPSTR(MIT_LICENSE); +#endif +#if HASP_USE_QRCODE > 0 + httpMessage += F("

QR Code generator

Copyright© Project Nayuki"); + httpMessage += FPSTR(MIT_LICENSE); +#endif + httpMessage += F("

AceButton

Copyright© 2018 Brian T. Park"); + httpMessage += FPSTR(MIT_LICENSE); + + httpMessage += FPSTR(MAIN_MENU_BUTTON); + + webSendPage(httpGetNodename(), httpMessage.length(), false); + webServer.sendContent(httpMessage); + } + // httpMessage.clear(); + webSendFooter(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +void webHandleInfo() +{ // http://plate01/ + if(!httpIsAuthenticated(F("info"))) return; + + { + char size_buf[32]; + String httpMessage((char*)0); + httpMessage.reserve(HTTP_PAGE_SIZE); + httpMessage += F("

"); + httpMessage += httpGetNodename(); + httpMessage += F("


"); + + /* HASP Stats */ + httpMessage += F("HASP Version: "); + { + char version[32]; + haspGetVersion(version, sizeof(version)); + httpMessage += version; + } + httpMessage += F("
Build DateTime: "); + httpMessage += __DATE__; + httpMessage += F(" "); + httpMessage += __TIME__; + httpMessage += F(" CET
Uptime: "); + + unsigned long time = millis() / 1000; + uint16_t day = time / 86400; + time = time % 86400; + uint8_t hour = time / 3600; + time = time % 3600; + uint8_t min = time / 60; + time = time % 60; + uint8_t sec = time; + + if(day > 0) { + httpMessage += String(day); + httpMessage += F("d "); + } + if(day > 0 || hour > 0) { + httpMessage += String(hour); + httpMessage += F("h "); + } + if(day > 0 || hour > 0 || min > 0) { + httpMessage += String(min); + httpMessage += F("m "); + } + httpMessage += String(sec); + httpMessage += F("s"); + + httpMessage += F("
Free Memory: "); + hasp_util_format_bytes(halGetFreeHeap(), size_buf, sizeof(size_buf)); + httpMessage += size_buf; + httpMessage += F("
Memory Fragmentation: "); + httpMessage += String(halGetHeapFragmentation()); + +#if ARDUINO_ARCH_ESP32 + if(psramFound()) { + httpMessage += F("
Free PSRam: "); + hasp_util_format_bytes(ESP.getFreePsram(), size_buf, sizeof(size_buf)); + httpMessage += size_buf; + httpMessage += F("
PSRam Size: "); + hasp_util_format_bytes(ESP.getPsramSize(), size_buf, sizeof(size_buf)); + httpMessage += size_buf; + } +#endif + + /* LVGL Stats */ + lv_mem_monitor_t mem_mon; + lv_mem_monitor(&mem_mon); + httpMessage += F("

LVGL Memory: "); + hasp_util_format_bytes(mem_mon.total_size, size_buf, sizeof(size_buf)); + httpMessage += size_buf; + httpMessage += F("
LVGL Free: "); + hasp_util_format_bytes(mem_mon.free_size, size_buf, sizeof(size_buf)); + httpMessage += size_buf; + httpMessage += F("
LVGL Fragmentation: "); + httpMessage += mem_mon.frag_pct; + + // httpMessage += F("
LCD Model: ")) + String(LV_HASP_HOR_RES_MAX) + " x " + + // String(LV_HASP_VER_RES_MAX); httpMessage += F("
LCD Version: ")) + + // String(lcdVersion); + httpMessage += F("

LCD Active Page: "); + httpMessage += String(haspGetPage()); + + /* Wifi Stats */ +#if HASP_USE_WIFI > 0 + httpMessage += F("

SSID: "); + httpMessage += String(WiFi.SSID()); + httpMessage += F("
Signal Strength: "); + + int8_t rssi = WiFi.RSSI(); + httpMessage += String(rssi); + httpMessage += F("dBm ("); + + if(rssi >= -50) { + httpMessage += F("Excellent)"); + } else if(rssi >= -60) { + httpMessage += F("Good)"); + } else if(rssi >= -70) { + httpMessage += F("Fair)"); + } else if(rssi >= -80) { + httpMessage += F("Weak)"); + } else { + httpMessage += F("Very Bad)"); + } +#if defined(STM32F4xx) + byte mac[6]; + WiFi.macAddress(mac); + char macAddress[16]; + snprintf_P(macAddress, sizeof(macAddress), PSTR("%02x%02x%02x"), mac[0], mac[1], mac[2], mac[3], mac[4], + mac[5]); + httpMessage += F("
IP Address: "); + httpMessage += String(WiFi.localIP()); + httpMessage += F("
Gateway: "); + httpMessage += String(WiFi.gatewayIP()); + httpMessage += F("
MAC Address: "); + httpMessage += String(macAddress); +#else + httpMessage += F("
IP Address: "); + httpMessage += String(WiFi.localIP().toString()); + httpMessage += F("
Gateway: "); + httpMessage += String(WiFi.gatewayIP().toString()); + httpMessage += F("
DNS Server: "); + httpMessage += String(WiFi.dnsIP().toString()); + httpMessage += F("
MAC Address: "); + httpMessage += String(WiFi.macAddress()); +#endif +#endif +#if HASP_USE_ETHERNET > 0 +#if defined(ARDUINO_ARCH_ESP32) + httpMessage += F("

Ethernet: "); + httpMessage += String(ETH.linkSpeed()); + httpMessage += F(" Mbps"); + if(ETH.fullDuplex()) { + httpMessage += F(" FULL_DUPLEX"); + } + httpMessage += F("
IP Address: "); + httpMessage += String(ETH.localIP().toString()); + httpMessage += F("
Gateway: "); + httpMessage += String(ETH.gatewayIP().toString()); + httpMessage += F("
DNS Server: "); + httpMessage += String(ETH.dnsIP().toString()); + httpMessage += F("
MAC Address: "); + httpMessage += String(ETH.macAddress()); +#endif +#endif +/* Mqtt Stats */ +#if HASP_USE_MQTT > 0 + httpMessage += F("

MQTT Status: "); + if(mqttIsConnected()) { // Check MQTT connection + httpMessage += F("Connected"); + } else { + httpMessage += F("Disconnected, return code: "); + // +String(mqttClient.returnCode()); + } + httpMessage += F("
MQTT ClientID: "); + + { + char mqttClientId[64]; + String mac = halGetMacAddress(3, ""); + mac.toLowerCase(); + snprintf_P(mqttClientId, sizeof(mqttClientId), PSTR("%s-%s"), mqttNodeName, mac.c_str()); + httpMessage += mqttClientId; + } + +#endif // MQTT + + /* ESP Stats */ + httpMessage += F("

MCU Model: "); + httpMessage += halGetChipModel(); + httpMessage += F("
CPU Frequency: "); + httpMessage += String(halGetCpuFreqMHz()); + httpMessage += F("MHz"); + +#if defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_ESP8266) + httpMessage += F("
Flash Chip Size: "); + hasp_util_format_bytes(ESP.getFlashChipSize(), size_buf, sizeof(size_buf)); + httpMessage += size_buf; + + httpMessage += F("
Program Size: "); + hasp_util_format_bytes(ESP.getSketchSize(), size_buf, sizeof(size_buf)); + httpMessage += size_buf; + + httpMessage += F("
Free Program Space: "); + hasp_util_format_bytes(ESP.getFreeSketchSpace(), size_buf, sizeof(size_buf)); + httpMessage += size_buf; +#endif + + //#if defined(ARDUINO_ARCH_ESP32) + // httpMessage += F("
ESP SDK version: "); + // httpMessage += String(ESP.getSdkVersion()); + //#else + httpMessage += F("
Core version: "); + httpMessage += String(halGetCoreVersion()); + //#endif + httpMessage += F("
Last Reset: "); + httpMessage += halGetResetInfo(); + + httpMessage += FPSTR(MAIN_MENU_BUTTON); + + webSendPage(httpGetNodename(), httpMessage.length(), false); + webServer.sendContent(httpMessage); + } + // httpMessage.clear(); + webSendFooter(); +} + +// String getContentType(String filename) +// { +// if(webServer.hasArg(F("download"))) { +// return F("application/octet-stream"); +// } else if(filename.endsWith(F(".htm")) || filename.endsWith(F(".html"))) { +// return F("text/html"); +// } else if(filename.endsWith(F(".css"))) { +// return F("text/css"); +// } else if(filename.endsWith(F(".js"))) { +// return F("application/javascript"); +// } else if(filename.endsWith(F(".png"))) { +// return F("image/png"); +// } else if(filename.endsWith(F(".gif"))) { +// return F("image/gif"); +// } else if(filename.endsWith(F(".jpg"))) { +// return F("image/jpeg"); +// } else if(filename.endsWith(F(".ico"))) { +// return F("image/x-icon"); +// } else if(filename.endsWith(F(".xml"))) { +// return F("text/xml"); +// } else if(filename.endsWith(F(".pdf"))) { +// return F("application/x-pdf"); +// } else if(filename.endsWith(F(".zip"))) { +// return F("application/x-zip"); +// } else if(filename.endsWith(F(".gz"))) { +// return F("application/x-gzip"); +// } +// return F("text/plain"); +// } + +static String getContentType(const String& path) +{ + char buff[sizeof(mime::mimeTable[0].mimeType)]; + // Check all entries but last one for match, return if found + for(size_t i = 0; i < sizeof(mime::mimeTable) / sizeof(mime::mimeTable[0]) - 1; i++) { + strcpy_P(buff, mime::mimeTable[i].endsWith); + if(path.endsWith(buff)) { + strcpy_P(buff, mime::mimeTable[i].mimeType); + return String(buff); + } + } + // Fall-through and just return default type + strcpy_P(buff, mime::mimeTable[sizeof(mime::mimeTable) / sizeof(mime::mimeTable[0]) - 1].mimeType); + return String(buff); +} + +/* String urldecode(String str) +{ + String encodedString = ""; + char c; + for(unsigned int i = 0; i < str.length(); i++) { + c = str.charAt(i); + if(c == '+') { + encodedString += ' '; + } else if(c == '%') { + // char buffer[3]; + char buffer[128]; + i++; + buffer[0] = str.charAt(i); + i++; + buffer[1] = str.charAt(i); + buffer[2] = '\0'; + c = (char)strtol((const char *)&buffer, NULL, 16); + encodedString += c; + } else { + encodedString += c; + } + yield(); + } + return encodedString; +} */ + +static unsigned long htppLastLoopTime = 0; +void webUploadProgress() +{ + long t = webServer.header("Content-Length").toInt(); + if(millis() - htppLastLoopTime >= 1250) { + LOG_VERBOSE(TAG_HTTP, F(D_BULLET "Uploaded %u / %d bytes"), upload->totalSize + upload->currentSize, t); + htppLastLoopTime = millis(); + } + if(t > 0) t = (upload->totalSize + upload->currentSize) * 100 / t; + haspProgressVal(t); +} + +#if defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_ESP8266) +static inline void webUpdatePrintError() +{ +#if defined(ARDUINO_ARCH_ESP8266) + String output((char*)0); + output.reserve(128); + StringStream stream((String&)output); + Update.printError(stream); // ESP8266 only has printError() + LOG_ERROR(TAG_HTTP, output.c_str()); + haspProgressMsg(output.c_str()); +#elif defined(ARDUINO_ARCH_ESP32) + LOG_ERROR(TAG_HTTP, Update.errorString()); // ESP32 has errorString() + haspProgressMsg(Update.errorString()); +#endif +} + +void webUpdateReboot() +{ + LOG_INFO(TAG_HTTP, F("Update Success: %u bytes received. Rebooting..."), upload->totalSize); + + { + String httpMessage((char*)0); + httpMessage.reserve(HTTP_PAGE_SIZE); + httpMessage += F("

"); + httpMessage += httpGetNodename(); + httpMessage += F("


"); + httpMessage += F("Upload complete. Rebooting device, please wait..."); + + webSendPage(httpGetNodename(), httpMessage.length(), true); + webServer.sendContent(httpMessage); + } + // httpMessage.clear(); + webSendFooter(); + + delay(250); + dispatch_reboot(true); // Save the current config +} + +void webHandleFirmwareUpload() +{ + upload = &webServer.upload(); + + if(upload->status == UPLOAD_FILE_START) { + if(!httpIsAuthenticated(F("update"))) return; + LOG_TRACE(TAG_HTTP, F("Update: %s"), upload->filename.c_str()); + haspProgressMsg(upload->filename.c_str()); + // WiFiUDP::stopAll(); + uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000; + // if(!Update.begin(UPDATE_SIZE_UNKNOWN)) { // start with max available size + if(!Update.begin(maxSketchSpace)) { // start with max available size + webUpdatePrintError(); + } + + } else if(upload->status == UPLOAD_FILE_WRITE) { + // flashing firmware to ESP + if(Update.write(upload->buf, upload->currentSize) != upload->currentSize) { + webUpdatePrintError(); + } else { + webUploadProgress(); + } + + } else if(upload->status == UPLOAD_FILE_END) { + haspProgressVal(100); + if(Update.end(true)) { // true to set the size to the current progress + haspProgressMsg(F(D_OTA_UPDATE_APPLY)); + webUpdateReboot(); + } else { + webUpdatePrintError(); + } + } +} +#endif + +#if HASP_USE_SPIFFS > 0 || HASP_USE_LITTLEFS > 0 +bool handleFileRead(String path) +{ + if(!httpIsAuthenticated(F("fileread"))) return false; + + path = webServer.urlDecode(path).substring(0, 31); + if(path.endsWith("/")) { + path += F("index.htm"); + } + String pathWithGz = path + F(".gz"); + if(HASP_FS.exists(pathWithGz) || HASP_FS.exists(path)) { + if(HASP_FS.exists(pathWithGz)) path += F(".gz"); + + File file = HASP_FS.open(path, "r"); + String contentType = getContentType(path); + if(path == F("/edit.htm.gz")) { + contentType = F("text/html"); + } + webServer.streamFile(file, contentType); + file.close(); + return true; + } + return false; +} + +void handleFileUpload() +{ + if(webServer.uri() != "/edit") { + return; + } + upload = &webServer.upload(); + if(upload->status == UPLOAD_FILE_START) { + if(!httpIsAuthenticated(F("fileupload"))) return; + LOG_INFO(TAG_HTTP, F("Total size: %s"), webServer.headerName(0).c_str()); + String filename((char*)0); + filename.reserve(128); + filename = upload->filename; + if(!filename.startsWith("/")) { + filename = "/"; + filename += upload->filename; + } + if(filename.length() < 32) { + fsUploadFile = HASP_FS.open(filename, "w"); + LOG_TRACE(TAG_HTTP, F("handleFileUpload Name: %s"), filename.c_str()); + haspProgressMsg(fsUploadFile.name()); + } else { + LOG_ERROR(TAG_HTTP, F("Filename %s is too long"), filename.c_str()); + } + } else if(upload->status == UPLOAD_FILE_WRITE) { + // DBG_OUTPUT_PORT.print("handleFileUpload Data: "); debugPrintln(upload.currentSize); + if(fsUploadFile) { + if(fsUploadFile.write(upload->buf, upload->currentSize) != upload->currentSize) { + LOG_ERROR(TAG_HTTP, F("Failed to write received data to file")); + } else { + webUploadProgress(); // Moved to httpEverySecond Loop + } + } + } else if(upload->status == UPLOAD_FILE_END) { + if(fsUploadFile) { + LOG_INFO(TAG_HTTP, F("Uploaded %s (%u bytes)"), fsUploadFile.name(), upload->totalSize); + fsUploadFile.close(); + } + haspProgressVal(255); + + // Redirect to /config/hasp page. This flushes the web buffer and frees the memory + webServer.sendHeader(String(F("Location")), String(F("/config/hasp")), true); + webServer.send_P(302, PSTR("text/plain"), ""); + // httpReconnect(); + } +} + +void handleFileDelete() +{ + if(!httpIsAuthenticated(F("filedelete"))) return; + + char mimetype[16]; + snprintf_P(mimetype, sizeof(mimetype), PSTR("text/plain")); + + if(webServer.args() == 0) { + return webServer.send_P(500, mimetype, PSTR("BAD ARGS")); + } + String path = webServer.arg(0); + LOG_TRACE(TAG_HTTP, F("handleFileDelete: %s"), path.c_str()); + if(path == "/") { + return webServer.send_P(500, mimetype, PSTR("BAD PATH")); + } + if(!HASP_FS.exists(path)) { + return webServer.send_P(404, mimetype, PSTR("FileNotFound")); + } + HASP_FS.remove(path); + webServer.send_P(200, mimetype, PSTR("")); + // path.clear(); +} + +void handleFileCreate() +{ + if(!httpIsAuthenticated(F("filecreate"))) return; + + if(webServer.args() == 0) { + return webServer.send(500, PSTR("text/plain"), PSTR("BAD ARGS")); + } + String path = webServer.arg(0); + LOG_TRACE(TAG_HTTP, F("handleFileCreate: %s"), path.c_str()); + if(path == "/") { + return webServer.send(500, PSTR("text/plain"), PSTR("BAD PATH")); + } + if(HASP_FS.exists(path)) { + return webServer.send(500, PSTR("text/plain"), PSTR("FILE EXISTS")); + } + File file = HASP_FS.open(path, "w"); + if(file) { + file.close(); + } else { + return webServer.send(500, PSTR("text/plain"), PSTR("CREATE FAILED")); + } + webServer.send(200, PSTR("text/plain"), ""); + path.clear(); +} + +void handleFileList() +{ + if(!httpIsAuthenticated(F("filelist"))) return; + + if(!webServer.hasArg(F("dir"))) { + webServer.send(500, PSTR("text/plain"), PSTR("BAD ARGS")); + return; + } + + String path = webServer.arg(F("dir")); + LOG_TRACE(TAG_HTTP, F("handleFileList: %s"), path.c_str()); + path.clear(); + +#if defined(ARDUINO_ARCH_ESP32) + File root = HASP_FS.open("/", FILE_READ); + File file = root.openNextFile(); + String output = "["; + + while(file) { + if(output != "[") { + output += ','; + } + bool isDir = false; + output += F("{\"type\":\""); + output += (isDir) ? F("dir") : F("file"); + output += F("\",\"name\":\""); + if(file.name()[0] == '/') { + output += &(file.name()[1]); + } else { + output += file.name(); + } + output += F("\"}"); + + // file.close(); + file = root.openNextFile(); + } + output += "]"; + webServer.send(200, PSTR("text/json"), output); +#elif defined(ARDUINO_ARCH_ESP8266) + Dir dir = HASP_FS.openDir(path); + String output = "["; + while(dir.next()) { + File entry = dir.openFile("r"); + if(output != "[") { + output += ','; + } + bool isDir = false; + output += F("{\"type\":\""); + output += (isDir) ? F("dir") : F("file"); + output += F("\",\"name\":\""); + if(entry.name()[0] == '/') { + output += &(entry.name()[1]); + } else { + output += entry.name(); + } + output += F("\"}"); + entry.close(); + } + output += "]"; + webServer.send(200, PSTR("text/json"), output); +#endif +} +#endif + +//////////////////////////////////////////////////////////////////////////////////////////////////// +#if HASP_USE_CONFIG > 0 +void webHandleConfig() +{ // http://plate01/config + if(!httpIsAuthenticated(F("config"))) return; + + saveConfig(); + +// Reboot after saving wifi config in AP mode +#if HASP_USE_WIFI > 0 && !defined(STM32F4xx) + if(WiFi.getMode() != WIFI_STA) { + httpHandleReboot(); + } +#endif + + { + String httpMessage((char*)0); + httpMessage.reserve(HTTP_PAGE_SIZE); + httpMessage += F("

"); + httpMessage += httpGetNodename(); + httpMessage += F("


"); + +#if HASP_USE_WIFI > 0 + httpMessage += F("

"); +#endif + +#if HASP_USE_MQTT > 0 + httpMessage += F("

"); +#endif + + httpMessage += F("

"); + + httpMessage += F("

"); + + // httpMessage += + // F("

"); + +#if HASP_USE_GPIO > 0 + httpMessage += F("

"); +#endif + + httpMessage += F("

"); + + httpMessage += + F("

"); + + httpMessage += FPSTR(MAIN_MENU_BUTTON); + + webSendPage(httpGetNodename(), httpMessage.length(), false); + webServer.sendContent(httpMessage); + } + // httpMessage.clear(); + webSendFooter(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +#if HASP_USE_MQTT > 0 +void webHandleMqttConfig() +{ // http://plate01/config/mqtt + if(!httpIsAuthenticated(F("config/mqtt"))) return; + + StaticJsonDocument<256> settings; + mqttGetConfig(settings.to()); + + { + // char buffer[128]; + String httpMessage((char*)0); + httpMessage.reserve(HTTP_PAGE_SIZE); + httpMessage += F("

"); + httpMessage += httpGetNodename(); + httpMessage += F("


"); + + httpMessage += F("
"); + httpMessage += F("HASP Node Name (required. lowercase letters, numbers, and _ only)" + "

Group Name (required)

MQTT Broker (required)
MQTT Port (required)
MQTT User (optional)
MQTT Password (optional)

"); + + add_form_button(httpMessage, F("↩ " D_HTTP_CONFIGURATION), F("/config"), F("")); + // httpMessage += PSTR("

"); + + webSendPage(httpGetNodename(), httpMessage.length(), false); + webServer.sendContent(httpMessage); + } + // httpMessage.clear(); + webSendFooter(); +} +#endif + +//////////////////////////////////////////////////////////////////////////////////////////////////// +void webHandleGuiConfig() +{ // http://plate01/config/wifi + if(!httpIsAuthenticated(F("config/gui"))) return; + + { + StaticJsonDocument<256> settings; + guiGetConfig(settings.to()); + + String httpMessage((char*)0); + httpMessage.reserve(HTTP_PAGE_SIZE); + httpMessage += F("

"); + httpMessage += httpGetNodename(); + httpMessage += F("


"); + + httpMessage += F("
"); + + httpMessage += F("

Short Idle

"); + + httpMessage += F("

Long Idle

"); + + int8_t rotation = settings[FPSTR(FP_GUI_ROTATION)].as(); + httpMessage += F("

Orientation

"); + + httpMessage += F("

()) httpMessage += F(" checked"); + httpMessage += F(">Invert Colors"); + + httpMessage += F("

()) httpMessage += F(" checked"); + httpMessage += F(">Show Pointer"); + + int8_t bcklpin = settings[FPSTR(FP_GUI_BACKLIGHTPIN)].as(); + httpMessage += F("

Backlight Control

"); + + add_button(httpMessage, F(D_HTTP_SAVE_SETTINGS), F("name='save' value='gui'")); + close_form(httpMessage); + // httpMessage += + // F("

"); + +#if TOUCH_DRIVER == 2046 && defined(TOUCH_CS) + add_form_button(httpMessage, F(D_HTTP_CALIBRATE), F("/config/gui"), F("name='action' value='calibrate'")); + +// httpMessage += PSTR("

"); +#endif + + add_form_button(httpMessage, F("↩ " D_HTTP_CONFIGURATION), F("/config"), F("")); + + // httpMessage += PSTR("

"); + + webSendPage(httpGetNodename(), httpMessage.length(), false); + webServer.sendContent(httpMessage); + } + webSendFooter(); + + if(webServer.hasArg(F("action"))) dispatch_text_line(webServer.arg(F("action")).c_str()); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +#if HASP_USE_WIFI > 0 +void webHandleWifiConfig() +{ // http://plate01/config/wifi + if(!httpIsAuthenticated(F("config/wifi"))) return; + + StaticJsonDocument<256> settings; + wifiGetConfig(settings.to()); + + String httpMessage((char*)0); + httpMessage.reserve(HTTP_PAGE_SIZE); + httpMessage += F("

"); + httpMessage += httpGetNodename(); + httpMessage += F("


"); + + httpMessage += F("
"); + httpMessage += F("WiFi SSID (required)
WiFi Password (required)

"); + +#if HASP_USE_WIFI > 0 && !defined(STM32F4xx) + if(WiFi.getMode() == WIFI_STA) { + add_form_button(httpMessage, F("↩ " D_HTTP_CONFIGURATION), F("/config"), F("")); + // httpMessage += PSTR("

"); + } +#endif + + webSendPage(httpGetNodename(), httpMessage.length(), false); + webServer.sendContent(httpMessage); +#if defined(STM32F4xx) + httpMessage = ""; +#else + httpMessage.clear(); +#endif + webSendFooter(); +} +#endif + +//////////////////////////////////////////////////////////////////////////////////////////////////// +#if HASP_USE_HTTP > 0 +void webHandleHttpConfig() +{ // http://plate01/config/http + if(!httpIsAuthenticated(F("config/http"))) return; + + { + StaticJsonDocument<256> settings; + httpGetConfig(settings.to()); + + // String httpMessage((char *)0); + // httpMessage.reserve(HTTP_PAGE_SIZE); + // httpMessage += F("

"); + // httpMessage += httpGetNodename(); + // httpMessage += F("


"); + + // httpMessage += F("
"); + // httpMessage += F("Web Username (optional)
Web Password (optional)

"); + + // httpMessage += PSTR("

"); + + char httpMessage[HTTP_PAGE_SIZE]; + + size_t len = snprintf_P( + httpMessage, sizeof(httpMessage), + PSTR("

%s


" + "
" + "Web Username (optional)" + "
" + "Web Password (optional)" + "" + "

" + "

"), + httpGetNodename(), settings[FPSTR(FP_CONFIG_USER)].as().c_str(), + settings[FPSTR(FP_CONFIG_PASS)].as().c_str()); + + // if(settings[FPSTR(FP_CONFIG_PASS)].as() != "") { + // httpMessage += F(D_PASSWORD_MASK); + // } + + webSendPage(httpGetNodename(), len, false); + webServer.sendContent(httpMessage); + } + // httpMessage.clear(); + webSendFooter(); +} +#endif + +//////////////////////////////////////////////////////////////////////////////////////////////////// +#if defined(HASP_USE_GPIO) && (HASP_USE_GPIO > 0) +void webHandleGpioConfig() +{ // http://plate01/config/gpio + if(!httpIsAuthenticated(F("config/gpio"))) return; + uint8_t configCount = 0; + + // StaticJsonDocument<256> settings; + // gpioGetConfig(settings.to()); + + if(webServer.hasArg(PSTR("save"))) { + uint8_t id = webServer.arg(F("id")).toInt(); + uint8_t pin = webServer.arg(F("pin")).toInt(); + uint8_t type = webServer.arg(F("type")).toInt() + webServer.arg(F("state")).toInt(); + uint8_t group = webServer.arg(F("group")).toInt(); + uint8_t pinfunc = webServer.arg(F("func")).toInt(); + gpioSavePinConfig(id, pin, type, group, pinfunc); + } + if(webServer.hasArg(PSTR("del"))) { + uint8_t id = webServer.arg(F("id")).toInt(); + uint8_t pin = webServer.arg(F("pin")).toInt(); + gpioSavePinConfig(id, pin, HASP_GPIO_FREE, 0, 0); + } + + { + String httpMessage((char*)0); + httpMessage.reserve(HTTP_PAGE_SIZE); + httpMessage += F("

"); + httpMessage += httpGetNodename(); + httpMessage += F("


"); + + httpMessage += F("
"); + + httpMessage += F(""); + + for(uint8_t gpio = 0; gpio < NUM_DIGITAL_PINS; gpio++) { + for(uint8_t id = 0; id < HASP_NUM_GPIO_CONFIG; id++) { + hasp_gpio_config_t conf = gpioGetPinConfig(id); + if((conf.pin == gpio) && gpioConfigInUse(id) && gpioInUse(gpio) && !gpioIsSystemPin(gpio)) { + httpMessage += F(""); + configCount++; + } + } + } + + httpMessage += F("
PinTypeGroupDefaultAction
"); + httpMessage += halGpioName(gpio); + httpMessage += F(""); + + switch(conf.type & 0xfe) { + case HASP_GPIO_SWITCH: + // case HASP_GPIO_SWITCH_INVERTED: + httpMessage += F("Switch"); + break; + case HASP_GPIO_BUTTON: + // case HASP_GPIO_BUTTON_INVERTED: + httpMessage += F("Button"); + break; + case HASP_GPIO_LED: + // case HASP_GPIO_LED_INVERTED: + httpMessage += F("Led"); + break; + case HASP_GPIO_LED_R: + case HASP_GPIO_LED_G: + case HASP_GPIO_LED_B: + // case HASP_GPIO_LED_INVERTED: + httpMessage += F("Mood "); + break; + case HASP_GPIO_RELAY: + // case HASP_GPIO_RELAY_INVERTED: + httpMessage += F("Relay"); + break; + case HASP_GPIO_PWM: + // case HASP_GPIO_PWM_INVERTED: + httpMessage += F("PWM"); + break; + default: + httpMessage += F("Unknown"); + } + + switch(conf.type & 0xfe) { + case HASP_GPIO_LED_R: + httpMessage += F("Red"); + break; + case HASP_GPIO_LED_G: + httpMessage += F("Green"); + break; + case HASP_GPIO_LED_B: + httpMessage += F("Blue"); + break; + } + + httpMessage += F(""); + httpMessage += conf.group; + httpMessage += F(""); + + // bool inverted = (conf.type == HASP_GPIO_BUTTON_INVERTED) || + // (conf.type == HASP_GPIO_SWITCH_INVERTED) || (conf.type == HASP_GPIO_LED_INVERTED) + // || (conf.type == HASP_GPIO_RELAY_INVERTED) || (conf.type == + // HASP_GPIO_PWM_INVERTED); + + httpMessage += (conf.type & 0x1) ? F("High") : F("Low"); + + httpMessage += F("Edit Delete
"); + + if(configCount < HASP_NUM_GPIO_CONFIG) { + httpMessage += F("

"); + httpMessage += F("

"); + } + + add_form_button(httpMessage, F("↩ " D_HTTP_CONFIGURATION), F("/config"), F("")); + // httpMessage += F("

"); + + webSendPage(httpGetNodename(), httpMessage.length(), false); + webServer.sendContent(httpMessage); + } + // httpMessage.clear(); + webSendFooter(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +void webHandleGpioOptions() +{ // http://plate01/config/gpio/options + if(!httpIsAuthenticated(F("config/gpio/options"))) return; + + { + StaticJsonDocument<256> settings; + guiGetConfig(settings.to()); + + uint8_t config_id = webServer.arg(F("id")).toInt(); + + String httpMessage((char*)0); + httpMessage.reserve(HTTP_PAGE_SIZE); + httpMessage += F("

"); + httpMessage += httpGetNodename(); + httpMessage += F("


"); + + httpMessage += F("
"); + httpMessage += F(""); + + httpMessage += F("

GPIO Options"); + httpMessage += config_id; + httpMessage += F(" Options

"); + + httpMessage += F("

Pin

"); + + bool selected; + httpMessage += F("

Type

"); + + httpMessage += F("

Group

"); + + httpMessage += F("

Default State

"); + + httpMessage += + F("

"); + + httpMessage += PSTR("

"); + + webSendPage(httpGetNodename(), httpMessage.length(), false); + webServer.sendContent(httpMessage); + } + webSendFooter(); + + if(webServer.hasArg(F("action"))) dispatch_text_line(webServer.arg(F("action")).c_str()); // Security check +} +#endif // HASP_USE_GPIO + +//////////////////////////////////////////////////////////////////////////////////////////////////// +void webHandleDebugConfig() +{ // http://plate01/config/debug + if(!httpIsAuthenticated(F("config/debug"))) return; + + StaticJsonDocument<256> settings; + debugGetConfig(settings.to()); + + { + String httpMessage((char*)0); + httpMessage.reserve(HTTP_PAGE_SIZE); + httpMessage += F("

"); + httpMessage += httpGetNodename(); + httpMessage += F("


"); + + httpMessage += F("
"); + + uint16_t baudrate = settings[FPSTR(FP_CONFIG_BAUD)].as(); + httpMessage += F("

Serial Port

Telemetry Period (Seconds, 0=disable) " + "

"); + +#if HASP_USE_SYSLOG > 0 + httpMessage += F("Syslog Hostame (optional)
Syslog Port (optional) Syslog Facility
Syslog Protocol () == 0) httpMessage += F(" checked"); + httpMessage += F(">IETF (RFC 5424)   () == 1) httpMessage += F(" checked"); + httpMessage += F(">BSD (RFC 3164)"); +#endif + + httpMessage += + F("

"); + + add_form_button(httpMessage, F("↩ " D_HTTP_CONFIGURATION), F("/config"), F("")); + // httpMessage += PSTR("

"); + + webSendPage(httpGetNodename(), httpMessage.length(), false); + webServer.sendContent(httpMessage); + } + // httpMessage.clear(); + webSendFooter(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +void webHandleHaspConfig() +{ // http://plate01/config/http + if(!httpIsAuthenticated(F("config/hasp"))) return; + + StaticJsonDocument<256> settings; + haspGetConfig(settings.to()); + + { + String httpMessage((char*)0); + httpMessage.reserve(HTTP_PAGE_SIZE); + httpMessage += F("

"); + httpMessage += httpGetNodename(); + httpMessage += F("


"); + + httpMessage += F("

"); + httpMessage += F("


"); + + // httpMessage += F("
"); + httpMessage += F(""); + httpMessage += F("

UI Theme (required)
"); + httpMessage += + F("Hue

"); + httpMessage += F("

Default Font

"); + + httpMessage += F("

Startup Layout (optional)
Startup Page (required)

Startup Brightness (required)

"); + + httpMessage += + F("

"); + + // httpMessage += + // F("

"); + httpMessage += FPSTR(MAIN_MENU_BUTTON); + + webSendPage(httpGetNodename(), httpMessage.length(), false); + webServer.sendContent(httpMessage); + } + // httpMessage.clear(); + webSendFooter(); +} +#endif // HASP_USE_CONFIG + +//////////////////////////////////////////////////////////////////////////////////////////////////// +void httpHandleNotFound() +{ // webServer 404 +#if HASP_USE_SPIFFS > 0 || HASP_USE_LITTLEFS > 0 + if(handleFileRead(webServer.uri())) return; +#endif + +#if defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_ESP8266) + LOG_TRACE(TAG_HTTP, F("Sending 404 to client connected from: %s"), + webServer.client().remoteIP().toString().c_str()); +#else + // LOG_TRACE(TAG_HTTP,F("Sending 404 to client connected from: %s"), String(webServer.client().remoteIP()).c_str()); +#endif + + String httpMessage((char*)0); + httpMessage.reserve(HTTP_PAGE_SIZE); + + httpMessage += F("File Not Found\n\nURI: "); + httpMessage += webServer.uri(); + httpMessage += F("\nMethod: "); + httpMessage += (webServer.method() == HTTP_GET) ? F("GET") : F("POST"); + httpMessage += F("\nArguments: "); + httpMessage += webServer.args(); + httpMessage += "\n"; + for(int i = 0; i < webServer.args(); i++) { + httpMessage += " " + webServer.argName(i) + ": " + webServer.arg(i) + "\n"; + } + webServer.send(404, PSTR("text/plain"), httpMessage.c_str()); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +void webHandleFirmware() +{ + if(!httpIsAuthenticated(F("firmware"))) return; + + { + String httpMessage((char*)0); + httpMessage.reserve(HTTP_PAGE_SIZE); + httpMessage += F("

"); + httpMessage += httpGetNodename(); + httpMessage += F("


"); + + httpMessage += F("

"); + httpMessage += F("

"); + + // httpMessage += F("

"); + // httpMessage += F("

"); + + httpMessage += FPSTR(MAIN_MENU_BUTTON); + + webSendPage(httpGetNodename(), httpMessage.length(), false); + webServer.sendContent(httpMessage); + } + // httpMessage.clear(); + webSendFooter(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +void httpHandleEspFirmware() +{ // http://plate01/espfirmware + if(!httpIsAuthenticated(F("espfirmware"))) return; + + { + String httpMessage((char*)0); + httpMessage.reserve(HTTP_PAGE_SIZE); + httpMessage += F("

"); + httpMessage += httpGetNodename(); + httpMessage += F("


"); + + httpMessage += F("

ESP update

Updating ESP firmware from: "); + httpMessage += webServer.arg("espFirmware"); + + webSendPage(httpGetNodename(), httpMessage.length(), true); + webServer.sendContent(httpMessage); + // httpMessage.clear(); + } + webSendFooter(); + + LOG_TRACE(TAG_HTTP, F("Attempting ESP firmware update from: %s"), webServer.arg("espFirmware").c_str()); + // espStartOta(webServer.arg("espFirmware")); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +#if HASP_USE_CONFIG > 0 +void webHandleSaveConfig() +{ + if(!httpIsAuthenticated(F("saveConfig"))) return; + + configWriteConfig(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +void httpHandleResetConfig() +{ // http://plate01/resetConfig + if(!httpIsAuthenticated(F("resetConfig"))) return; + + bool resetConfirmed = webServer.arg(F("confirm")) == F("yes"); + + { + String httpMessage((char*)0); + httpMessage.reserve(HTTP_PAGE_SIZE); + httpMessage += F("

"); + httpMessage += httpGetNodename(); + httpMessage += F("


"); + + if(resetConfirmed) { // User has confirmed, so reset everything + bool formatted = configClearEeprom(); + if(formatted) { + httpMessage += F("Resetting all saved settings and restarting device"); + } else { + httpMessage += F("Failed to format the internal flash partition"); + resetConfirmed = false; + } + } else { + httpMessage += + F("

Warning

This process will reset all settings to the default values. The internal flash " + "will " + "be erased and the device is restarted. You may need to connect to the WiFi AP displayed on the " + "panel to " + "re-configure the device before accessing it again. ALL FILES WILL BE LOST!" + "


" + "

" + "


"); + + add_form_button(httpMessage, F("↩ " D_HTTP_CONFIGURATION), F("/config"), F("")); + // httpMessage += + // PSTR("

"); + } + + webSendPage(httpGetNodename(), httpMessage.length(), resetConfirmed); + webServer.sendContent(httpMessage); + } + // httpMessage.clear(); + webSendFooter(); + + if(resetConfirmed) { + delay(250); + // configClearSaved(); + dispatch_reboot(false); // Do not save the current config + } +} +#endif // HASP_USE_CONFIG + +void httpStart() +{ + webServer.begin(); + webServerStarted = true; +#if HASP_USE_WIFI > 0 +#if defined(STM32F4xx) + IPAddress ip; + ip = WiFi.localIP(); + LOG_INFO(TAG_HTTP, F(D_SERVICE_STARTED " @ http://%d.%d.%d.%d"), ip[0], ip[1], ip[2], ip[3]); +#else + LOG_INFO(TAG_HTTP, F(D_SERVICE_STARTED " @ http://%s"), + (WiFi.getMode() != WIFI_STA ? WiFi.softAPIP().toString().c_str() : WiFi.localIP().toString().c_str())); +#endif +#else + IPAddress ip; +#if defined(ARDUINO_ARCH_ESP32) + ip = ETH.localIP(); +#else + ip = Ethernet.localIP(); +#endif + LOG_INFO(TAG_HTTP, F(D_SERVICE_STARTED " @ http://%d.%d.%d.%d"), ip[0], ip[1], ip[2], ip[3]); +#endif +} + +void httpStop() +{ + webServer.stop(); + webServerStarted = false; + LOG_WARNING(TAG_HTTP, F(D_SERVICE_STOPPED)); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +void httpSetup() +{ + // httpSetConfig(settings); + + // ask server to track these headers + const char* headerkeys[] = {"Content-Length"}; // "Authentication" + size_t headerkeyssize = sizeof(headerkeys) / sizeof(char*); + webServer.collectHeaders(headerkeys, headerkeyssize); + + // Shared pages + webServer.on(F("/about"), webHandleAbout); + webServer.onNotFound(httpHandleNotFound); + +#if HASP_USE_WIFI > 0 +#if !defined(STM32F4xx) + +#if HASP_USE_CONFIG > 0 + if(WiFi.getMode() != WIFI_STA) { + LOG_TRACE(TAG_HTTP, F("Wifi access point")); + webServer.on(F("/"), webHandleWifiConfig); + webServer.on(F("/config"), webHandleConfig); + return; + } + +#endif +#endif +#endif + + webServer.on(F("/page/"), []() { + String pageid = webServer.arg(F("page")); + webServer.send(200, PSTR("text/plain"), "Page: '" + pageid + "'"); + haspSetPage(pageid.toInt()); + }); + +#if HASP_USE_SPIFFS > 0 || HASP_USE_LITTLEFS > 0 + webServer.on(F("/list"), HTTP_GET, handleFileList); + // load editor + webServer.on(F("/edit"), HTTP_GET, []() { + if(!handleFileRead("/edit.htm")) { + char mimetype[16]; + snprintf_P(mimetype, sizeof(mimetype), PSTR("text/plain")); + webServer.send_P(404, mimetype, PSTR("FileNotFound")); + } + }); + webServer.on(F("/edit"), HTTP_PUT, handleFileCreate); + webServer.on(F("/edit"), HTTP_DELETE, handleFileDelete); + // first callback is called after the request has ended with all parsed arguments + // second callback handles file uploads at that location + webServer.on( + F("/edit"), HTTP_POST, + []() { + webServer.send(200, "text/plain", ""); + LOG_VERBOSE(TAG_HTTP, F("Headers: %d"), webServer.headers()); + }, + handleFileUpload); +#endif + + webServer.on(F("/"), webHandleRoot); + webServer.on(F("/info"), webHandleInfo); + webServer.on(F("/screenshot"), webHandleScreenshot); + webServer.on(F("/firmware"), webHandleFirmware); + webServer.on(F("/reboot"), httpHandleReboot); + +#if HASP_USE_CONFIG > 0 + webServer.on(F("/config/hasp"), webHandleHaspConfig); + webServer.on(F("/config/http"), webHandleHttpConfig); + webServer.on(F("/config/gui"), webHandleGuiConfig); + webServer.on(F("/config/debug"), webHandleDebugConfig); +#if HASP_USE_MQTT > 0 + webServer.on(F("/config/mqtt"), webHandleMqttConfig); +#endif +#if HASP_USE_WIFI > 0 + webServer.on(F("/config/wifi"), webHandleWifiConfig); +#endif +#if HASP_USE_GPIO > 0 + webServer.on(F("/config/gpio"), webHandleGpioConfig); + webServer.on(F("/config/gpio/options"), webHandleGpioOptions); +#endif + webServer.on(F("/saveConfig"), webHandleSaveConfig); + webServer.on(F("/resetConfig"), httpHandleResetConfig); + webServer.on(F("/config"), webHandleConfig); +#endif // HASP_USE_CONFIG + +#if defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_ESP8266) + webServer.on( + F("/update"), HTTP_POST, + []() { + webServer.send(200, "text/plain", ""); + LOG_VERBOSE(TAG_HTTP, F("Total size: %s"), webServer.hostHeader().c_str()); + }, + webHandleFirmwareUpload); + webServer.on(F("/espfirmware"), httpHandleEspFirmware); +#endif + + LOG_INFO(TAG_HTTP, F(D_SERVICE_STARTED)); + // webStart(); Wait for network connection +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +void httpReconnect() +{ + if(!http_config.enable) return; + + if(webServerStarted) { + httpStop(); + } else +#if HASP_USE_WIFI > 0 && !defined(STM32F4xx) + if(WiFi.status() == WL_CONNECTED || WiFi.getMode() != WIFI_STA) +#endif + { + httpStart(); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +void httpLoop(void) +{ + if(http_config.enable) webServer.handleClient(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +void httpEvery5Seconds() +{ + // if(httpEnable && !webServerStarted) httpReconnect(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +#if HASP_USE_CONFIG > 0 +bool httpGetConfig(const JsonObject& settings) +{ + bool changed = false; + + settings[FPSTR(FP_CONFIG_ENABLE)] = http_config.enable; + + if(http_config.port != settings[FPSTR(FP_CONFIG_PORT)].as()) changed = true; + settings[FPSTR(FP_CONFIG_PORT)] = http_config.port; + + if(strcmp(http_config.user, settings[FPSTR(FP_CONFIG_USER)].as().c_str()) != 0) changed = true; + settings[FPSTR(FP_CONFIG_USER)] = http_config.user; + + if(strcmp(http_config.password, settings[FPSTR(FP_CONFIG_PASS)].as().c_str()) != 0) changed = true; + settings[FPSTR(FP_CONFIG_PASS)] = http_config.password; + + if(changed) configOutput(settings, TAG_HTTP); + return changed; +} + +/** Set HTTP Configuration. + * + * Read the settings from json and sets the application variables. + * + * @note: data pixel should be formated to uint32_t RGBA. Imagemagick requirements. + * + * @param[in] settings JsonObject with the config settings. + **/ +bool httpSetConfig(const JsonObject& settings) +{ + configOutput(settings, TAG_HTTP); + bool changed = false; + + changed |= configSet(http_config.port, settings[FPSTR(FP_CONFIG_PORT)], F("httpPort")); + + if(!settings[FPSTR(FP_CONFIG_USER)].isNull()) { + changed |= strcmp(http_config.user, settings[FPSTR(FP_CONFIG_USER)]) != 0; + strncpy(http_config.user, settings[FPSTR(FP_CONFIG_USER)], sizeof(http_config.user)); + } + + if(!settings[FPSTR(FP_CONFIG_PASS)].isNull()) { + changed |= strcmp(http_config.password, settings[FPSTR(FP_CONFIG_PASS)]) != 0; + strncpy(http_config.password, settings[FPSTR(FP_CONFIG_PASS)], sizeof(http_config.password)); + } + + return changed; +} +#endif // HASP_USE_CONFIG + +size_t httpClientWrite(const uint8_t* buf, size_t size) +{ + /***** Sending 16Kb at once freezes on STM32 EthernetClient *****/ + size_t bytes_sent = 0; + while(bytes_sent < size) { + if(!webServer.client()) return bytes_sent; + if(size - bytes_sent >= 2048) { + bytes_sent += webServer.client().write(buf + bytes_sent, 2048); + } else { + bytes_sent += webServer.client().write(buf + bytes_sent, size - bytes_sent); + } + // Serial.println(bytes_sent); + + // stm32_eth_scheduler(); // already in write + // webServer.client().flush(); + delay(1); // Fixes the freeze + } + return bytes_sent; +} + +#endif \ No newline at end of file