From e4fdf26dc750d3b81f9024265fe783af478b1ae5 Mon Sep 17 00:00:00 2001 From: Jm Date: Tue, 19 Jan 2021 21:26:23 -0800 Subject: [PATCH 1/8] #649 - First pass on the refactoring of the webserver --- src/mesh/http/ContentHandler.cpp | 930 +++++++++++++++++++++++++++++++ src/mesh/http/ContentHandler.h | 46 ++ src/mesh/http/ContentHelper.h | 2 +- src/mesh/http/WebServer.cpp | 906 +----------------------------- src/mesh/http/WebServer.h | 21 - 5 files changed, 986 insertions(+), 919 deletions(-) create mode 100644 src/mesh/http/ContentHandler.cpp create mode 100644 src/mesh/http/ContentHandler.h diff --git a/src/mesh/http/ContentHandler.cpp b/src/mesh/http/ContentHandler.cpp new file mode 100644 index 000000000..df47776b2 --- /dev/null +++ b/src/mesh/http/ContentHandler.cpp @@ -0,0 +1,930 @@ +#include "NodeDB.h" +#include "PowerFSM.h" +#include "airtime.h" +#include "mesh/http/ContentHelper.h" +#include "mesh/http/ContentStatic.h" +#include "mesh/http/WiFiAPClient.h" +#include "power.h" +#include +#include +#include +#include + +#ifndef NO_ESP32 +#include "esp_task_wdt.h" +#endif + +/* + Including the esp32_https_server library will trigger a compile time error. I've + tracked it down to a reoccurrance of this bug: + https://gcc.gnu.org/bugzilla/show_bug.cgi?id=57824 + The work around is described here: + https://forums.xilinx.com/t5/Embedded-Development-Tools/Error-with-Standard-Libaries-in-Zynq/td-p/450032 + + Long story short is we need "#undef str" before including the esp32_https_server. + - Jm Casler (jm@casler.org) Oct 2020 +*/ +#undef str + +// Includes for the https server +// https://github.com/fhessel/esp32_https_server +#include +#include +#include +#include +#include + +// The HTTPS Server comes in a separate namespace. For easier use, include it here. +using namespace httpsserver; + +#include "mesh/http/ContentHandler.h" + +// We need to specify some content-type mapping, so the resources get delivered with the +// right content type and are displayed correctly in the browser +char contentTypes[][2][32] = {{".txt", "text/plain"}, {".html", "text/html"}, + {".js", "text/javascript"}, {".png", "image/png"}, + {".jpg", "image/jpg"}, {".gz", "application/gzip"}, + {".gif", "image/gif"}, {".json", "application/json"}, + {".css", "text/css"}, {".ico", "image/vnd.microsoft.icon"}, + {".svg", "image/svg+xml"}, {"", ""}}; + +// Our API to handle messages to and from the radio. +HttpAPI webAPI; + +uint32_t numberOfRequests = 0; +uint32_t timeSpeedUp = 0; + +uint32_t getTimeSpeedUp() +{ + return timeSpeedUp; +} + +void setTimeSpeedUp() +{ + timeSpeedUp = millis(); +} + +void registerHandlers(HTTPServer *insecureServer, HTTPSServer *secureServer) +{ + + // For every resource available on the server, we need to create a ResourceNode + // The ResourceNode links URL and HTTP method to a handler function + + ResourceNode *nodeAPIv1ToRadioOptions = new ResourceNode("/api/v1/toradio", "OPTIONS", &handleAPIv1ToRadio); + ResourceNode *nodeAPIv1ToRadio = new ResourceNode("/api/v1/toradio", "PUT", &handleAPIv1ToRadio); + ResourceNode *nodeAPIv1FromRadio = new ResourceNode("/api/v1/fromradio", "GET", &handleAPIv1FromRadio); + + ResourceNode *nodeHotspot = new ResourceNode("/hotspot-detect.html", "GET", &handleHotspot); + ResourceNode *nodeFavicon = new ResourceNode("/favicon.ico", "GET", &handleFavicon); + ResourceNode *nodeRoot = new ResourceNode("/", "GET", &handleRoot); + ResourceNode *nodeStaticBrowse = new ResourceNode("/static", "GET", &handleStaticBrowse); + ResourceNode *nodeStaticPOST = new ResourceNode("/static", "POST", &handleStaticPost); + ResourceNode *nodeStatic = new ResourceNode("/static/*", "GET", &handleStatic); + ResourceNode *nodeRestart = new ResourceNode("/restart", "POST", &handleRestart); + ResourceNode *node404 = new ResourceNode("", "GET", &handle404); + ResourceNode *nodeFormUpload = new ResourceNode("/upload", "POST", &handleFormUpload); + ResourceNode *nodeJsonScanNetworks = new ResourceNode("/json/scanNetworks", "GET", &handleScanNetworks); + ResourceNode *nodeJsonBlinkLED = new ResourceNode("/json/blink", "POST", &handleBlinkLED); + ResourceNode *nodeJsonReport = new ResourceNode("/json/report", "GET", &handleReport); + ResourceNode *nodeJsonSpiffsBrowseStatic = new ResourceNode("/json/spiffs/browse/static/", "GET", &handleSpiffsBrowseStatic); + ResourceNode *nodeJsonDelete = new ResourceNode("/json/spiffs/delete/static", "DELETE", &handleSpiffsDeleteStatic); + + // Secure nodes + secureServer->registerNode(nodeAPIv1ToRadioOptions); + secureServer->registerNode(nodeAPIv1ToRadio); + secureServer->registerNode(nodeAPIv1FromRadio); + secureServer->registerNode(nodeHotspot); + secureServer->registerNode(nodeFavicon); + secureServer->registerNode(nodeRoot); + secureServer->registerNode(nodeStaticBrowse); + secureServer->registerNode(nodeStaticPOST); + secureServer->registerNode(nodeStatic); + secureServer->registerNode(nodeRestart); + secureServer->registerNode(nodeFormUpload); + secureServer->registerNode(nodeJsonScanNetworks); + secureServer->registerNode(nodeJsonBlinkLED); + secureServer->registerNode(nodeJsonSpiffsBrowseStatic); + secureServer->registerNode(nodeJsonDelete); + secureServer->registerNode(nodeJsonReport); + secureServer->setDefaultNode(node404); + + secureServer->addMiddleware(&middlewareSpeedUp240); + + // Insecure nodes + insecureServer->registerNode(nodeAPIv1ToRadioOptions); + insecureServer->registerNode(nodeAPIv1ToRadio); + insecureServer->registerNode(nodeAPIv1FromRadio); + insecureServer->registerNode(nodeHotspot); + insecureServer->registerNode(nodeFavicon); + insecureServer->registerNode(nodeRoot); + insecureServer->registerNode(nodeStaticBrowse); + insecureServer->registerNode(nodeStaticPOST); + insecureServer->registerNode(nodeStatic); + insecureServer->registerNode(nodeRestart); + insecureServer->registerNode(nodeFormUpload); + insecureServer->registerNode(nodeJsonScanNetworks); + insecureServer->registerNode(nodeJsonBlinkLED); + insecureServer->registerNode(nodeJsonSpiffsBrowseStatic); + insecureServer->registerNode(nodeJsonDelete); + insecureServer->registerNode(nodeJsonReport); + insecureServer->setDefaultNode(node404); + + insecureServer->addMiddleware(&middlewareSpeedUp160); +} + +void middlewareSpeedUp240(HTTPRequest *req, HTTPResponse *res, std::function next) +{ + // We want to print the response status, so we need to call next() first. + next(); + + // Phone (or other device) has contacted us over WiFi. Keep the radio turned on. + // TODO: This should go into its own middleware layer separate from the speedup. + powerFSM.trigger(EVENT_CONTACT_FROM_PHONE); + + setCpuFrequencyMhz(240); + setTimeSpeedUp(); + + numberOfRequests++; +} + +void middlewareSpeedUp160(HTTPRequest *req, HTTPResponse *res, std::function next) +{ + // We want to print the response status, so we need to call next() first. + next(); + + // Phone (or other device) has contacted us over WiFi. Keep the radio turned on. + // TODO: This should go into its own middleware layer separate from the speedup. + powerFSM.trigger(EVENT_CONTACT_FROM_PHONE); + + // If the frequency is 240mhz, we have recently gotten a HTTPS request. + // In that case, leave the frequency where it is and just update the + // countdown timer (timeSpeedUp). + if (getCpuFrequencyMhz() != 240) { + setCpuFrequencyMhz(160); + } + setTimeSpeedUp(); + + numberOfRequests++; +} + +void handleAPIv1FromRadio(HTTPRequest *req, HTTPResponse *res) +{ + + DEBUG_MSG("+++++++++++++++ webAPI handleAPIv1FromRadio\n"); + + /* + For documentation, see: + https://github.com/meshtastic/Meshtastic-device/wiki/HTTP-REST-API-discussion + https://github.com/meshtastic/Meshtastic-device/blob/master/docs/software/device-api.md + + Example: + http://10.10.30.198/api/v1/fromradio + */ + + // Get access to the parameters + ResourceParameters *params = req->getParams(); + + // std::string paramAll = "all"; + std::string valueAll; + + // Status code is 200 OK by default. + res->setHeader("Content-Type", "application/x-protobuf"); + res->setHeader("Access-Control-Allow-Origin", "*"); + res->setHeader("Access-Control-Allow-Methods", "PUT, GET"); + res->setHeader("X-Protobuf-Schema", "https://raw.githubusercontent.com/meshtastic/Meshtastic-protobufs/master/mesh.proto"); + + uint8_t txBuf[MAX_STREAM_BUF_SIZE]; + uint32_t len = 1; + + if (params->getQueryParameter("all", valueAll)) { + + // If all is ture, return all the buffers we have available + // to us at this point in time. + if (valueAll == "true") { + while (len) { + len = webAPI.getFromRadio(txBuf); + res->write(txBuf, len); + } + + // Otherwise, just return one protobuf + } else { + len = webAPI.getFromRadio(txBuf); + res->write(txBuf, len); + } + + // the param "all" was not spcified. Return just one protobuf + } else { + len = webAPI.getFromRadio(txBuf); + res->write(txBuf, len); + } + + DEBUG_MSG("--------------- webAPI handleAPIv1FromRadio, len %d\n", len); +} + +void handleAPIv1ToRadio(HTTPRequest *req, HTTPResponse *res) +{ + DEBUG_MSG("+++++++++++++++ webAPI handleAPIv1ToRadio\n"); + + /* + For documentation, see: + https://github.com/meshtastic/Meshtastic-device/wiki/HTTP-REST-API-discussion + https://github.com/meshtastic/Meshtastic-device/blob/master/docs/software/device-api.md + + Example: + http://10.10.30.198/api/v1/toradio + */ + + // Status code is 200 OK by default. + + res->setHeader("Content-Type", "application/x-protobuf"); + res->setHeader("Access-Control-Allow-Headers", "Content-Type"); + res->setHeader("Access-Control-Allow-Origin", "*"); + res->setHeader("Access-Control-Allow-Methods", "PUT, OPTIONS"); + res->setHeader("X-Protobuf-Schema", "https://raw.githubusercontent.com/meshtastic/Meshtastic-protobufs/master/mesh.proto"); + + if (req->getMethod() == "OPTIONS") { + res->setStatusCode(204); // Success with no content + res->print(""); + return; + } + + byte buffer[MAX_TO_FROM_RADIO_SIZE]; + size_t s = req->readBytes(buffer, MAX_TO_FROM_RADIO_SIZE); + + DEBUG_MSG("Received %d bytes from PUT request\n", s); + webAPI.handleToRadio(buffer, s); + + res->write(buffer, s); + DEBUG_MSG("--------------- webAPI handleAPIv1ToRadio\n"); +} + +void handleFavicon(HTTPRequest *req, HTTPResponse *res) +{ + // Set Content-Type + res->setHeader("Content-Type", "image/vnd.microsoft.icon"); + // Write data from header file + res->write(FAVICON_DATA, FAVICON_LENGTH); +} + +void handleStaticPost(HTTPRequest *req, HTTPResponse *res) +{ + // Assume POST request. Contains submitted data. + res->println("File Edited

