From 84f5685ca73b7ad2371abb99c86a49453e5615a9 Mon Sep 17 00:00:00 2001 From: fvanroie <15969459+fvanroie@users.noreply.github.com> Date: Fri, 2 Jul 2021 16:00:51 +0200 Subject: [PATCH] Add HASP_USE_HTTP_ASYNC --- include/hasp_conf.h | 8 + src/main_arduino.cpp | 6 +- src/sys/net/hasp_network.cpp | 4 +- src/sys/svc/hasp_http_async.cpp | 2419 +++++++++++++++++++++++++++++++ src/sys/svc/hasp_telnet.cpp | 26 +- 5 files changed, 2448 insertions(+), 15 deletions(-) create mode 100644 src/sys/svc/hasp_http_async.cpp diff --git a/include/hasp_conf.h b/include/hasp_conf.h index 76a30be1..eb609306 100644 --- a/include/hasp_conf.h +++ b/include/hasp_conf.h @@ -58,6 +58,10 @@ #define HASP_USE_HTTP (HASP_HAS_NETWORK) #endif +#ifndef HASP_USE_HTTP_ASYNC +#define HASP_USE_HTTP_ASYNC 0 //(HASP_HAS_NETWORK) +#endif + #ifndef HASP_USE_MDNS #define HASP_USE_MDNS (HASP_HAS_NETWORK) #endif @@ -224,6 +228,10 @@ static WiFiSpiClass WiFi; #include "sys/svc/hasp_http.h" #endif +#if HASP_USE_HTTP_ASYNC > 0 +#include "sys/svc/hasp_http.h" +#endif + #if HASP_USE_CONSOLE > 0 #include "sys/svc/hasp_console.h" #endif diff --git a/src/main_arduino.cpp b/src/main_arduino.cpp index 0047a7f2..567bf9c0 100644 --- a/src/main_arduino.cpp +++ b/src/main_arduino.cpp @@ -90,7 +90,7 @@ void setup() otaSetup(); #endif -#if HASP_USE_HTTP > 0 +#if HASP_USE_HTTP > 0 || HASP_USE_HTTP_ASYNC > 0 httpSetup(); #endif @@ -160,8 +160,8 @@ IRAM_ATTR void loop() break; case 2: -#if HASP_USE_HTTP > 0 - // httpEvery5Seconds(); +#if HASP_USE_HTTP_ASYNC > 0 + httpEvery5Seconds(); #endif break; diff --git a/src/sys/net/hasp_network.cpp b/src/sys/net/hasp_network.cpp index be0915db..3ee09239 100644 --- a/src/sys/net/hasp_network.cpp +++ b/src/sys/net/hasp_network.cpp @@ -31,7 +31,7 @@ void networkStart(void) haspReconnect(); debugStartSyslog(); // mqttStart(); -#if HASP_USE_HTTP > 0 +#if HASP_USE_HTTP > 0 || HASP_USE_HTTP_ASYNC > 0 httpStart(); #endif #if HASP_USE_MDNS > 0 @@ -45,7 +45,7 @@ void networkStop(void) debugStopSyslog(); // mqttStop(); -#if HASP_USE_HTTP > 0 +#if HASP_USE_HTTP > 0 || HASP_USE_HTTP_ASYNC > 0 httpStop(); #endif mdnsStop(); diff --git a/src/sys/svc/hasp_http_async.cpp b/src/sys/svc/hasp_http_async.cpp new file mode 100644 index 00000000..a3385d0e --- /dev/null +++ b/src/sys/svc/hasp_http_async.cpp @@ -0,0 +1,2419 @@ +/* MIT License - Copyright (c) 2019-2021 Francis Van Roie + For full license information read the LICENSE file in the project folder */ + +//#include "webServer.h" +#include "hasplib.h" +#include "ArduinoLog.h" + +#if defined(ARDUINO_ARCH_ESP32) +#include "Update.h" +#endif + +#include "hasp_conf.h" +#include "dev/device.h" +#include "hal/hasp_hal.h" + +#include "hasp_gui.h" +#include "hasp_debug.h" +#include "hasp_config.h" + +#if HASP_USE_HTTP_ASYNC > 0 +#include "sys/net/hasp_network.h" + +/* clang-format off */ +//default theme +#ifndef D_HTTP_COLOR_TEXT +#define D_HTTP_COLOR_TEXT "#000" // Global text color - Black +#endif +#ifndef D_HTTP_COLOR_BACKGROUND +#define D_HTTP_COLOR_BACKGROUND "#fff" // Global background color - White +#endif +#ifndef D_HTTP_COLOR_INPUT_TEXT +#define D_HTTP_COLOR_INPUT_TEXT "#000" // Input text color - Black +#endif +#ifndef D_HTTP_COLOR_INPUT +#define D_HTTP_COLOR_INPUT "#fff" // Input background color - White +#endif +#ifndef D_HTTP_COLOR_INPUT_WARNING +#define D_HTTP_COLOR_INPUT_WARNING "#f00" // Input warning border color - Red +#endif +#ifndef D_HTTP_COLOR_BUTTON_TEXT +#define D_HTTP_COLOR_BUTTON_TEXT "#fff" // Button text color - White +#endif +#ifndef D_HTTP_COLOR_BUTTON +#define D_HTTP_COLOR_BUTTON "#1fa3ec" // Button color - Vivid blue +#endif +#ifndef D_HTTP_COLOR_BUTTON_RESET +#define D_HTTP_COLOR_BUTTON_RESET "#f00" // Restart/Reset button color - red +#endif +/* clang-format on */ + +#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 +#include +#include +AsyncWebServer webServer(80); +#endif + +#if defined(ARDUINO_ARCH_ESP32) +#include "AsyncTCP.h" +#include "ESPAsyncWebServer.h" +#include +AsyncWebServer webServer(80); +extern const uint8_t EDIT_HTM_GZ_START[] asm("_binary_data_edit_htm_gz_start"); +extern const uint8_t EDIT_HTM_GZ_END[] asm("_binary_data_edit_htm_gz_end"); +#endif // ESP32 + +AsyncWebSocket ws("/ws"); // access at ws://[esp ip]/ws + +// 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_CSS[] PROGMEM = + "body,.c{text-align:center;}" + "div,input{padding:5px;font-size:1em;}" + "a{color:" D_HTTP_COLOR_TEXT "}" + "input:not([type=file]){width:90%;background-color:" D_HTTP_COLOR_INPUT ";color:" D_HTTP_COLOR_INPUT_TEXT ";}" + "input[type=checkbox],input[type=radio]{width:1em;}" + "select{background-color:" D_HTTP_COLOR_INPUT ";color:" D_HTTP_COLOR_INPUT_TEXT ";}" + "input:invalid{border:1px solid " D_HTTP_COLOR_INPUT_WARNING ";}" + //"#hue{width:100%;}" + "body{font-family:verdana;width:60%;margin:auto;background:" D_HTTP_COLOR_BACKGROUND ";color:" D_HTTP_COLOR_TEXT + ";}" + "button{border:0;border-radius:0.6rem;background-color:" D_HTTP_COLOR_BUTTON ";color:" D_HTTP_COLOR_BUTTON_TEXT + ";line-height:2.4rem;font-size:1.2rem;width:100%;}" + //".q{float:right;width:64px;text-align:right;}" + ".red{background-color:" D_HTTP_COLOR_BUTTON_RESET ";}" + // ".button3{background-color:#f44336;}" + // ".button4{background-color:#e7e7e7;color:black;}" + // ".button5{background-color:#555555;}" + // ".button6{background-color:#4CAF50;}" + "td{font-size:0.87rem;padding-bottom:0px;padding-top:0px;}th{padding-top:0.5em;}"; +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); +} + +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); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void webHandleHaspConfig(AsyncWebServerRequest* request); + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool httpIsAuthenticated() +{ + if(http_config.password[0] != '\0') { // Request HTTP auth if httpPassword is set + // if(!webServer.authenticate(http_config.user, http_config.password)) { + // return false; + // } + } + return true; +} + +bool httpIsAuthenticated(AsyncWebServerRequest* request, const __FlashStringHelper* notused) +{ + // if(!httpIsAuthenticated()) return false; + +#if defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_ESP8266) + LOG_TRACE(TAG_HTTP, F(D_HTTP_SENDING_PAGE), request->url().c_str(), + request->client()->remoteIP().toString().c_str()); +#else + // LOG_INFO(TAG_HTTP,F(D_HTTP_SENDING_PAGE), page, + // String(webServer.client()->remoteIP()).c_str()); +#endif + + return true; +} + +void webSendFooter(AsyncResponseStream* response) +{ +#if defined(STM32F4xx) + // webSendFooter(reponse);(HTTP_END); + // webSendFooter(reponse);(haspDevice.get_version()); + // webSendFooter(reponse);(HTTP_FOOTER); +#else + response->printf_P(HTTP_END); + response->printf(haspDevice.get_version()); + response->printf_P(HTTP_FOOTER); +#endif +} + +static int webSendCached(AsyncWebServerRequest* request, int statuscode, const char* contenttype, const char* data, + size_t size) +{ +#if defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_ESP8266) + AsyncWebServerResponse* response = request->beginResponse_P(statuscode, "text/html", data); + response->addHeader(F("Cache-Control"), F("public, max-age=604800, immutable")); + request->send(response); +#else + request->send(statuscode, contenttype, data); +#endif + return statuscode; +} + +void webSendPage(AsyncWebServerRequest* request, const char* nodename, const char* httpMessage, bool gohome = false) +{ + char buffer[64]; + AsyncResponseStream* response = request->beginResponseStream("text/html"); + size_t httpdatalength = strlen(httpMessage); + + /* Calculate Content Length upfront */ + uint32_t contentLength = strlen(haspDevice.get_version()); // version 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*0) { + LOG_WARNING(TAG_HTTP, F("Sending page with %u static and %u dynamic bytes"), contentLength, httpdatalength); + } + + // response->setContentLength(contentLength + httpdatalength); +#if defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_ESP8266) + response->printf_P(HTTP_DOCTYPE); // 122 +#else + response->print(HTTP_DOCTYPE); // 122 +#endif + + snprintf_P(buffer, sizeof(buffer), HTTP_HEADER, nodename); + response->print(buffer); // 17-2+len + +#if defined(STM32F4xx) + // webSendFooter(reponse);(HTTP_SCRIPT); // 131 + // webSendFooter(reponse);(HTTP_STYLE); // 487 + // //webSendFooter(reponse);(HASP_STYLE); // 145 + if(gohome) // webSendFooter(reponse);(HTTP_META_GO_BACK); // 47 + // webSendFooter(reponse);(HTTP_HEADER_END); // 80 +#else + response->printf_P(HTTP_SCRIPT); // 131 + response->printf_P(HTTP_STYLE); // 487 + // //webSendFooter(reponse);_P(HASP_STYLE); // 145 + if(gohome) response->printf_P(HTTP_META_GO_BACK); // 47 + response->printf_P(HTTP_HEADER_END); // 80 +#endif + + response->print(httpMessage); + + webSendFooter(response); + request->send(response); +} + +void saveConfig() +{ + /* if(webServer.method() == HTTP_POST) { + if(request->hasArg(PSTR("save"))) { + String save = request->arg(PSTR("save")); + + StaticJsonDocument<256> settings; + for(int i = 0; i < request->args(); i++) settings[request->argName(i)] = request->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)] = request->hasArg(PSTR("cur")); + settings[FPSTR(FP_GUI_INVERT)] = request->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(request,F("config"))) return; + + #if HASP_USE_WIFI > 0 + } else if(save == String(PSTR("wifi"))) { + wifiSetConfig(settings.as()); + #endif + } + } + } */ +} + +void webHandleRoot(AsyncWebServerRequest* request) +{ + if(!httpIsAuthenticated(request, F("root"))) return; + AsyncResponseStream* response = request->beginResponseStream("text/html"); + + saveConfig(); + + String httpMessage((char*)0); + httpMessage.reserve(HTTP_PAGE_SIZE); + httpMessage += F("

