diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 4bb9a2bbe..bf05765ed 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -785,6 +785,84 @@ void MyMesh::removeNeighbor(const uint8_t *pubkey, int key_len) { #endif } +void MyMesh::formatStatsReply(char* reply, const char* subcommand) { + RepeaterStats stats; + stats.batt_milli_volts = board.getBattMilliVolts(); + stats.curr_tx_queue_len = _mgr->getOutboundCount(0xFFFFFFFF); + stats.noise_floor = (int16_t)_radio->getNoiseFloor(); + stats.last_rssi = (int16_t)radio_driver.getLastRSSI(); + stats.n_packets_recv = radio_driver.getPacketsRecv(); + stats.n_packets_sent = radio_driver.getPacketsSent(); + stats.total_air_time_secs = getTotalAirTime() / 1000; + stats.total_up_time_secs = _ms->getMillis() / 1000; + stats.n_sent_flood = getNumSentFlood(); + stats.n_sent_direct = getNumSentDirect(); + stats.n_recv_flood = getNumRecvFlood(); + stats.n_recv_direct = getNumRecvDirect(); + stats.err_events = _err_flags; + stats.last_snr = (int16_t)(radio_driver.getLastSNR() * 4); + stats.n_direct_dups = ((SimpleMeshTables *)getTables())->getNumDirectDups(); + stats.n_flood_dups = ((SimpleMeshTables *)getTables())->getNumFloodDups(); + stats.total_rx_air_time_secs = getReceiveAirTime() / 1000; + + if (subcommand) { + if (strcmp(subcommand, "sent") == 0) { + snprintf(reply, 128, "{\"sent\":\"%lu/%lu\"}", stats.n_sent_flood, stats.n_sent_direct); + } else if (strcmp(subcommand, "recv") == 0) { + snprintf(reply, 128, "{\"recv\":\"%lu/%lu\"}", stats.n_recv_flood, stats.n_recv_direct); + } else if (strcmp(subcommand, "uptime") == 0) { + snprintf(reply, 128, "{\"uptime\":%lu}", stats.total_up_time_secs); + } else if (strcmp(subcommand, "batt") == 0) { + snprintf(reply, 128, "{\"batt\":%u}", stats.batt_milli_volts); + } else if (strcmp(subcommand, "queue") == 0) { + snprintf(reply, 128, "{\"queue\":%u}", stats.curr_tx_queue_len); + } else if (strcmp(subcommand, "noise") == 0) { + snprintf(reply, 128, "{\"noise\":%d}", stats.noise_floor); + } else if (strcmp(subcommand, "rssi") == 0) { + snprintf(reply, 128, "{\"rssi\":%d}", stats.last_rssi); + } else if (strcmp(subcommand, "snr") == 0) { + snprintf(reply, 128, "{\"snr\":%.2f}", stats.last_snr / 4.0f); + } else if (strcmp(subcommand, "errors") == 0) { + snprintf(reply, 128, "{\"errors\":%u}", stats.err_events); + } else if (strcmp(subcommand, "airtime") == 0) { + snprintf(reply, 128, "{\"airtime\":%lu}", stats.total_air_time_secs); + } else if (strcmp(subcommand, "rxairtime") == 0) { + snprintf(reply, 128, "{\"rxairtime\":%lu}", stats.total_rx_air_time_secs); + } else { + snprintf(reply, 128, "{\"error\":\"Unknown stat: %s\"}", subcommand+1); + } + return; + } + + // Default: full stats as JSON + snprintf(reply, 512, + "{" + "\"sent\":\"%lu/%lu\"," + "\"recv\":\"%lu/%lu\"," + "\"uptime\":%lu," + "\"batt\":%u," + "\"queue\":%u," + "\"noise\":%d," + "\"rssi\":%d," + "\"snr\":%.2f," + "\"errors\":%u," + "\"airtime\":%lu," + "\"rxairtime\":%lu" + "}", + stats.n_sent_flood, stats.n_sent_direct, + stats.n_recv_flood, stats.n_recv_direct, + stats.total_up_time_secs, + stats.batt_milli_volts, + stats.curr_tx_queue_len, + stats.noise_floor, + stats.last_rssi, + stats.last_snr / 4.0f, + stats.err_events, + stats.total_air_time_secs, + stats.total_rx_air_time_secs + ); +} + void MyMesh::saveIdentity(const mesh::LocalIdentity &new_id) { self_id = new_id; #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) @@ -861,6 +939,18 @@ void MyMesh::loop() { mesh::Mesh::loop(); + // Handle serial CLI frames if serial interface is enabled + if (_serial) { + size_t len = _serial->checkRecvFrame(cmd_frame); + if (len > 0) { + char reply[160] = {0}; + handleCommand(0, (char*)cmd_frame, reply); + if (reply[0]) { + _serial->writeFrame((const uint8_t*)reply, strlen(reply)); + } + } + } + if (next_flood_advert && millisHasNowPassed(next_flood_advert)) { mesh::Packet *pkt = createSelfAdvert(); if (pkt) sendFlood(pkt); diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index c45c141dc..66db10b9d 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -31,6 +31,7 @@ #include #include #include +#include #ifdef WITH_BRIDGE extern AbstractBridge* bridge; @@ -100,6 +101,8 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { #elif defined(WITH_ESPNOW_BRIDGE) ESPNowBridge bridge; #endif + BaseSerialInterface *_serial = nullptr; + uint8_t cmd_frame[MAX_PACKET_PAYLOAD+1]; void putNeighbour(const mesh::Identity& id, uint32_t timestamp, float snr); uint8_t handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data); @@ -180,6 +183,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { void dumpLogFile() override; void setTxPower(uint8_t power_dbm) override; void formatNeighborsReply(char *reply) override; + void formatStatsReply(char* reply, const char* subcommand) override; void removeNeighbor(const uint8_t* pubkey, int key_len) override; mesh::LocalIdentity& getSelfId() override { return self_id; } @@ -189,6 +193,11 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { void handleCommand(uint32_t sender_timestamp, char* command, char* reply); void loop(); + void startInterface(BaseSerialInterface &serial) { + _serial = &serial; + serial.enable(); + } + #if defined(WITH_BRIDGE) void setBridgeState(bool enable) override { if (enable == bridge.isRunning()) return; diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index 7db0e7d47..c1de480df 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -19,6 +19,29 @@ void halt() { static char command[160]; +#ifdef ESP32 + #ifdef WIFI_SSID + #include + SerialWifiInterface serial_interface; + #ifndef TCP_PORT + #define TCP_PORT 5000 + #endif + #elif defined(BLE_PIN_CODE) + #include + SerialBLEInterface serial_interface; + #elif defined(SERIAL_RX) + #include + ArduinoSerialInterface serial_interface; + HardwareSerial companion_serial(1); + #else + #include + ArduinoSerialInterface serial_interface; + #endif +#else + #include + ArduinoSerialInterface serial_interface; +#endif + void setup() { Serial.begin(115200); delay(1000); @@ -82,33 +105,29 @@ void setup() { // send out initial Advertisement to the mesh the_mesh.sendSelfAdvertisement(16000); + +#ifdef ESP32 +#ifdef WIFI_SSID + WiFi.begin(WIFI_SSID, WIFI_PWD); + serial_interface.begin(TCP_PORT); +#elif defined(BLE_PIN_CODE) + char dev_name[32+16]; + sprintf(dev_name, "%s%s", BLE_NAME_PREFIX, the_mesh.getNodeName()); + serial_interface.begin(dev_name, the_mesh.getBLEPin()); +#elif defined(SERIAL_RX) + companion_serial.setPins(SERIAL_RX, SERIAL_TX); + companion_serial.begin(115200); + serial_interface.begin(companion_serial); +#else + serial_interface.begin(Serial); +#endif +#else + serial_interface.begin(Serial); +#endif + the_mesh.startInterface(serial_interface); } void loop() { - int len = strlen(command); - while (Serial.available() && len < sizeof(command)-1) { - char c = Serial.read(); - if (c != '\n') { - command[len++] = c; - command[len] = 0; - } - Serial.print(c); - } - if (len == sizeof(command)-1) { // command buffer full - command[sizeof(command)-1] = '\r'; - } - - if (len > 0 && command[len - 1] == '\r') { // received complete line - command[len - 1] = 0; // replace newline with C string null terminator - char reply[160]; - the_mesh.handleCommand(0, command, reply); // NOTE: there is no sender_timestamp via serial! - if (reply[0]) { - Serial.print(" -> "); Serial.println(reply); - } - - command[0] = 0; // reset command buffer - } - the_mesh.loop(); sensors.loop(); #ifdef DISPLAY_CLASS diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 87f20f5ad..85345e254 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -245,6 +245,12 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch } else if (memcmp(command, "clear stats", 11) == 0) { _callbacks->clearStats(); strcpy(reply, "(OK - stats reset)"); + } else if (memcmp(command, "stats", 5) == 0) { + if (command[5] == '.' && command[6]) { + _callbacks->formatStatsReply(reply, &command[6]); + } else { + _callbacks->formatStatsReply(reply, nullptr); + } /* * GET commands */ @@ -664,6 +670,8 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch _callbacks->dumpLogFile(); strcpy(reply, " EOF"); } else { - strcpy(reply, "Unknown command"); + sprintf(reply, "Unknown command: %s", command); } + // Clear the command buffer after processing + if (command) memset((char*)command, 0, strlen(command) + 1); } diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index ce5670164..afbac1d8b 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -63,6 +63,7 @@ class CommonCLICallbacks { virtual void dumpLogFile() = 0; virtual void setTxPower(uint8_t power_dbm) = 0; virtual void formatNeighborsReply(char *reply) = 0; + virtual void formatStatsReply(char *reply, const char* subcommand) = 0; virtual void removeNeighbor(const uint8_t* pubkey, int key_len) { // no op by default }; diff --git a/variants/heltec_v4/platformio.ini b/variants/heltec_v4/platformio.ini index 2c06b9781..0ba432b71 100644 --- a/variants/heltec_v4/platformio.ini +++ b/variants/heltec_v4/platformio.ini @@ -57,11 +57,15 @@ build_flags = -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=50 + -D WIFI_DEBUG_LOGGING=1 + -D WIFI_SSID='"myssid"' + -D WIFI_PWD='"mypwd"' ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_lora32_v4.build_src_filter} + +<../examples/simple_repeater> + + lib_deps = ${Heltec_lora32_v4.lib_deps} ${esp32_ota.lib_deps} diff --git a/variants/lilygo_tbeam_SX1276/platformio.ini b/variants/lilygo_tbeam_SX1276/platformio.ini index 3562c40e9..4d28320a5 100644 --- a/variants/lilygo_tbeam_SX1276/platformio.ini +++ b/variants/lilygo_tbeam_SX1276/platformio.ini @@ -72,6 +72,28 @@ lib_deps = ${LilyGo_TBeam_SX1276.lib_deps} ${esp32_ota.lib_deps} +[env:Tbeam_SX1276_repeater_wifi] +extends = LilyGo_TBeam_SX1276 +build_flags = + ${LilyGo_TBeam_SX1276.build_flags} + -D ADVERT_NAME='"Tbeam Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 + -D PERSISTANT_GPS=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 + -D WIFI_DEBUG_LOGGING=1 + -D WIFI_SSID='"myssid"' + -D WIFI_PWD='"mypwd"' +build_src_filter = ${LilyGo_TBeam_SX1276.build_src_filter} + +<../examples/simple_repeater> + + +lib_deps = + ${LilyGo_TBeam_SX1276.lib_deps} + ${esp32_ota.lib_deps} + ; [env:Tbeam_SX1276_repeater_bridge_rs232] ; extends = LilyGo_TBeam_SX1276 ; build_flags =