diff --git a/bin/build-all.sh b/bin/build-all.sh index c27760c4..caa7c59a 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 diff --git a/docs/radio-settings.md b/docs/radio-settings.md index 2bf9ccda..7af9d79e 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) | diff --git a/platformio.ini b/platformio.ini index 149d2d78..9e3b123d 100644 --- a/platformio.ini +++ b/platformio.ini @@ -185,7 +185,7 @@ build_type = debug ; I'm debugging with ICE a lot now build_flags = ${arduino_base.build_flags} -Wno-unused-variable -Isrc/nrf52 - -Isdk-nrfxlib/crypto/nrf_oberon/include -Lsdk-nrfxlib/crypto/nrf_oberon/lib/cortex-m4/hard-float/ -lliboberon_3.0.3 + -Isdk-nrfxlib/crypto/nrf_oberon/include -Lsdk-nrfxlib/crypto/nrf_oberon/lib/cortex-m4/hard-float/ -lliboberon_3.0.7 ;-DCFG_DEBUG=3 src_filter = ${arduino_base.src_filter} - - - - diff --git a/proto b/proto index 4c62d8e5..855da870 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit 4c62d8e53696074d7c368518afd9d564df374fcf +Subproject commit 855da8701edbb19818069ad8545d5b9f030bb33f diff --git a/sdk-nrfxlib b/sdk-nrfxlib index 17e84535..e6e02cb8 160000 --- a/sdk-nrfxlib +++ b/sdk-nrfxlib @@ -1 +1 @@ -Subproject commit 17e8453553d4cfc21ab87c53c9627f0cf1216429 +Subproject commit e6e02cb83d238fae2f54f084858bd5e49a31afa1 diff --git a/src/Power.cpp b/src/Power.cpp index 82e88f8c..f4aa700a 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -1,4 +1,5 @@ #include "power.h" +#include "NodeDB.h" #include "PowerFSM.h" #include "main.h" #include "sleep.h" @@ -268,9 +269,42 @@ bool Power::axp192Init() DEBUG_MSG("DCDC3: %s\n", axp.isDCDC3Enable() ? "ENABLE" : "DISABLE"); DEBUG_MSG("Exten: %s\n", axp.isExtenEnable() ? "ENABLE" : "DISABLE"); - //axp.setChargeControlCur(AXP1XX_CHARGE_CUR_1320MA); // actual limit (in HW) on the tbeam is 450mA - axp.setChargeControlCur(AXP1XX_CHARGE_CUR_450MA); // There's no HW limit on the tbeam. Setting to 450mz to be a good neighbor on the usb bus. - + if (radioConfig.preferences.charge_current == ChargeCurrent_MAUnset) { + axp.setChargeControlCur(AXP1XX_CHARGE_CUR_450MA); + } else if (radioConfig.preferences.charge_current == ChargeCurrent_MA100) { + axp.setChargeControlCur(AXP1XX_CHARGE_CUR_100MA); + } else if (radioConfig.preferences.charge_current == ChargeCurrent_MA190) { + axp.setChargeControlCur(AXP1XX_CHARGE_CUR_190MA); + } else if (radioConfig.preferences.charge_current == ChargeCurrent_MA280) { + axp.setChargeControlCur(AXP1XX_CHARGE_CUR_280MA); + } else if (radioConfig.preferences.charge_current == ChargeCurrent_MA360) { + axp.setChargeControlCur(AXP1XX_CHARGE_CUR_360MA); + } else if (radioConfig.preferences.charge_current == ChargeCurrent_MA450) { + axp.setChargeControlCur(AXP1XX_CHARGE_CUR_450MA); + } else if (radioConfig.preferences.charge_current == ChargeCurrent_MA550) { + axp.setChargeControlCur(AXP1XX_CHARGE_CUR_550MA); + } else if (radioConfig.preferences.charge_current == ChargeCurrent_MA630) { + axp.setChargeControlCur(AXP1XX_CHARGE_CUR_630MA); + } else if (radioConfig.preferences.charge_current == ChargeCurrent_MA700) { + axp.setChargeControlCur(AXP1XX_CHARGE_CUR_700MA); + } else if (radioConfig.preferences.charge_current == ChargeCurrent_MA780) { + axp.setChargeControlCur(AXP1XX_CHARGE_CUR_780MA); + } else if (radioConfig.preferences.charge_current == ChargeCurrent_MA880) { + axp.setChargeControlCur(AXP1XX_CHARGE_CUR_880MA); + } else if (radioConfig.preferences.charge_current == ChargeCurrent_MA960) { + axp.setChargeControlCur(AXP1XX_CHARGE_CUR_960MA); + } else if (radioConfig.preferences.charge_current == ChargeCurrent_MA1000) { + axp.setChargeControlCur(AXP1XX_CHARGE_CUR_1000MA); + } else if (radioConfig.preferences.charge_current == ChargeCurrent_MA1080) { + axp.setChargeControlCur(AXP1XX_CHARGE_CUR_1080MA); + } else if (radioConfig.preferences.charge_current == ChargeCurrent_MA1160) { + axp.setChargeControlCur(AXP1XX_CHARGE_CUR_1160MA); + } else if (radioConfig.preferences.charge_current == ChargeCurrent_MA1240) { + axp.setChargeControlCur(AXP1XX_CHARGE_CUR_1240MA); + } else if (radioConfig.preferences.charge_current == ChargeCurrent_MA1320) { + axp.setChargeControlCur(AXP1XX_CHARGE_CUR_1320MA); + } + #if 0 // Not connected diff --git a/src/main.cpp b/src/main.cpp index b63c193a..aa7b191d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -303,17 +303,20 @@ void setup() digitalWrite(RESET_OLED, 1); #endif +#ifdef BUTTON_PIN +#ifndef NO_ESP32 // If BUTTON_PIN is held down during the startup process, // force the device to go into a SoftAP mode. bool forceSoftAP = 0; -#ifdef BUTTON_PIN -#ifndef NO_ESP32 pinMode(BUTTON_PIN, INPUT); +#ifdef BUTTON_NEED_PULLUP + gpio_pullup_en((gpio_num_t)BUTTON_PIN); +#endif // BUTTON_PIN is pulled high by a 12k resistor. if (!digitalRead(BUTTON_PIN)) { forceSoftAP = 1; - DEBUG_MSG("-------------------- Setting forceSoftAP = 1\n"); + DEBUG_MSG("Setting forceSoftAP = 1\n"); } #endif @@ -513,7 +516,6 @@ void setup() } #endif - #ifndef NO_ESP32 // Initialize Wifi initWifi(forceSoftAP); diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index dc13bc42..42ce71ea 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -7,6 +7,7 @@ #include "CryptoEngine.h" #include "FSCommon.h" #include "GPS.h" +#include "main.h" #include "MeshRadio.h" #include "NodeDB.h" #include "PacketHistory.h" @@ -583,8 +584,14 @@ NodeInfo *NodeDB::getOrCreateNode(NodeNum n) /// Record an error that should be reported via analytics void recordCriticalError(CriticalErrorCode code, uint32_t address) { + // Print error to screen and serial port + String lcd = String("Critical error ") + code + "!\n"; + screen->print(lcd.c_str()); DEBUG_MSG("NOTE! Recording critical error %d, address=%x\n", code, address); + + // Record error to DB myNodeInfo.error_code = code; myNodeInfo.error_address = address; myNodeInfo.error_count++; + } diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index 0266614a..f2fafef7 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -3,6 +3,7 @@ #include "NodeDB.h" #include "SPILock.h" #include "mesh-pb-constants.h" +#include "error.h" #include #include #include @@ -67,9 +68,20 @@ bool RadioLibInterface::canSendImmediately() bool busyTx = sendingPacket != NULL; bool busyRx = isReceiving && isActivelyReceiving(); + if (busyTx || busyRx) { if (busyTx) DEBUG_MSG("Can not send yet, busyTx\n"); + // If we've been trying to send the same packet more than one minute and we haven't gotten a + // TX IRQ from the radio, the radio is probably broken. + if (busyTx && (millis() - lastTxStart > 60000)){ + DEBUG_MSG("Hardware Failure! busyTx for more than 60s\n"); + recordCriticalError(CriticalErrorCode_TransmitFailed); +#ifndef NO_ESP32 + if (busyTx && (millis() - lastTxStart > 65000)) // After 5s more, reboot + ESP.restart(); +#endif + } if (busyRx) DEBUG_MSG("Can not send yet, busyRx\n"); return false; diff --git a/src/mesh/generated/deviceonly.pb.h b/src/mesh/generated/deviceonly.pb.h index 39f39275..c3e55666 100644 --- a/src/mesh/generated/deviceonly.pb.h +++ b/src/mesh/generated/deviceonly.pb.h @@ -80,7 +80,7 @@ extern const pb_msgdesc_t DeviceState_msg; #define DeviceState_fields &DeviceState_msg /* Maximum encoded size of messages (where known) */ -#define DeviceState_size 6176 +#define DeviceState_size 6206 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/mesh/generated/mesh.pb.c b/src/mesh/generated/mesh.pb.c index 8f976cac..3bc2b080 100644 --- a/src/mesh/generated/mesh.pb.c +++ b/src/mesh/generated/mesh.pb.c @@ -57,3 +57,4 @@ PB_BIND(ToRadio, ToRadio, 2) + diff --git a/src/mesh/generated/mesh.pb.h b/src/mesh/generated/mesh.pb.h index 0962effa..50ae14a6 100644 --- a/src/mesh/generated/mesh.pb.h +++ b/src/mesh/generated/mesh.pb.h @@ -35,6 +35,26 @@ typedef enum _RegionCode { RegionCode_TW = 8 } RegionCode; +typedef enum _ChargeCurrent { + ChargeCurrent_MAUnset = 0, + ChargeCurrent_MA100 = 1, + ChargeCurrent_MA190 = 2, + ChargeCurrent_MA280 = 3, + ChargeCurrent_MA360 = 4, + ChargeCurrent_MA450 = 5, + ChargeCurrent_MA550 = 6, + ChargeCurrent_MA630 = 7, + ChargeCurrent_MA700 = 8, + ChargeCurrent_MA780 = 9, + ChargeCurrent_MA880 = 10, + ChargeCurrent_MA960 = 11, + ChargeCurrent_MA1000 = 12, + ChargeCurrent_MA1080 = 13, + ChargeCurrent_MA1160 = 14, + ChargeCurrent_MA1240 = 15, + ChargeCurrent_MA1320 = 16 +} ChargeCurrent; + typedef enum _GpsOperation { GpsOperation_GpsOpUnset = 0, GpsOperation_GpsOpMobile = 2, @@ -56,7 +76,8 @@ typedef enum _CriticalErrorCode { CriticalErrorCode_Unspecified = 4, CriticalErrorCode_UBloxInitFailed = 5, CriticalErrorCode_NoAXP192 = 6, - CriticalErrorCode_InvalidRadioSetting = 7 + CriticalErrorCode_InvalidRadioSetting = 7, + CriticalErrorCode_TransmitFailed = 8 } CriticalErrorCode; typedef enum _ChannelSettings_ModemConfig { @@ -145,6 +166,7 @@ typedef struct _RadioConfig_UserPreferences { char wifi_password[64]; bool wifi_ap_mode; RegionCode region; + ChargeCurrent charge_current; LocationSharing location_share; GpsOperation gps_operation; uint32_t gps_update_interval; @@ -156,6 +178,11 @@ typedef struct _RadioConfig_UserPreferences { bool debug_log_enabled; pb_size_t ignore_incoming_count; uint32_t ignore_incoming[3]; + bool serialplugin_enabled; + bool serialplugin_echo; + uint32_t serialplugin_rxd; + uint32_t serialplugin_txd; + uint32_t serialplugin_timeout; } RadioConfig_UserPreferences; typedef struct _RouteDiscovery { @@ -265,6 +292,10 @@ typedef struct _ToRadio { #define _RegionCode_MAX RegionCode_TW #define _RegionCode_ARRAYSIZE ((RegionCode)(RegionCode_TW+1)) +#define _ChargeCurrent_MIN ChargeCurrent_MAUnset +#define _ChargeCurrent_MAX ChargeCurrent_MA1320 +#define _ChargeCurrent_ARRAYSIZE ((ChargeCurrent)(ChargeCurrent_MA1320+1)) + #define _GpsOperation_MIN GpsOperation_GpsOpUnset #define _GpsOperation_MAX GpsOperation_GpsOpDisabled #define _GpsOperation_ARRAYSIZE ((GpsOperation)(GpsOperation_GpsOpDisabled+1)) @@ -299,7 +330,7 @@ extern "C" { #define MeshPacket_init_default {0, 0, 0, {SubPacket_init_default}, 0, 0, 0, 0, 0, 0} #define ChannelSettings_init_default {0, _ChannelSettings_ModemConfig_MIN, {0, {0}}, "", 0, 0, 0, 0, 0, 0, 0} #define RadioConfig_init_default {false, RadioConfig_UserPreferences_init_default, false, ChannelSettings_init_default} -#define RadioConfig_UserPreferences_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "", "", 0, _RegionCode_MIN, _LocationSharing_MIN, _GpsOperation_MIN, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}} +#define RadioConfig_UserPreferences_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "", "", 0, _RegionCode_MIN, _ChargeCurrent_MIN, _LocationSharing_MIN, _GpsOperation_MIN, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0, 0, 0, 0, 0} #define NodeInfo_init_default {0, false, User_init_default, false, Position_init_default, 0, 0} #define MyNodeInfo_init_default {0, 0, 0, "", "", "", _CriticalErrorCode_MIN, 0, 0, 0, 0, 0, 0, 0} #define LogRecord_init_default {"", 0, "", _LogRecord_Level_MIN} @@ -313,7 +344,7 @@ extern "C" { #define MeshPacket_init_zero {0, 0, 0, {SubPacket_init_zero}, 0, 0, 0, 0, 0, 0} #define ChannelSettings_init_zero {0, _ChannelSettings_ModemConfig_MIN, {0, {0}}, "", 0, 0, 0, 0, 0, 0, 0} #define RadioConfig_init_zero {false, RadioConfig_UserPreferences_init_zero, false, ChannelSettings_init_zero} -#define RadioConfig_UserPreferences_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "", "", 0, _RegionCode_MIN, _LocationSharing_MIN, _GpsOperation_MIN, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}} +#define RadioConfig_UserPreferences_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "", "", 0, _RegionCode_MIN, _ChargeCurrent_MIN, _LocationSharing_MIN, _GpsOperation_MIN, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0, 0, 0, 0, 0} #define NodeInfo_init_zero {0, false, User_init_zero, false, Position_init_zero, 0, 0} #define MyNodeInfo_init_zero {0, 0, 0, "", "", "", _CriticalErrorCode_MIN, 0, 0, 0, 0, 0, 0, 0} #define LogRecord_init_zero {"", 0, "", _LogRecord_Level_MIN} @@ -371,6 +402,7 @@ extern "C" { #define RadioConfig_UserPreferences_wifi_password_tag 13 #define RadioConfig_UserPreferences_wifi_ap_mode_tag 14 #define RadioConfig_UserPreferences_region_tag 15 +#define RadioConfig_UserPreferences_charge_current_tag 16 #define RadioConfig_UserPreferences_location_share_tag 32 #define RadioConfig_UserPreferences_gps_operation_tag 33 #define RadioConfig_UserPreferences_gps_update_interval_tag 34 @@ -381,6 +413,11 @@ extern "C" { #define RadioConfig_UserPreferences_factory_reset_tag 100 #define RadioConfig_UserPreferences_debug_log_enabled_tag 101 #define RadioConfig_UserPreferences_ignore_incoming_tag 103 +#define RadioConfig_UserPreferences_serialplugin_enabled_tag 120 +#define RadioConfig_UserPreferences_serialplugin_echo_tag 121 +#define RadioConfig_UserPreferences_serialplugin_rxd_tag 122 +#define RadioConfig_UserPreferences_serialplugin_txd_tag 123 +#define RadioConfig_UserPreferences_serialplugin_timeout_tag 124 #define RouteDiscovery_route_tag 2 #define User_id_tag 1 #define User_long_name_tag 2 @@ -533,6 +570,7 @@ X(a, STATIC, SINGULAR, STRING, wifi_ssid, 12) \ X(a, STATIC, SINGULAR, STRING, wifi_password, 13) \ X(a, STATIC, SINGULAR, BOOL, wifi_ap_mode, 14) \ X(a, STATIC, SINGULAR, UENUM, region, 15) \ +X(a, STATIC, SINGULAR, UENUM, charge_current, 16) \ X(a, STATIC, SINGULAR, UENUM, location_share, 32) \ X(a, STATIC, SINGULAR, UENUM, gps_operation, 33) \ X(a, STATIC, SINGULAR, UINT32, gps_update_interval, 34) \ @@ -542,7 +580,12 @@ X(a, STATIC, SINGULAR, BOOL, is_low_power, 38) \ X(a, STATIC, SINGULAR, BOOL, fixed_position, 39) \ X(a, STATIC, SINGULAR, BOOL, factory_reset, 100) \ X(a, STATIC, SINGULAR, BOOL, debug_log_enabled, 101) \ -X(a, STATIC, REPEATED, UINT32, ignore_incoming, 103) +X(a, STATIC, REPEATED, UINT32, ignore_incoming, 103) \ +X(a, STATIC, SINGULAR, BOOL, serialplugin_enabled, 120) \ +X(a, STATIC, SINGULAR, BOOL, serialplugin_echo, 121) \ +X(a, STATIC, SINGULAR, UINT32, serialplugin_rxd, 122) \ +X(a, STATIC, SINGULAR, UINT32, serialplugin_txd, 123) \ +X(a, STATIC, SINGULAR, UINT32, serialplugin_timeout, 124) #define RadioConfig_UserPreferences_CALLBACK NULL #define RadioConfig_UserPreferences_DEFAULT NULL @@ -654,13 +697,13 @@ extern const pb_msgdesc_t ToRadio_msg; #define SubPacket_size 275 #define MeshPacket_size 320 #define ChannelSettings_size 95 -#define RadioConfig_size 319 -#define RadioConfig_UserPreferences_size 219 +#define RadioConfig_size 349 +#define RadioConfig_UserPreferences_size 249 #define NodeInfo_size 132 #define MyNodeInfo_size 106 #define LogRecord_size 81 -#define FromRadio_size 329 -#define ToRadio_size 323 +#define FromRadio_size 358 +#define ToRadio_size 353 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/mesh/generated/portnums.pb.h b/src/mesh/generated/portnums.pb.h index bef19c12..91c988fd 100644 --- a/src/mesh/generated/portnums.pb.h +++ b/src/mesh/generated/portnums.pb.h @@ -18,13 +18,15 @@ typedef enum _PortNum { PortNum_NODEINFO_APP = 4, PortNum_REPLY_APP = 32, PortNum_IP_TUNNEL_APP = 33, - PortNum_PRIVATE_APP = 256 + PortNum_SERIAL_APP = 64, + PortNum_PRIVATE_APP = 256, + PortNum_ATAK_FORWARDER = 257 } PortNum; /* Helper constants for enums */ #define _PortNum_MIN PortNum_UNKNOWN_APP -#define _PortNum_MAX PortNum_PRIVATE_APP -#define _PortNum_ARRAYSIZE ((PortNum)(PortNum_PRIVATE_APP+1)) +#define _PortNum_MAX PortNum_ATAK_FORWARDER +#define _PortNum_ARRAYSIZE ((PortNum)(PortNum_ATAK_FORWARDER+1)) #ifdef __cplusplus diff --git a/src/mesh/http/ContentHandler.cpp b/src/mesh/http/ContentHandler.cpp new file mode 100644 index 00000000..5f79702f --- /dev/null +++ b/src/mesh/http/ContentHandler.cpp @@ -0,0 +1,1073 @@ +#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 "power.h" +#include "sleep.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", "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"); + res->setHeader("Access-Control-Allow-Origin", "*"); + res->setHeader("Access-Control-Allow-Methods", "GET"); + + } 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("}");
+}
+
+// --------
+
+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->setHeader("Access-Control-Allow-Origin", "*"); + res->setHeader("Access-Control-Allow-Methods", "GET"); + + // res->println(""); + res->println("\n"); +} + +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"); + + ESP.restart(); +} + +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; + + 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("Access-Control-Allow-Origin", "*"); + res->setHeader("Access-Control-Allow-Methods", "GET"); + // 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/ContentHandler.h b/src/mesh/http/ContentHandler.h new file mode 100644 index 00000000..8e22faef --- /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 f94b9816..ca3fee3c 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 1c1b5a82..2633de52 100644 --- a/src/mesh/http/WebServer.cpp +++ b/src/mesh/http/WebServer.cpp @@ -1,16 +1,11 @@ #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 +13,7 @@ #include "esp_task_wdt.h" #endif + // Persistant Data Storage #include Preferences prefs; @@ -44,50 +40,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; - -// 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() { @@ -108,9 +72,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(); } } @@ -251,69 +216,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(); @@ -326,911 +229,3 @@ 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(); -} - -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(); -} - -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) -{ - - // 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"); -} - -/* - 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) -{ - 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 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->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("\"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)
-{
-    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("}");
-}
-
-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 b01d5960..aacb2e00 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
 {
 
diff --git a/src/plugins/Plugins.cpp b/src/plugins/Plugins.cpp
index eee8280c..096b9cf5 100644
--- a/src/plugins/Plugins.cpp
+++ b/src/plugins/Plugins.cpp
@@ -1,14 +1,15 @@
 #include "plugins/NodeInfoPlugin.h"
 #include "plugins/PositionPlugin.h"
+#include "plugins/RemoteHardwarePlugin.h"
 #include "plugins/ReplyPlugin.h"
 #include "plugins/SerialPlugin.h"
-#include "plugins/RemoteHardwarePlugin.h"
 #include "plugins/TextMessagePlugin.h"
 
 /**
  * Create plugin instances here.  If you are adding a new plugin, you must 'new' it here (or somewhere else)
  */
-void setupPlugins() {
+void setupPlugins()
+{
     nodeInfoPlugin = new NodeInfoPlugin();
     positionPlugin = new PositionPlugin();
     textMessagePlugin = new TextMessagePlugin();
@@ -18,5 +19,11 @@ void setupPlugins() {
 
     new RemoteHardwarePlugin();
     new ReplyPlugin();
+
+#ifndef NO_ESP32
+    // Only run on an esp32 based device.
+
     new SerialPlugin(); // Maintained by MC Hamster (Jm Casler) jm@casler.org
+#endif
+
 }
\ No newline at end of file
diff --git a/src/plugins/SerialPlugin.cpp b/src/plugins/SerialPlugin.cpp
index 82c8baf8..47f27908 100644
--- a/src/plugins/SerialPlugin.cpp
+++ b/src/plugins/SerialPlugin.cpp
@@ -10,43 +10,38 @@
 
 /*
     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.
 
-    Originally designed for lora32 v1.0
-        Manufacture Info: http://www.lilygo.cn/prod_view.aspx?TypeId=50003&Id=1133&FId=t3:50003:3
-        Pin Mapping:      http://ae01.alicdn.com/kf/HTB1fLBcxkSWBuNjSszdq6zeSpXaJ.jpg
-
-    This will probably and most likely work on other esp32 devices, given possible change the RX/TX
-        selection.
+        Default is to use RX GPIO 16 and TX GPIO 17.
 
     Need help with this plugin? Post your question on the Meshtastic Discourse:
        https://meshtastic.discourse.group
 
     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.
-        3) Set SERIALPLUGIN_TIMEOUT to the amount of time to wait before we consider
+        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:
+                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):
-        * Add check for esp32 and only build code sections for esp32.
-        * 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.
 
     KNOWN PROBLEMS
         * Until the plugin is initilized by the startup sequence, the TX pin is in a floating
           state. Device connected to that pin may see this as "noise".
-        * This will not work on the NRF or Linux target.
+        * Will not work on NRF and the Linux device targets.
 
 
 */
@@ -57,9 +52,7 @@
 #define SERIALPLUGIN_STRING_MAX Constants_DATA_PAYLOAD_LEN
 #define SERIALPLUGIN_TIMEOUT 250
 #define SERIALPLUGIN_BAUD 38400
-#define SERIALPLUGIN_ENABLED 0
-#define SERIALPLUGIN_ECHO 0
-#define SERIALPLUGIN_ACK 0
+#define SERIALPLUGIN_ACK 1
 
 SerialPlugin *serialPlugin;
 SerialPluginRadio *serialPluginRadio;
@@ -70,43 +63,68 @@ char serialStringChar[Constants_DATA_PAYLOAD_LEN];
 
 int32_t SerialPlugin::runOnce()
 {
-#ifdef NO_ESP32
+#ifndef NO_ESP32
 
-#if SERIALPLUGIN_ENABLED == 1
+    /*
+        Uncomment the preferences below if you want to use the plugin
+        without having to configure it from the PythonAPI or WebUI.
+    */
 
-    if (firstTime) {
+    // radioConfig.preferences.serialplugin_enabled = 1;
+    // radioConfig.preferences.serialplugin_rxd = 35;
+    // radioConfig.preferences.serialplugin_txd = 15;
+    // radioConfig.preferences.serialplugin_timeout = 1000;
+    // radioConfig.preferences.serialplugin_echo = 1;
 
-        // Interface with the serial peripheral from in here.
-        DEBUG_MSG("Initilizing serial peripheral interface\n");
+    if (radioConfig.preferences.serialplugin_enabled) {
 
-        Serial2.begin(SERIALPLUGIN_BAUD, SERIAL_8N1, RXD2, TXD2);
-        Serial2.setTimeout(SERIALPLUGIN_TIMEOUT); // Number of MS to wait to set the timeout for the string.
-        Serial2.setRxBufferSize(SERIALPLUGIN_RX_BUFFER);
+        if (firstTime) {
 
-        serialPluginRadio = new SerialPluginRadio();
+            // Interface with the serial peripheral from in here.
+            DEBUG_MSG("Initializing serial peripheral interface\n");
 
-        firstTime = 0;
+            if (radioConfig.preferences.serialplugin_rxd && radioConfig.preferences.serialplugin_txd) {
+                Serial2.begin(SERIALPLUGIN_BAUD, SERIAL_8N1, radioConfig.preferences.serialplugin_rxd,
+                              radioConfig.preferences.serialplugin_txd);
 
-    } else {
-        String serialString;
+            } else {
+                Serial2.begin(SERIALPLUGIN_BAUD, SERIAL_8N1, RXD2, TXD2);
+            }
 
-        while (Serial2.available()) {
-            serialString = Serial2.readString();
-            serialString.toCharArray(serialStringChar, Constants_DATA_PAYLOAD_LEN);
+            if (radioConfig.preferences.serialplugin_timeout) {
+                Serial2.setTimeout(
+                    radioConfig.preferences.serialplugin_timeout); // Number of MS to wait to set the timeout for the string.
 
-            serialPluginRadio->sendPayload();
+            } else {
+                Serial2.setTimeout(SERIALPLUGIN_TIMEOUT); // Number of MS to wait to set the timeout for the string.
+            }
 
-            DEBUG_MSG("Received: %s\n", serialStringChar);
+            Serial2.setRxBufferSize(SERIALPLUGIN_RX_BUFFER);
+
+            serialPluginRadio = new SerialPluginRadio();
+
+            firstTime = 0;
+
+        } else {
+            String serialString;
+
+            while (Serial2.available()) {
+                serialString = Serial2.readString();
+                serialString.toCharArray(serialStringChar, Constants_DATA_PAYLOAD_LEN);
+
+                serialPluginRadio->sendPayload();
+
+                DEBUG_MSG("Received: %s\n", serialStringChar);
+            }
         }
+
+        return (10);
+    } else {
+        DEBUG_MSG("Serial Plugin Disabled\n");
+
+        return (INT32_MAX);
     }
 
-    return (10);
-#else
-    DEBUG_MSG("Serial Plugin Disabled\n");
-
-    return (INT32_MAX);
-#endif
-
 #endif
 }
 
@@ -134,44 +152,42 @@ void SerialPluginRadio::sendPayload(NodeNum dest, bool wantReplies)
 
 bool SerialPluginRadio::handleReceived(const MeshPacket &mp)
 {
+#ifndef NO_ESP32
 
-#ifdef NO_ESP32
+    if (radioConfig.preferences.serialplugin_enabled) {
 
-#if SERIALPLUGIN_ENABLED == 1
+        auto &p = mp.decoded.data;
+        // DEBUG_MSG("Received text msg self=0x%0x, from=0x%0x, to=0x%0x, id=%d, msg=%.*s\n",
+        //          nodeDB.getNodeNum(), mp.from, mp.to, mp.id, p.payload.size, p.payload.bytes);
 
-    auto &p = mp.decoded.data;
-    // DEBUG_MSG("Received text msg self=0x%0x, from=0x%0x, to=0x%0x, id=%d, msg=%.*s\n", nodeDB.getNodeNum(),
-    //          mp.from, mp.to, mp.id, p.payload.size, p.payload.bytes);
+        if (mp.from == nodeDB.getNodeNum()) {
 
-    if (mp.from == nodeDB.getNodeNum()) {
+            /*
+             * If radioConfig.preferences.serialplugin_echo is true, then echo the packets that are sent out back to the TX
+             * of the serial interface.
+             */
+            if (radioConfig.preferences.serialplugin_echo) {
 
-        /*
-         * If SERIALPLUGIN_ECHO is true, then echo the packets that are sent out back to the TX
-         * of the serial interface.
-         */
-        if (SERIALPLUGIN_ECHO) {
-
-            // For some reason, we get the packet back twice when we send out of the radio.
-            //   TODO: need to find out why.
-            if (lastRxID != mp.id) {
-                lastRxID = mp.id;
-                // DEBUG_MSG("* * Message came this device\n");
-                // Serial2.println("* * Message came this device");
-                Serial2.printf("%s", p.payload.bytes);
+                // For some reason, we get the packet back twice when we send out of the radio.
+                //   TODO: need to find out why.
+                if (lastRxID != mp.id) {
+                    lastRxID = mp.id;
+                    // DEBUG_MSG("* * Message came this device\n");
+                    // Serial2.println("* * Message came this device");
+                    Serial2.printf("%s", p.payload.bytes);
+                }
             }
+
+        } else {
+            // DEBUG_MSG("* * Message came from the mesh\n");
+            // Serial2.println("* * Message came from the mesh");
+            Serial2.printf("%s", p.payload.bytes);
         }
 
     } else {
-        // DEBUG_MSG("* * Message came from the mesh\n");
-        // Serial2.println("* * Message came from the mesh");
-        Serial2.printf("%s", p.payload.bytes);
+        DEBUG_MSG("Serial Plugin Disabled\n");
     }
 
-#else
-    DEBUG_MSG("Serial Plugin Disabled\n");
-
-#endif
-
 #endif
 
     return true; // Let others look at this message also if they want
diff --git a/src/plugins/SerialPlugin.h b/src/plugins/SerialPlugin.h
index c6d79f3e..4555c65b 100644
--- a/src/plugins/SerialPlugin.h
+++ b/src/plugins/SerialPlugin.h
@@ -33,8 +33,8 @@ class SerialPluginRadio : public SinglePortPlugin
               from the main code.
     */
 
-    SerialPluginRadio() : SinglePortPlugin("SerialPluginRadio", PortNum_TEXT_MESSAGE_APP) {}
-    // SerialPluginRadio() : SinglePortPlugin("SerialPluginRadio", PortNum_SERIAL_APP) {}
+    // SerialPluginRadio() : SinglePortPlugin("SerialPluginRadio", PortNum_TEXT_MESSAGE_APP) {}
+    SerialPluginRadio() : SinglePortPlugin("SerialPluginRadio", PortNum_SERIAL_APP) {}
 
     /**
      * Send our payload into the mesh
diff --git a/version.properties b/version.properties
index ac4a0363..556f3f84 100644
--- a/version.properties
+++ b/version.properties
@@ -1,4 +1,4 @@
 [VERSION]  
 major = 1  
 minor = 1
-build = 32
+build = 33