"); + httpMessage += haspDevice.get_hostname(); + 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 +#ifdef ARDUINO_ARCH_ESP32 + bool flashfile = true; +#else + bool flashfile = false; +#endif + if(flashfile || HASP_FS.exists(F("/edit.htm.gz")) || HASP_FS.exists(F("/edit.htm"))) { + httpMessage += F("

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

"); + + webSendPage(request, haspDevice.get_hostname(), httpMessage.c_str(), false); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +void httpHandleReboot(AsyncWebServerRequest* request) +{ // http://plate01/reboot + if(!httpIsAuthenticated(request, F("reboot"))) return; + + AsyncResponseStream* response = request->beginResponseStream("text/html"); + String httpMessage((char*)0); + httpMessage.reserve(HTTP_PAGE_SIZE); + + httpMessage += F("

"); + httpMessage += F(haspDevice.get_hostname()); + httpMessage += F("


"); + httpMessage += F(D_DISPATCH_REBOOT); + + webSendPage(request, haspDevice.get_hostname(), httpMessage.c_str(), true); + + delay(200); + dispatch_reboot(true); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +void webHandleScreenshot(AsyncWebServerRequest* request) +{ // http://plate01/screenshot + if(!httpIsAuthenticated(request, F("screenshot"))) return; + + if(request->hasArg(F("a"))) { + if(request->arg(F("a")) == F("next")) { + dispatch_page_next(LV_SCR_LOAD_ANIM_NONE); + } else if(request->arg(F("a")) == F("prev")) { + dispatch_page_prev(LV_SCR_LOAD_ANIM_NONE); + } else if(request->arg(F("a")) == F("back")) { + dispatch_page_back(LV_SCR_LOAD_ANIM_NONE); + } + } + + if(request->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)); + // request->send_P(200, PSTR("image/bmp"), ""); + // guiTakeScreenshot(); + // webServer.client().stop(); + + } else { + + String httpMessage((char*)0); + httpMessage.reserve(HTTP_PAGE_SIZE); + httpMessage += F("

