diff --git a/README.md b/README.md index 851a1b2e..bd914356 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ We currently support three models of radios. - 3D printable cases - [T-Beam V0](https://www.thingiverse.com/thing:3773717) (GPS and LoRa antenna misaligned if GPS placed as pictured) - [T-Beam V1 (SMA-antenna)](https://www.thingiverse.com/thing:3830711) + - [T-Beam V1 (SMA-antenna)](https://www.thingiverse.com/thing:4677388) (Mounting option for larger GPS antenna but LoRa antenna enclosed) - [T-Beam V1 (IPEX-antenna)](https://www.thingiverse.com/thing:4587297) (GPS and LoRa antenna misaligned if GPS placed as pictured) - [T-Beam V1 (IPEX-antenna)](https://www.thingiverse.com/thing:4589651) - [T-Beam V1 (IPEX-antenna)](https://www.thingiverse.com/thing:4619981) (GPS and LoRa antenna misaligned if GPS placed as pictured) diff --git a/platformio.ini b/platformio.ini index 50d381c8..14e6ddbd 100644 --- a/platformio.ini +++ b/platformio.ini @@ -38,8 +38,8 @@ build_flags = -Wno-missing-field-initializers -Isrc -Isrc/mesh -Isrc/gps -Ilib/n ;upload_port = /dev/ttyUSB0 ;monitor_port = /dev/ttyUSB0 -;upload_port = /dev/cu.SLAB_USBtoUART -;monitor_port = /dev/cu.SLAB_USBtoUART +upload_port = /dev/cu.SLAB_USBtoUART +monitor_port = /dev/cu.SLAB_USBtoUART ; the default is esptool ; upload_protocol = esp-prog diff --git a/proto b/proto index 323b814f..ce422b7c 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit 323b814f4392ae0f9c42a0f14557c6b9333efce3 +Subproject commit ce422b7c448906c6fee3eef64bbd41adfbc990f0 diff --git a/src/Power.cpp b/src/Power.cpp index ba908e3f..3d185cb2 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -268,7 +268,9 @@ 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_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 0 // Not connected diff --git a/src/airtime.cpp b/src/airtime.cpp new file mode 100644 index 00000000..ec0bf8c8 --- /dev/null +++ b/src/airtime.cpp @@ -0,0 +1,88 @@ +#include "airtime.h" +#include + +#define periodsToLog 48 + +// A reminder that there are 3600 seconds in an hour so I don't have +// to keep googling it. +// This can be changed to a smaller number to speed up testing. +// +uint32_t secondsPerPeriod = 3600; +uint32_t lastMillis = 0; +uint32_t secSinceBoot = 0; + +// Don't read out of this directly. Use the helper functions. +struct airtimeStruct { + uint16_t periodTX[periodsToLog]; + uint16_t periodRX[periodsToLog]; + uint16_t periodRX_ALL[periodsToLog]; + uint8_t lastPeriodIndex; +} airtimes; + +void logAirtime(reportTypes reportType, uint32_t airtime_ms) +{ + + if (reportType == TX_LOG) { + airtimes.periodTX[0] = airtimes.periodTX[0] + round(airtime_ms / 1000); + } else if (reportType == RX_LOG) { + airtimes.periodRX[0] = airtimes.periodRX[0] + round(airtime_ms / 1000); + } else if (reportType == RX_ALL_LOG) { + airtimes.periodRX_ALL[0] = airtimes.periodRX_ALL[0] + round(airtime_ms / 1000); + } else { + // Unknown report type + } +} + +uint8_t currentPeriodIndex() +{ + return ((getSecondsSinceBoot() / secondsPerPeriod) % periodsToLog); +} + +void airtimeCalculator() +{ + if (millis() - lastMillis > 1000) { + lastMillis = millis(); + secSinceBoot++; + if (airtimes.lastPeriodIndex != currentPeriodIndex()) { + for (int i = periodsToLog - 2; i >= 0; --i) { + airtimes.periodTX[i + 1] = airtimes.periodTX[i]; + airtimes.periodRX[i + 1] = airtimes.periodRX[i]; + airtimes.periodRX_ALL[i + 1] = airtimes.periodRX_ALL[i]; + } + airtimes.periodTX[0] = 0; + airtimes.periodRX[0] = 0; + airtimes.periodRX_ALL[0] = 0; + + airtimes.lastPeriodIndex = currentPeriodIndex(); + } + } +} + +uint16_t *airtimeReport(reportTypes reportType) +{ + // currentHourIndexReset(); + + if (reportType == TX_LOG) { + return airtimes.periodTX; + } else if (reportType == RX_LOG) { + return airtimes.periodRX; + } else if (reportType == RX_ALL_LOG) { + return airtimes.periodRX_ALL; + } + return 0; +} + +uint8_t getPeriodsToLog() +{ + return periodsToLog; +} + +uint32_t getSecondsPerPeriod() +{ + return secondsPerPeriod; +} + +uint32_t getSecondsSinceBoot() +{ + return secSinceBoot; +} diff --git a/src/airtime.h b/src/airtime.h new file mode 100644 index 00000000..4cfbb82c --- /dev/null +++ b/src/airtime.h @@ -0,0 +1,38 @@ +#pragma once + +#include "configuration.h" +#include +#include + +/* + TX_LOG - Time on air this device has transmitted + + RX_LOG - Time on air used by valid and routable mesh packets, does not include + TX air time + + RX_ALL_LOG - Time of all received lora packets. This includes packets that are not + for meshtastic devices. Does not include TX air time. + + Example analytics: + + TX_LOG + RX_LOG = Total air time for a perticular meshtastic channel. + + TX_LOG + RX_LOG = Total air time for a perticular meshtastic channel, including + other lora radios. + + RX_ALL_LOG - RX_LOG = Other lora radios on our frequency channel. +*/ +enum reportTypes { TX_LOG, RX_LOG, RX_ALL_LOG }; + +void logAirtime(reportTypes reportType, uint32_t airtime_ms); + +void airtimeCalculator(); + +uint8_t currentPeriodIndex(); +uint8_t getPeriodsToLog(); + +uint32_t getSecondsSinceBoot(); + +uint16_t *airtimeReport(reportTypes reportType); + +uint32_t getSecondsPerPeriod(); \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 64851022..22648b13 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,6 +5,7 @@ #include "NodeDB.h" #include "PowerFSM.h" #include "UBloxGPS.h" +#include "airtime.h" #include "configuration.h" #include "error.h" #include "power.h" @@ -569,4 +570,7 @@ void loop() // We want to sleep as long as possible here - because it saves power mainDelay.delay(delayMsec); // if (didWake) DEBUG_MSG("wake!\n"); + + // Handles cleanup for the airtime calculator. + airtimeCalculator(); } diff --git a/src/mesh/RadioInterface.h b/src/mesh/RadioInterface.h index e5253d7a..b76d3035 100644 --- a/src/mesh/RadioInterface.h +++ b/src/mesh/RadioInterface.h @@ -5,6 +5,7 @@ #include "MeshTypes.h" #include "Observer.h" #include "PointerQueue.h" +#include "airtime.h" #include "mesh.pb.h" #define MAX_TX_QUEUE 16 // max number of packets which can be waiting for transmission @@ -36,7 +37,7 @@ typedef struct { * * This defines the SOLE API for talking to radios (because soon we will have alternate radio implementations) */ -class RadioInterface +class RadioInterface { friend class MeshRadio; // for debugging we let that class touch pool PointerQueue *rxDest = NULL; diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index 9a5ec536..f4ceeded 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -58,7 +58,6 @@ void INTERRUPT_ATTR RadioLibInterface::isrTxLevel0() */ RadioLibInterface *RadioLibInterface::instance; - /** Could we send right now (i.e. either not actively receving or transmitting)? */ bool RadioLibInterface::canSendImmediately() { @@ -96,6 +95,10 @@ ErrorCode RadioLibInterface::send(MeshPacket *p) return res; } + // Count the packet toward our TX airtime utilization. + // We only count it if it can be added to the TX queue. + logAirtime(TX_LOG, xmitMsec); + // We want all sending/receiving to be done by our daemon thread, We use a delay here because this packet might have been sent // in response to a packet we just received. So we want to make sure the other side has had a chance to reconfigure its radio startTransmitTimer(true); @@ -205,12 +208,16 @@ void RadioLibInterface::completeSending() void RadioLibInterface::handleReceiveInterrupt() { + uint32_t xmitMsec; assert(isReceiving); isReceiving = false; // read the number of actually received bytes size_t length = iface->getPacketLength(); + xmitMsec = getPacketTime(length); + logAirtime(RX_ALL_LOG, xmitMsec); + int state = iface->readData(radiobuf, length); if (state != ERR_NONE) { DEBUG_MSG("ignoring received packet due to error=%d\n", state); @@ -250,11 +257,14 @@ void RadioLibInterface::handleReceiveInterrupt() printPacket("Lora RX", mp); + xmitMsec = getPacketTime(mp); + logAirtime(RX_LOG, xmitMsec); + deliverToReceiver(mp); } } } - + /** start an immediate transmit */ void RadioLibInterface::startSend(MeshPacket *txp) { diff --git a/src/meshwifi/meshhttp.cpp b/src/meshwifi/meshhttp.cpp index b6953c32..d6d2e22b 100644 --- a/src/meshwifi/meshhttp.cpp +++ b/src/meshwifi/meshhttp.cpp @@ -1,10 +1,11 @@ #include "meshwifi/meshhttp.h" #include "NodeDB.h" +#include "PowerFSM.h" +#include "airtime.h" #include "configuration.h" #include "main.h" #include "meshhttpStatic.h" #include "meshwifi/meshwifi.h" -#include "PowerFSM.h" #include "sleep.h" #include #include @@ -64,6 +65,7 @@ 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); @@ -80,7 +82,7 @@ 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"}, + {".css", "text/css"}, {".ico", "image/vnd.microsoft.icon"}, {".svg", "image/svg+xml"}, {"", ""}}; void handleWebResponse() @@ -248,6 +250,7 @@ void initWebServer() 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); @@ -267,6 +270,7 @@ void initWebServer() secureServer->registerNode(nodeJsonBlinkLED); secureServer->registerNode(nodeJsonSpiffsBrowseStatic); secureServer->registerNode(nodeJsonDelete); + secureServer->registerNode(nodeJsonReport); secureServer->setDefaultNode(node404); secureServer->addMiddleware(&middlewareSpeedUp240); @@ -287,6 +291,7 @@ void initWebServer() insecureServer->registerNode(nodeJsonBlinkLED); insecureServer->registerNode(nodeJsonSpiffsBrowseStatic); insecureServer->registerNode(nodeJsonDelete); + insecureServer->registerNode(nodeJsonReport); insecureServer->setDefaultNode(node404); insecureServer->addMiddleware(&middlewareSpeedUp160); @@ -458,26 +463,26 @@ void handleSpiffsBrowseStatic(HTTPRequest *req, HTTPResponse *res) void handleSpiffsDeleteStatic(HTTPRequest *req, HTTPResponse *res) { - ResourceParameters *params = req->getParams(); - std::string paramValDelete; + ResourceParameters *params = req->getParams(); + std::string paramValDelete; - res->setHeader("Content-Type", "application/json"); - if (params->getQueryParameter("delete", paramValDelete)) { - std::string pathDelete = "/" + paramValDelete; - if (SPIFFS.remove(pathDelete.c_str())) { - Serial.println(pathDelete.c_str()); - res->println("{"); - res->println("\"status\": \"ok\""); - res->println("}"); - return; - } else { - Serial.println(pathDelete.c_str()); - res->println("{"); - res->println("\"status\": \"Error\""); - res->println("}"); - return; + res->setHeader("Content-Type", "application/json"); + if (params->getQueryParameter("delete", paramValDelete)) { + std::string pathDelete = "/" + paramValDelete; + if (SPIFFS.remove(pathDelete.c_str())) { + Serial.println(pathDelete.c_str()); + res->println("{"); + res->println("\"status\": \"ok\""); + res->println("}"); + return; + } else { + Serial.println(pathDelete.c_str()); + res->println("{"); + res->println("\"status\": \"Error\""); + res->println("}"); + return; + } } - } } void handleStaticBrowse(HTTPRequest *req, HTTPResponse *res) @@ -1041,6 +1046,98 @@ 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\": {");
+
+    uint16_t *logArray;
+
+    res->print("\"tx_log\": [");
+
+    logArray = airtimeReport(TX_LOG);
+    for (int i = 0; i < getPeriodsToLog(); i++) {
+        uint16_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++) {
+        uint16_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++) {
+        uint16_t tmp;
+        tmp = *(logArray + i);
+        res->printf("%d", tmp);
+        if (i != getPeriodsToLog() - 1) {
+            res->print(", ");
+        }
+    }
+
+    res->println("],");
+    res->printf("\"seconds_since_boot\": %u,\n", getSecondsSinceBoot());
+    res->printf("\"seconds_per_period\": %u,\n", getSecondsPerPeriod());
+    res->printf("\"periods_to_log\": %u\n", getPeriodsToLog());
+
+    res->println("},");
+
+    res->println("\"wifi\": {");
+
+    res->println("\"rssi\": " + String(WiFi.RSSI()) + ",");
+
+    if (radioConfig.preferences.wifi_ap_mode || isSoftAPForced()) {
+        res->println("\"ip\": \"" + String(WiFi.softAPIP().toString().c_str()) + "\"");
+    } else {
+        res->println("\"ip\": \"" + String(WiFi.localIP().toString().c_str()) + "\"");
+    }
+
+
+    res->println("},");
+
+    res->println("\"test\": 123");
+
+    res->println("},");
+
+    res->println("\"status\": \"ok\"");
+    res->println("}");
+}
+
 void handleScanNetworks(HTTPRequest *req, HTTPResponse *res)
 {
     res->setHeader("Content-Type", "application/json");