File Edited

"); + + // The form is submitted with the x-www-form-urlencoded content type, so we need the + // HTTPURLEncodedBodyParser to read the fields. + // Note that the content of the file's content comes from a
"); + res->println(""); + res->println(""); + res->println(""); + + return; + } + + res->println("

Upload new file

"); + res->println("

This form allows you to upload files. Keep your filenames small and files under 200k.

"); + res->println("
"); + res->println("file:
"); + res->println(""); + res->println("
"); + + res->println("

All Files

"); + + File root = SPIFFS.open("/"); + if (root.isDirectory()) { + res->println(""); + + res->println(""); + res->println(""); + res->println(""); + res->println(""); + res->println(""); + res->println(""); + + File file = root.openNextFile(); + while (file) { + String filePath = String(file.name()); + if (filePath.indexOf("/static") == 0) { + res->println(""); + res->println(""); + res->println(""); + res->println(""); + res->println(""); + res->println(""); + } + + file = root.openNextFile(); + } + res->println("
File"); + res->println("Size"); + res->println("Actions"); + res->println("
"); + + if (String(file.name()).substring(1).endsWith(".gz")) { + String modifiedFile = String(file.name()).substring(1); + modifiedFile.remove((modifiedFile.length() - 3), 3); + res->print("" + String(file.name()).substring(1) + ""); + } else { + res->print("" + String(file.name()).substring(1) + + ""); + } + res->println(""); + res->print(String(file.size())); + res->println(""); + res->print("Delete "); + res->println(""); + if (!String(file.name()).substring(1).endsWith(".gz")) { + res->print("Edit"); + } + res->println("
"); + + res->print("
"); + // res->print("Total : " + String(SPIFFS.totalBytes()) + " Bytes
"); + res->print("Used : " + String(SPIFFS.usedBytes()) + " Bytes
"); + res->print("Free : " + String(SPIFFS.totalBytes() - SPIFFS.usedBytes()) + " Bytes
"); + } +} + +void handleStatic(HTTPRequest *req, HTTPResponse *res) +{ + // Get access to the parameters + ResourceParameters *params = req->getParams(); + + std::string parameter1; + // Print the first parameter value + if (params->getPathParameter(0, parameter1)) { + + std::string filename = "/static/" + parameter1; + std::string filenameGzip = "/static/" + parameter1 + ".gz"; + + if (!SPIFFS.exists(filename.c_str()) && !SPIFFS.exists(filenameGzip.c_str())) { + // Send "404 Not Found" as response, as the file doesn't seem to exist + res->setStatusCode(404); + res->setStatusText("Not found"); + res->println("404 Not Found"); + res->printf("

File not found: %s

\n", filename.c_str()); + return; + } + + // Try to open the file from SPIFFS + File file; + + if (SPIFFS.exists(filename.c_str())) { + file = SPIFFS.open(filename.c_str()); + if (!file.available()) { + DEBUG_MSG("File not available - %s\n", filename.c_str()); + } + + } else if (SPIFFS.exists(filenameGzip.c_str())) { + file = SPIFFS.open(filenameGzip.c_str()); + res->setHeader("Content-Encoding", "gzip"); + if (!file.available()) { + DEBUG_MSG("File not available\n"); + } + } + + res->setHeader("Content-Length", httpsserver::intToString(file.size())); + + bool has_set_content_type = false; + // Content-Type is guessed using the definition of the contentTypes-table defined above + int cTypeIdx = 0; + do { + if (filename.rfind(contentTypes[cTypeIdx][0]) != std::string::npos) { + res->setHeader("Content-Type", contentTypes[cTypeIdx][1]); + has_set_content_type = true; + break; + } + cTypeIdx += 1; + } while (strlen(contentTypes[cTypeIdx][0]) > 0); + + if (!has_set_content_type) { + // Set a default content type + res->setHeader("Content-Type", "application/octet-stream"); + } + + // Read the file from SPIFFS and write it to the HTTP response body + size_t length = 0; + do { + char buffer[256]; + length = file.read((uint8_t *)buffer, 256); + std::string bufferString(buffer, length); + res->write((uint8_t *)bufferString.c_str(), bufferString.size()); + } while (length > 0); + + file.close(); + + return; + + } else { + res->println("ERROR: This should not have happened..."); + } +} + +void handleFormUpload(HTTPRequest *req, HTTPResponse *res) +{ + + DEBUG_MSG("Form Upload - Disabling keep-alive\n"); + res->setHeader("Connection", "close"); + + DEBUG_MSG("Form Upload - Set frequency to 240mhz\n"); + // The upload process is very CPU intensive. Let's speed things up a bit. + setCpuFrequencyMhz(240); + + // First, we need to check the encoding of the form that we have received. + // The browser will set the Content-Type request header, so we can use it for that purpose. + // Then we select the body parser based on the encoding. + // Actually we do this only for documentary purposes, we know the form is going + // to be multipart/form-data. + DEBUG_MSG("Form Upload - Creating body parser reference\n"); + HTTPBodyParser *parser; + std::string contentType = req->getHeader("Content-Type"); + + // The content type may have additional properties after a semicolon, for exampel: + // Content-Type: text/html;charset=utf-8 + // Content-Type: multipart/form-data;boundary=------s0m3w31rdch4r4c73rs + // As we're interested only in the actual mime _type_, we strip everything after the + // first semicolon, if one exists: + size_t semicolonPos = contentType.find(";"); + if (semicolonPos != std::string::npos) { + contentType = contentType.substr(0, semicolonPos); + } + + // Now, we can decide based on the content type: + if (contentType == "multipart/form-data") { + DEBUG_MSG("Form Upload - multipart/form-data\n"); + parser = new HTTPMultipartBodyParser(req); + } else { + Serial.printf("Unknown POST Content-Type: %s\n", contentType.c_str()); + return; + } + + res->println("File " + "Upload

File Upload

"); + + // We iterate over the fields. Any field with a filename is uploaded. + // Note that the BodyParser consumes the request body, meaning that you can iterate over the request's + // fields only a single time. The reason for this is that it allows you to handle large requests + // which would not fit into memory. + bool didwrite = false; + + // parser->nextField() will move the parser to the next field in the request body (field meaning a + // form field, if you take the HTML perspective). After the last field has been processed, nextField() + // returns false and the while loop ends. + while (parser->nextField()) { + // For Multipart data, each field has three properties: + // The name ("name" value of the tag) + // The filename (If it was a , this is the filename on the machine of the + // user uploading it) + // The mime type (It is determined by the client. So do not trust this value and blindly start + // parsing files only if the type matches) + std::string name = parser->getFieldName(); + std::string filename = parser->getFieldFilename(); + std::string mimeType = parser->getFieldMimeType(); + // We log all three values, so that you can observe the upload on the serial monitor: + DEBUG_MSG("handleFormUpload: field name='%s', filename='%s', mimetype='%s'\n", name.c_str(), filename.c_str(), + mimeType.c_str()); + + // Double check that it is what we expect + if (name != "file") { + DEBUG_MSG("Skipping unexpected field\n"); + res->println("

No file found.

"); + return; + } + + // Double check that it is what we expect + if (filename == "") { + DEBUG_MSG("Skipping unexpected field\n"); + res->println("

No file found.

"); + return; + } + + // SPIFFS limits the total lenth of a path + file to 31 characters. + if (filename.length() + 8 > 31) { + DEBUG_MSG("Uploaded filename too long!\n"); + res->println("

Uploaded filename too long! Limit of 23 characters.