"); + httpMessage += haspDevice.get_hostname(); + httpMessage += F("


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

"); + + httpMessage += F("

"); + httpMessage += + F("

"); + httpMessage += + F("

"); + httpMessage += FPSTR(MAIN_MENU_BUTTON); + + webSendPage(request, haspDevice.get_hostname(), httpMessage.c_str(), false); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void webHandleAbout(AsyncWebServerRequest* request) +{ // http://plate01/about + if(!httpIsAuthenticated(request, 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(request, haspDevice.get_hostname(), httpMessage.c_str(), false); + // //webSendFooter(reponse);(httpMessage); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void add_json(String& data, JsonDocument& doc) +{ + char buffer[512]; + size_t len = serializeJson(doc, buffer, sizeof(buffer)); + if(doc.isNull()) return; // empty document + + buffer[len - 1] = ','; + char* start = buffer + 1; + data += String(start); + doc.clear(); +} + +void webHandleInfoJson(AsyncWebServerRequest* request) +{ // http://plate01/ + if(!httpIsAuthenticated(request, F("infojson"))) return; + + String htmldata((char*)0); + htmldata.reserve(HTTP_PAGE_SIZE); + DynamicJsonDocument doc(512); + + htmldata += F("

"); + htmldata += haspDevice.get_hostname(); + htmldata += F("


"); + + htmldata += "
"; + + // String path = F(".html"); + // request->send(200, getContentType(path), htmldata); + + htmldata += FPSTR(MAIN_MENU_BUTTON); + + webSendPage(request, haspDevice.get_hostname(), htmldata.c_str(), false); + // //webSendFooter(reponse);(htmldata); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +void webHandleInfo(AsyncWebServerRequest* request) +{ // http://plate01/ + if(!httpIsAuthenticated(request, F("info"))) return; + + char size_buf[32]; + String httpMessage((char*)0); + httpMessage.reserve(HTTP_PAGE_SIZE); + httpMessage += F("

"); + httpMessage += haspDevice.get_hostname(); + httpMessage += F("


"); + + /* HASP Stats */ + httpMessage += F("HASP Version: "); + httpMessage += haspDevice.get_version(); + httpMessage += F("
Build DateTime: "); + httpMessage += __DATE__; + httpMessage += F(" "); + httpMessage += __TIME__; + httpMessage += F(" UTC
Uptime: "); // Github buildservers are in UTC + + 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: "); + Parser::format_bytes(haspDevice.get_free_heap(), size_buf, sizeof(size_buf)); + httpMessage += size_buf; + httpMessage += F("
Memory Fragmentation: "); + httpMessage += String(haspDevice.get_heap_fragmentation()); + +#if ARDUINO_ARCH_ESP32 + if(psramFound()) { + httpMessage += F("
Free PSRam: "); + Parser::format_bytes(ESP.getFreePsram(), size_buf, sizeof(size_buf)); + httpMessage += size_buf; + httpMessage += F("
PSRam Size: "); + Parser::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: "); + Parser::format_bytes(mem_mon.total_size, size_buf, sizeof(size_buf)); + httpMessage += size_buf; + httpMessage += F("
LVGL Free: "); + Parser::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(haspPages.get()); + + /* 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(" " D_INFO_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"), haspDevice.get_hostname(), mac.c_str()); + httpMessage += mqttClientId; + } + +#endif // MQTT + + /* ESP Stats */ + httpMessage += F("

MCU Model: "); + httpMessage += haspDevice.get_chip_model(); + httpMessage += F("
CPU Frequency: "); + httpMessage += String(haspDevice.get_cpu_frequency()); + httpMessage += F("MHz"); + +#if defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_ESP8266) + httpMessage += F("
Flash Chip Size: "); + Parser::format_bytes(ESP.getFlashChipSize(), size_buf, sizeof(size_buf)); + httpMessage += size_buf; + + httpMessage += F("
Program Size Used: "); + Parser::format_bytes(ESP.getSketchSize(), size_buf, sizeof(size_buf)); + httpMessage += size_buf; + + httpMessage += F("
Program Size Free: "); + Parser::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 += haspDevice.get_core_version(); + //#endif + httpMessage += F("
Last Reset: "); + // httpMessage += halGetResetInfo(); + + httpMessage += FPSTR(MAIN_MENU_BUTTON); + + webSendPage(request, haspDevice.get_hostname(), httpMessage.c_str(), false); + // //webSendFooter(reponse);(httpMessage); +} + +/* 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(AsyncWebServerRequest* request, size_t size) +{ + LOG_INFO(TAG_HTTP, F("Update Success: %u bytes received. Rebooting..."), size); + + String httpMessage((char*)0); + httpMessage.reserve(HTTP_PAGE_SIZE); + httpMessage += F("

"); + httpMessage += haspDevice.get_hostname(); + httpMessage += F("


