From dbf0569e29bbe3ed088f5f9f16b4d7f4f4b17581 Mon Sep 17 00:00:00 2001 From: Jm Date: Thu, 14 Jan 2021 22:36:51 -0800 Subject: [PATCH 01/26] Update comments for serial pins recommended by @ryguy --- src/plugins/SerialPlugin.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/plugins/SerialPlugin.cpp b/src/plugins/SerialPlugin.cpp index cc355c06..fa32ceb6 100644 --- a/src/plugins/SerialPlugin.cpp +++ b/src/plugins/SerialPlugin.cpp @@ -27,6 +27,9 @@ 1) Enable the plugin by setting SERIALPLUGIN_ENABLED to 1. 2) Set the pins (RXD2 / TXD2) 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 your packet as "done". 4) (Optional) In SerialPlugin.h set the port to PortNum_TEXT_MESSAGE_APP if you want to @@ -45,6 +48,7 @@ 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". + * Will not work on NRF and the Linux device targets. */ From cafe00e46391534e94bb23bf69fc957f93f6abab Mon Sep 17 00:00:00 2001 From: Jm Date: Sat, 16 Jan 2021 19:40:47 -0800 Subject: [PATCH 02/26] Update of serialplugin --- src/plugins/SerialPlugin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/SerialPlugin.cpp b/src/plugins/SerialPlugin.cpp index 82c8baf8..861cbf50 100644 --- a/src/plugins/SerialPlugin.cpp +++ b/src/plugins/SerialPlugin.cpp @@ -59,7 +59,7 @@ #define SERIALPLUGIN_BAUD 38400 #define SERIALPLUGIN_ENABLED 0 #define SERIALPLUGIN_ECHO 0 -#define SERIALPLUGIN_ACK 0 +#define SERIALPLUGIN_ACK 1 SerialPlugin *serialPlugin; SerialPluginRadio *serialPluginRadio; From cd84f2867cbf09201e1b3a1e443176c7e48361cc Mon Sep 17 00:00:00 2001 From: Jm Date: Sat, 16 Jan 2021 20:00:37 -0800 Subject: [PATCH 03/26] Updated submodule proto --- proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proto b/proto index 75078afe..7b46033b 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit 75078afe43934f4ce15ef86ebc6950658a170145 +Subproject commit 7b46033b0acb953e6dc2a09e94cc31d613b6241f From 3dcdf372d7202f437f3a4b5c6085d08017f0beba Mon Sep 17 00:00:00 2001 From: Jm Date: Sat, 16 Jan 2021 20:12:31 -0800 Subject: [PATCH 04/26] add remote update --- proto | 2 +- sdk-nrfxlib | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/proto b/proto index 7b46033b..ff4ff06d 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit 7b46033b0acb953e6dc2a09e94cc31d613b6241f +Subproject commit ff4ff06d5ee7d5c63d2dd1b7f2c5f8e29a36be11 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 From f435086a5a3ce554f4899aa3f4ca3f960055e7b9 Mon Sep 17 00:00:00 2001 From: Jm Date: Sat, 16 Jan 2021 20:50:58 -0800 Subject: [PATCH 05/26] Update generated protobufs --- src/mesh/generated/deviceonly.pb.h | 2 +- src/mesh/generated/mesh.pb.c | 1 + src/mesh/generated/mesh.pb.h | 56 ++++++++++++++++++++++++++---- src/mesh/generated/portnums.pb.h | 8 +++-- 4 files changed, 56 insertions(+), 11 deletions(-) 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..815fb2e2 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, @@ -145,6 +165,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 +177,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 +291,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 +329,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 +343,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 +401,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 +412,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 +569,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 +579,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 +696,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 From b9d025dd582133b873ee86e65f527b325918b06d Mon Sep 17 00:00:00 2001 From: Jm Date: Sat, 16 Jan 2021 20:53:45 -0800 Subject: [PATCH 06/26] Missed adding the proto file --- proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proto b/proto index ff4ff06d..855da870 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit ff4ff06d5ee7d5c63d2dd1b7f2c5f8e29a36be11 +Subproject commit 855da8701edbb19818069ad8545d5b9f030bb33f From d7f26493a5d5ee66582adadfdace6da34f5372d6 Mon Sep 17 00:00:00 2001 From: Jm Date: Sat, 16 Jan 2021 22:27:33 -0800 Subject: [PATCH 07/26] Update to SerialPlugin to take advantage of the configs --- src/plugins/Plugins.cpp | 11 +++- src/plugins/SerialPlugin.cpp | 108 ++++++++++++++++++----------------- 2 files changed, 64 insertions(+), 55 deletions(-) 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 861cbf50..1d7f5c62 100644 --- a/src/plugins/SerialPlugin.cpp +++ b/src/plugins/SerialPlugin.cpp @@ -25,7 +25,7 @@ Basic Usage: - 1) Enable the plugin by setting SERIALPLUGIN_ENABLED to 1. + 1) Enable the plugin by setting serialplugin_enabled in the device configuration 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 your packet as "done". @@ -57,8 +57,6 @@ #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 1 SerialPlugin *serialPlugin; @@ -70,43 +68,48 @@ char serialStringChar[Constants_DATA_PAYLOAD_LEN]; int32_t SerialPlugin::runOnce() { -#ifdef NO_ESP32 +#ifndef NO_ESP32 + if (radioConfig.preferences.serialplugin_enabled) { -#if SERIALPLUGIN_ENABLED == 1 + if (firstTime) { - if (firstTime) { + // Interface with the serial peripheral from in here. + DEBUG_MSG("Initilizing serial peripheral interface\n"); - // Interface with the serial peripheral from in here. - DEBUG_MSG("Initilizing serial peripheral interface\n"); + if (radioConfig.preferences.serialplugin_rxd && radioConfig.preferences.serialplugin_txd) { + Serial2.begin(SERIALPLUGIN_BAUD, SERIAL_8N1, radioConfig.preferences.serialplugin_rxd, + radioConfig.preferences.serialplugin_txd); - 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); + } else { + 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); - serialPluginRadio = new SerialPluginRadio(); + serialPluginRadio = new SerialPluginRadio(); - firstTime = 0; + firstTime = 0; - } else { - String serialString; + } else { + String serialString; - while (Serial2.available()) { - serialString = Serial2.readString(); - serialString.toCharArray(serialStringChar, Constants_DATA_PAYLOAD_LEN); + while (Serial2.available()) { + serialString = Serial2.readString(); + serialString.toCharArray(serialStringChar, Constants_DATA_PAYLOAD_LEN); - serialPluginRadio->sendPayload(); + serialPluginRadio->sendPayload(); - DEBUG_MSG("Received: %s\n", serialStringChar); + 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 } @@ -135,43 +138,42 @@ void SerialPluginRadio::sendPayload(NodeNum dest, bool wantReplies) bool SerialPluginRadio::handleReceived(const MeshPacket &mp) { -#ifdef NO_ESP32 +#ifndef NO_ESP32 -#if SERIALPLUGIN_ENABLED == 1 + if (radioConfig.preferences.serialplugin_enabled) { - 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 SERIALPLUGIN_ECHO is true, then echo the packets that are sent out back to the TX - * of the serial interface. - */ - if (SERIALPLUGIN_ECHO) { + /* + * 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) { - // 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 From 79532210e8a421c8dc74af2c05e464a82ac8f46d Mon Sep 17 00:00:00 2001 From: Jm Date: Sat, 16 Jan 2021 22:31:56 -0800 Subject: [PATCH 08/26] SerialPlugin - Update comments with better instructions --- src/plugins/SerialPlugin.cpp | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/plugins/SerialPlugin.cpp b/src/plugins/SerialPlugin.cpp index 1d7f5c62..e7053837 100644 --- a/src/plugins/SerialPlugin.cpp +++ b/src/plugins/SerialPlugin.cpp @@ -13,12 +13,7 @@ An overly simplistic 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 @@ -26,7 +21,8 @@ Basic Usage: 1) Enable the plugin by setting serialplugin_enabled in the device configuration to 1. - 2) Set the pins (RXD2 / TXD2) for your preferred RX and TX GPIO pins. + 2) Set the pins (serialplugin_rxd / serialplugin_txd) for your preferred RX and TX GPIO pins. + If you're using a tbeam 1.0 or newer, recommend to use RX 35 / TX 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 From 73ae151971b39d7fa1611e6b38188ce20e423dba Mon Sep 17 00:00:00 2001 From: Jm Date: Sat, 16 Jan 2021 22:39:28 -0800 Subject: [PATCH 09/26] Added serialplugin_timeout to SerialPlugin --- src/plugins/SerialPlugin.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/plugins/SerialPlugin.cpp b/src/plugins/SerialPlugin.cpp index bded71c7..236b29f9 100644 --- a/src/plugins/SerialPlugin.cpp +++ b/src/plugins/SerialPlugin.cpp @@ -80,7 +80,15 @@ int32_t SerialPlugin::runOnce() } else { Serial2.begin(SERIALPLUGIN_BAUD, SERIAL_8N1, RXD2, TXD2); } - Serial2.setTimeout(SERIALPLUGIN_TIMEOUT); // Number of MS to wait to set the timeout for the string. + + if (radioConfig.preferences.serialplugin_timeout) { + Serial2.setTimeout( + radioConfig.preferences.serialplugin_timeout); // Number of MS to wait to set the timeout for the string. + + } else { + Serial2.setTimeout(SERIALPLUGIN_TIMEOUT); // Number of MS to wait to set the timeout for the string. + } + Serial2.setRxBufferSize(SERIALPLUGIN_RX_BUFFER); serialPluginRadio = new SerialPluginRadio(); From 20b8d2c4a58e674ed5baf77f5eadc114fc24de7d Mon Sep 17 00:00:00 2001 From: Jm Date: Sat, 16 Jan 2021 22:41:33 -0800 Subject: [PATCH 10/26] Set the port to PortNum_SERIAL_APP --- src/plugins/SerialPlugin.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 From c0711fde699e7c1dd3ee6309312f1625409d49ce Mon Sep 17 00:00:00 2001 From: Jm Date: Sat, 16 Jan 2021 23:10:08 -0800 Subject: [PATCH 11/26] #615 Allow rate for tbeam battery charger to be configurable --- src/Power.cpp | 40 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/src/Power.cpp b/src/Power.cpp index 3d185cb2..26c40f92 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 From 125eb2b7849233fafb8f931d18db03124fcdec62 Mon Sep 17 00:00:00 2001 From: Jm Date: Sun, 17 Jan 2021 00:11:26 -0800 Subject: [PATCH 12/26] Fix for build fail on NRF --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 1f962a8d..2033b2fa 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} - - - - From 8e8264efb08a39000c635ace9fb676ca09ab56c4 Mon Sep 17 00:00:00 2001 From: Jm Date: Sun, 17 Jan 2021 00:29:29 -0800 Subject: [PATCH 13/26] #635 - Added memory usage statistics --- src/mesh/http/WebServer.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/mesh/http/WebServer.cpp b/src/mesh/http/WebServer.cpp index 1c1b5a82..50330ca2 100644 --- a/src/mesh/http/WebServer.cpp +++ b/src/mesh/http/WebServer.cpp @@ -1169,8 +1169,18 @@ void handleReport(HTTPRequest *req, HTTPResponse *res) res->println("},"); + res->println("\"memory\": {"); + res->printf("\"heap_total\": %u,\n", ESP.getHeapSize()); + res->printf("\"heap_free\": %u,\n", ESP.getFreeHeap()); + res->printf("\"psram_total\": %s,\n", ESP.getPsramSize()); + res->printf("\"psram_free\": %s,\n", ESP.getFreePsram()); + res->print("\"spiffs_total\" : " + String(SPIFFS.totalBytes()) + ","); + res->print("\"spiffs_used\" : " + String(SPIFFS.usedBytes()) + ","); + res->print("\"spiffs_free\" : " + String(SPIFFS.totalBytes() - SPIFFS.usedBytes())); + res->println("},"); + res->println("\"power\": {"); -#define BoolToString(x) ((x)?"true":"false") +#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())); From c0d27e2ce99cde4bae5540f324872d75ec92778f Mon Sep 17 00:00:00 2001 From: Jm Date: Sun, 17 Jan 2021 10:30:34 -0800 Subject: [PATCH 14/26] #635 Added web_request_count and fixed printf of psram --- src/mesh/http/WebServer.cpp | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/mesh/http/WebServer.cpp b/src/mesh/http/WebServer.cpp index 50330ca2..193508b7 100644 --- a/src/mesh/http/WebServer.cpp +++ b/src/mesh/http/WebServer.cpp @@ -80,6 +80,8 @@ 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"}, @@ -337,6 +339,8 @@ void middlewareSpeedUp240(HTTPRequest *req, HTTPResponse *res, std::function next) @@ -355,6 +359,8 @@ void middlewareSpeedUp160(HTTPRequest *req, HTTPResponse *res, std::functionprintln("\"wifi\": {"); + res->printf("\"web_request_count\": %d,\n", numberOfRequests); res->println("\"rssi\": " + String(WiFi.RSSI()) + ","); if (radioConfig.preferences.wifi_ap_mode || isSoftAPForced()) { @@ -1170,13 +1177,13 @@ void handleReport(HTTPRequest *req, HTTPResponse *res) res->println("},"); res->println("\"memory\": {"); - res->printf("\"heap_total\": %u,\n", ESP.getHeapSize()); - res->printf("\"heap_free\": %u,\n", ESP.getFreeHeap()); - res->printf("\"psram_total\": %s,\n", ESP.getPsramSize()); - res->printf("\"psram_free\": %s,\n", ESP.getFreePsram()); - res->print("\"spiffs_total\" : " + String(SPIFFS.totalBytes()) + ","); - res->print("\"spiffs_used\" : " + String(SPIFFS.usedBytes()) + ","); - res->print("\"spiffs_free\" : " + String(SPIFFS.totalBytes() - SPIFFS.usedBytes())); + 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\": {"); From fa8cc741418c7ca7251ad278c6a7fda119eed28a Mon Sep 17 00:00:00 2001 From: Jm Date: Sun, 17 Jan 2021 15:40:25 -0800 Subject: [PATCH 15/26] Update to Serial Plugin to make it easy to override the device configuration --- src/plugins/SerialPlugin.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/plugins/SerialPlugin.cpp b/src/plugins/SerialPlugin.cpp index 236b29f9..7e764ee8 100644 --- a/src/plugins/SerialPlugin.cpp +++ b/src/plugins/SerialPlugin.cpp @@ -66,6 +66,17 @@ char serialStringChar[Constants_DATA_PAYLOAD_LEN]; int32_t SerialPlugin::runOnce() { #ifndef NO_ESP32 + + /* + Uncomment the preferences below if you want to use the plugin + without having to configure it from the PythonAPI or WebUI. + */ + + // radioConfig.preferences.serialplugin_enabled = 1; + // radioConfig.preferences.serialplugin_rxd = 35; + // radioConfig.preferences.serialplugin_txd = 15; + // radioConfig.preferences.serialplugin_timeout = 1000; + if (radioConfig.preferences.serialplugin_enabled) { if (firstTime) { @@ -147,8 +158,8 @@ bool SerialPluginRadio::handleReceived(const MeshPacket &mp) if (radioConfig.preferences.serialplugin_enabled) { 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); + // 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()) { From b5f50efdcdd84f36583019b45269b0e24d21868b Mon Sep 17 00:00:00 2001 From: Jm Date: Mon, 18 Jan 2021 10:43:15 -0800 Subject: [PATCH 16/26] #647 - Fix for admin mode being forced on boards without hardware pullup --- src/main.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) 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); From 79dad8ec8c8dfe4bb749dd9bffdd7f3506755cd1 Mon Sep 17 00:00:00 2001 From: Andrew Mark Date: Tue, 19 Jan 2021 18:21:54 -0800 Subject: [PATCH 17/26] Set critical error and reboot when radio fails to generate tx IRQ --- src/mesh/NodeDB.cpp | 7 +++++++ src/mesh/RadioLibInterface.cpp | 10 ++++++++++ src/mesh/generated/mesh.pb.h | 3 ++- 3 files changed, 19 insertions(+), 1 deletion(-) 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..f0eb53b2 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,18 @@ 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); + if (busyTx && (millis() - lastTxStart > 65000)) // After 5s more, reboot + ESP.restart(); + } if (busyRx) DEBUG_MSG("Can not send yet, busyRx\n"); return false; diff --git a/src/mesh/generated/mesh.pb.h b/src/mesh/generated/mesh.pb.h index 815fb2e2..50ae14a6 100644 --- a/src/mesh/generated/mesh.pb.h +++ b/src/mesh/generated/mesh.pb.h @@ -76,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 { From dd511588a2f29328cc7141901eddfcd1ed713806 Mon Sep 17 00:00:00 2001 From: Andrew Mark Date: Tue, 19 Jan 2021 20:13:19 -0800 Subject: [PATCH 18/26] Oops, let's only try to reboot ESP32 when there's an ESP32 --- src/mesh/RadioLibInterface.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index f0eb53b2..f2fafef7 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -77,8 +77,10 @@ bool RadioLibInterface::canSendImmediately() 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"); From e4fdf26dc750d3b81f9024265fe783af478b1ae5 Mon Sep 17 00:00:00 2001 From: Jm Date: Tue, 19 Jan 2021 21:26:23 -0800 Subject: [PATCH 19/26] #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 00000000..df47776b --- /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 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 193508b7..da64cd23 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 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
 {
 

From 2a47819fd68eed677d21fbb046eb84b91a1ee3eb Mon Sep 17 00:00:00 2001
From: Jm 
Date: Tue, 19 Jan 2021 21:38:17 -0800
Subject: [PATCH 20/26] #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 df47776b..af00de68 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 da64cd23..2633de52 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 21/26] 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 7e764ee8..47f27908 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 22/26] 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 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 From 9db5f9ff6773b36e1e45ca8ba9aa6f9f2362c321 Mon Sep 17 00:00:00 2001 From: Sacha Weatherstone Date: Sat, 23 Jan 2021 17:42:15 +1100 Subject: [PATCH 23/26] 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 af00de68..a87b7c9e 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 24/26] 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 a87b7c9e..5f79702f 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 25/26] 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 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) | From 532b06c2808d4e85ceedc3dd102e42b0aefe9be3 Mon Sep 17 00:00:00 2001 From: Jm Date: Mon, 25 Jan 2021 17:01:47 -0800 Subject: [PATCH 26/26] 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 ac4a0363..556f3f84 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 1 minor = 1 -build = 32 +build = 33