"); + delete parser; + return; + } + + // You should check file name validity and all that, but we skip that to make the core + // concepts of the body parser functionality easier to understand. + std::string pathname = "/static/" + filename; + + // Create a new file on spiffs to stream the data into + File file = SPIFFS.open(pathname.c_str(), "w"); + size_t fileLength = 0; + didwrite = true; + + // With endOfField you can check whether the end of field has been reached or if there's + // still data pending. With multipart bodies, you cannot know the field size in advance. + while (!parser->endOfField()) { + esp_task_wdt_reset(); + + byte buf[512]; + size_t readLength = parser->read(buf, 512); + // DEBUG_MSG("\n\nreadLength - %i\n", readLength); + + // Abort the transfer if there is less than 50k space left on the filesystem. + if (SPIFFS.totalBytes() - SPIFFS.usedBytes() < 51200) { + file.close(); + res->println("

Write aborted! Reserving 50k on filesystem.

"); + + // enableLoopWDT(); + + delete parser; + return; + } + + // if (readLength) { + file.write(buf, readLength); + fileLength += readLength; + DEBUG_MSG("File Length %i\n", fileLength); + //} + } + // enableLoopWDT(); + + file.close(); + res->printf("

Saved %d bytes to %s

", (int)fileLength, pathname.c_str()); + } + if (!didwrite) { + res->println("

Did not write any file

"); + } + res->println(""); + delete parser; +} + +void handleReport(HTTPRequest *req, HTTPResponse *res) +{ + + ResourceParameters *params = req->getParams(); + std::string content; + + if (!params->getQueryParameter("content", content)) { + content = "json"; + } + + if (content == "json") { + res->setHeader("Content-Type", "application/json"); + + } else { + res->setHeader("Content-Type", "text/html"); + res->println("
");
+    }
+
+    res->println("{");
+
+    res->println("\"data\": {");
+
+    res->println("\"airtime\": {");
+
+    uint32_t *logArray;
+
+    res->print("\"tx_log\": [");
+
+    logArray = airtimeReport(TX_LOG);
+    for (int i = 0; i < getPeriodsToLog(); i++) {
+        uint32_t tmp;
+        tmp = *(logArray + i);
+        res->printf("%d", tmp);
+        if (i != getPeriodsToLog() - 1) {
+            res->print(", ");
+        }
+    }
+
+    res->println("],");
+    res->print("\"rx_log\": [");
+
+    logArray = airtimeReport(RX_LOG);
+    for (int i = 0; i < getPeriodsToLog(); i++) {
+        uint32_t tmp;
+        tmp = *(logArray + i);
+        res->printf("%d", tmp);
+        if (i != getPeriodsToLog() - 1) {
+            res->print(", ");
+        }
+    }
+
+    res->println("],");
+    res->print("\"rx_all_log\": [");
+
+    logArray = airtimeReport(RX_ALL_LOG);
+    for (int i = 0; i < getPeriodsToLog(); i++) {
+        uint32_t tmp;
+        tmp = *(logArray + i);
+        res->printf("%d", tmp);
+        if (i != getPeriodsToLog() - 1) {
+            res->print(", ");
+        }
+    }
+
+    res->println("],");
+    res->printf("\"seconds_since_boot\": %u,\n", getSecondsSinceBoot());
+    res->printf("\"seconds_per_period\": %u,\n", getSecondsPerPeriod());
+    res->printf("\"periods_to_log\": %u\n", getPeriodsToLog());
+
+    res->println("},");
+
+    res->println("\"wifi\": {");
+
+    res->printf("\"web_request_count\": %d,\n", numberOfRequests);
+    res->println("\"rssi\": " + String(WiFi.RSSI()) + ",");
+
+    if (radioConfig.preferences.wifi_ap_mode || isSoftAPForced()) {
+        res->println("\"ip\": \"" + String(WiFi.softAPIP().toString().c_str()) + "\"");
+    } else {
+        res->println("\"ip\": \"" + String(WiFi.localIP().toString().c_str()) + "\"");
+    }
+
+    res->println("},");
+
+    res->println("\"memory\": {");
+    res->printf("\"heap_total\": %d,\n", ESP.getHeapSize());
+    res->printf("\"heap_free\": %d,\n", ESP.getFreeHeap());
+    res->printf("\"psram_total\": %d,\n", ESP.getPsramSize());
+    res->printf("\"psram_free\": %d,\n", ESP.getFreePsram());
+    res->println("\"spiffs_total\" : " + String(SPIFFS.totalBytes()) + ",");
+    res->println("\"spiffs_used\" : " + String(SPIFFS.usedBytes()) + ",");
+    res->println("\"spiffs_free\" : " + String(SPIFFS.totalBytes() - SPIFFS.usedBytes()));
+    res->println("},");
+
+    res->println("\"power\": {");
+    res->printf("\"battery_percent\": %u,\n", powerStatus->getBatteryChargePercent());
+    res->printf("\"battery_voltage_mv\": %u,\n", powerStatus->getBatteryVoltageMv());
+    res->printf("\"has_battery\": %s,\n", BoolToString(powerStatus->getHasBattery()));
+    res->printf("\"has_usb\": %s,\n", BoolToString(powerStatus->getHasUSB()));
+    res->printf("\"is_charging\": %s\n", BoolToString(powerStatus->getIsCharging()));
+    res->println("}");
+
+    res->println("},");
+
+    res->println("\"status\": \"ok\"");
+    res->println("}");
+}
\ No newline at end of file
diff --git a/src/mesh/http/ContentHandler.h b/src/mesh/http/ContentHandler.h
new file mode 100644
index 000000000..8e22faefc
--- /dev/null
+++ b/src/mesh/http/ContentHandler.h
@@ -0,0 +1,46 @@
+#pragma once
+
+void registerHandlers(HTTPServer *insecureServer, HTTPSServer *secureServer);
+
+// Declare some handler functions for the various URLs on the server
+void handleAPIv1FromRadio(HTTPRequest *req, HTTPResponse *res);
+void handleAPIv1ToRadio(HTTPRequest *req, HTTPResponse *res);
+void handleStyleCSS(HTTPRequest *req, HTTPResponse *res);
+void handleHotspot(HTTPRequest *req, HTTPResponse *res);
+void handleRoot(HTTPRequest *req, HTTPResponse *res);
+void handleStaticBrowse(HTTPRequest *req, HTTPResponse *res);
+void handleStaticPost(HTTPRequest *req, HTTPResponse *res);
+void handleStatic(HTTPRequest *req, HTTPResponse *res);
+void handleRestart(HTTPRequest *req, HTTPResponse *res);
+void handle404(HTTPRequest *req, HTTPResponse *res);
+void handleFormUpload(HTTPRequest *req, HTTPResponse *res);
+void handleScanNetworks(HTTPRequest *req, HTTPResponse *res);
+void handleSpiffsBrowseStatic(HTTPRequest *req, HTTPResponse *res);
+void handleSpiffsDeleteStatic(HTTPRequest *req, HTTPResponse *res);
+void handleBlinkLED(HTTPRequest *req, HTTPResponse *res);
+void handleReport(HTTPRequest *req, HTTPResponse *res);
+void handleFavicon(HTTPRequest *req, HTTPResponse *res);
+
+void middlewareSpeedUp240(HTTPRequest *req, HTTPResponse *res, std::function next);
+void middlewareSpeedUp160(HTTPRequest *req, HTTPResponse *res, std::function next);
+void middlewareSession(HTTPRequest *req, HTTPResponse *res, std::function next);
+
+uint32_t getTimeSpeedUp();
+void setTimeSpeedUp();
+
+
+// Interface to the PhoneAPI to access the protobufs with messages
+class HttpAPI : public PhoneAPI
+{
+
+  public:
+    // Nothing here yet
+
+  private:
+    // Nothing here yet
+
+  protected:
+    // Nothing here yet
+};
+
+
diff --git a/src/mesh/http/ContentHelper.h b/src/mesh/http/ContentHelper.h
index f94b98162..ca3fee3c2 100644
--- a/src/mesh/http/ContentHelper.h
+++ b/src/mesh/http/ContentHelper.h
@@ -1,7 +1,7 @@
 #include 
 #include 
 
-
+#define BoolToString(x) ((x) ? "true" : "false")
 
 
 void replaceAll(std::string &str, const std::string &from, const std::string &to);
diff --git a/src/mesh/http/WebServer.cpp b/src/mesh/http/WebServer.cpp
index 193508b7e..da64cd233 100644
--- a/src/mesh/http/WebServer.cpp
+++ b/src/mesh/http/WebServer.cpp
@@ -1,16 +1,13 @@
 #include "mesh/http/WebServer.h"
 #include "NodeDB.h"
-#include "PowerFSM.h"
-#include "airtime.h"
 #include "main.h"
-#include "mesh/http/ContentHelper.h"
-#include "mesh/http/ContentStatic.h"
 #include "mesh/http/WiFiAPClient.h"
 #include "sleep.h"
 #include 
 #include 
 #include 
-#include 
+
+
 #include 
 #include 
 
@@ -18,6 +15,7 @@
 #include "esp_task_wdt.h"
 #endif
 
+
 // Persistant Data Storage
 #include 
 Preferences prefs;
@@ -44,52 +42,18 @@ Preferences prefs;
 
 // The HTTPS Server comes in a separate namespace. For easier use, include it here.
 using namespace httpsserver;
+#include "mesh/http/ContentHandler.h"
 
 SSLCert *cert;
 HTTPSServer *secureServer;
 HTTPServer *insecureServer;
 
-// Our API to handle messages to and from the radio.
-HttpAPI webAPI;
 