"); + httpMessage += F("Upload complete. Rebooting device, please wait..."); + + webSendPage(request, haspDevice.get_hostname(), httpMessage.c_str(), true); + // //webSendFooter(reponse);(httpMessage); + + delay(250); + dispatch_reboot(true); // Save the current config +} + +void webHandleFirmwareUpload(AsyncWebServerRequest* request, String filename, size_t index, uint8_t* data, size_t len, + bool final) +{ + if(!index) { // START + if(!httpIsAuthenticated(request, F("update"))) return; + + // WiFiUDP::stopAll(); + + int command = request->arg(F("cmd")).toInt(); + size_t size = 0; + if(command == U_FLASH) { + LOG_TRACE(TAG_HTTP, F("Update flash: %s"), filename.c_str()); + size = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000; +#ifdef ESP32 + } else if(command == U_SPIFFS) { + LOG_TRACE(TAG_HTTP, F("Update filesystem: %s"), filename.c_str()); + size = UPDATE_SIZE_UNKNOWN; +#endif + } + haspProgressMsg(filename.c_str()); + + // if(!Update.begin(UPDATE_SIZE_UNKNOWN)) { // start with max available size + // const char label[] = "spiffs"; + if(!Update.begin(size, command, -1, 0U)) { // start with max available size + webUpdatePrintError(); + } + } + + // write buffered data + if(Update.write(data, len) != len) { + webUpdatePrintError(); + } else { + webUploadProgress(); + } + + if(final) { // END + haspProgressVal(100); + if(Update.end(true)) { // true to set the size to the current progress + haspProgressMsg(F(D_OTA_UPDATE_APPLY)); + webUpdateReboot(request, index + len); + } else { + webUpdatePrintError(); + } + } +} +#endif + +#if HASP_USE_SPIFFS > 0 || HASP_USE_LITTLEFS > 0 +int handleFileRead(AsyncWebServerRequest* request, String path) +{ + // if(!httpIsAuthenticated(request,F("fileread"))) return false; + if(!httpIsAuthenticated()) 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)) { + + String contentType((char*)0); + if(request->hasArg(F("download"))) + contentType = F("application/octet-stream"); + else + contentType = getContentType(path); + + if(!HASP_FS.exists(path) && HASP_FS.exists(pathWithGz)) + path = pathWithGz; // Only use .gz if normal file doesn't exist + File file = HASP_FS.open(path, "r"); + + String configFile((char*)0); // Verify if the file is config.json + configFile = String(FPSTR(FP_HASP_CONFIG_FILE)); + + if(!strncasecmp(file.name(), configFile.c_str(), configFile.length())) { + file.close(); + DynamicJsonDocument settings(8 * 256); + DeserializationError error = configParseFile(configFile, settings); + + if(error) return 500; // Internal Server Error + + configMaskPasswords(settings); // Output settings to the client with masked passwords! + char buffer[1024]; + size_t len = serializeJson(settings, buffer, sizeof(buffer)); + // request->setContentLength(len); + request->send(200, contentType, buffer); + + } else { + + // Stream other files directly from filesystem + request->send(HASP_FS, path, contentType); + file.close(); + } + + return 200; // OK + } + +#ifdef ARDUINO_ARCH_ESP32 + if(path == F("/edit.htm")) { + size_t size = EDIT_HTM_GZ_END - EDIT_HTM_GZ_START; + AsyncWebServerResponse* response = request->beginResponse_P(200, "text/html", EDIT_HTM_GZ_START, size); + response->addHeader(F("Content-Encoding"), F("gzip")); + request->send(response); + return 200; + } +#endif + + if(!strcasecmp_P(path.c_str(), PSTR("/favicon.ico"))) + return webSendCached(request, 204, PSTR("image/bmp"), "", 0); // No content + + return 404; // Not found +} + +void handleFileUpload(AsyncWebServerRequest* request, String filename, size_t index, uint8_t* data, size_t len, + bool final) +{ + if(request->url() != "/edit") { + return; + } + + if(!index) { // START + if(!httpIsAuthenticated(request, F("fileupload"))) return; + LOG_INFO(TAG_HTTP, F("Total size: %s"), request->headerName(0).c_str()); + if(!filename.startsWith("/")) { + filename = "/" + 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()); + } + } + + // DBG_OUTPUT_PORT.print("handleFileUpload Data: "); debugPrintln(upload.currentSize); + if(fsUploadFile) { + if(fsUploadFile.write(data, len) != len) { + LOG_ERROR(TAG_HTTP, F("Failed to write received data to file")); + } else { + webUploadProgress(); // Moved to httpEverySecond Loop + } + } + + if(final) { // END + if(fsUploadFile) { + LOG_INFO(TAG_HTTP, F("Uploaded %s (%u bytes)"), fsUploadFile.name(), index + len); + fsUploadFile.close(); + } + haspProgressVal(255); + + // Redirect to /config/hasp page. This flushes the web buffer and frees the memory + // request->sendHeader(String(F("Location")), String(F("/config/hasp")), true); + // request->send_P(302, PSTR("text/plain"), ""); + + AsyncWebServerResponse* response = request->beginResponse(302, "text/plain", ""); + response->addHeader("Location", String(F("/config/hasp"))); + request->send(response); + + // httpReconnect(); + } +} + +void handleFileDelete(AsyncWebServerRequest* request) +{ + if(!httpIsAuthenticated(request, F("filedelete"))) return; + + char mimetype[16]; + snprintf_P(mimetype, sizeof(mimetype), PSTR("text/plain")); + + if(request->args() == 0) { + return request->send_P(500, mimetype, PSTR("BAD ARGS")); + } + String path = request->arg((size_t)0); + LOG_TRACE(TAG_HTTP, F("handleFileDelete: %s"), path.c_str()); + if(path == "/") { + return request->send_P(500, mimetype, PSTR("BAD PATH")); + } + if(!HASP_FS.exists(path)) { + return request->send_P(404, mimetype, PSTR("FileNotFound")); + } + HASP_FS.remove(path); + request->send_P(200, mimetype, PSTR("")); + // path.clear(); +} + +void handleFileCreate(AsyncWebServerRequest* request) +{ + if(!httpIsAuthenticated(request, F("filecreate"))) return; + + if(request->args() == 0) { + return request->send(500, PSTR("text/plain"), PSTR("BAD ARGS")); + } + + if(request->hasArg(F("path"))) { + String path = request->arg(F("path")); + LOG_TRACE(TAG_HTTP, F("handleFileCreate: %s"), path.c_str()); + if(path == "/") { + return request->send(500, PSTR("text/plain"), PSTR("BAD PATH")); + } + if(HASP_FS.exists(path)) { + return request->send(500, PSTR("text/plain"), PSTR("FILE EXISTS")); + } + File file = HASP_FS.open(path, "w"); + if(file) { + file.close(); + } else { + return request->send(500, PSTR("text/plain"), PSTR("CREATE FAILED")); + } + } + if(request->hasArg(F("init"))) { + dispatch_idle(NULL, "0"); + hasp_init(); + } + if(request->hasArg(F("load"))) { + dispatch_idle(NULL, "0"); + hasp_load_json(); + } + if(request->hasArg(F("page"))) { + uint8_t pageid = atoi(request->arg(F("page")).c_str()); + dispatch_idle(NULL, "0"); + dispatch_set_page(pageid, LV_SCR_LOAD_ANIM_NONE); + } + request->send(200, PSTR("text/plain"), ""); +} + +void handleFileList(AsyncWebServerRequest* request) +{ + if(!httpIsAuthenticated(request, F("filelist"))) return; + + if(!request->hasArg(F("dir"))) { + request->send(500, PSTR("text/plain"), PSTR("BAD ARGS")); + return; + } + + String path = request->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((char*)0); + output.reserve(HTTP_PAGE_SIZE); + 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 += "]"; + request->send(200, PSTR("text/json"), output); +#elif defined(ARDUINO_ARCH_ESP8266) + Dir dir = HASP_FS.openDir(path); + String output((char*)0); + output.reserve(HTTP_PAGE_SIZE); + 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 += "]"; + request->send(200, PSTR("text/json"), output); +#endif +} +#endif + +//////////////////////////////////////////////////////////////////////////////////////////////////// +#if HASP_USE_CONFIG > 0 +void webHandleConfig(AsyncWebServerRequest* request) +{ // http://plate01/config + if(!httpIsAuthenticated(request, 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(request); + } +#endif + + { + String httpMessage((char*)0); + httpMessage.reserve(HTTP_PAGE_SIZE); + httpMessage += F("

