From 7c4bb809778df20dd3fbf10219335074eb47f8d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sat, 2 Apr 2022 12:52:50 +0200 Subject: [PATCH 1/7] Variable GPS Thread Timing, keep default of 100 msec --- src/configuration.h | 4 ++++ src/gps/GPS.cpp | 4 ++-- variants/t-echo/variant.h | 2 ++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/configuration.h b/src/configuration.h index 6b9fc67a6..2597e2a72 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -155,6 +155,10 @@ along with this program. If not, see . #define GPS_BAUDRATE 9600 +#ifndef GPS_THREAD_INTERVAL +#define GPS_THREAD_INTERVAL 100 +#endif + #if defined(TBEAM_V10) // This string must exactly match the case used in release file names or the android updater won't work #define HW_VENDOR HardwareModel_TBEAM diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 8af6bd779..8c0f7571d 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -51,7 +51,7 @@ bool GPS::setup() { // Master power for the GPS #ifdef PIN_GPS_EN - digitalWrite(PIN_GPS_EN, PIN_GPS_EN); + digitalWrite(PIN_GPS_EN, 1); pinMode(PIN_GPS_EN, OUTPUT); #endif @@ -268,7 +268,7 @@ int32_t GPS::runOnce() // 9600bps is approx 1 byte per msec, so considering our buffer size we never need to wake more often than 200ms // if not awake we can run super infrquently (once every 5 secs?) to see if we need to wake. - return isAwake ? 100 : 5000; + return isAwake ? GPS_THREAD_INTERVAL : 5000; } void GPS::forceWake(bool on) diff --git a/variants/t-echo/variant.h b/variants/t-echo/variant.h index c5b767d05..4f8946e95 100644 --- a/variants/t-echo/variant.h +++ b/variants/t-echo/variant.h @@ -177,6 +177,8 @@ External serial flash WP25R1635FZUIL0 #define PIN_GPS_TX (32 + 9) // This is for bits going TOWARDS the CPU #define PIN_GPS_RX (32 + 8) // This is for bits going TOWARDS the GPS +#define GPS_THREAD_INTERVAL 50 + #define PIN_SERIAL1_RX PIN_GPS_TX #define PIN_SERIAL1_TX PIN_GPS_RX From fd407a2a9e3163bf88119a673454a2d8f77140c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sat, 2 Apr 2022 12:57:10 +0200 Subject: [PATCH 2/7] GPS Pin Change for V2 and V1 too. --- variants/heltec_v1/variant.h | 2 +- variants/heltec_v2/variant.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/variants/heltec_v1/variant.h b/variants/heltec_v1/variant.h index 25cb14070..7e1af42d1 100644 --- a/variants/heltec_v1/variant.h +++ b/variants/heltec_v1/variant.h @@ -3,7 +3,7 @@ #undef GPS_RX_PIN #undef GPS_TX_PIN #define GPS_RX_PIN 36 -#define GPS_TX_PIN 37 +#define GPS_TX_PIN 34 #ifndef USE_JTAG // gpio15 is TDO for JTAG, so no I2C on this board while doing jtag #define I2C_SDA 4 // I2C pins for this board diff --git a/variants/heltec_v2/variant.h b/variants/heltec_v2/variant.h index 76b7314b5..e5981fd63 100644 --- a/variants/heltec_v2/variant.h +++ b/variants/heltec_v2/variant.h @@ -3,7 +3,7 @@ #undef GPS_RX_PIN #undef GPS_TX_PIN #define GPS_RX_PIN 36 -#define GPS_TX_PIN 37 +#define GPS_TX_PIN 33 #ifndef USE_JTAG // gpio15 is TDO for JTAG, so no I2C on this board while doing jtag #define I2C_SDA 4 // I2C pins for this board From 364d81e9063757b8bde4d2f6e1f696f37e5bf048 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sat, 2 Apr 2022 16:02:26 +0200 Subject: [PATCH 3/7] adjust for serial wait time during nRF52 bootup --- src/graphics/Screen.cpp | 3 ++- src/main.cpp | 4 ++++ src/main.h | 2 ++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index cf6b26c39..e30367f5b 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -854,8 +854,9 @@ int32_t Screen::runOnce() } // Show boot screen for first 3 seconds, then switch to normal operation. + // serialSinceMsec adjusts for additional serial wait time during nRF52 bootup static bool showingBootScreen = true; - if (showingBootScreen && (millis() > 5000)) { + if (showingBootScreen && (millis() > (5000 + serialSinceMsec))) { DEBUG_MSG("Done with boot screen...\n"); stopBootScreen(); showingBootScreen = false; diff --git a/src/main.cpp b/src/main.cpp index d90bce00d..5a7887608 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -78,6 +78,8 @@ uint8_t cardkb_found; // The I2C address of the Faces Keyboard (if found) uint8_t faceskb_found; +uint32_t serialSinceMsec; + bool axp192_found; Router *router = NULL; // Users of router don't care what sort of subclass implements that API @@ -141,6 +143,8 @@ void setup() consoleInit(); // Set serial baud rate and init our mesh console } #endif + + serialSinceMsec = millis(); DEBUG_MSG("\n\n//\\ E S H T /\\ S T / C\n\n"); diff --git a/src/main.h b/src/main.h index c5fc62f32..21138d3f0 100644 --- a/src/main.h +++ b/src/main.h @@ -30,6 +30,8 @@ extern uint32_t timeLastPowered; extern uint32_t rebootAtMsec; extern uint32_t shutdownAtMsec; +extern uint32_t serialSinceMsec; + // If a thread does something that might need for it to be rescheduled ASAP it can set this flag // This will supress the current delay and instead try to run ASAP. extern bool runASAP; From e5b19fdf5f9efc102f1418db7fc7d64055052fa5 Mon Sep 17 00:00:00 2001 From: Jm Casler Date: Sun, 3 Apr 2022 19:05:56 -0700 Subject: [PATCH 4/7] Bump to 1.3.5 --- version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.properties b/version.properties index 8ec26fb45..df418fa9b 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 1 minor = 3 -build = 4 +build = 5 From 92d32f722d5853de153900b54ae2d7b771564aa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Mon, 4 Apr 2022 08:51:29 +0200 Subject: [PATCH 5/7] Add second scancode for SH1106 This display is found on the Nano G1 --- src/debug/i2cScan.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/debug/i2cScan.h b/src/debug/i2cScan.h index a99fa4508..7bb5eadd5 100644 --- a/src/debug/i2cScan.h +++ b/src/debug/i2cScan.h @@ -15,7 +15,7 @@ uint8_t oled_probe(byte addr) r = Wire.read(); } r &= 0x0f; - if (r == 0x08) { + if (r == 0x08 || r == 0x00) { o_probe = 2; // SH1106 } else if ( r == 0x03 || r == 0x06 || r == 0x07) { o_probe = 1; // SSD1306 From 0c600363c8d828e8f1cc4d9f36447ed5af74c05a Mon Sep 17 00:00:00 2001 From: neilhao Date: Mon, 4 Apr 2022 18:16:19 +0800 Subject: [PATCH 6/7] add nano g1 (#1351) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add nano g1 * Update platformio.ini * Update configuration.h * Revert platformio.ini to previous state * Update configuration.h * Update platformio.ini Co-authored-by: Thomas Göttgens --- platformio.ini | 3 ++- src/configuration.h | 4 ++++ src/mesh/generated/mesh.pb.h | 2 ++ variants/nano-g1/platformio.ini | 8 ++++++++ variants/nano-g1/variant.h | 30 ++++++++++++++++++++++++++++++ 5 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 variants/nano-g1/platformio.ini create mode 100644 variants/nano-g1/variant.h diff --git a/platformio.ini b/platformio.ini index 4fa73a94d..c91b95247 100644 --- a/platformio.ini +++ b/platformio.ini @@ -19,6 +19,7 @@ default_envs = tbeam ;default_envs = rak4631_5005 ;default_envs = rak4631_5005_eink ;default_envs = rak4631_19003 +;default_envs = nano-g1 ;default_envs = meshtastic-diy-v1 ;default_envs = meshtastic-diy-v1.1 @@ -157,4 +158,4 @@ extends = nrf52_base board = rak815 debug_tool = jlink upload_protocol = jlink -monitor_speed = 115200 \ No newline at end of file +monitor_speed = 115200 diff --git a/src/configuration.h b/src/configuration.h index 2597e2a72..d1b335ecb 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -240,6 +240,10 @@ along with this program. If not, see . #define HW_VENDOR HardwareModel_T_ECHO +#elif defined(NANO_G1) + +#define HW_VENDOR HardwareModel_NANO_G1 + #elif NRF52_SERIES #define HW_VENDOR HardwareModel_NRF52_UNKNOWN diff --git a/src/mesh/generated/mesh.pb.h b/src/mesh/generated/mesh.pb.h index eadcfa32a..653c36e2d 100644 --- a/src/mesh/generated/mesh.pb.h +++ b/src/mesh/generated/mesh.pb.h @@ -61,6 +61,8 @@ typedef enum _HardwareModel { HardwareModel_DIY_V1 = 39, /* RAK WisBlock ESP32 core: https://docs.rakwireless.com/Product-Categories/WisBlock/RAK11200/Overview/ */ HardwareModel_RAK11200 = 40, + /* B&Q Consulting Nano Edition G1: https://uniteng.com/wiki/doku.php?id=meshtastic:nano */ + HardwareModel_NANO_G1 = 41, /* Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits. */ HardwareModel_PRIVATE_HW = 255 } HardwareModel; diff --git a/variants/nano-g1/platformio.ini b/variants/nano-g1/platformio.ini new file mode 100644 index 000000000..a3107423e --- /dev/null +++ b/variants/nano-g1/platformio.ini @@ -0,0 +1,8 @@ +; The 1.0 release of the nano-g1 board +[env:nano-g1] +extends = esp32_base +board = ttgo-t-beam +lib_deps = + ${esp32_base.lib_deps} +build_flags = + ${esp32_base.build_flags} -D NANO_G1 -I variants/nano-g1 \ No newline at end of file diff --git a/variants/nano-g1/variant.h b/variants/nano-g1/variant.h new file mode 100644 index 000000000..0915013a0 --- /dev/null +++ b/variants/nano-g1/variant.h @@ -0,0 +1,30 @@ +// #define BUTTON_NEED_PULLUP // if set we need to turn on the internal CPU pullup during sleep + +#define I2C_SDA 21 +#define I2C_SCL 22 + +#define BUTTON_PIN 36 // The middle button GPIO on the Nano G1 +//#define BUTTON_PIN_ALT 13 // Alternate GPIO for an external button if needed. Does anyone use this? It is not documented anywhere. +#define EXT_NOTIFY_OUT 13 // Default pin to use for Ext Notify Module. + +// common pinout for their SX1262 vs RF95 modules - both can be enabled and we will probe at runtime for RF95 and if +// not found then probe for SX1262 +#define USE_RF95 +#define USE_SX1262 + +#define LORA_DIO0 26 // a No connect on the SX1262 module +#define LORA_RESET 23 +#define LORA_DIO1 33 // SX1262 IRQ +#define LORA_DIO2 32 // SX1262 BUSY +#define LORA_DIO3 // Not connected on PCB + +#ifdef USE_SX1262 +#define SX126X_CS RF95_NSS // FIXME - we really should define LORA_CS instead +#define SX126X_DIO1 LORA_DIO1 +#define SX126X_BUSY LORA_DIO2 +#define SX126X_RESET LORA_RESET +#define SX126X_E22 // Not really an E22 +// Internally the module hooks the SX1262-DIO2 in to control the TX/RX switch (which is the default for the sx1262interface +// code) +#endif + From 998c90d326731ab75a3f2d6eb946aed7c4f75cf7 Mon Sep 17 00:00:00 2001 From: Michael Kleinhenz Date: Mon, 4 Apr 2022 22:22:38 +0200 Subject: [PATCH 7/7] Use JSON library for Web UI REST Endpoints (#1340) * Updated rest endpoint json handling. * Fixes, typos corrected. Co-authored-by: Ben Meadors Co-authored-by: Sacha Weatherstone --- src/mesh/http/ContentHandler.cpp | 245 +++++++++++++++---------------- 1 file changed, 116 insertions(+), 129 deletions(-) diff --git a/src/mesh/http/ContentHandler.cpp b/src/mesh/http/ContentHandler.cpp index cd0bcfe90..754360d56 100644 --- a/src/mesh/http/ContentHandler.cpp +++ b/src/mesh/http/ContentHandler.cpp @@ -8,10 +8,11 @@ #include "mesh/http/WiFiAPClient.h" #include "power.h" #include "sleep.h" +#include #include #include #include -#include +#include #ifndef NO_ESP32 #include "esp_task_wdt.h" @@ -272,8 +273,6 @@ void handleAPIv1ToRadio(HTTPRequest *req, HTTPResponse *res) DEBUG_MSG("webAPI handleAPIv1ToRadio\n"); } -bool firstFile = 1; - void htmlDeleteDir(const char * dirname) { File root = FSCom.open(dirname); @@ -301,70 +300,72 @@ void htmlDeleteDir(const char * dirname) root.close(); } -void htmlListDir(HTTPResponse *res, const char * dirname, uint8_t levels) +std::vector>* htmlListDir(std::vector> *fileList, const char *dirname, uint8_t levels) { File root = FSCom.open(dirname); if(!root){ - return; + return NULL; } if(!root.isDirectory()){ - return; + return NULL; } + // iterate over the file list File file = root.openNextFile(); while(file){ if(file.isDirectory() && !String(file.name()).endsWith(".")) { if(levels){ - htmlListDir(res, file.name(), levels -1); + htmlListDir(fileList, file.name(), levels -1); } - } else { - if (firstFile) { - firstFile = 0; - } else { - res->println(","); - } - res->println("{"); + } else { + std::map thisFileMap; + thisFileMap[strdup("size")] = strdup(String(file.size()).c_str()); + thisFileMap[strdup("name")] = strdup(String(file.name()).substring(1).c_str()); if (String(file.name()).substring(1).endsWith(".gz")) { String modifiedFile = String(file.name()).substring(1); modifiedFile.remove((modifiedFile.length() - 3), 3); - res->print("\"nameModified\": \"" + modifiedFile + "\","); - res->print("\"name\": \"" + String(file.name()).substring(1) + "\","); - } else { - res->print("\"name\": \"" + String(file.name()).substring(1) + "\","); + thisFileMap[strdup("nameModified")] = strdup(modifiedFile.c_str()); } - res->print("\"size\": " + String(file.size())); - res->print("}"); + fileList->push_back(thisFileMap); } file.close(); file = root.openNextFile(); } root.close(); + return fileList; } void handleFsBrowseStatic(HTTPRequest *req, HTTPResponse *res) { - res->setHeader("Content-Type", "application/json"); res->setHeader("Access-Control-Allow-Origin", "*"); res->setHeader("Access-Control-Allow-Methods", "GET"); - res->println("{"); - res->println("\"data\": {"); - res->print("\"files\": ["); - htmlListDir(res, "/", 10); - res->print("],"); - res->print("\"filesystem\" : {"); - res->print("\"total\" : " + String(FSCom.totalBytes()) + ","); - res->print("\"used\" : " + String(FSCom.usedBytes()) + ","); - res->print("\"free\" : " + String(FSCom.totalBytes() - FSCom.usedBytes())); - res->println("}"); - res->println("},"); - res->println("\"status\": \"ok\""); - res->println("}"); -} + using namespace json11; + auto fileList = htmlListDir(new std::vector>(), "/", 10); + + // create json output structure + Json filesystemObj = Json::object{ + {"total", String(FSCom.totalBytes()).c_str()}, + {"used", String(FSCom.usedBytes()).c_str()}, + {"free", String(FSCom.totalBytes() - FSCom.usedBytes()).c_str()}, + }; + + Json jsonObjInner = Json::object{{"files", Json(*fileList)}, + {"filesystem", filesystemObj} + }; + + Json jsonObjOuter = Json::object{{"data", jsonObjInner}, {"status", "ok"}}; + + // serialize and write it to the stream + std::string jsonStr = jsonObjOuter.dump(); + res->print(jsonStr.c_str()); +} void handleFsDeleteStatic(HTTPRequest *req, HTTPResponse *res) { + using namespace json11; + ResourceParameters *params = req->getParams(); std::string paramValDelete; @@ -375,15 +376,15 @@ void handleFsDeleteStatic(HTTPRequest *req, HTTPResponse *res) std::string pathDelete = "/" + paramValDelete; if (FSCom.remove(pathDelete.c_str())) { Serial.println(pathDelete.c_str()); - res->println("{"); - res->println("\"status\": \"ok\""); - res->println("}"); + Json jsonObjOuter = Json::object{{"status", "ok"}}; + std::string jsonStr = jsonObjOuter.dump(); + res->print(jsonStr.c_str()); return; } else { Serial.println(pathDelete.c_str()); - res->println("{"); - res->println("\"status\": \"Error\""); - res->println("}"); + Json jsonObjOuter = Json::object{{"status", "Error"}}; + std::string jsonStr = jsonObjOuter.dump(); + res->print(jsonStr.c_str()); return; } } @@ -600,6 +601,7 @@ void handleFormUpload(HTTPRequest *req, HTTPResponse *res) void handleReport(HTTPRequest *req, HTTPResponse *res) { + using namespace json11; ResourceParameters *params = req->getParams(); std::string content; @@ -618,104 +620,89 @@ void handleReport(HTTPRequest *req, HTTPResponse *res) res->println("
");
     }
 