-// Declare some handler functions for the various URLs on the server
-void handleAPIv1FromRadio(HTTPRequest *req, HTTPResponse *res);
-void handleAPIv1ToRadio(HTTPRequest *req, HTTPResponse *res);
-void handleStyleCSS(HTTPRequest *req, HTTPResponse *res);
-void handleHotspot(HTTPRequest *req, HTTPResponse *res);
-void handleFavicon(HTTPRequest *req, HTTPResponse *res);
-void handleRoot(HTTPRequest *req, HTTPResponse *res);
-void handleStaticBrowse(HTTPRequest *req, HTTPResponse *res);
-void handleStaticPost(HTTPRequest *req, HTTPResponse *res);
-void handleStatic(HTTPRequest *req, HTTPResponse *res);
-void handleRestart(HTTPRequest *req, HTTPResponse *res);
-void handle404(HTTPRequest *req, HTTPResponse *res);
-void handleFormUpload(HTTPRequest *req, HTTPResponse *res);
-void handleScanNetworks(HTTPRequest *req, HTTPResponse *res);
-void handleSpiffsBrowseStatic(HTTPRequest *req, HTTPResponse *res);
-void handleSpiffsDeleteStatic(HTTPRequest *req, HTTPResponse *res);
-void handleBlinkLED(HTTPRequest *req, HTTPResponse *res);
-void handleReport(HTTPRequest *req, HTTPResponse *res);
 
-void middlewareSpeedUp240(HTTPRequest *req, HTTPResponse *res, std::function next);
-void middlewareSpeedUp160(HTTPRequest *req, HTTPResponse *res, std::function next);
-void middlewareSession(HTTPRequest *req, HTTPResponse *res, std::function next);
 
 bool isWebServerReady = 0;
 bool isCertReady = 0;
 
-uint32_t timeSpeedUp = 0;
-
-uint32_t numberOfRequests = 0;
-
-// We need to specify some content-type mapping, so the resources get delivered with the
-// right content type and are displayed correctly in the browser
-char contentTypes[][2][32] = {{".txt", "text/plain"},     {".html", "text/html"},
-                              {".js", "text/javascript"}, {".png", "image/png"},
-                              {".jpg", "image/jpg"},      {".gz", "application/gzip"},
-                              {".gif", "image/gif"},      {".json", "application/json"},
-                              {".css", "text/css"},       {".ico", "image/vnd.microsoft.icon"},
-                              {".svg", "image/svg+xml"},  {"", ""}};
 
 void handleWebResponse()
 {
@@ -110,9 +74,10 @@ void handleWebResponse()
         Slow down the CPU if we have not received a request within the last few
         seconds.
     */
-    if (millis() - timeSpeedUp >= (25 * 1000)) {
+   
+    if (millis() - getTimeSpeedUp() >= (25 * 1000)) {
         setCpuFrequencyMhz(80);
-        timeSpeedUp = millis();
+        setTimeSpeedUp();
     }
 }
 
@@ -253,69 +218,7 @@ void initWebServer()
     secureServer = new HTTPSServer(cert);
     insecureServer = new HTTPServer();
 
-    // For every resource available on the server, we need to create a ResourceNode
-    // The ResourceNode links URL and HTTP method to a handler function
-
-    ResourceNode *nodeAPIv1ToRadioOptions = new ResourceNode("/api/v1/toradio", "OPTIONS", &handleAPIv1ToRadio);
-    ResourceNode *nodeAPIv1ToRadio = new ResourceNode("/api/v1/toradio", "PUT", &handleAPIv1ToRadio);
-    ResourceNode *nodeAPIv1FromRadio = new ResourceNode("/api/v1/fromradio", "GET", &handleAPIv1FromRadio);
-
-    ResourceNode *nodeHotspot = new ResourceNode("/hotspot-detect.html", "GET", &handleHotspot);
-    ResourceNode *nodeFavicon = new ResourceNode("/favicon.ico", "GET", &handleFavicon);
-    ResourceNode *nodeRoot = new ResourceNode("/", "GET", &handleRoot);
-    ResourceNode *nodeStaticBrowse = new ResourceNode("/static", "GET", &handleStaticBrowse);
-    ResourceNode *nodeStaticPOST = new ResourceNode("/static", "POST", &handleStaticPost);
-    ResourceNode *nodeStatic = new ResourceNode("/static/*", "GET", &handleStatic);
-    ResourceNode *nodeRestart = new ResourceNode("/restart", "POST", &handleRestart);
-    ResourceNode *node404 = new ResourceNode("", "GET", &handle404);
-    ResourceNode *nodeFormUpload = new ResourceNode("/upload", "POST", &handleFormUpload);
-    ResourceNode *nodeJsonScanNetworks = new ResourceNode("/json/scanNetworks", "GET", &handleScanNetworks);
-    ResourceNode *nodeJsonBlinkLED = new ResourceNode("/json/blink", "POST", &handleBlinkLED);
-    ResourceNode *nodeJsonReport = new ResourceNode("/json/report", "GET", &handleReport);
-    ResourceNode *nodeJsonSpiffsBrowseStatic = new ResourceNode("/json/spiffs/browse/static/", "GET", &handleSpiffsBrowseStatic);
-    ResourceNode *nodeJsonDelete = new ResourceNode("/json/spiffs/delete/static", "DELETE", &handleSpiffsDeleteStatic);
-
-    // Secure nodes
-    secureServer->registerNode(nodeAPIv1ToRadioOptions);
-    secureServer->registerNode(nodeAPIv1ToRadio);
-    secureServer->registerNode(nodeAPIv1FromRadio);
-    secureServer->registerNode(nodeHotspot);
-    secureServer->registerNode(nodeFavicon);
-    secureServer->registerNode(nodeRoot);
-    secureServer->registerNode(nodeStaticBrowse);
-    secureServer->registerNode(nodeStaticPOST);
-    secureServer->registerNode(nodeStatic);
-    secureServer->registerNode(nodeRestart);
-    secureServer->registerNode(nodeFormUpload);
-    secureServer->registerNode(nodeJsonScanNetworks);
-    secureServer->registerNode(nodeJsonBlinkLED);
-    secureServer->registerNode(nodeJsonSpiffsBrowseStatic);
-    secureServer->registerNode(nodeJsonDelete);
-    secureServer->registerNode(nodeJsonReport);
-    secureServer->setDefaultNode(node404);
-
-    secureServer->addMiddleware(&middlewareSpeedUp240);
-
-    // Insecure nodes
-    insecureServer->registerNode(nodeAPIv1ToRadioOptions);
-    insecureServer->registerNode(nodeAPIv1ToRadio);
-    insecureServer->registerNode(nodeAPIv1FromRadio);
-    insecureServer->registerNode(nodeHotspot);
-    insecureServer->registerNode(nodeFavicon);
-    insecureServer->registerNode(nodeRoot);
-    insecureServer->registerNode(nodeStaticBrowse);
-    insecureServer->registerNode(nodeStaticPOST);
-    insecureServer->registerNode(nodeStatic);
-    insecureServer->registerNode(nodeRestart);
-    insecureServer->registerNode(nodeFormUpload);
-    insecureServer->registerNode(nodeJsonScanNetworks);
-    insecureServer->registerNode(nodeJsonBlinkLED);
-    insecureServer->registerNode(nodeJsonSpiffsBrowseStatic);
-    insecureServer->registerNode(nodeJsonDelete);
-    insecureServer->registerNode(nodeJsonReport);
-    insecureServer->setDefaultNode(node404);
-
-    insecureServer->addMiddleware(&middlewareSpeedUp160);
+    registerHandlers(insecureServer, secureServer);
 
     DEBUG_MSG("Starting Web Servers...\n");
     secureServer->start();
@@ -328,625 +231,9 @@ void initWebServer()
     }
 }
 
