From f45c71e5a4b2ccc07e3c4847cd260a82411d4e2e Mon Sep 17 00:00:00 2001 From: Gerard Hickey Date: Thu, 16 Jul 2026 09:33:03 -0400 Subject: [PATCH 1/6] feat: Add support for bridging over USB and IP Signed-off-by: Gerard Hickey --- examples/simple_repeater/MyMesh.cpp | 18 +++- examples/simple_repeater/MyMesh.h | 21 +++- src/helpers/CommonCLI.cpp | 8 +- src/helpers/CommonCLI.h | 2 +- src/helpers/bridges/TCPBridge.cpp | 129 ++++++++++++++++++++++++ src/helpers/bridges/TCPBridge.h | 47 +++++++++ src/helpers/bridges/USBSerialBridge.cpp | 102 +++++++++++++++++++ src/helpers/bridges/USBSerialBridge.h | 43 ++++++++ 8 files changed, 362 insertions(+), 8 deletions(-) create mode 100644 src/helpers/bridges/TCPBridge.cpp create mode 100644 src/helpers/bridges/TCPBridge.h create mode 100644 src/helpers/bridges/USBSerialBridge.cpp create mode 100644 src/helpers/bridges/USBSerialBridge.h diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 096907494b..cc3645f85c 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -198,9 +198,13 @@ uint8_t MyMesh::handleAnonClockReq(const mesh::Identity& sender, uint32_t sender memcpy(&reply_data[4], &now, 4); // include our clock (for easy clock sync, and packet hash uniqueness) reply_data[8] = 0; // features #ifdef WITH_RS232_BRIDGE - reply_data[8] |= 0x01; // is bridge, type UART + reply_data[8] |= RS232_BRIDGE; // is bridge, type UART #elif WITH_ESPNOW_BRIDGE - reply_data[8] |= 0x03; // is bridge, type ESP-NOW + reply_data[8] |= ESPNOW_BRIDGE; // is bridge, type ESP-NOW +#elif WITH_USB_SERIAL_BRIDGE + reply_data[8] |= USB_SERIAL_BRIDGE; // is bridge, type USB serial +#elif WITH_TCP_BRIDGE + reply_data[8] |= TCP_BRIDGE; // is bridge, type TCP #endif if (_prefs.disable_fwd) { // is this repeater currently disabled reply_data[8] |= 0x80; // is disabled @@ -344,7 +348,7 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t int results_offset = 0; uint8_t results_buffer[130]; for(int index = 0; index < count && index + offset < neighbours_count; index++){ - + // stop if we can't fit another entry in results int entry_size = pubkey_prefix_length + 4 + 1; if(results_offset + entry_size > sizeof(results_buffer)){ @@ -859,6 +863,12 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc #if defined(WITH_ESPNOW_BRIDGE) , bridge(&_prefs, _mgr, &rtc) #endif +#if defined(WITH_USB_SERIAL_BRIDGE) + , bridge(&_prefs, WITH_USB_SERIAL_BRIDGE, _mgr, &rtc) +#endif +#if defined(WITH_TCP_BRIDGE) + , bridge(&_prefs, _mgr, &rtc, WITH_TCP_BRIDGE) +#endif { last_millis = 0; uptime_millis = 0; @@ -1148,7 +1158,7 @@ void MyMesh::formatRadioStatsReply(char *reply) { } void MyMesh::formatPacketStatsReply(char *reply) { - StatsFormatHelper::formatPacketStats(reply, radio_driver, getNumSentFlood(), getNumSentDirect(), + StatsFormatHelper::formatPacketStats(reply, radio_driver, getNumSentFlood(), getNumSentDirect(), getNumRecvFlood(), getNumRecvDirect()); } diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 7597c6c6f6..3fc79f21c5 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -23,6 +23,16 @@ #define WITH_BRIDGE #endif +#ifdef WITH_USB_SERIAL_BRIDGE +#include "helpers/bridges/USBSerialBridge.h" +#define WITH_BRIDGE +#endif + +#ifdef WITH_TCP_BRIDGE +#include "helpers/bridges/TCPBridge.h" +#define WITH_BRIDGE +#endif + #include #include #include @@ -37,8 +47,13 @@ #ifdef WITH_BRIDGE extern AbstractBridge* bridge; +#define RS232_BRIDGE 0x01 +#define ESPNOW_BRIDGE 0x03 +#define USB_SERIAL_BRIDGE 0x04 +#define TCP_BRIDGE 0x05 #endif + struct RepeaterStats { uint16_t batt_milli_volts; uint16_t curr_tx_queue_len; @@ -117,6 +132,10 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { RS232Bridge bridge; #elif defined(WITH_ESPNOW_BRIDGE) ESPNowBridge bridge; +#elif defined(WITH_USB_SERIAL_BRIDGE) + USBSerialBridge bridge; +#elif defined(WITH_TCP_BRIDGE) + TCPBridge bridge; #endif void putNeighbour(const mesh::Identity& id, uint32_t timestamp, float snr); @@ -233,7 +252,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { { bridge.begin(); } - else + else { bridge.end(); } diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index b78ad6ebd6..edf6d03ca4 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -859,6 +859,10 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep "rs232" #elif WITH_ESPNOW_BRIDGE "espnow" +#elif WITH_USB_SERIAL_BRIDGE + "usbserial" +#elif WITH_TCP_BRIDGE + "tcp" #else "none" #endif @@ -1102,7 +1106,7 @@ void CommonCLI::handleRegionCmd(char* command, char* reply) { } else if (n >= 3 && strcmp(parts[1], "list") == 0) { uint8_t mask = 0; bool invert = false; - + if (strcmp(parts[2], "allowed") == 0) { mask = REGION_DENY_FLOOD; invert = false; // list regions that DON'T have DENY flag @@ -1113,7 +1117,7 @@ void CommonCLI::handleRegionCmd(char* command, char* reply) { strcpy(reply, "Err - use 'allowed' or 'denied'"); return; } - + int len = _region_map->exportNamesTo(reply, 160, mask, invert); if (len == 0) { strcpy(reply, "-none-"); diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index b509c2b31a..871036dfb8 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -6,7 +6,7 @@ #include #include -#if defined(WITH_RS232_BRIDGE) || defined(WITH_ESPNOW_BRIDGE) +#if defined(WITH_RS232_BRIDGE) || defined(WITH_ESPNOW_BRIDGE) || defined(WITH_USB_SERIAL_BRIDGE) || defined(WITH_TCP_BRIDGE) #define WITH_BRIDGE #endif diff --git a/src/helpers/bridges/TCPBridge.cpp b/src/helpers/bridges/TCPBridge.cpp new file mode 100644 index 0000000000..ada48009da --- /dev/null +++ b/src/helpers/bridges/TCPBridge.cpp @@ -0,0 +1,129 @@ +#include "TCPBridge.h" + +#ifdef WITH_TCP_BRIDGE + +TCPBridge::TCPBridge(NodePrefs* prefs, mesh::PacketManager* mgr, mesh::RTCClock* rtc, uint16_t port) + : BridgeBase(prefs, mgr, rtc), _port(port), _server(port) {} + +void TCPBridge::begin() { + _server.begin(); + _initialized = true; + BRIDGE_DEBUG_PRINTLN("TCP bridge listening on port %d\n", _port); +} + +void TCPBridge::end() { + if (_client_connected) { + _client.stop(); + _client_connected = false; + } + _server.end(); + _initialized = false; + BRIDGE_DEBUG_PRINTLN("TCP bridge stopped\n"); +} + +void TCPBridge::loop() { + if (!_initialized) return; + + // Accept new connection, displacing any existing one + WiFiClient incoming = _server.available(); + if (incoming) { + if (_client_connected) { + _client.stop(); + } + _client = incoming; + _client_connected = true; + _rx_buffer_pos = 0; + BRIDGE_DEBUG_PRINTLN("TCP client connected\n"); + } + + if (_client_connected) { + if (!_client.connected()) { + _client.stop(); + _client_connected = false; + BRIDGE_DEBUG_PRINTLN("TCP client disconnected\n"); + return; + } + + while (_client.available()) { + uint8_t b = _client.read(); + + if (_rx_buffer_pos < 2) { + if ((_rx_buffer_pos == 0 && b == ((BRIDGE_PACKET_MAGIC >> 8) & 0xFF)) || + (_rx_buffer_pos == 1 && b == (BRIDGE_PACKET_MAGIC & 0xFF))) { + _rx_buffer[_rx_buffer_pos++] = b; + } else { + _rx_buffer_pos = 0; + if (b == ((BRIDGE_PACKET_MAGIC >> 8) & 0xFF)) { + _rx_buffer[_rx_buffer_pos++] = b; + } + } + } else { + _rx_buffer[_rx_buffer_pos++] = b; + + if (_rx_buffer_pos >= 4) { + uint16_t len = (_rx_buffer[2] << 8) | _rx_buffer[3]; + + if (len > (MAX_TRANS_UNIT + 1)) { + BRIDGE_DEBUG_PRINTLN("RX invalid length %d, resetting\n", len); + _rx_buffer_pos = 0; + continue; + } + + if (_rx_buffer_pos == len + TCP_OVERHEAD) { + uint16_t received_checksum = (_rx_buffer[4 + len] << 8) | _rx_buffer[5 + len]; + + if (validateChecksum(_rx_buffer + 4, len, received_checksum)) { + BRIDGE_DEBUG_PRINTLN("RX, len=%d crc=0x%04x\n", len, received_checksum); + mesh::Packet* pkt = _mgr->allocNew(); + if (pkt) { + if (pkt->readFrom(_rx_buffer + 4, len)) { + onPacketReceived(pkt); + } else { + BRIDGE_DEBUG_PRINTLN("RX failed to parse packet\n"); + _mgr->free(pkt); + } + } else { + BRIDGE_DEBUG_PRINTLN("RX failed to allocate packet\n"); + } + } else { + BRIDGE_DEBUG_PRINTLN("RX checksum mismatch, rcv=0x%04x\n", received_checksum); + } + _rx_buffer_pos = 0; + } + } + } + } + } +} + +void TCPBridge::sendPacket(mesh::Packet* packet) { + if (!_initialized || !packet || !_client_connected) return; + + if (!_seen_packets.hasSeen(packet)) { + uint8_t buffer[MAX_TCP_PACKET_SIZE]; + uint16_t len = packet->writeTo(buffer + 4); + + if (len > (MAX_TRANS_UNIT + 1)) { + BRIDGE_DEBUG_PRINTLN("TX packet too large (payload=%d, max=%d)\n", len, MAX_TRANS_UNIT + 1); + return; + } + + buffer[0] = (BRIDGE_PACKET_MAGIC >> 8) & 0xFF; + buffer[1] = BRIDGE_PACKET_MAGIC & 0xFF; + buffer[2] = (len >> 8) & 0xFF; + buffer[3] = len & 0xFF; + + uint16_t checksum = fletcher16(buffer + 4, len); + buffer[4 + len] = (checksum >> 8) & 0xFF; + buffer[5 + len] = checksum & 0xFF; + + _client.write(buffer, len + TCP_OVERHEAD); + BRIDGE_DEBUG_PRINTLN("TX, len=%d crc=0x%04x\n", len, checksum); + } +} + +void TCPBridge::onPacketReceived(mesh::Packet* packet) { + handleReceivedPacket(packet); +} + +#endif diff --git a/src/helpers/bridges/TCPBridge.h b/src/helpers/bridges/TCPBridge.h new file mode 100644 index 0000000000..a255daf928 --- /dev/null +++ b/src/helpers/bridges/TCPBridge.h @@ -0,0 +1,47 @@ +#pragma once + +#include "helpers/bridges/BridgeBase.h" + +#ifdef WITH_TCP_BRIDGE + +#include +#include + +/** + * Bridge using the RS232Bridge framing protocol over a WiFi TCP socket. + * + * The server listens on the port set by WITH_TCP_BRIDGE. One client is + * accepted at a time; a new connection displaces the previous one. + * WiFi must be connected before calling begin(). + * + * Frame format (identical to RS232Bridge / USBSerialBridge): + * [2 bytes] Magic header: 0xC03E + * [2 bytes] Payload length + * [N bytes] Raw mesh packet bytes + * [2 bytes] Fletcher-16 checksum (over payload only) + * + * Enable with: -DWITH_TCP_BRIDGE= (e.g. -DWITH_TCP_BRIDGE=4403) + */ +class TCPBridge : public BridgeBase { +public: + TCPBridge(NodePrefs* prefs, mesh::PacketManager* mgr, mesh::RTCClock* rtc, uint16_t port); + + void begin() override; + void end() override; + void loop() override; + void sendPacket(mesh::Packet* packet) override; + void onPacketReceived(mesh::Packet* packet) override; + +private: + static constexpr uint16_t TCP_OVERHEAD = BRIDGE_MAGIC_SIZE + BRIDGE_LENGTH_SIZE + BRIDGE_CHECKSUM_SIZE; + static constexpr uint16_t MAX_TCP_PACKET_SIZE = (MAX_TRANS_UNIT + 1) + TCP_OVERHEAD; + + uint16_t _port; + WiFiServer _server; + WiFiClient _client; + bool _client_connected = false; + uint8_t _rx_buffer[MAX_TCP_PACKET_SIZE]; + uint16_t _rx_buffer_pos = 0; +}; + +#endif diff --git a/src/helpers/bridges/USBSerialBridge.cpp b/src/helpers/bridges/USBSerialBridge.cpp new file mode 100644 index 0000000000..9b5b801035 --- /dev/null +++ b/src/helpers/bridges/USBSerialBridge.cpp @@ -0,0 +1,102 @@ +#include "USBSerialBridge.h" + +#ifdef WITH_USB_SERIAL_BRIDGE + +USBSerialBridge::USBSerialBridge(NodePrefs* prefs, Stream& serial, mesh::PacketManager* mgr, mesh::RTCClock* rtc) + : BridgeBase(prefs, mgr, rtc), _serial(&serial) {} + +void USBSerialBridge::begin() { + _initialized = true; + BRIDGE_DEBUG_PRINTLN("USB serial bridge started\n"); +} + +void USBSerialBridge::end() { + _initialized = false; + BRIDGE_DEBUG_PRINTLN("USB serial bridge stopped\n"); +} + +void USBSerialBridge::loop() { + if (!_initialized) return; + + while (_serial->available()) { + uint8_t b = _serial->read(); + + if (_rx_buffer_pos < 2) { + if ((_rx_buffer_pos == 0 && b == ((BRIDGE_PACKET_MAGIC >> 8) & 0xFF)) || + (_rx_buffer_pos == 1 && b == (BRIDGE_PACKET_MAGIC & 0xFF))) { + _rx_buffer[_rx_buffer_pos++] = b; + } else { + _rx_buffer_pos = 0; + if (b == ((BRIDGE_PACKET_MAGIC >> 8) & 0xFF)) { + _rx_buffer[_rx_buffer_pos++] = b; + } + } + } else { + _rx_buffer[_rx_buffer_pos++] = b; + + if (_rx_buffer_pos >= 4) { + uint16_t len = (_rx_buffer[2] << 8) | _rx_buffer[3]; + + if (len > (MAX_TRANS_UNIT + 1)) { + BRIDGE_DEBUG_PRINTLN("RX invalid length %d, resetting\n", len); + _rx_buffer_pos = 0; + continue; + } + + if (_rx_buffer_pos == len + SERIAL_OVERHEAD) { + uint16_t received_checksum = (_rx_buffer[4 + len] << 8) | _rx_buffer[5 + len]; + + if (validateChecksum(_rx_buffer + 4, len, received_checksum)) { + BRIDGE_DEBUG_PRINTLN("RX, len=%d crc=0x%04x\n", len, received_checksum); + mesh::Packet* pkt = _mgr->allocNew(); + if (pkt) { + if (pkt->readFrom(_rx_buffer + 4, len)) { + onPacketReceived(pkt); + } else { + BRIDGE_DEBUG_PRINTLN("RX failed to parse packet\n"); + _mgr->free(pkt); + } + } else { + BRIDGE_DEBUG_PRINTLN("RX failed to allocate packet\n"); + } + } else { + BRIDGE_DEBUG_PRINTLN("RX checksum mismatch, rcv=0x%04x\n", received_checksum); + } + _rx_buffer_pos = 0; + } + } + } + } +} + +void USBSerialBridge::sendPacket(mesh::Packet* packet) { + if (!_initialized || !packet) return; + + if (!_seen_packets.hasSeen(packet)) { + uint8_t buffer[MAX_SERIAL_PACKET_SIZE]; + uint16_t len = packet->writeTo(buffer + 4); + + if (len > (MAX_TRANS_UNIT + 1)) { + BRIDGE_DEBUG_PRINTLN("TX packet too large (payload=%d, max=%d)\n", len, MAX_TRANS_UNIT + 1); + return; + } + + buffer[0] = (BRIDGE_PACKET_MAGIC >> 8) & 0xFF; + buffer[1] = BRIDGE_PACKET_MAGIC & 0xFF; + buffer[2] = (len >> 8) & 0xFF; + buffer[3] = len & 0xFF; + + uint16_t checksum = fletcher16(buffer + 4, len); + buffer[4 + len] = (checksum >> 8) & 0xFF; + buffer[5 + len] = checksum & 0xFF; + + _serial->write(buffer, len + SERIAL_OVERHEAD); + BRIDGE_DEBUG_PRINTLN("TX, len=%d crc=0x%04x\n", len, checksum); + } +} + +void USBSerialBridge::onPacketReceived(mesh::Packet* packet) { + handleReceivedPacket(packet); +} + +#endif diff --git a/src/helpers/bridges/USBSerialBridge.h b/src/helpers/bridges/USBSerialBridge.h new file mode 100644 index 0000000000..d631205494 --- /dev/null +++ b/src/helpers/bridges/USBSerialBridge.h @@ -0,0 +1,43 @@ +#pragma once + +#include "helpers/bridges/BridgeBase.h" + +#include + +#ifdef WITH_USB_SERIAL_BRIDGE + +/** + * Bridge using the RS232Bridge framing protocol over a USB CDC serial stream. + * + * Unlike RS232Bridge, this class takes any Stream (e.g. Serial on ESP32-S3 native USB) + * and skips all HardwareSerial pin/baud configuration — the USB CDC port is already + * initialised by the framework before setup() runs. + * + * Frame format (identical to RS232Bridge): + * [2 bytes] Magic header: 0xC03E + * [2 bytes] Payload length + * [N bytes] Raw mesh packet bytes + * [2 bytes] Fletcher-16 checksum (over payload only) + * + * Enable with: -DWITH_USB_SERIAL_BRIDGE=Serial (or USBSerial, etc.) + */ +class USBSerialBridge : public BridgeBase { +public: + USBSerialBridge(NodePrefs* prefs, Stream& serial, mesh::PacketManager* mgr, mesh::RTCClock* rtc); + + void begin() override; + void end() override; + void loop() override; + void sendPacket(mesh::Packet* packet) override; + void onPacketReceived(mesh::Packet* packet) override; + +private: + static constexpr uint16_t SERIAL_OVERHEAD = BRIDGE_MAGIC_SIZE + BRIDGE_LENGTH_SIZE + BRIDGE_CHECKSUM_SIZE; + static constexpr uint16_t MAX_SERIAL_PACKET_SIZE = (MAX_TRANS_UNIT + 1) + SERIAL_OVERHEAD; + + Stream* _serial; + uint8_t _rx_buffer[MAX_SERIAL_PACKET_SIZE]; + uint16_t _rx_buffer_pos = 0; +}; + +#endif From 3907833897193d73e2fac43fffe85a3b6db2f928 Mon Sep 17 00:00:00 2001 From: Gerard Hickey Date: Tue, 21 Jul 2026 11:05:29 -0400 Subject: [PATCH 2/6] Refactor build flags so bridge type is reported Signed-off-by: Gerard Hickey --- examples/simple_repeater/MyMesh.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index cc3645f85c..67081e69eb 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -864,10 +864,10 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc , bridge(&_prefs, _mgr, &rtc) #endif #if defined(WITH_USB_SERIAL_BRIDGE) - , bridge(&_prefs, WITH_USB_SERIAL_BRIDGE, _mgr, &rtc) + , bridge(&_prefs, USB_SERIAL_STREAM, _mgr, &rtc) #endif #if defined(WITH_TCP_BRIDGE) - , bridge(&_prefs, _mgr, &rtc, WITH_TCP_BRIDGE) + , bridge(&_prefs, _mgr, &rtc, TCP_BRIDGE_PORT) #endif { last_millis = 0; From b358256c7e90420dc226140ce577f0c9fc868928 Mon Sep 17 00:00:00 2001 From: Gerard Hickey Date: Tue, 21 Jul 2026 11:07:08 -0400 Subject: [PATCH 3/6] Add usbserial and tcp bridge environments Signed-off-by: Gerard Hickey --- variants/lilygo_tbeam_SX1262/platformio.ini | 67 ++++++++++++++++++--- 1 file changed, 58 insertions(+), 9 deletions(-) diff --git a/variants/lilygo_tbeam_SX1262/platformio.ini b/variants/lilygo_tbeam_SX1262/platformio.ini index 62ac09f875..74fe1a3fd5 100644 --- a/variants/lilygo_tbeam_SX1262/platformio.ini +++ b/variants/lilygo_tbeam_SX1262/platformio.ini @@ -51,8 +51,8 @@ build_flags = -D BLE_PIN_CODE=123456 -D OFFLINE_QUEUE_SIZE=128 ; -D BLE_DEBUG_LOGGING=1 -; -D MESH_PACKET_LOGGING=1 -; -D MESH_DEBUG=1 + -D MESH_PACKET_LOGGING=1 + -D MESH_DEBUG=1 ; -D RADIOLIB_DEBUG_BASIC=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 @@ -74,9 +74,58 @@ build_flags = -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=50 -; -D MESH_PACKET_LOGGING=1 -; -D MESH_DEBUG=1 + -D MESH_PACKET_LOGGING=1 + -D MESH_DEBUG=1 +build_src_filter = ${LilyGo_TBeam_SX1262.build_src_filter} + +<../examples/simple_repeater> +lib_deps = + ${LilyGo_TBeam_SX1262.lib_deps} + ${esp32_ota.lib_deps} + +# If using WITH_USB_SERIAL_BRIDGE, load the standard repeater code +# first so that the repeater can be configured over USB. Then load +# this code. Once USBSerial bridge is running the node can not use +# the repeater configuration at flasher.meshcode.io. +[env:Tbeam_SX1262_repeater_bridge_usbserial] +extends = LilyGo_TBeam_SX1262 +build_flags = + ${LilyGo_TBeam_SX1262.build_flags} + -D ADVERT_NAME='"Tbeam SX1262 Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 + -D MESH_PACKET_LOGGING=1 + -D MESH_DEBUG=1 + -D WITH_USB_SERIAL_BRIDGE=1 + -D USB_SERIAL_STREAM=Serial + -D BRIDGE_DEBUG=1 build_src_filter = ${LilyGo_TBeam_SX1262.build_src_filter} + + + +<../examples/simple_repeater> +lib_deps = + ${LilyGo_TBeam_SX1262.lib_deps} + ${esp32_ota.lib_deps} + +[env:Tbeam_SX1262_repeater_bridge_wifi] +extends = LilyGo_TBeam_SX1262 +build_flags = + ${LilyGo_TBeam_SX1262.build_flags} + -D ADVERT_NAME='"Tbeam SX1262 Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 + -D MESH_PACKET_LOGGING=1 + -D MESH_DEBUG=1 + -D WITH_TCP_BRIDGE=1 + -D TCP_BRIDGE_PORT=4403 + -D WIFI_DEBUG_LOGGING=1 + -D WIFI_SSID='"myssid"' + -D WIFI_PWD='"mypasswd"' + -D BRIDGE_DEBUG=1 +build_src_filter = ${LilyGo_TBeam_SX1262.build_src_filter} + + +<../examples/simple_repeater> lib_deps = ${LilyGo_TBeam_SX1262.lib_deps} @@ -114,9 +163,9 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=50 -D WITH_ESPNOW_BRIDGE=1 -; -D BRIDGE_DEBUG=1 -; -D MESH_PACKET_LOGGING=1 -; -D MESH_DEBUG=1 + -D BRIDGE_DEBUG=1 + -D MESH_PACKET_LOGGING=1 + -D MESH_DEBUG=1 build_src_filter = ${LilyGo_TBeam_SX1262.build_src_filter} + +<../examples/simple_repeater> @@ -133,8 +182,8 @@ build_flags = -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' -D ROOM_PASSWORD='"hello"' -; -D MESH_PACKET_LOGGING=1 -; -D MESH_DEBUG=1 + -D MESH_PACKET_LOGGING=1 + -D MESH_DEBUG=1 build_src_filter = ${LilyGo_TBeam_SX1262.build_src_filter} +<../examples/simple_room_server> lib_deps = From 6721023098d0f16302441d969181c0d9b6cd7ece Mon Sep 17 00:00:00 2001 From: Gerard Hickey Date: Tue, 21 Jul 2026 14:42:00 -0400 Subject: [PATCH 4/6] WIFI configured for examples/simple_repeater Signed-off-by: Gerard Hickey --- examples/simple_repeater/main.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index 2ce056f521..7b09364a51 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -27,6 +27,12 @@ static unsigned long userBtnDownAt = 0; #define USER_BTN_HOLD_OFF_MILLIS 1500 #endif +#ifdef ESP32 + #ifdef WIFI_SSID + #include + #endif +#endif + void setup() { Serial.begin(115200); delay(1000); @@ -82,6 +88,11 @@ void setup() { store.save("_main", the_mesh.self_id); } +#ifdef WIFI_SSID + board.setInhibitSleep(true); // prevent sleep when WiFi is active + WiFi.begin(WIFI_SSID, WIFI_PWD); +#endif + Serial.print("Repeater ID: "); mesh::Utils::printHex(Serial, the_mesh.self_id.pub_key, PUB_KEY_SIZE); Serial.println(); From 6c0a6f5910d58d67f8aefb8f8d85e40ce5731cf1 Mon Sep 17 00:00:00 2001 From: Gerard Hickey Date: Tue, 21 Jul 2026 14:43:20 -0400 Subject: [PATCH 5/6] Added Lilygo TBeam companion WIFI environment Signed-off-by: Gerard Hickey --- variants/lilygo_tbeam_SX1262/platformio.ini | 28 +++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/variants/lilygo_tbeam_SX1262/platformio.ini b/variants/lilygo_tbeam_SX1262/platformio.ini index 74fe1a3fd5..8132a80557 100644 --- a/variants/lilygo_tbeam_SX1262/platformio.ini +++ b/variants/lilygo_tbeam_SX1262/platformio.ini @@ -46,8 +46,8 @@ board_build.upload.maximum_ram_size=2000000 build_flags = ${LilyGo_TBeam_SX1262.build_flags} -I examples/companion_radio/ui-new - -D MAX_CONTACTS=160 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=100 + -D MAX_GROUP_CHANNELS=60 -D BLE_PIN_CODE=123456 -D OFFLINE_QUEUE_SIZE=128 ; -D BLE_DEBUG_LOGGING=1 @@ -65,6 +65,30 @@ lib_deps = ${LilyGo_TBeam_SX1262.lib_deps} densaugeo/base64 @ ~1.4.0 +[env:Tbeam_SX1262_companion_radio_wifi] +extends = LilyGo_TBeam_SX1262 +board_build.upload.maximum_ram_size=2000000 +build_flags = + ${LilyGo_TBeam_SX1262.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=100 + -D MAX_GROUP_CHANNELS=60 + -D OFFLINE_QUEUE_SIZE=256 + -D WIFI_DEBUG_LOGGING=1 + -D WIFI_SSID='"myssid"' + -D WIFI_PWD='"mypasswd"' +; -D RADIOLIB_DEBUG_BASIC=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${LilyGo_TBeam_SX1262.build_src_filter} + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${LilyGo_TBeam_SX1262.lib_deps} + densaugeo/base64 @ ~1.4.0 + [env:Tbeam_SX1262_repeater] extends = LilyGo_TBeam_SX1262 build_flags = From 1d1fc4ee347c24d39a2f495fac87000233cdd4b8 Mon Sep 17 00:00:00 2001 From: Gerard Hickey Date: Tue, 21 Jul 2026 14:59:19 -0400 Subject: [PATCH 6/6] fix: Made set bridge.source consistent with CLI docs Signed-off-by: Gerard Hickey --- docs/cli_commands.md | 97 +++++++++++++++++++++------------------ src/helpers/CommonCLI.cpp | 17 +++++-- 2 files changed, 67 insertions(+), 47 deletions(-) diff --git a/docs/cli_commands.md b/docs/cli_commands.md index 66a9b77afe..6ea679b171 100644 --- a/docs/cli_commands.md +++ b/docs/cli_commands.md @@ -25,7 +25,7 @@ This document provides an overview of CLI commands that can be sent to MeshCore ## Operational ### Reboot the node -**Usage:** +**Usage:** - `reboot` **Note:** No reply is sent. @@ -50,7 +50,7 @@ This document provides an overview of CLI commands that can be sent to MeshCore --- ### Sync the clock with the remote device -**Usage:** +**Usage:** - `clock sync` --- @@ -62,7 +62,7 @@ This document provides an overview of CLI commands that can be sent to MeshCore --- ### Set the time to a specific timestamp -**Usage:** +**Usage:** - `time ` **Parameters:** @@ -71,7 +71,7 @@ This document provides an overview of CLI commands that can be sent to MeshCore --- ### Send a flood advert -**Usage:** +**Usage:** - `advert` --- @@ -101,7 +101,7 @@ This document provides an overview of CLI commands that can be sent to MeshCore ## Neighbors (Repeater Only) ### List nearby neighbors -**Usage:** +**Usage:** - `neighbors` **Note:** The output of this command is limited to the 8 most recent adverts. @@ -111,10 +111,10 @@ This document provides an overview of CLI commands that can be sent to MeshCore --- ### Remove a neighbor -**Usage:** +**Usage:** - `neighbor.remove ` -**Parameters:** +**Parameters:** - `pubkey_prefix`: The public key of the node to remove from the neighbors list. This can be a short prefix or the full key. All neighbors matching the provided prefix will be removed. **Note:** You can remove all neighbors by sending a space character as the prefix. The space indicates an empty prefix, which matches all existing neighbors. @@ -123,7 +123,7 @@ This document provides an overview of CLI commands that can be sent to MeshCore ### Discover zero hop neighbors -**Usage:** +**Usage:** - `discover.neighbors` --- @@ -136,7 +136,7 @@ This document provides an overview of CLI commands that can be sent to MeshCore --- ### System Stats - Battery, Uptime, Queue Length and Debug Flags -**Usage:** +**Usage:** - `stats-core` **Serial Only:** Yes @@ -233,7 +233,7 @@ This document provides an overview of CLI commands that can be sent to MeshCore --- #### Change the radio parameters for a set duration -**Usage:** +**Usage:** - `tempradio ,,,,` **Parameters:** @@ -420,7 +420,7 @@ This document provides an overview of CLI commands that can be sent to MeshCore - `powersaving on` - `powersaving off` -**Parameters:** +**Parameters:** - `on`: enable power saving - `off`: disable power saving @@ -454,13 +454,13 @@ This document provides an overview of CLI commands that can be sent to MeshCore - `0`: 1 Byte hash size (256 unique ids)[64 max flood] - `1`: 2 Byte hash size (65,536 unique ids)[32 max flood] - `2`: 3 Byte hash size (16,777,216 unique ids)[21 max flood] - - `3`: DO NOT USE (Reserved) + - `3`: DO NOT USE (Reserved) **Default:** `0` **Note:** the 'path.hash.mode' sets the low-level ID/hash encoding size used when the repeater adverts. This setting has no impact on what packet ID/hash size this repeater forwards, all sizes should be forwarded on firmware >= 1.14. This feature was added in firmware 1.14 -**Temporary Note:** adverts with ID/hash sizes of 2 or 3 bytes may have limited flood propagation in your network while this feature is new as v1.13.0 firmware and older will drop packets with multibyte path ID/hashes as only 1-byte hashes are supported. Consider your install base of firmware >=1.14 has reached a criticality for effective network flooding before implementing higher ID/hash sizes. +**Temporary Note:** adverts with ID/hash sizes of 2 or 3 bytes may have limited flood propagation in your network while this feature is new as v1.13.0 firmware and older will drop packets with multibyte path ID/hashes as only 1-byte hashes are supported. Consider your install base of firmware >=1.14 has reached a criticality for effective network flooding before implementing higher ID/hash sizes. --- @@ -470,17 +470,17 @@ This document provides an overview of CLI commands that can be sent to MeshCore - `set loop.detect ` **Parameters:** -- `state`: +- `state`: - `off`: no loop detection is performed - `minimal`: packets are dropped if repeater's ID/hash appears 4 or more times (1-byte), 2 or more (2-byte), 1 or more (3-byte) - `moderate`: packets are dropped if repeater's ID/hash appears 2 or more times (1-byte), 1 or more (2-byte), 1 or more (3-byte) - `strict`: packets are dropped if repeater's ID/hash appears 1 or more times (1-byte), 1 or more (2-byte), 1 or more (3-byte) - + **Default:** `off` **Note:** When it is enabled, repeaters will now reject flood packets which look like they are in a loop. This has been happening recently in some meshes when there is just a single 'bad' repeater firmware out there (probably some forked or custom firmware). If the payload is messed with, then forwarded, the same packet ends up causing a packet storm, repeated up to the max 64 hops. This feature was added in firmware 1.14 -**Example:** If preference is `loop.detect minimal`, and a 1-byte path size packet is received, the repeater will see if its own ID/hash is already in the path. If it's already encoded 4 times, it will reject the packet. If the packet uses 2-byte path size, and repeater's own ID/hash is already encoded 2 times, it rejects. If the packet uses 3-byte path size, and the repeater's own ID/hash is already encoded 1 time, it rejects. +**Example:** If preference is `loop.detect minimal`, and a 1-byte path size packet is received, the repeater will see if its own ID/hash is already in the path. If it's already encoded 4 times, it will reject the packet. If the packet uses 2-byte path size, and repeater's own ID/hash is already encoded 2 times, it rejects. If the packet uses 3-byte path size, and the repeater's own ID/hash is already encoded 1 time, it rejects. --- @@ -666,12 +666,12 @@ This document provides an overview of CLI commands that can be sent to MeshCore ### ACL #### Add, update or remove permissions for a companion -**Usage:** +**Usage:** - `setperm ` **Parameters:** - `pubkey`: Companion public key -- `permissions`: +- `permissions`: - `0`: Guest - `1`: Read-only - `2`: Read-write @@ -682,7 +682,7 @@ This document provides an overview of CLI commands that can be sent to MeshCore --- #### View the current ACL -**Usage:** +**Usage:** - `get acl` **Serial Only:** Yes @@ -704,7 +704,7 @@ This document provides an overview of CLI commands that can be sent to MeshCore ### Region Management (v1.10.+) #### Bulk-load region lists -**Usage:** +**Usage:** - `region load` - `region load [flood_flag]` @@ -720,16 +720,16 @@ This document provides an overview of CLI commands that can be sent to MeshCore --- #### Save any changes to regions made since reboot -**Usage:** +**Usage:** - `region save` --- #### Allow a region -**Usage:** +**Usage:** - `region allowf ` -**Parameters:** +**Parameters:** - `name`: Region name (or `*` for wildcard) **Note:** Setting on wildcard `*` allows packets without region transport codes @@ -737,10 +737,10 @@ This document provides an overview of CLI commands that can be sent to MeshCore --- #### Block a region -**Usage:** +**Usage:** - `region denyf ` -**Parameters:** +**Parameters:** - `name`: Region name (or `*` for wildcard) **Note:** Setting on wildcard `*` drops packets without region transport codes @@ -748,7 +748,7 @@ This document provides an overview of CLI commands that can be sent to MeshCore --- #### Show information for a region -**Usage:** +**Usage:** - `region get ` **Parameters:** @@ -757,7 +757,7 @@ This document provides an overview of CLI commands that can be sent to MeshCore --- #### View or change the home region for this node -**Usage:** +**Usage:** - `region home` - `region home ` @@ -767,7 +767,7 @@ This document provides an overview of CLI commands that can be sent to MeshCore --- #### View or change the default scope region for this node -**Usage:** +**Usage:** - `region default` - `region default {name|}` @@ -777,7 +777,7 @@ This document provides an overview of CLI commands that can be sent to MeshCore --- #### Create a new region -**Usage:** +**Usage:** - `region put [parent_name]` **Parameters:** @@ -828,18 +828,18 @@ region save --- #### Remove a region -**Usage:** +**Usage:** - `region remove ` **Parameters:** - `name`: Region name -**Note:** Must remove all child regions before the region can be removed +**Note:** Must remove all child regions before the region can be removed --- #### View all regions -**Usage:** +**Usage:** - `region list ` **Serial Only:** Yes @@ -852,7 +852,7 @@ region save --- #### Dump all defined regions and flood permissions -**Usage:** +**Usage:** - `region` **Serial Only:** For firmware older than 1.12.0 @@ -877,7 +877,7 @@ region save **Example 2: Using Wildcard with F Flag** ``` -region load +region load * F region save @@ -892,7 +892,7 @@ region save **Example 3: Using Wildcard Without F Flag** ``` -region load +region load * region save @@ -906,7 +906,7 @@ region save **Example 4: Nested Public Region with F Flag** ``` -region load +region load #Europe F #UK #London @@ -927,7 +927,7 @@ region save **Example 5: Wildcard with Nested Public Regions** ``` -region load +region load * F #NorthAmerica #USA @@ -966,13 +966,13 @@ region save --- #### Sync this node's clock with GPS time -**Usage:** +**Usage:** - `gps sync` --- #### Set this node's location based on the GPS coordinates -**Usage:** +**Usage:** - `gps setloc` --- @@ -982,8 +982,8 @@ region save - `gps advert` - `gps advert ` -**Parameters:** -- `policy`: `none`|`share`|`prefs` +**Parameters:** +- `policy`: `none`|`share`|`prefs` - `none`: don't include location in adverts - `share`: share gps location (from SensorManager) - `prefs`: location stored in node's lat and lon settings @@ -1005,7 +1005,7 @@ region save --- #### View or change the value of a sensor -**Usage:** +**Usage:** - `sensor get ` - `sensor set ` @@ -1020,6 +1020,15 @@ region save #### View the compiled bridge type **Usage:** `get bridge.type` +**Returns:** +- `none` +- `rs232` +- `espnow` +- `usbserial` +- `tcp` + +**Default:** `none` + --- #### View or change the bridge enabled flag @@ -1052,7 +1061,7 @@ region save - `set bridge.source ` **Parameters:** -- `source`: +- `source`: - `logRx`: bridges received packets - `logTx`: bridges transmitted packets @@ -1083,7 +1092,7 @@ region save --- #### Set the ESP-Now secret -**Usage:** +**Usage:** - `get bridge.secret` - `set bridge.secret ` diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index edf6d03ca4..6bde518434 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -713,9 +713,20 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep strcpy(reply, "Error: delay must be between 0-10000 ms"); } } else if (memcmp(config, "bridge.source ", 14) == 0) { - _prefs->bridge_pkt_src = memcmp(&config[14], "rx", 2) == 0; - savePrefs(); - strcpy(reply, "OK"); + if (memcmp(&config[14], "log", 3) == 0) { + if (memcmp(&config[17], "Rx", 2) == 0 || memcmp(&config[17], "rx", 2) == 0) { + _prefs->bridge_pkt_src = 1; + strcpy(reply, "OK"); + } else if (memcmp(&config[17], "Tx", 2) == 0 || memcmp(&config[17], "tx", 2) == 0) { + _prefs->bridge_pkt_src = 0; + strcpy(reply, "OK"); + } + } + if (memcmp(reply, "OK", 2) == 0) { + savePrefs(); + } else { + strcpy(reply, "Error: source must be logRx or logTx."); + } #endif #ifdef WITH_RS232_BRIDGE } else if (memcmp(config, "bridge.baud ", 12) == 0) {