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/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 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; 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/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());
 }
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
+
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