-void middlewareSpeedUp240(HTTPRequest *req, HTTPResponse *res, std::function next)
-{
-    // We want to print the response status, so we need to call next() first.
-    next();
 
-    // Phone (or other device) has contacted us over WiFi. Keep the radio turned on.
-    //   TODO: This should go into its own middleware layer separate from the speedup.
-    powerFSM.trigger(EVENT_CONTACT_FROM_PHONE);
+// --------
 
-    setCpuFrequencyMhz(240);
-    timeSpeedUp = millis();
-
-    numberOfRequests++;
-}
-
-void middlewareSpeedUp160(HTTPRequest *req, HTTPResponse *res, std::function next)
-{
-    // We want to print the response status, so we need to call next() first.
-    next();
-
-    // Phone (or other device) has contacted us over WiFi. Keep the radio turned on.
-    //   TODO: This should go into its own middleware layer separate from the speedup.
-    powerFSM.trigger(EVENT_CONTACT_FROM_PHONE);
-
-    // If the frequency is 240mhz, we have recently gotten a HTTPS request.
-    //   In that case, leave the frequency where it is and just update the
-    //   countdown timer (timeSpeedUp).
-    if (getCpuFrequencyMhz() != 240) {
-        setCpuFrequencyMhz(160);
-    }
-    timeSpeedUp = millis();
-
-    numberOfRequests++;
-}
-
-void handleAPIv1FromRadio(HTTPRequest *req, HTTPResponse *res)
-{
-
-    DEBUG_MSG("+++++++++++++++ webAPI handleAPIv1FromRadio\n");
-
-    /*
-        For documentation, see:
-            https://github.com/meshtastic/Meshtastic-device/wiki/HTTP-REST-API-discussion
-            https://github.com/meshtastic/Meshtastic-device/blob/master/docs/software/device-api.md
-
-        Example:
-            http://10.10.30.198/api/v1/fromradio
-    */
-
-    // Get access to the parameters
-    ResourceParameters *params = req->getParams();
-
-    // std::string paramAll = "all";
-    std::string valueAll;
-
-    // Status code is 200 OK by default.
-    res->setHeader("Content-Type", "application/x-protobuf");
-    res->setHeader("Access-Control-Allow-Origin", "*");
-    res->setHeader("Access-Control-Allow-Methods", "PUT, GET");
-    res->setHeader("X-Protobuf-Schema", "https://raw.githubusercontent.com/meshtastic/Meshtastic-protobufs/master/mesh.proto");
-
-    uint8_t txBuf[MAX_STREAM_BUF_SIZE];
-    uint32_t len = 1;
-
-    if (params->getQueryParameter("all", valueAll)) {
-
-        // If all is ture, return all the buffers we have available
-        //   to us at this point in time.
-        if (valueAll == "true") {
-            while (len) {
-                len = webAPI.getFromRadio(txBuf);
-                res->write(txBuf, len);
-            }
-
-            // Otherwise, just return one protobuf
-        } else {
-            len = webAPI.getFromRadio(txBuf);
-            res->write(txBuf, len);
-        }
-
-        // the param "all" was not spcified. Return just one protobuf
-    } else {
-        len = webAPI.getFromRadio(txBuf);
-        res->write(txBuf, len);
-    }
-
-    DEBUG_MSG("--------------- webAPI handleAPIv1FromRadio, len %d\n", len);
-}
-
-void handleAPIv1ToRadio(HTTPRequest *req, HTTPResponse *res)
-{
-    DEBUG_MSG("+++++++++++++++ webAPI handleAPIv1ToRadio\n");
-
-    /*
-        For documentation, see:
-            https://github.com/meshtastic/Meshtastic-device/wiki/HTTP-REST-API-discussion
-            https://github.com/meshtastic/Meshtastic-device/blob/master/docs/software/device-api.md
-
-        Example:
-            http://10.10.30.198/api/v1/toradio
-    */
-
-    // Status code is 200 OK by default.
-
-    res->setHeader("Content-Type", "application/x-protobuf");
-    res->setHeader("Access-Control-Allow-Headers", "Content-Type");
-    res->setHeader("Access-Control-Allow-Origin", "*");
-    res->setHeader("Access-Control-Allow-Methods", "PUT, OPTIONS");
-    res->setHeader("X-Protobuf-Schema", "https://raw.githubusercontent.com/meshtastic/Meshtastic-protobufs/master/mesh.proto");
-
-    if (req->getMethod() == "OPTIONS") {
-        res->setStatusCode(204); // Success with no content
-        res->print("");
-        return;
-    }
-
-    byte buffer[MAX_TO_FROM_RADIO_SIZE];
-    size_t s = req->readBytes(buffer, MAX_TO_FROM_RADIO_SIZE);
-
-    DEBUG_MSG("Received %d bytes from PUT request\n", s);
-    webAPI.handleToRadio(buffer, s);
-
-    res->write(buffer, s);
-    DEBUG_MSG("--------------- webAPI handleAPIv1ToRadio\n");
-}
-
-void handleStaticPost(HTTPRequest *req, HTTPResponse *res)
-{
-    // Assume POST request. Contains submitted data.
-    res->println("File Edited

File Edited

"); - - // The form is submitted with the x-www-form-urlencoded content type, so we need the - // HTTPURLEncodedBodyParser to read the fields. - // Note that the content of the file's content comes from a
"); - res->println(""); - res->println(""); - res->println(""); - - return; - } - - res->println("

Upload new file

"); - res->println("

This form allows you to upload files. Keep your filenames small and files under 200k.

"); - res->println("
"); - res->println("file:
"); - res->println(""); - res->println("
"); - - res->println("

All Files

"); - - File root = SPIFFS.open("/"); - if (root.isDirectory()) { - res->println(""); - - res->println(""); - res->println(""); - res->println(""); - res->println(""); - res->println(""); - res->println(""); - - File file = root.openNextFile(); - while (file) { - String filePath = String(file.name()); - if (filePath.indexOf("/static") == 0) { - res->println(""); - res->println(""); - res->println(""); - res->println(""); - res->println(""); - res->println(""); - } - - file = root.openNextFile(); - } - res->println("
File"); - res->println("Size"); - res->println("Actions"); - res->println("
"); - - if (String(file.name()).substring(1).endsWith(".gz")) { - String modifiedFile = String(file.name()).substring(1); - modifiedFile.remove((modifiedFile.length() - 3), 3); - res->print("" + String(file.name()).substring(1) + ""); - } else { - res->print("" + String(file.name()).substring(1) + - ""); - } - res->println(""); - res->print(String(file.size())); - res->println(""); - res->print("Delete "); - res->println(""); - if (!String(file.name()).substring(1).endsWith(".gz")) { - res->print("Edit"); - } - res->println("
"); - - res->print("
"); - // res->print("Total : " + String(SPIFFS.totalBytes()) + " Bytes
"); - res->print("Used : " + String(SPIFFS.usedBytes()) + " Bytes
"); - res->print("Free : " + String(SPIFFS.totalBytes() - SPIFFS.usedBytes()) + " Bytes
"); - } -} - -void handleStatic(HTTPRequest *req, HTTPResponse *res) -{ - // Get access to the parameters - ResourceParameters *params = req->getParams(); - - std::string parameter1; - // Print the first parameter value - if (params->getPathParameter(0, parameter1)) { - - std::string filename = "/static/" + parameter1; - std::string filenameGzip = "/static/" + parameter1 + ".gz"; - - if (!SPIFFS.exists(filename.c_str()) && !SPIFFS.exists(filenameGzip.c_str())) { - // Send "404 Not Found" as response, as the file doesn't seem to exist - res->setStatusCode(404); - res->setStatusText("Not found"); - res->println("404 Not Found"); - res->printf("

File not found: %s

\n", filename.c_str()); - return; - } - - // Try to open the file from SPIFFS - File file; - - if (SPIFFS.exists(filename.c_str())) { - file = SPIFFS.open(filename.c_str()); - if (!file.available()) { - DEBUG_MSG("File not available - %s\n", filename.c_str()); - } - - } else if (SPIFFS.exists(filenameGzip.c_str())) { - file = SPIFFS.open(filenameGzip.c_str()); - res->setHeader("Content-Encoding", "gzip"); - if (!file.available()) { - DEBUG_MSG("File not available\n"); - } - } - - res->setHeader("Content-Length", httpsserver::intToString(file.size())); - - bool has_set_content_type = false; - // Content-Type is guessed using the definition of the contentTypes-table defined above - int cTypeIdx = 0; - do { - if (filename.rfind(contentTypes[cTypeIdx][0]) != std::string::npos) { - res->setHeader("Content-Type", contentTypes[cTypeIdx][1]); - has_set_content_type = true; - break; - } - cTypeIdx += 1; - } while (strlen(contentTypes[cTypeIdx][0]) > 0); - - if (!has_set_content_type) { - // Set a default content type - res->setHeader("Content-Type", "application/octet-stream"); - } - - // Read the file from SPIFFS and write it to the HTTP response body - size_t length = 0; - do { - char buffer[256]; - length = file.read((uint8_t *)buffer, 256); - std::string bufferString(buffer, length); - res->write((uint8_t *)bufferString.c_str(), bufferString.size()); - } while (length > 0); - - file.close(); - - return; - - } else { - res->println("ERROR: This should not have happened..."); - } -} - -void handleFormUpload(HTTPRequest *req, HTTPResponse *res) -{ - - DEBUG_MSG("Form Upload - Disabling keep-alive\n"); - res->setHeader("Connection", "close"); - - DEBUG_MSG("Form Upload - Set frequency to 240mhz\n"); - // The upload process is very CPU intensive. Let's speed things up a bit. - setCpuFrequencyMhz(240); - - // First, we need to check the encoding of the form that we have received. - // The browser will set the Content-Type request header, so we can use it for that purpose. - // Then we select the body parser based on the encoding. - // Actually we do this only for documentary purposes, we know the form is going - // to be multipart/form-data. - DEBUG_MSG("Form Upload - Creating body parser reference\n"); - HTTPBodyParser *parser; - std::string contentType = req->getHeader("Content-Type"); - - // The content type may have additional properties after a semicolon, for exampel: - // Content-Type: text/html;charset=utf-8 - // Content-Type: multipart/form-data;boundary=------s0m3w31rdch4r4c73rs - // As we're interested only in the actual mime _type_, we strip everything after the - // first semicolon, if one exists: - size_t semicolonPos = contentType.find(";"); - if (semicolonPos != std::string::npos) { - contentType = contentType.substr(0, semicolonPos); - } - - // Now, we can decide based on the content type: - if (contentType == "multipart/form-data") { - DEBUG_MSG("Form Upload - multipart/form-data\n"); - parser = new HTTPMultipartBodyParser(req); - } else { - Serial.printf("Unknown POST Content-Type: %s\n", contentType.c_str()); - return; - } - - res->println("File " - "Upload

File Upload

"); - - // We iterate over the fields. Any field with a filename is uploaded. - // Note that the BodyParser consumes the request body, meaning that you can iterate over the request's - // fields only a single time. The reason for this is that it allows you to handle large requests - // which would not fit into memory. - bool didwrite = false; - - // parser->nextField() will move the parser to the next field in the request body (field meaning a - // form field, if you take the HTML perspective). After the last field has been processed, nextField() - // returns false and the while loop ends. - while (parser->nextField()) { - // For Multipart data, each field has three properties: - // The name ("name" value of the tag) - // The filename (If it was a , this is the filename on the machine of the - // user uploading it) - // The mime type (It is determined by the client. So do not trust this value and blindly start - // parsing files only if the type matches) - std::string name = parser->getFieldName(); - std::string filename = parser->getFieldFilename(); - std::string mimeType = parser->getFieldMimeType(); - // We log all three values, so that you can observe the upload on the serial monitor: - DEBUG_MSG("handleFormUpload: field name='%s', filename='%s', mimetype='%s'\n", name.c_str(), filename.c_str(), - mimeType.c_str()); - - // Double check that it is what we expect - if (name != "file") { - DEBUG_MSG("Skipping unexpected field\n"); - res->println("

No file found.

"); - return; - } - - // Double check that it is what we expect - if (filename == "") { - DEBUG_MSG("Skipping unexpected field\n"); - res->println("

No file found.

"); - return; - } - - // SPIFFS limits the total lenth of a path + file to 31 characters. - if (filename.length() + 8 > 31) { - DEBUG_MSG("Uploaded filename too long!\n"); - res->println("

Uploaded filename too long! Limit of 23 characters.

"); - delete parser; - return; - } - - // You should check file name validity and all that, but we skip that to make the core - // concepts of the body parser functionality easier to understand. - std::string pathname = "/static/" + filename; - - // Create a new file on spiffs to stream the data into - File file = SPIFFS.open(pathname.c_str(), "w"); - size_t fileLength = 0; - didwrite = true; - - // With endOfField you can check whether the end of field has been reached or if there's - // still data pending. With multipart bodies, you cannot know the field size in advance. - while (!parser->endOfField()) { - esp_task_wdt_reset(); - - byte buf[512]; - size_t readLength = parser->read(buf, 512); - // DEBUG_MSG("\n\nreadLength - %i\n", readLength); - - // Abort the transfer if there is less than 50k space left on the filesystem. - if (SPIFFS.totalBytes() - SPIFFS.usedBytes() < 51200) { - file.close(); - res->println("

Write aborted! Reserving 50k on filesystem.

"); - - // enableLoopWDT(); - - delete parser; - return; - } - - // if (readLength) { - file.write(buf, readLength); - fileLength += readLength; - DEBUG_MSG("File Length %i\n", fileLength); - //} - } - // enableLoopWDT(); - - file.close(); - res->printf("

Saved %d bytes to %s

", (int)fileLength, pathname.c_str()); - } - if (!didwrite) { - res->println("

Did not write any file

"); - } - res->println(""); - delete parser; -} void handle404(HTTPRequest *req, HTTPResponse *res) { @@ -990,66 +277,6 @@ void handleHotspot(HTTPRequest *req, HTTPResponse *res) res->println("\n"); } -/* - To convert text to c strings: - - https://tomeko.net/online_tools/cpp_text_escape.php?lang=en -*/ -void handleRoot(HTTPRequest *req, HTTPResponse *res) -{ - res->setHeader("Content-Type", "text/html"); - - res->setHeader("Set-Cookie", - "mt_session=" + httpsserver::intToString(random(1, 9999999)) + "; Expires=Wed, 20 Apr 2049 4:20:00 PST"); - - std::string cookie = req->getHeader("Cookie"); - // String cookieString = cookie.c_str(); - // uint8_t nameIndex = cookieString.indexOf("mt_session"); - // DEBUG_MSG(cookie.c_str()); - - std::string filename = "/static/index.html"; - std::string filenameGzip = "/static/index.html.gz"; - - if (!SPIFFS.exists(filename.c_str()) && !SPIFFS.exists(filenameGzip.c_str())) { - // Send "404 Not Found" as response, as the file doesn't seem to exist - res->setStatusCode(404); - res->setStatusText("Not found"); - res->println("404 Not Found"); - res->printf("

File not found: %s

\n", filename.c_str()); - res->printf("

\n"); - res->printf("

You have gotten this error because the filesystem for the web server has not been loaded.

\n"); - res->printf("

Please review the 'Common Problems' section of the web interface documentation.

\n"); - return; - } - - // Try to open the file from SPIFFS - File file; - - if (SPIFFS.exists(filename.c_str())) { - file = SPIFFS.open(filename.c_str()); - if (!file.available()) { - DEBUG_MSG("File not available - %s\n", filename.c_str()); - } - - } else if (SPIFFS.exists(filenameGzip.c_str())) { - file = SPIFFS.open(filenameGzip.c_str()); - res->setHeader("Content-Encoding", "gzip"); - if (!file.available()) { - DEBUG_MSG("File not available\n"); - } - } - - // Read the file from SPIFFS and write it to the HTTP response body - size_t length = 0; - do { - char buffer[256]; - length = file.read((uint8_t *)buffer, 256); - std::string bufferString(buffer, length); - res->write((uint8_t *)bufferString.c_str(), bufferString.size()); - } while (length > 0); -} void handleRestart(HTTPRequest *req, HTTPResponse *res) { @@ -1092,114 +319,6 @@ void handleBlinkLED(HTTPRequest *req, HTTPResponse *res) res->println("}"); } -void handleReport(HTTPRequest *req, HTTPResponse *res) -{ - - ResourceParameters *params = req->getParams(); - std::string content; - - if (!params->getQueryParameter("content", content)) { - content = "json"; - } - - if (content == "json") { - res->setHeader("Content-Type", "application/json"); - - } else { - res->setHeader("Content-Type", "text/html"); - res->println("
");
-    }
-
-    res->println("{");
-
-    res->println("\"data\": {");
-
-    res->println("\"airtime\": {");
-
-    uint32_t *logArray;
-
-    res->print("\"tx_log\": [");
-
-    logArray = airtimeReport(TX_LOG);
-    for (int i = 0; i < getPeriodsToLog(); i++) {
-        uint32_t tmp;
-        tmp = *(logArray + i);
-        res->printf("%d", tmp);
-        if (i != getPeriodsToLog() - 1) {
-            res->print(", ");
-        }
-    }
-
-    res->println("],");
-    res->print("\"rx_log\": [");
-
-    logArray = airtimeReport(RX_LOG);
-    for (int i = 0; i < getPeriodsToLog(); i++) {
-        uint32_t tmp;
-        tmp = *(logArray + i);
-        res->printf("%d", tmp);
-        if (i != getPeriodsToLog() - 1) {
-            res->print(", ");
-        }
-    }
-
-    res->println("],");
-    res->print("\"rx_all_log\": [");
-
-    logArray = airtimeReport(RX_ALL_LOG);
-    for (int i = 0; i < getPeriodsToLog(); i++) {
-        uint32_t tmp;
-        tmp = *(logArray + i);
-        res->printf("%d", tmp);
-        if (i != getPeriodsToLog() - 1) {
-            res->print(", ");
-        }
-    }
-
-    res->println("],");
-    res->printf("\"seconds_since_boot\": %u,\n", getSecondsSinceBoot());
-    res->printf("\"seconds_per_period\": %u,\n", getSecondsPerPeriod());
-    res->printf("\"periods_to_log\": %u\n", getPeriodsToLog());
-
-    res->println("},");
-
-    res->println("\"wifi\": {");
-
-    res->printf("\"web_request_count\": %d,\n", numberOfRequests);
-    res->println("\"rssi\": " + String(WiFi.RSSI()) + ",");
-
-    if (radioConfig.preferences.wifi_ap_mode || isSoftAPForced()) {
-        res->println("\"ip\": \"" + String(WiFi.softAPIP().toString().c_str()) + "\"");
-    } else {
-        res->println("\"ip\": \"" + String(WiFi.localIP().toString().c_str()) + "\"");
-    }
-
-    res->println("},");
-
-    res->println("\"memory\": {");
-    res->printf("\"heap_total\": %d,\n", ESP.getHeapSize());
-    res->printf("\"heap_free\": %d,\n", ESP.getFreeHeap());
-    res->printf("\"psram_total\": %d,\n", ESP.getPsramSize());
-    res->printf("\"psram_free\": %d,\n", ESP.getFreePsram());
-    res->println("\"spiffs_total\" : " + String(SPIFFS.totalBytes()) + ",");
-    res->println("\"spiffs_used\" : " + String(SPIFFS.usedBytes()) + ",");
-    res->println("\"spiffs_free\" : " + String(SPIFFS.totalBytes() - SPIFFS.usedBytes()));
-    res->println("},");
-
-    res->println("\"power\": {");
-#define BoolToString(x) ((x) ? "true" : "false")
-    res->printf("\"battery_percent\": %u,\n", powerStatus->getBatteryChargePercent());
-    res->printf("\"battery_voltage_mv\": %u,\n", powerStatus->getBatteryVoltageMv());
-    res->printf("\"has_battery\": %s,\n", BoolToString(powerStatus->getHasBattery()));
-    res->printf("\"has_usb\": %s,\n", BoolToString(powerStatus->getHasUSB()));
-    res->printf("\"is_charging\": %s\n", BoolToString(powerStatus->getIsCharging()));
-    res->println("}");
-
-    res->println("},");
-
-    res->println("\"status\": \"ok\"");
-    res->println("}");
-}
 
 void handleScanNetworks(HTTPRequest *req, HTTPResponse *res)
 {
@@ -1244,10 +363,3 @@ void handleScanNetworks(HTTPRequest *req, HTTPResponse *res)
     res->println("}");
 }
 