-    res->println("{");
-
-    res->println("\"data\": {");
-
-    res->println("\"airtime\": {");
-
+    // data->airtime->tx_log
+    std::vector txLogValues;
     uint32_t *logArray;
-
-    res->print("\"tx_log\": [");
-
     logArray = airTime->airtimeReport(TX_LOG);
     for (int i = 0; i < airTime->getPeriodsToLog(); i++) {
         uint32_t tmp;
         tmp = *(logArray + i);
-        res->printf("%d", tmp);
-        if (i != airTime->getPeriodsToLog() - 1) {
-            res->print(", ");
-        }
+        txLogValues.push_back(String(tmp));
     }
 
-    res->println("],");
-    res->print("\"rx_log\": [");
-
+    // data->airtime->rx_log
+    std::vector rxLogValues;
     logArray = airTime->airtimeReport(RX_LOG);
     for (int i = 0; i < airTime->getPeriodsToLog(); i++) {
         uint32_t tmp;
         tmp = *(logArray + i);
-        res->printf("%d", tmp);
-        if (i != airTime->getPeriodsToLog() - 1) {
-            res->print(", ");
-        }
+        rxLogValues.push_back(String(tmp));
     }
 
-    res->println("],");
-    res->print("\"rx_all_log\": [");
-
+    // data->airtime->rx_all_log
+    std::vector rxAllLogValues;
     logArray = airTime->airtimeReport(RX_ALL_LOG);
     for (int i = 0; i < airTime->getPeriodsToLog(); i++) {
         uint32_t tmp;
         tmp = *(logArray + i);
-        res->printf("%d", tmp);
-        if (i != airTime->getPeriodsToLog() - 1) {
-            res->print(", ");
-        }
+        rxAllLogValues.push_back(String(tmp));
     }
 