"); + httpMessage += haspDevice.get_hostname(); + httpMessage += F("


"); + +#if HASP_USE_WIFI > 0 + add_form_button(httpMessage, F(D_HTTP_WIFI_SETTINGS), F("/config/wifi"), F("")); +#endif +#if HASP_USE_MQTT > 0 + add_form_button(httpMessage, F(D_HTTP_MQTT_SETTINGS), F("/config/mqtt"), F("")); +#endif + add_form_button(httpMessage, F(D_HTTP_HTTP_SETTINGS), F("/config/http"), F("")); + add_form_button(httpMessage, F(D_HTTP_GUI_SETTINGS), F("/config/gui"), F("")); + + // httpMessage += + // F("

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

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

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

"); + + httpMessage += FPSTR(MAIN_MENU_BUTTON); + + webSendPage(request, haspDevice.get_hostname(), httpMessage.c_str(), false); + // //webSendFooter(reponse);(httpMessage); + } + // httpMessage.clear(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +#if HASP_USE_MQTT > 0 +void webHandleMqttConfig(AsyncWebServerRequest* request) +{ // http://plate01/config/mqtt + if(!httpIsAuthenticated(request, 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 += haspDevice.get_hostname(); + 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_BACK_ICON D_HTTP_CONFIGURATION), F("/config"), F("")); + webSendPage(request, haspDevice.get_hostname(), httpMessage.c_str(), false); + // //webSendFooter(reponse);(httpMessage); + } + // httpMessage.clear(); +} +#endif + +//////////////////////////////////////////////////////////////////////////////////////////////////// +void webHandleGuiConfig(AsyncWebServerRequest* request) +{ // http://plate01/config/wifi + if(!httpIsAuthenticated(request, F("config/gui"))) return; + + { + StaticJsonDocument<256> settings; + guiGetConfig(settings.to()); + + String httpMessage((char*)0); + httpMessage.reserve(HTTP_PAGE_SIZE); + httpMessage += F("

"); + httpMessage += haspDevice.get_hostname(); + 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='cal' value='1'")); +#endif + + add_form_button(httpMessage, F(D_BACK_ICON D_HTTP_CONFIGURATION), F("/config"), F("")); + webSendPage(request, haspDevice.get_hostname(), httpMessage.c_str(), false); + // //webSendFooter(reponse);(httpMessage); + } + + if(request->hasArg(F("cal"))) dispatch_calibrate(NULL, NULL); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +#if HASP_USE_WIFI > 0 +void webHandleWifiConfig(AsyncWebServerRequest* request) +{ // http://plate01/config/wifi + if(!httpIsAuthenticated(request, F("config/wifi"))) return; + + StaticJsonDocument<256> settings; + wifiGetConfig(settings.to()); + + String httpMessage((char*)0); + httpMessage.reserve(HTTP_PAGE_SIZE); + httpMessage += F("

"); + httpMessage += haspDevice.get_hostname(); + 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_BACK_ICON D_HTTP_CONFIGURATION), F("/config"), F("")); + } +#endif + + webSendPage(request, haspDevice.get_hostname(), httpMessage.c_str(), false); + // //webSendFooter(reponse);(httpMessage); +// #if defined(STM32F4xx) +// httpMessage = ""; +// #else +// httpMessage.clear(); +// #endif +} +#endif + +//////////////////////////////////////////////////////////////////////////////////////////////////// +void webHandleHttpConfig(AsyncWebServerRequest* request) +{ // http://plate01/config/http + if(!httpIsAuthenticated(request, F("config/http"))) return; + + { + StaticJsonDocument<256> settings; + httpGetConfig(settings.to()); + + char httpMessage[HTTP_PAGE_SIZE]; + + size_t len = snprintf_P( + httpMessage, sizeof(httpMessage), + PSTR("

%s


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

" + "

"), + haspDevice.get_hostname(), 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(request, haspDevice.get_hostname(), httpMessage, false); + // //webSendFooter(reponse);(httpMessage); + } + // httpMessage.clear(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +#if HASP_USE_GPIO > 0 +void webHandleGpioConfig(AsyncWebServerRequest* request) +{ // http://plate01/config/gpio + if(!httpIsAuthenticated(request, F("config/gpio"))) return; + uint8_t configCount = 0; + + // StaticJsonDocument<256> settings; + // gpioGetConfig(settings.to()); + + if(request->hasArg(PSTR("save"))) { + uint8_t id = request->arg(F("id")).toInt(); + uint8_t pin = request->arg(F("pin")).toInt(); + uint8_t type = request->arg(F("type")).toInt(); + uint8_t group = request->arg(F("group")).toInt(); + uint8_t pinfunc = request->arg(F("func")).toInt(); + bool inverted = request->arg(F("state")).toInt(); + gpioSavePinConfig(id, pin, type, group, pinfunc, inverted); + } + if(request->hasArg(PSTR("del"))) { + uint8_t id = request->arg(F("id")).toInt(); + uint8_t pin = request->arg(F("pin")).toInt(); + gpioSavePinConfig(id, pin, hasp_gpio_type_t::FREE, 0, 0, false); + } + + { + String httpMessage((char*)0); + httpMessage.reserve(HTTP_PAGE_SIZE); + httpMessage += F("

