diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index e37078ce..6170fdd9 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -2,6 +2,11 @@ #include #include "MyMesh.h" +#include + +#if MESH_PACKET_LOGGING && BLE_PACKET_LOGGING && (defined(NRF52_PLATFORM) || defined(ESP32)) + static BLELogInterface ble_log; +#endif #ifdef DISPLAY_CLASS #include "UITask.h" @@ -95,6 +100,11 @@ void setup() { the_mesh.begin(fs); +#if MESH_PACKET_LOGGING && BLE_PACKET_LOGGING && (defined(NRF52_PLATFORM) || defined(ESP32)) + ble_log.begin(the_mesh.getNodeName()); + the_mesh.setPacketLogStream(&ble_log); +#endif + #ifdef DISPLAY_CLASS ui_task.begin(the_mesh.getNodePrefs(), FIRMWARE_BUILD_DATE, FIRMWARE_VERSION); #endif @@ -153,6 +163,9 @@ void loop() { ui_task.loop(); #endif rtc_clock.tick(); +#if MESH_PACKET_LOGGING && BLE_PACKET_LOGGING && defined(ESP32) + ble_log.loop(); +#endif if (the_mesh.getNodePrefs()->powersaving_enabled && !the_mesh.hasPendingWork()) { #if defined(NRF52_PLATFORM) diff --git a/examples/simple_room_server/main.cpp b/examples/simple_room_server/main.cpp index 825fb007..94438649 100644 --- a/examples/simple_room_server/main.cpp +++ b/examples/simple_room_server/main.cpp @@ -2,6 +2,11 @@ #include #include "MyMesh.h" +#include + +#if MESH_PACKET_LOGGING && BLE_PACKET_LOGGING && (defined(NRF52_PLATFORM) || defined(ESP32)) + static BLELogInterface ble_log; +#endif #ifdef DISPLAY_CLASS #include "UITask.h" @@ -72,6 +77,11 @@ void setup() { the_mesh.begin(fs); +#if MESH_PACKET_LOGGING && BLE_PACKET_LOGGING && (defined(NRF52_PLATFORM) || defined(ESP32)) + ble_log.begin(the_mesh.getNodeName()); + the_mesh.setPacketLogStream(&ble_log); +#endif + #ifdef DISPLAY_CLASS ui_task.begin(the_mesh.getNodePrefs(), FIRMWARE_BUILD_DATE, FIRMWARE_VERSION); #endif @@ -113,4 +123,7 @@ void loop() { ui_task.loop(); #endif rtc_clock.tick(); +#if MESH_PACKET_LOGGING && BLE_PACKET_LOGGING && defined(ESP32) + ble_log.loop(); +#endif } diff --git a/src/Dispatcher.cpp b/src/Dispatcher.cpp index 9d7a1113..78262559 100644 --- a/src/Dispatcher.cpp +++ b/src/Dispatcher.cpp @@ -217,21 +217,24 @@ void Dispatcher::checkRecv() { } if (pkt) { #if MESH_PACKET_LOGGING - Serial.print(getLogDateTime()); - Serial.printf(": RX, len=%d (type=%d, route=%s, payload_len=%d) SNR=%d RSSI=%d score=%d time=%d", - pkt->getRawLength(), pkt->getPayloadType(), pkt->isRouteDirect() ? "D" : "F", pkt->payload_len, - (int)pkt->getSNR(), (int)_radio->getLastRSSI(), (int)(score*1000), air_time); - + { + char buf[128]; + snprintf(buf, sizeof(buf), "%s: RX, len=%d (type=%d, route=%s, payload_len=%d) SNR=%d RSSI=%d score=%d time=%d", + getLogDateTime(), pkt->getRawLength(), pkt->getPayloadType(), pkt->isRouteDirect() ? "D" : "F", pkt->payload_len, + (int)pkt->getSNR(), (int)_radio->getLastRSSI(), (int)(score*1000), air_time); + _packet_log->print(buf); + } static uint8_t packet_hash[MAX_HASH_SIZE]; pkt->calculatePacketHash(packet_hash); - Serial.print(" hash="); - mesh::Utils::printHex(Serial, packet_hash, MAX_HASH_SIZE); - + _packet_log->print(" hash="); + mesh::Utils::printHex(*_packet_log, packet_hash, MAX_HASH_SIZE); if (pkt->getPayloadType() == PAYLOAD_TYPE_PATH || pkt->getPayloadType() == PAYLOAD_TYPE_REQ || pkt->getPayloadType() == PAYLOAD_TYPE_RESPONSE || pkt->getPayloadType() == PAYLOAD_TYPE_TXT_MSG) { - Serial.printf(" [%02X -> %02X]\n", (uint32_t)pkt->payload[1], (uint32_t)pkt->payload[0]); + char buf[16]; + snprintf(buf, sizeof(buf), " [%02X -> %02X]\n", (uint32_t)pkt->payload[1], (uint32_t)pkt->payload[0]); + _packet_log->print(buf); } else { - Serial.printf("\n"); + _packet_log->print("\n"); } #endif logRx(pkt, pkt->getRawLength(), score); // hook for custom logging @@ -338,14 +341,19 @@ void Dispatcher::checkSend() { outbound_expiry = futureMillis(max_airtime); #if MESH_PACKET_LOGGING - Serial.print(getLogDateTime()); - Serial.printf(": TX, len=%d (type=%d, route=%s, payload_len=%d)", - len, outbound->getPayloadType(), outbound->isRouteDirect() ? "D" : "F", outbound->payload_len); + { + char buf[128]; + snprintf(buf, sizeof(buf), "%s: TX, len=%d (type=%d, route=%s, payload_len=%d)", + getLogDateTime(), len, outbound->getPayloadType(), outbound->isRouteDirect() ? "D" : "F", outbound->payload_len); + _packet_log->print(buf); + } if (outbound->getPayloadType() == PAYLOAD_TYPE_PATH || outbound->getPayloadType() == PAYLOAD_TYPE_REQ || outbound->getPayloadType() == PAYLOAD_TYPE_RESPONSE || outbound->getPayloadType() == PAYLOAD_TYPE_TXT_MSG) { - Serial.printf(" [%02X -> %02X]\n", (uint32_t)outbound->payload[1], (uint32_t)outbound->payload[0]); + char buf[16]; + snprintf(buf, sizeof(buf), " [%02X -> %02X]\n", (uint32_t)outbound->payload[1], (uint32_t)outbound->payload[0]); + _packet_log->print(buf); } else { - Serial.printf("\n"); + _packet_log->print("\n"); } #endif } diff --git a/src/Dispatcher.h b/src/Dispatcher.h index 2a99d068..52b3bb7a 100644 --- a/src/Dispatcher.h +++ b/src/Dispatcher.h @@ -1,6 +1,9 @@ #pragma once #include +#if MESH_PACKET_LOGGING && ARDUINO + #include +#endif #include #include #include @@ -129,6 +132,9 @@ class Dispatcher { void processRecvPacket(Packet* pkt); void updateTxBudget(); +#if MESH_PACKET_LOGGING + Print* _packet_log; +#endif protected: PacketManager* _mgr; @@ -150,6 +156,9 @@ protected: tx_budget_ms = 0; last_budget_update = 0; duty_cycle_window_ms = 3600000; +#if MESH_PACKET_LOGGING + _packet_log = &Serial; +#endif } virtual DispatcherAction onRecvPacket(Packet* pkt) = 0; @@ -172,6 +181,9 @@ protected: public: void begin(); void loop(); +#if MESH_PACKET_LOGGING + void setPacketLogStream(Print* s) { if (s) _packet_log = s; } +#endif Packet* obtainNewPacket(); void releasePacket(Packet* packet); diff --git a/src/Utils.cpp b/src/Utils.cpp index 186c8720..1b353711 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -99,7 +99,7 @@ void Utils::toHex(char* dest, const uint8_t* src, size_t len) { *dest = 0; } -void Utils::printHex(Stream& s, const uint8_t* src, size_t len) { +void Utils::printHex(Print& s, const uint8_t* src, size_t len) { while (len > 0) { uint8_t b = *src++; s.print(hex_chars[b >> 4]); diff --git a/src/Utils.h b/src/Utils.h index 5736b874..34b5751f 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -1,7 +1,7 @@ #pragma once #include -#include +#include #include namespace mesh { @@ -69,7 +69,7 @@ public: /** * \brief Prints the hexadecimal representation of 'src' bytes of given length, to Stream 's'. */ - static void printHex(Stream& s, const uint8_t* src, size_t len); + static void printHex(Print& s, const uint8_t* src, size_t len); /** * \brief parse 'text' into parts separated by 'separator' char. diff --git a/src/helpers/BLELogInterface.h b/src/helpers/BLELogInterface.h new file mode 100644 index 00000000..65565953 --- /dev/null +++ b/src/helpers/BLELogInterface.h @@ -0,0 +1,17 @@ +#pragma once + +/** + * Platform-selecting shim for BLELogInterface. + * Include this header and use BLELogInterface directly; the correct + * platform implementation is pulled in automatically. + * + * Supported platforms: ESP32, nRF52. + * On unsupported platforms this header intentionally defines nothing — + * guard usage with #if defined(NRF52_PLATFORM) || defined(ESP32). + */ + +#if defined(NRF52_PLATFORM) + #include "nrf52/BLELogInterface.h" +#elif defined(ESP32) + #include "esp32/BLELogInterface.h" +#endif diff --git a/src/helpers/esp32/BLELogInterface.h b/src/helpers/esp32/BLELogInterface.h new file mode 100644 index 00000000..069a693e --- /dev/null +++ b/src/helpers/esp32/BLELogInterface.h @@ -0,0 +1,93 @@ +#pragma once + +#include +#include +#include +#include +#include + +// Nordic UART Service UUIDs (standard, recognised by nRF Connect and bleak) +#define NUS_SERVICE_UUID "6E400001-B5A3-F393-E0A9-E50E24DCCA9E" +#define NUS_TX_UUID "6E400003-B5A3-F393-E0A9-E50E24DCCA9E" + +#define BLE_LOG_ADVERT_RESTART_DELAY_MS 1000 + +/** + * Unsecured BLE UART (Nordic UART Service) logger for ESP32 platforms. + * Implements Print so it can be passed to Dispatcher::setPacketLogStream(). + * Any BLE NUS client (e.g. nRF Connect, a Raspberry Pi running bleak) can + * connect without pairing and receive the log stream as plain text. + * + * Lines are buffered and flushed to BLE on each newline character. + * Call loop() from the Arduino loop() to handle advertising restart on disconnect. + */ +class BLELogInterface : public Print, BLEServerCallbacks { + BLEServer* _server; + BLECharacteristic* _tx_char; + bool _connected; + unsigned long _adv_restart_time; + char _line_buf[256]; + int _line_len; + + void flushLine() { + if (_line_len > 0 && _connected) { + _tx_char->setValue((uint8_t*)_line_buf, _line_len); + _tx_char->notify(); + } + _line_len = 0; + } + + void onConnect(BLEServer* pServer) override { + _connected = true; + } + + void onDisconnect(BLEServer* pServer) override { + _connected = false; + _line_len = 0; // discard partial line + _adv_restart_time = millis() + BLE_LOG_ADVERT_RESTART_DELAY_MS; + } + +public: + BLELogInterface() + : _server(nullptr), _tx_char(nullptr), + _connected(false), _adv_restart_time(0), _line_len(0) {} + + void begin(const char* device_name) { + BLEDevice::init(device_name); + + _server = BLEDevice::createServer(); + _server->setCallbacks(this); + + BLEService* service = _server->createService(NUS_SERVICE_UUID); + _tx_char = service->createCharacteristic(NUS_TX_UUID, BLECharacteristic::PROPERTY_NOTIFY); + _tx_char->addDescriptor(new BLE2902()); + service->start(); + + _server->getAdvertising()->addServiceUUID(NUS_SERVICE_UUID); + _server->getAdvertising()->start(); + } + + void loop() { + if (_adv_restart_time && millis() >= _adv_restart_time) { + if (_server->getConnectedCount() == 0) { + _server->getAdvertising()->start(); + } + _adv_restart_time = 0; + } + } + + size_t write(uint8_t c) override { + if (_line_len < (int)sizeof(_line_buf) - 1) { + _line_buf[_line_len++] = c; + } + if (c == '\n' || _line_len >= (int)sizeof(_line_buf) - 1) { + flushLine(); + } + return 1; + } + + size_t write(const uint8_t* buf, size_t size) override { + for (size_t i = 0; i < size; i++) write(buf[i]); + return size; + } +}; diff --git a/src/helpers/nrf52/BLELogInterface.h b/src/helpers/nrf52/BLELogInterface.h new file mode 100644 index 00000000..06cfc5b7 --- /dev/null +++ b/src/helpers/nrf52/BLELogInterface.h @@ -0,0 +1,43 @@ +#pragma once + +#include +#include + +#ifndef BLE_LOG_TX_POWER + #define BLE_LOG_TX_POWER 4 +#endif + +/** + * Unsecured BLE UART (Nordic UART Service) logger for nRF52 platforms. + * Implements Print so it can be passed to Dispatcher::setPacketLogStream(). + * Any BLE NUS client (e.g. nRF Connect, a Raspberry Pi running bleak) can + * connect without pairing and receive the log stream as plain text. + */ +class BLELogInterface : public Print { + BLEUart _uart; + +public: + void begin(const char* device_name) { + Bluefruit.begin(); + Bluefruit.setTxPower(BLE_LOG_TX_POWER); + Bluefruit.setName(device_name); + + _uart.begin(); + + Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); + Bluefruit.Advertising.addTxPower(); + Bluefruit.Advertising.addService(_uart); + Bluefruit.ScanResponse.addName(); + + Bluefruit.Advertising.restartOnDisconnect(true); + Bluefruit.Advertising.start(0); + } + + size_t write(uint8_t c) override { + return _uart.write(c); + } + + size_t write(const uint8_t* buf, size_t size) override { + return _uart.write(buf, size); + } +};