-void handleFavicon(HTTPRequest *req, HTTPResponse *res)
-{
-    // Set Content-Type
-    res->setHeader("Content-Type", "image/vnd.microsoft.icon");
-    // Write data from header file
-    res->write(FAVICON_DATA, FAVICON_LENGTH);
-}
diff --git a/src/mesh/http/WebServer.h b/src/mesh/http/WebServer.h
index b01d59605..aacb2e00a 100644
--- a/src/mesh/http/WebServer.h
+++ b/src/mesh/http/WebServer.h
@@ -8,31 +8,10 @@
 void initWebServer();
 void createSSLCert();
 
-void handleNotFound();
 
 void handleWebResponse();
 
 
-//void handleHotspot();
-
-//void handleStyleCSS();
-//void handleRoot();
-
-
-// Interface to the PhoneAPI to access the protobufs with messages
-class HttpAPI : public PhoneAPI
-{
-
-  public:
-    // Nothing here yet
-
-  private:
-    // Nothing here yet
-
-  protected:
-    // Nothing here yet
-};
-
 class WebServerThread : private concurrency::OSThread
 {
 

From 2a47819fd68eed677d21fbb046eb84b91a1ee3eb Mon Sep 17 00:00:00 2001
From: Jm 
Date: Tue, 19 Jan 2021 21:38:17 -0800
Subject: [PATCH 2/8] #649 More webserver refactoring

---
 src/mesh/http/ContentHandler.cpp | 132 +++++++++++++++++++++++++++++-
 src/mesh/http/WebServer.cpp      | 134 -------------------------------
 2 files changed, 131 insertions(+), 135 deletions(-)

diff --git a/src/mesh/http/ContentHandler.cpp b/src/mesh/http/ContentHandler.cpp
index df47776b2..af00de68a 100644
--- a/src/mesh/http/ContentHandler.cpp
+++ b/src/mesh/http/ContentHandler.cpp
@@ -5,10 +5,12 @@
 #include "mesh/http/ContentStatic.h"
 #include "mesh/http/WiFiAPClient.h"
 #include "power.h"
+#include "sleep.h"
 #include 
 #include 
 #include 
 #include 
+#include "main.h"
 
 #ifndef NO_ESP32
 #include "esp_task_wdt.h"
@@ -927,4 +929,132 @@ void handleReport(HTTPRequest *req, HTTPResponse *res)
 
     res->println("\"status\": \"ok\"");
     res->println("}");
-}
\ No newline at end of file
+}
+
+// --------
+
+void handle404(HTTPRequest *req, HTTPResponse *res)
+{
+
+    // Discard request body, if we received any
+    // We do this, as this is the default node and may also server POST/PUT requests
+    req->discardRequestBody();
+
+    // Set the response status
+    res->setStatusCode(404);
+    res->setStatusText("Not Found");
+
+    // Set content type of the response
+    res->setHeader("Content-Type", "text/html");
+
+    // Write a tiny HTTP page
+    res->println("");
+    res->println("");
+    res->println("Not Found");
+    res->println("

404 Not Found

The requested resource was not found on this server.

"); + res->println(""); +} + +/* + This supports the Apple Captive Network Assistant (CNA) Portal +*/ +void handleHotspot(HTTPRequest *req, HTTPResponse *res) +{ + DEBUG_MSG("Hotspot Request\n"); + + /* + If we don't do a redirect, be sure to return a "Success" message + otherwise iOS will have trouble detecting that the connection to the SoftAP worked. + */ + + // Status code is 200 OK by default. + // We want to deliver a simple HTML page, so we send a corresponding content type: + res->setHeader("Content-Type", "text/html"); + + // res->println(""); + res->println("\n"); +} + +void handleRestart(HTTPRequest *req, HTTPResponse *res) +{ + res->setHeader("Content-Type", "text/html"); + + DEBUG_MSG("***** Restarted on HTTP(s) Request *****\n"); + res->println("Restarting"); + + ESP.restart(); +} + +void handleBlinkLED(HTTPRequest *req, HTTPResponse *res) +{ + res->setHeader("Content-Type", "application/json"); + + ResourceParameters *params = req->getParams(); + std::string blink_target; + + if (!params->getQueryParameter("blink_target", blink_target)) { + // if no blink_target was supplied in the URL parameters of the + // POST request, then assume we should blink the LED + blink_target = "LED"; + } + + if (blink_target == "LED") { + uint8_t count = 10; + while (count > 0) { + setLed(true); + delay(50); + setLed(false); + delay(50); + count = count - 1; + } + } else { + screen->blink(); + } + + res->println("{"); + res->println("\"status\": \"ok\""); + res->println("}"); +} + +void handleScanNetworks(HTTPRequest *req, HTTPResponse *res) +{ + res->setHeader("Content-Type", "application/json"); + // res->setHeader("Content-Type", "text/html"); + + int n = WiFi.scanNetworks(); + res->println("{"); + res->println("\"data\": {"); + if (n == 0) { + // No networks found. + res->println("\"networks\": []"); + + } else { + res->println("\"networks\": ["); + + for (int i = 0; i < n; ++i) { + char ssidArray[50]; + String ssidString = String(WiFi.SSID(i)); + // String ssidString = String(WiFi.SSID(i)).toCharArray(ssidArray, WiFi.SSID(i).length()); + ssidString.replace("\"", "\\\""); + ssidString.toCharArray(ssidArray, 50); + + if (WiFi.encryptionType(i) != WIFI_AUTH_OPEN) { + // res->println("{\"ssid\": \"%s\",\"rssi\": -75}, ", String(WiFi.SSID(i).c_str() ); + + res->printf("{\"ssid\": \"%s\",\"rssi\": %d}", ssidArray, WiFi.RSSI(i)); + // WiFi.RSSI(i) + if (i != n - 1) { + res->printf(","); + } + } + // Yield some cpu cycles to IP stack. + // This is important in case the list is large and it takes us time to return + // to the main loop. + yield(); + } + res->println("]"); + } + res->println("},"); + res->println("\"status\": \"ok\""); + res->println("}"); +} diff --git a/src/mesh/http/WebServer.cpp b/src/mesh/http/WebServer.cpp index da64cd233..2633de529 100644 --- a/src/mesh/http/WebServer.cpp +++ b/src/mesh/http/WebServer.cpp @@ -1,8 +1,6 @@ #include "mesh/http/WebServer.h" #include "NodeDB.h" -#include "main.h" #include "mesh/http/WiFiAPClient.h" -#include "sleep.h" #include #include #include @@ -231,135 +229,3 @@ void initWebServer() } } - -// -------- - - -void handle404(HTTPRequest *req, HTTPResponse *res) -{ - - // Discard request body, if we received any - // We do this, as this is the default node and may also server POST/PUT requests - req->discardRequestBody(); - - // Set the response status - res->setStatusCode(404); - res->setStatusText("Not Found"); - - // Set content type of the response - res->setHeader("Content-Type", "text/html"); - - // Write a tiny HTTP page - res->println(""); - res->println(""); - res->println("Not Found"); - res->println("

404 Not Found

The requested resource was not found on this server.

"); - res->println(""); -} - -/* - This supports the Apple Captive Network Assistant (CNA) Portal -*/ -void handleHotspot(HTTPRequest *req, HTTPResponse *res) -{ - DEBUG_MSG("Hotspot Request\n"); - - /* - If we don't do a redirect, be sure to return a "Success" message - otherwise iOS will have trouble detecting that the connection to the SoftAP worked. - */ - - // Status code is 200 OK by default. - // We want to deliver a simple HTML page, so we send a corresponding content type: - res->setHeader("Content-Type", "text/html"); - - // res->println(""); - res->println("\n"); -} - - -void handleRestart(HTTPRequest *req, HTTPResponse *res) -{ - res->setHeader("Content-Type", "text/html"); - - DEBUG_MSG("***** Restarted on HTTP(s) Request *****\n"); - res->println("Restarting"); - - ESP.restart(); -} - -void handleBlinkLED(HTTPRequest *req, HTTPResponse *res) -{ - res->setHeader("Content-Type", "application/json"); - - ResourceParameters *params = req->getParams(); - std::string blink_target; - - if (!params->getQueryParameter("blink_target", blink_target)) { - // if no blink_target was supplied in the URL parameters of the - // POST request, then assume we should blink the LED - blink_target = "LED"; - } - - if (blink_target == "LED") { - uint8_t count = 10; - while (count > 0) { - setLed(true); - delay(50); - setLed(false); - delay(50); - count = count - 1; - } - } else { - screen->blink(); - } - - res->println("{"); - res->println("\"status\": \"ok\""); - res->println("}"); -} - - -void handleScanNetworks(HTTPRequest *req, HTTPResponse *res) -{ - res->setHeader("Content-Type", "application/json"); - // res->setHeader("Content-Type", "text/html"); - - int n = WiFi.scanNetworks(); - res->println("{"); - res->println("\"data\": {"); - if (n == 0) { - // No networks found. - res->println("\"networks\": []"); - - } else { - res->println("\"networks\": ["); - - for (int i = 0; i < n; ++i) { - char ssidArray[50]; - String ssidString = String(WiFi.SSID(i)); - // String ssidString = String(WiFi.SSID(i)).toCharArray(ssidArray, WiFi.SSID(i).length()); - ssidString.replace("\"", "\\\""); - ssidString.toCharArray(ssidArray, 50); - - if (WiFi.encryptionType(i) != WIFI_AUTH_OPEN) { - // res->println("{\"ssid\": \"%s\",\"rssi\": -75}, ", String(WiFi.SSID(i).c_str() ); - - res->printf("{\"ssid\": \"%s\",\"rssi\": %d}", ssidArray, WiFi.RSSI(i)); - // WiFi.RSSI(i) - if (i != n - 1) { - res->printf(","); - } - } - // Yield some cpu cycles to IP stack. - // This is important in case the list is large and it takes us time to return - // to the main loop. - yield(); - } - res->println("]"); - } - res->println("},"); - res->println("\"status\": \"ok\""); - res->println("}"); -} - From af0a1b5db5b7c5c468d75daf501abdf9e9434712 Mon Sep 17 00:00:00 2001 From: Jm Date: Wed, 20 Jan 2021 19:02:08 -0800 Subject: [PATCH 3/8] Update comments of SerialPlugin --- src/plugins/SerialPlugin.cpp | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/plugins/SerialPlugin.cpp b/src/plugins/SerialPlugin.cpp index 7e764ee8b..47f279089 100644 --- a/src/plugins/SerialPlugin.cpp +++ b/src/plugins/SerialPlugin.cpp @@ -10,7 +10,7 @@ /* SerialPlugin - An overly simplistic interface to send messages over the mesh network by sending strings + A simple interface to send messages over the mesh network by sending strings over a serial port. Default is to use RX GPIO 16 and TX GPIO 17. @@ -20,23 +20,21 @@ Basic Usage: - 1) Enable the plugin by setting SERIALPLUGIN_ENABLED to 1. - 2) Set the pins (RXD2 / TXD2) for your preferred RX and TX GPIO pins. + 1) Enable the plugin by setting serialplugin_enabled to 1. + 2) Set the pins (serialplugin_rxd / serialplugin_rxd) for your preferred RX and TX GPIO pins. On tbeam, recommend to use: - #define RXD2 35 - #define TXD2 15 - 3) Set SERIALPLUGIN_TIMEOUT to the amount of time to wait before we consider + RXD 35 + TXD 15 + 3) Set serialplugin_timeout to the amount of time to wait before we consider your packet as "done". 4) (Optional) In SerialPlugin.h set the port to PortNum_TEXT_MESSAGE_APP if you want to send messages to/from the general text message channel. 5) Connect to your device over the serial interface at 38400 8N1. 6) Send a packet up to 240 bytes in length. This will get relayed over the mesh network. - 7) (Optional) Set SERIALPLUGIN_ECHO to 1 and any message you send out will be echoed back + 7) (Optional) Set serialplugin_echo to 1 and any message you send out will be echoed back to your device. TODO (in this order): - * Once protobufs regenerated with the new port, update SerialPlugin.h - * Ensure this works on a tbeam * Define a verbose RX mode to report on mesh and packet infomration. - This won't happen any time soon. @@ -76,13 +74,14 @@ int32_t SerialPlugin::runOnce() // radioConfig.preferences.serialplugin_rxd = 35; // radioConfig.preferences.serialplugin_txd = 15; // radioConfig.preferences.serialplugin_timeout = 1000; + // radioConfig.preferences.serialplugin_echo = 1; if (radioConfig.preferences.serialplugin_enabled) { if (firstTime) { // Interface with the serial peripheral from in here. - DEBUG_MSG("Initilizing serial peripheral interface\n"); + DEBUG_MSG("Initializing serial peripheral interface\n"); if (radioConfig.preferences.serialplugin_rxd && radioConfig.preferences.serialplugin_txd) { Serial2.begin(SERIALPLUGIN_BAUD, SERIAL_8N1, radioConfig.preferences.serialplugin_rxd, From ca83a78e13063328d3147362fe257ff16b5eebeb Mon Sep 17 00:00:00 2001 From: Jm Date: Fri, 22 Jan 2021 19:50:12 -0800 Subject: [PATCH 4/8] Fix for #650 - build-all.sh will fail --- bin/build-all.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/build-all.sh b/bin/build-all.sh index c27760c42..caa7c59af 100755 --- a/bin/build-all.sh +++ b/bin/build-all.sh @@ -14,7 +14,7 @@ BOARDS_ESP32="tlora-v2 tlora-v1 tlora-v2-1-1.6 tbeam heltec tbeam0.7" # FIXME note nrf52840dk build is for some reason only generating a BIN file but not a HEX file nrf52840dk-geeksville is fine BOARDS_NRF52="lora-relay-v1" -NUM_JOBS=2 +NUM_JOBS=2 || true OUTDIR=release/latest From 9db5f9ff6773b36e1e45ca8ba9aa6f9f2362c321 Mon Sep 17 00:00:00 2001 From: Sacha Weatherstone Date: Sat, 23 Jan 2021 17:42:15 +1100 Subject: [PATCH 5/8] fix cors for API requsts & fix spiffs url --- src/mesh/http/ContentHandler.cpp | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/mesh/http/ContentHandler.cpp b/src/mesh/http/ContentHandler.cpp index af00de68a..a87b7c9e8 100644 --- a/src/mesh/http/ContentHandler.cpp +++ b/src/mesh/http/ContentHandler.cpp @@ -1,6 +1,7 @@ #include "NodeDB.h" #include "PowerFSM.h" #include "airtime.h" +#include "main.h" #include "mesh/http/ContentHelper.h" #include "mesh/http/ContentStatic.h" #include "mesh/http/WiFiAPClient.h" @@ -10,7 +11,6 @@ #include #include #include -#include "main.h" #ifndef NO_ESP32 #include "esp_task_wdt.h" @@ -88,7 +88,7 @@ void registerHandlers(HTTPServer *insecureServer, HTTPSServer *secureServer) ResourceNode *nodeJsonScanNetworks = new ResourceNode("/json/scanNetworks", "GET", &handleScanNetworks); ResourceNode *nodeJsonBlinkLED = new ResourceNode("/json/blink", "POST", &handleBlinkLED); ResourceNode *nodeJsonReport = new ResourceNode("/json/report", "GET", &handleReport); - ResourceNode *nodeJsonSpiffsBrowseStatic = new ResourceNode("/json/spiffs/browse/static/", "GET", &handleSpiffsBrowseStatic); + ResourceNode *nodeJsonSpiffsBrowseStatic = new ResourceNode("/json/spiffs/browse/static", "GET", &handleSpiffsBrowseStatic); ResourceNode *nodeJsonDelete = new ResourceNode("/json/spiffs/delete/static", "DELETE", &handleSpiffsDeleteStatic); // Secure nodes @@ -192,7 +192,7 @@ void handleAPIv1FromRadio(HTTPRequest *req, HTTPResponse *res) // Status code is 200 OK by default. res->setHeader("Content-Type", "application/x-protobuf"); res->setHeader("Access-Control-Allow-Origin", "*"); - res->setHeader("Access-Control-Allow-Methods", "PUT, GET"); + res->setHeader("Access-Control-Allow-Methods", "GET"); res->setHeader("X-Protobuf-Schema", "https://raw.githubusercontent.com/meshtastic/Meshtastic-protobufs/master/mesh.proto"); uint8_t txBuf[MAX_STREAM_BUF_SIZE]; @@ -342,7 +342,8 @@ void handleSpiffsBrowseStatic(HTTPRequest *req, HTTPResponse *res) // jm res->setHeader("Content-Type", "application/json"); - // res->setHeader("Content-Type", "text/html"); + res->setHeader("Access-Control-Allow-Origin", "*"); + res->setHeader("Access-Control-Allow-Methods", "GET"); File root = SPIFFS.open("/"); @@ -397,6 +398,8 @@ void handleSpiffsDeleteStatic(HTTPRequest *req, HTTPResponse *res) std::string paramValDelete; res->setHeader("Content-Type", "application/json"); + res->setHeader("Access-Control-Allow-Origin", "*"); + res->setHeader("Access-Control-Allow-Methods", "DELETE"); if (params->getQueryParameter("delete", paramValDelete)) { std::string pathDelete = "/" + paramValDelete; if (SPIFFS.remove(pathDelete.c_str())) { @@ -835,6 +838,8 @@ void handleReport(HTTPRequest *req, HTTPResponse *res) if (content == "json") { res->setHeader("Content-Type", "application/json"); + res->setHeader("Access-Control-Allow-Origin", "*"); + res->setHeader("Access-Control-Allow-Methods", "GET"); } else { res->setHeader("Content-Type", "text/html"); @@ -978,6 +983,8 @@ void handleHotspot(HTTPRequest *req, HTTPResponse *res) void handleRestart(HTTPRequest *req, HTTPResponse *res) { res->setHeader("Content-Type", "text/html"); + res->setHeader("Access-Control-Allow-Origin", "*"); + res->setHeader("Access-Control-Allow-Methods", "GET"); DEBUG_MSG("***** Restarted on HTTP(s) Request *****\n"); res->println("Restarting"); @@ -988,6 +995,8 @@ void handleRestart(HTTPRequest *req, HTTPResponse *res) void handleBlinkLED(HTTPRequest *req, HTTPResponse *res) { res->setHeader("Content-Type", "application/json"); + res->setHeader("Access-Control-Allow-Origin", "*"); + res->setHeader("Access-Control-Allow-Methods", "POST"); ResourceParameters *params = req->getParams(); std::string blink_target; @@ -1019,6 +1028,8 @@ void handleBlinkLED(HTTPRequest *req, HTTPResponse *res) void handleScanNetworks(HTTPRequest *req, HTTPResponse *res) { res->setHeader("Content-Type", "application/json"); + res->setHeader("Access-Control-Allow-Origin", "*"); + res->setHeader("Access-Control-Allow-Methods", "GET"); // res->setHeader("Content-Type", "text/html"); int n = WiFi.scanNetworks(); From 31b89e29326c75714c058da3d9c270b122e2379f Mon Sep 17 00:00:00 2001 From: Sacha Weatherstone Date: Sun, 24 Jan 2021 10:48:48 +1100 Subject: [PATCH 6/8] Another header required --- src/mesh/http/ContentHandler.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/mesh/http/ContentHandler.cpp b/src/mesh/http/ContentHandler.cpp index a87b7c9e8..5f79702fc 100644 --- a/src/mesh/http/ContentHandler.cpp +++ b/src/mesh/http/ContentHandler.cpp @@ -975,6 +975,8 @@ void handleHotspot(HTTPRequest *req, HTTPResponse *res) // Status code is 200 OK by default. // We want to deliver a simple HTML page, so we send a corresponding content type: res->setHeader("Content-Type", "text/html"); + res->setHeader("Access-Control-Allow-Origin", "*"); + res->setHeader("Access-Control-Allow-Methods", "GET"); // res->println(""); res->println("\n"); From a8480d1eaf53c9aed2b5e8632e43a0b22d45a104 Mon Sep 17 00:00:00 2001 From: IZ1IVA <75425638+IZ1IVA@users.noreply.github.com> Date: Mon, 25 Jan 2021 16:11:24 +0100 Subject: [PATCH 7/8] Update radio-settings.md Added data-rates --- docs/radio-settings.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/radio-settings.md b/docs/radio-settings.md index 2bf9ccda1..7af9d79e0 100644 --- a/docs/radio-settings.md +++ b/docs/radio-settings.md @@ -28,3 +28,14 @@ The maximum output power for North America is +30 dBm ERP. The band is from 902 to 928 MHz. It mentions channel number and its respective channel frequency. All the 13 channels are separated by 2.16 MHz with respect to the adjacent channels. Channel zero starts at 903.08 MHz center frequency. + +## Data-rates + +Various data-rates are selectable when configuring a channel and are inversely proportional to the theoretical range of the devices: + +| Channel setting | Data-rate | +|----------------------------|----------------------| +| Short range (but fast) | 21.875 kbps | +| Medium range (but fast) | 5.469 kbps | +| Long range (but slower) | 0.275 kbps | +| Very long range (but slow) | 0.183 kbps (default) | From 532b06c2808d4e85ceedc3dd102e42b0aefe9be3 Mon Sep 17 00:00:00 2001 From: Jm Date: Mon, 25 Jan 2021 17:01:47 -0800 Subject: [PATCH 8/8] Update version.properties to 1.1.33 --- version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.properties b/version.properties index ac4a03634..556f3f840 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 1 minor = 1 -build = 32 +build = 33