"); + httpMessage += haspDevice.get_hostname(); + 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("
" D_GPIO_PIN "Type" D_GPIO_GROUP + "DefaultAction
"); + // httpMessage += halGpioName(gpio); + httpMessage += haspDevice.gpio_name(gpio).c_str(); + if(conf.type >= 0x80) { + httpMessage += F(""); + + switch(conf.type) { + + case hasp_gpio_type_t::BUTTON: + httpMessage += F(D_GPIO_BUTTON); + break; + case hasp_gpio_type_t::SWITCH: + httpMessage += F(D_GPIO_SWITCH); + break; + case hasp_gpio_type_t::DOOR: + httpMessage += F("door"); + break; + case hasp_gpio_type_t::GARAGE_DOOR: + httpMessage += F("garage_door"); + break; + case hasp_gpio_type_t::GAS: + httpMessage += F("gas"); + break; + case hasp_gpio_type_t::LIGHT: + httpMessage += F("light"); + break; + case hasp_gpio_type_t::LOCK: + httpMessage += F("lock"); + break; + case hasp_gpio_type_t::MOISTURE: + httpMessage += F("moisture"); + break; + case hasp_gpio_type_t::MOTION: + httpMessage += F("motion"); + break; + case hasp_gpio_type_t::OCCUPANCY: + httpMessage += F("occupancy"); + break; + case hasp_gpio_type_t::OPENING: + httpMessage += F("opening"); + break; + case hasp_gpio_type_t::PRESENCE: + httpMessage += F("presence"); + break; + case hasp_gpio_type_t::PROBLEM: + httpMessage += F("problem"); + break; + case hasp_gpio_type_t::SAFETY: + httpMessage += F("Safety"); + break; + case hasp_gpio_type_t::SMOKE: + httpMessage += F("Smoke"); + break; + case hasp_gpio_type_t::VIBRATION: + httpMessage += F("Vibration"); + break; + case hasp_gpio_type_t::WINDOW: + httpMessage += F("Window"); + break; + + case hasp_gpio_type_t::TOUCH: + httpMessage += F(D_GPIO_TOUCH); + break; + case hasp_gpio_type_t::LED: + httpMessage += F(D_GPIO_LED); + break; + case hasp_gpio_type_t::LED_R: + httpMessage += F(D_GPIO_LED_R); + break; + case hasp_gpio_type_t::LED_G: + httpMessage += F(D_GPIO_LED_G); + break; + case hasp_gpio_type_t::LED_B: + httpMessage += F(D_GPIO_LED_B); + break; + case hasp_gpio_type_t::LIGHT_RELAY: + httpMessage += F(D_GPIO_LIGHT_RELAY); + break; + case hasp_gpio_type_t::POWER_RELAY: + httpMessage += F(D_GPIO_POWER_RELAY); + break; + case hasp_gpio_type_t::SHUTTER_RELAY: + httpMessage += F("SHUTTER_RELAY"); + break; + case hasp_gpio_type_t::PWM: + httpMessage += F(D_GPIO_PWM); + break; + case hasp_gpio_type_t::DAC: + httpMessage += F(D_GPIO_DAC); + break; + +#if defined(LANBONL8) + // case hasp_gpio_type_t::SERIAL_DIMMER: + // httpMessage += F(D_GPIO_SERIAL_DIMMER); + // break; + case hasp_gpio_type_t::SERIAL_DIMMER_EU: + httpMessage += F("L8-HD (EU)"); + break; + case hasp_gpio_type_t::SERIAL_DIMMER_AU: + httpMessage += F("L8-HD (AU)"); + break; +#endif + default: + httpMessage += F(D_GPIO_UNKNOWN); + } + + httpMessage += F(""); + httpMessage += conf.group; + httpMessage += F(""); + httpMessage += (conf.inverted) ? F(D_GPIO_STATE_INVERTED) : F(D_GPIO_STATE_NORMAL); + + httpMessage += ("Delete
"); + + if(configCount < HASP_NUM_GPIO_CONFIG) { + httpMessage += F("

"); + httpMessage += F("

"); + + httpMessage += F("

"); + httpMessage += F("

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

"); + + webSendPage(request, haspDevice.get_hostname(), httpMessage.c_str(), false); + // //webSendFooter(reponse);(httpMessage); + } + // httpMessage.clear(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +void webHandleGpioOutput(AsyncWebServerRequest* request) +{ // http://plate01/config/gpio/options + if(!httpIsAuthenticated(request, F("config/gpio/options"))) return; + + StaticJsonDocument<256> settings; + guiGetConfig(settings.to()); + + uint8_t config_id = request->arg(F("id")).toInt(); + + String httpMessage((char*)0); + httpMessage.reserve(HTTP_PAGE_SIZE); + httpMessage += F("

"); + httpMessage += haspDevice.get_hostname(); + httpMessage += F("


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

GPIO Output

"); + + httpMessage += F("

" D_GPIO_PIN "

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

Type

"); + + httpMessage += F("

" D_GPIO_GROUP "

"); + + httpMessage += F("

Value

"); + + httpMessage += F("

"); + + httpMessage += PSTR("

"); + + webSendPage(request, haspDevice.get_hostname(), httpMessage.c_str(), false); + // //webSendFooter(reponse);(httpMessage); + + // if(request->hasArg(F("action"))) dispatch_text_line(request->arg(F("action")).c_str()); // Security check +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +void webHandleGpioInput(AsyncWebServerRequest* request) +{ // http://plate01/config/gpio/options + if(!httpIsAuthenticated(request, F("config/gpio/input"))) return; + { + StaticJsonDocument<256> settings; + guiGetConfig(settings.to()); + + uint8_t config_id = request->arg(F("id")).toInt(); + + String httpMessage((char*)0); + httpMessage.reserve(HTTP_PAGE_SIZE); + httpMessage += F("

"); + httpMessage += haspDevice.get_hostname(); + httpMessage += F("


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

GPIO Input

"); + + httpMessage += F("

" D_GPIO_PIN "

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

Type

"); + + httpMessage += F("