-    res->println("],");
-    res->printf("\"channel_utilization\": %3.2f%,\n", airTime->channelUtilizationPercent());
-    res->printf("\"utilization_tx\": %3.2f%,\n", airTime->utilizationTXPercent());
-    res->printf("\"seconds_since_boot\": %u,\n", airTime->getSecondsSinceBoot());
-    res->printf("\"seconds_per_period\": %u,\n", airTime->getSecondsPerPeriod());
-    res->printf("\"periods_to_log\": %u\n", airTime->getPeriodsToLog());
-
-    res->println("},");
-
-    res->println("\"wifi\": {");
-
-    res->println("\"rssi\": " + String(WiFi.RSSI()) + ",");
+    Json jsonObjAirtime = Json::object{
+        {"tx_log", Json(txLogValues)},
+        {"rx_log", Json(rxLogValues)},
+        {"rx_all_log", Json(rxAllLogValues)},
+        {"channel_utilization", Json(airTime->channelUtilizationPercent())},
+        {"utilization_tx", Json(airTime->utilizationTXPercent())},
+        {"seconds_since_boot", Json(int(airTime->getSecondsSinceBoot()))},
+        {"seconds_per_period", Json(int(airTime->getSecondsPerPeriod()))},
+        {"periods_to_log", Json(airTime->getPeriodsToLog())},
+    };
 