" D_GPIO_GROUP "

"); + + httpMessage += F("

Default State

"); + + httpMessage += F("

Resistor

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

"); + + httpMessage += PSTR("

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

"); + httpMessage += haspDevice.get_hostname(); + 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 Hostname (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_BACK_ICON D_HTTP_CONFIGURATION), F("/config"), F("")); + // httpMessage += PSTR("

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

"); + httpMessage += haspDevice.get_hostname(); + 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(request, haspDevice.get_hostname(), httpMessage.c_str(), false); + // //webSendFooter(reponse);(httpMessage); + } + // httpMessage.clear(); +} +#endif // HASP_USE_CONFIG + +//////////////////////////////////////////////////////////////////////////////////////////////////// +void httpHandleNotFound(AsyncWebServerRequest* request) +{ // webServer 404 +#if HASP_USE_SPIFFS > 0 || HASP_USE_LITTLEFS > 0 + int statuscode = handleFileRead(request, request->url()); +#else + int statuscode = 404; +#endif + +#if defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_ESP8266) + LOG_TRACE(TAG_HTTP, F("Sending %d %s to client connected from: %s"), statuscode, request->url().c_str(), + request->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 + + if(statuscode < 300) return; // OK + + String httpMessage((char*)0); + httpMessage.reserve(HTTP_PAGE_SIZE); + + if(statuscode == 500) + httpMessage += F("Internal Server Error"); + else + httpMessage += F(D_FILE_NOT_FOUND); + + httpMessage += F("\n\nURI: "); + httpMessage += request->url(); + httpMessage += F("\nMethod: "); + httpMessage += (request->method() == HTTP_GET) ? F("GET") : F("POST"); + httpMessage += F("\nArguments: "); + httpMessage += request->args(); + httpMessage += "\n"; + for(int i = 0; i < request->args(); i++) { + httpMessage += " " + request->argName(i) + ": " + request->arg(i) + "\n"; + } + request->send(statuscode, PSTR("text/plain"), httpMessage.c_str()); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +void webHandleFirmware(AsyncWebServerRequest* request) +{ + if(!httpIsAuthenticated(request, F("firmware"))) return; + + { + String httpMessage((char*)0); + httpMessage.reserve(HTTP_PAGE_SIZE); + httpMessage += F("

"); + httpMessage += haspDevice.get_hostname(); + httpMessage += F("


"); + + httpMessage += F("

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

"); + + httpMessage += F("Firmware   " + "Filesystem"); + + add_button(httpMessage, F(D_HTTP_UPDATE_FIRMWARE), F("")); + httpMessage += F("

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

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

"); + + httpMessage += F("
"); + httpMessage += F("
Update ESP from URL"); + httpMessage += F("


"); + + httpMessage += FPSTR(MAIN_MENU_BUTTON); + + webSendPage(request, haspDevice.get_hostname(), httpMessage.c_str(), false); + // //webSendFooter(reponse);(httpMessage); + } + // httpMessage.clear(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +void httpHandleEspFirmware(AsyncWebServerRequest* request) +{ // http://plate01/espfirmware + char url[4]; + memcpy_P(url, PSTR("url"), 4); + + if(!httpIsAuthenticated(request, F("espfirmware"))) return; + + { + String httpMessage((char*)0); + httpMessage.reserve(HTTP_PAGE_SIZE); + httpMessage += F("

"); + httpMessage += haspDevice.get_hostname(); + httpMessage += F("


"); + + httpMessage += F("

ESP update

Updating ESP firmware from: "); + httpMessage += request->arg(url); + + webSendPage(request, haspDevice.get_hostname(), httpMessage.c_str(), true); + // //webSendFooter(reponse);(httpMessage); + // httpMessage.clear(); + } + + LOG_TRACE(TAG_HTTP, F("Updating ESP firmware from: %s"), request->arg(url).c_str()); + dispatch_web_update(NULL, request->arg(url).c_str()); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +#if HASP_USE_CONFIG > 0 +void webHandleSaveConfig(AsyncWebServerRequest* request) +{ + if(!httpIsAuthenticated(request, F("saveConfig"))) return; + configWrite(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +void httpHandleResetConfig(AsyncWebServerRequest* request) +{ // http://plate01/resetConfig + if(!httpIsAuthenticated(request, F("resetConfig"))) return; + + bool resetConfirmed = request->arg(F("confirm")) == F("yes"); + + { + String httpMessage((char*)0); + httpMessage.reserve(HTTP_PAGE_SIZE); + httpMessage += F("

"); + httpMessage += haspDevice.get_hostname(); + 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_button(httpMessage, F(D_HTTP_ERASE_DEVICE), F("name='confirm' value='yes'")); + close_form(httpMessage); + + add_form_button(httpMessage, F(D_BACK_ICON D_HTTP_CONFIGURATION), F("/config"), F("")); + } + + webSendPage(request, haspDevice.get_hostname(), httpMessage.c_str(), resetConfirmed); + // //webSendFooter(reponse);(httpMessage); + } + // httpMessage.clear(); + + 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.end(); + 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(("/about"), webHandleAbout); + webServer.on(("/css"), [](AsyncWebServerRequest* request) { request->send_P(200, PSTR("text/css"), HTTP_CSS); }); + webServer.onNotFound(httpHandleNotFound); + +#if HASP_USE_WIFI > 0 + + +#if !defined(STM32F4xx) + +#if HASP_USE_CONFIG > 0 + if(WiFi.getMode() != WIFI_STA) { + webServer.on(("/"), webHandleWifiConfig); + LOG_TRACE(TAG_HTTP, F("Wifi access point")); + return; + } + +#endif // HASP_USE_CONFIG +#endif // !STM32F4xx +#endif // HASP_USE_WIFI + + // The following endpoints are only needed in STA mode + webServer.on(("/page/"), [](AsyncWebServerRequest* request) { + String pageid = request->arg(F("page")); + request->send(200, PSTR("text/plain"), "Page: '" + pageid + "'"); + dispatch_set_page(pageid.toInt(), LV_SCR_LOAD_ANIM_NONE); + }); + +#if HASP_USE_SPIFFS > 0 || HASP_USE_LITTLEFS > 0 + webServer.on(("/list"), HTTP_GET, handleFileList); + // load editor + webServer.on(("/edit"), HTTP_GET, [](AsyncWebServerRequest* request) { + if(handleFileRead(request, "/edit.htm") != 200) { + char mimetype[16]; + snprintf_P(mimetype, sizeof(mimetype), PSTR("text/plain")); + request->send_P(404, mimetype, PSTR("FileNotFound")); + } + }); + webServer.on(("/edit"), HTTP_PUT, handleFileCreate); + webServer.on(("/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(("/edit"), HTTP_POST, + [](AsyncWebServerRequest* request) { + request->send(200); + LOG_VERBOSE(TAG_HTTP, F("Headers: %d"), request->headers()); + }, + handleFileUpload); +#endif + + webServer.on(("/"), webHandleRoot); + webServer.on(("/info"), webHandleInfoJson); + // webServer.on(F("/info"), webHandleInfo); + webServer.on(("/screenshot"), webHandleScreenshot); + webServer.on(("/firmware"), webHandleFirmware); + webServer.on(("/reboot"), httpHandleReboot); + +#if HASP_USE_CONFIG > 0 + webServer.on(("/config/hasp"), webHandleHaspConfig); + webServer.on(("/config/http"), webHandleHttpConfig); + webServer.on(("/config/gui"), webHandleGuiConfig); + webServer.on(("/config/debug"), webHandleDebugConfig); +#if HASP_USE_MQTT > 0 + webServer.on(("/config/mqtt"), webHandleMqttConfig); +#endif +#if HASP_USE_WIFI > 0 + webServer.on(("/config/wifi"), webHandleWifiConfig); +#endif +#if HASP_USE_GPIO > 0 + webServer.on(("/config/gpio/options"), webHandleGpioOutput); + webServer.on(("/config/gpio/input"), webHandleGpioInput); + webServer.on(("/config/gpio"), webHandleGpioConfig); +#endif + webServer.on(("/saveConfig"), webHandleSaveConfig); + webServer.on(("/resetConfig"), httpHandleResetConfig); +#endif // HASP_USE_CONFIG + +#if defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_ESP8266) + webServer.on(("/update"), HTTP_POST, [](AsyncWebServerRequest* request) { request->send(200); }, + webHandleFirmwareUpload); + webServer.on(("/espfirmware"), httpHandleEspFirmware); +#endif + + // These two endpoints are needed in STA and AP mode + webServer.on(("/config"), webHandleConfig); + + 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(); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +IRAM_ATTR void httpLoop(void) +{} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +void httpEvery5Seconds() +{} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +void httpEverySecond() +{ + ws.cleanupClients(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +#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 diff --git a/src/sys/svc/hasp_telnet.cpp b/src/sys/svc/hasp_telnet.cpp index 424ac4d2..0eb58d34 100644 --- a/src/sys/svc/hasp_telnet.cpp +++ b/src/sys/svc/hasp_telnet.cpp @@ -29,7 +29,7 @@ EthernetClient telnetClient; static EthernetServer telnetServer(23); #endif -#if HASP_USE_HTTP > 0 +#if HASP_USE_HTTP > 0 || HASP_USE_HTTP_ASYNC > 0 extern hasp_http_config_t http_config; #endif @@ -48,9 +48,10 @@ void telnet_update_prompt() bufferedTelnetClient.flush(); } -void telnetStop(void) +static void telnetClientDisconnect() { - LOG_TRACE(TAG_TELN, F(D_TELNET_CLOSING_CONNECTION), telnetClient.remoteIP().toString().c_str()); + if(telnetClient.connected()) + LOG_TRACE(TAG_TELN, F(D_TELNET_CLOSING_CONNECTION), telnetClient.remoteIP().toString().c_str()); Log.unregisterOutput(1); // telnetClient telnetClient.stop(); @@ -60,9 +61,11 @@ void telnetStop(void) telnetConsole = NULL; } -static inline void telnetClientDisconnect() +void telnetStop(void) { - telnetStop(); + telnetClientDisconnect(); + delete telnetServer; + telnetServer = NULL; } void telnetClientLogon() @@ -99,7 +102,7 @@ void telnetAcceptClient() // telnetClient.print((char)0xFD); // telnetClient.print((char)0x1B); -#if HASP_USE_HTTP > 0 +#if HASP_USE_HTTP > 0 || HASP_USE_HTTP_ASYNC > 0 if(strlen(http_config.user) != 0 || strlen(http_config.password) != 0) { telnetClient.println(F("\r\n" D_USERNAME " ")); telnetLoginState = TELNET_UNAUTHENTICATED; @@ -119,7 +122,7 @@ static inline void telnetProcessLine() switch(telnetLoginState) { case TELNET_UNAUTHENTICATED: { telnetClient.printf(PSTR(D_PASSWORD" %c%c%c"), 0xFF, 0xFB, 0x01); // Hide characters -#if HASP_USE_HTTP > 0 +#if HASP_USE_HTTP > 0 || HASP_USE_HTTP_ASYNC > 0 telnetLoginState = strcmp(telnetInputBuffer, http_config.user) == 0 ? TELNET_USERNAME_OK : TELNET_USERNAME_NOK; break; } @@ -205,7 +208,7 @@ static void telnetProcessLine(const char* input) snprintf_P(buffer, sizeof(buffer), PSTR(D_PASSWORD " %c%c%c\n"), 0xFF, 0xFB, 0x01); // Hide characters telnetClient.print(buffer); -#if HASP_USE_HTTP > 0 +#if HASP_USE_HTTP > 0 || HASP_USE_HTTP_ASYNC > 0 telnetLoginState = strcmp(input, http_config.user) == 0 ? TELNET_USERNAME_OK : TELNET_USERNAME_NOK; break; } @@ -235,7 +238,7 @@ static void telnetProcessLine(const char* input) strcasecmp_P(input, PSTR("bye")) == 0) { telnetClientDisconnect(); } else if(strcasecmp_P(input, PSTR("logoff")) == 0) { -#if HASP_USE_HTTP > 0 +#if HASP_USE_HTTP > 0 || HASP_USE_HTTP_ASYNC > 0 if(strcmp(input, http_config.password) == 0) { telnetClient.println(F("\r\n" D_USERNAME " ")); telnetLoginState = TELNET_UNAUTHENTICATED; @@ -250,7 +253,7 @@ static void telnetProcessLine(const char* input) } } -void telnetSetup() +void telnetStart() { // telnetSetConfig(settings); @@ -283,6 +286,9 @@ void telnetSetup() } } +void telnetSetup() +{telnetStart();} + IRAM_ATTR void telnetLoop() { // Basic telnet client handling code from: https://gist.github.com/tablatronix/4793677ca748f5f584c95ec4a2b10303