+    // data->wifi
+    String ipStr;
     if (radioConfig.preferences.wifi_ap_mode || isSoftAPForced()) {
-        res->println("\"ip\": \"" + String(WiFi.softAPIP().toString().c_str()) + "\"");
+        ipStr = String(WiFi.softAPIP().toString());
     } else {
-        res->println("\"ip\": \"" + String(WiFi.localIP().toString().c_str()) + "\"");
+        ipStr = String(WiFi.localIP().toString());
     }
+    Json jsonObjWifi = Json::object{
+        {"rssi", String(WiFi.RSSI())},
+        {"ip", ipStr.c_str()}
+    };
 
-    res->println("},");
+    // data->memory
+    Json jsonObjMemory = Json::object{{"heap_total", Json(int(ESP.getHeapSize()))},
+                                    {"heap_free", Json(int(ESP.getFreeHeap()))},
+                                    {"psram_total", Json(int(ESP.getPsramSize()))},
+                                    {"psram_free", Json(int(ESP.getFreePsram()))},
+                                    {"fs_total", String(FSCom.totalBytes()).c_str()},
+                                    {"fs_used", String(FSCom.usedBytes()).c_str()},
+                                    {"fs_free", String(FSCom.totalBytes() - FSCom.usedBytes()).c_str()}};
 
-    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("\"fs_total\" : " + String(FSCom.totalBytes()) + ",");
-    res->println("\"fs_used\" : " + String(FSCom.usedBytes()) + ",");
-    res->println("\"fs_free\" : " + String(FSCom.totalBytes() - FSCom.usedBytes()));
-    res->println("},");
+    // data->power
+    Json jsonObjPower = Json::object{{"battery_percent", Json(powerStatus->getBatteryChargePercent())},
+                                      {"battery_voltage_mv", Json(powerStatus->getBatteryVoltageMv())},
+                                      {"has_battery", BoolToString(powerStatus->getHasBattery())},
+                                      {"has_usb", BoolToString(powerStatus->getHasUSB())},
+                                      {"is_charging", BoolToString(powerStatus->getIsCharging())}};
 
-    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("},");
+    // data->device
+    Json jsonObjDevice = Json::object{{"reboot_counter", Json(int(myNodeInfo.reboot_count))}};
 
-    res->println("\"device\": {");
-    res->printf("\"reboot_counter\": %d\n", myNodeInfo.reboot_count);
-    res->println("},");
+    // data->radio
+    Json jsonObjRadio = Json::object{{"frequency", Json(RadioLibInterface::instance->getFreq())},
+                                     {"lora_channel", Json(int(RadioLibInterface::instance->getChannelNum()))}};
 
-    res->println("\"radio\": {");
-    res->printf("\"frequecy\": %f,\n", RadioLibInterface::instance->getFreq());
-    res->printf("\"lora_channel\": %d\n", RadioLibInterface::instance->getChannelNum());
-    res->println("}");
+    // collect data to inner data object
+    Json jsonObjInner = Json::object{{"airtime", jsonObjAirtime}, {"wifi", jsonObjWifi},     {"memory", jsonObjMemory},
+                                     {"power", jsonObjPower},     {"device", jsonObjDevice}, {"radio", jsonObjRadio}};
 
-    res->println("},");
-
-    res->println("\"status\": \"ok\"");
-    res->println("}");
+    // create json output structure
+    Json jsonObjOuter = Json::object{{"data", jsonObjInner}, {"status", "ok"}};
+    // serialize and write it to the stream
+    std::string jsonStr = jsonObjOuter.dump();
+    res->print(jsonStr.c_str());
 }
 
 /*
@@ -912,6 +899,8 @@ void handleRestart(HTTPRequest *req, HTTPResponse *res)
 
 void handleBlinkLED(HTTPRequest *req, HTTPResponse *res)
 {
+    using namespace json11;
+
     res->setHeader("Content-Type", "application/json");
     res->setHeader("Access-Control-Allow-Origin", "*");
     res->setHeader("Access-Control-Allow-Methods", "POST");
@@ -938,28 +927,25 @@ void handleBlinkLED(HTTPRequest *req, HTTPResponse *res)
         screen->blink();
     }
 
-    res->println("{");
-    res->println("\"status\": \"ok\"");
-    res->println("}");
+    Json jsonObjOuter = Json::object{{"status", "ok"}};
+    std::string jsonStr = jsonObjOuter.dump();
+    res->print(jsonStr.c_str());
 }
 
 void handleScanNetworks(HTTPRequest *req, HTTPResponse *res)
 {
+    using namespace json11;
+
     res->setHeader("Content-Type", "application/json");
     res->setHeader("Access-Control-Allow-Origin", "*");
     res->setHeader("Access-Control-Allow-Methods", "GET");
     // res->setHeader("Content-Type", "text/html");
 
     int n = WiFi.scanNetworks();
-    res->println("{");
-    res->println("\"data\": {");
-    if (n == 0) {
-        // No networks found.
-        res->println("\"networks\": []");
-
-    } else {
-        res->println("\"networks\": [");
 
+    // build list of network objects
+    std::vector networkObjs;
+    if (n > 0) {
         for (int i = 0; i < n; ++i) {
             char ssidArray[50];
             String ssidString = String(WiFi.SSID(i));
@@ -967,19 +953,20 @@ void handleScanNetworks(HTTPRequest *req, HTTPResponse *res)
             ssidString.toCharArray(ssidArray, 50);
 
             if (WiFi.encryptionType(i) != WIFI_AUTH_OPEN) {
-                res->printf("{\"ssid\": \"%s\",\"rssi\": %d}", ssidArray, WiFi.RSSI(i));
-                if (i != n - 1) {
-                    res->printf(",");
-                }
+                Json thisNetwork = Json::object{{"ssid", ssidArray}, {"rssi", WiFi.RSSI(i)}};
+                networkObjs.push_back(thisNetwork);
             }
             // 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("}");
+
+    // build output structure
+    Json jsonObjOuter = Json::object{{"data", networkObjs}, {"status", "ok"}};
+
+    // serialize and write it to the stream
+    std::string jsonStr = jsonObjOuter.dump();
+    res->print(jsonStr.c_str());
 }