Skip to content

Commit ea33f39

Browse files
authored
Merge pull request #454 from jbrazio/jbrazio/2025_3f11ad35
RS232/ESP-NOW Bridge/cross repeater implementation
2 parents c44d84c + a55fa8d commit ea33f39

File tree

29 files changed

+1981
-30
lines changed

29 files changed

+1981
-30
lines changed

examples/simple_repeater/main.cpp

Lines changed: 66 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -78,28 +78,38 @@
7878

7979
/* ------------------------------ Code -------------------------------- */
8080

81-
#define REQ_TYPE_GET_STATUS 0x01 // same as _GET_STATS
82-
#define REQ_TYPE_KEEP_ALIVE 0x02
83-
#define REQ_TYPE_GET_TELEMETRY_DATA 0x03
84-
85-
#define RESP_SERVER_LOGIN_OK 0 // response to ANON_REQ
86-
87-
struct RepeaterStats {
88-
uint16_t batt_milli_volts;
89-
uint16_t curr_tx_queue_len;
90-
int16_t noise_floor;
91-
int16_t last_rssi;
92-
uint32_t n_packets_recv;
93-
uint32_t n_packets_sent;
94-
uint32_t total_air_time_secs;
95-
uint32_t total_up_time_secs;
96-
uint32_t n_sent_flood, n_sent_direct;
97-
uint32_t n_recv_flood, n_recv_direct;
98-
uint16_t err_events; // was 'n_full_events'
99-
int16_t last_snr; // x 4
100-
uint16_t n_direct_dups, n_flood_dups;
101-
uint32_t total_rx_air_time_secs;
102-
};
81+
#ifdef WITH_RS232_BRIDGE
82+
#include "helpers/bridges/RS232Bridge.h"
83+
#define WITH_BRIDGE
84+
#endif
85+
86+
#ifdef WITH_ESPNOW_BRIDGE
87+
#include "helpers/bridges/ESPNowBridge.h"
88+
#define WITH_BRIDGE
89+
#endif
90+
91+
#define REQ_TYPE_GET_STATUS 0x01 // same as _GET_STATS
92+
#define REQ_TYPE_KEEP_ALIVE 0x02
93+
#define REQ_TYPE_GET_TELEMETRY_DATA 0x03
94+
95+
#define RESP_SERVER_LOGIN_OK 0 // response to ANON_REQ
96+
97+
struct RepeaterStats {
98+
uint16_t batt_milli_volts;
99+
uint16_t curr_tx_queue_len;
100+
int16_t noise_floor;
101+
int16_t last_rssi;
102+
uint32_t n_packets_recv;
103+
uint32_t n_packets_sent;
104+
uint32_t total_air_time_secs;
105+
uint32_t total_up_time_secs;
106+
uint32_t n_sent_flood, n_sent_direct;
107+
uint32_t n_recv_flood, n_recv_direct;
108+
uint16_t err_events; // was 'n_full_events'
109+
int16_t last_snr; // x 4
110+
uint16_t n_direct_dups, n_flood_dups;
111+
uint32_t total_rx_air_time_secs;
112+
};
103113

104114
struct ClientInfo {
105115
mesh::Identity id;
@@ -114,6 +124,10 @@ struct ClientInfo {
114124
#define MAX_CLIENTS 32
115125
#endif
116126

127+
#ifdef WITH_BRIDGE
128+
AbstractBridge* bridge;
129+
#endif
130+
117131
struct NeighbourInfo {
118132
mesh::Identity id;
119133
uint32_t advert_timestamp;
@@ -300,6 +314,9 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
300314
}
301315
}
302316
void logTx(mesh::Packet* pkt, int len) override {
317+
#ifdef WITH_BRIDGE
318+
bridge->onPacketTransmitted(pkt);
319+
#endif
303320
if (_logging) {
304321
File f = openAppend(PACKET_LOG_FILE);
305322
if (f) {
@@ -364,9 +381,9 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
364381
} else if (strcmp((char *) &data[4], _prefs.guest_password) == 0) { // check guest password
365382
is_admin = false;
366383
} else {
367-
#if MESH_DEBUG
384+
#if MESH_DEBUG
368385
MESH_DEBUG_PRINTLN("Invalid password: %s", &data[4]);
369-
#endif
386+
#endif
370387
return;
371388
}
372389

@@ -384,15 +401,15 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
384401

385402
uint32_t now = getRTCClock()->getCurrentTimeUnique();
386403
memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp
387-
#if 0
404+
#if 0
388405
memcpy(&reply_data[4], "OK", 2); // legacy response
389-
#else
406+
#else
390407
reply_data[4] = RESP_SERVER_LOGIN_OK;
391408
reply_data[5] = 0; // NEW: recommended keep-alive interval (secs / 16)
392409
reply_data[6] = is_admin ? 1 : 0;
393410
reply_data[7] = 0; // FUTURE: reserved
394411
getRNG()->random(&reply_data[8], 4); // random blob to help packet-hash uniqueness
395-
#endif
412+
#endif
396413

397414
if (packet->isRouteFlood()) {
398415
// let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response
@@ -565,14 +582,23 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
565582
: mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables),
566583
_cli(board, rtc, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4)
567584
{
585+
#ifdef WITH_BRIDGE
586+
#if defined(WITH_RS232_BRIDGE)
587+
bridge = new RS232Bridge(WITH_RS232_BRIDGE, _mgr, &rtc);
588+
#elif defined(WITH_ESPNOW_BRIDGE)
589+
bridge = new ESPNowBridge(_mgr, &rtc);
590+
#else
591+
#error "You must choose either RS232 or ESPNow bridge"
592+
#endif
593+
#endif
568594
memset(known_clients, 0, sizeof(known_clients));
569595
next_local_advert = next_flood_advert = 0;
570596
set_radio_at = revert_radio_at = 0;
571597
_logging = false;
572598

573-
#if MAX_NEIGHBOURS
599+
#if MAX_NEIGHBOURS
574600
memset(neighbours, 0, sizeof(neighbours));
575-
#endif
601+
#endif
576602

577603
// defaults
578604
memset(&_prefs, 0, sizeof(_prefs));
@@ -765,6 +791,10 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
765791
}
766792

767793
void loop() {
794+
#ifdef WITH_BRIDGE
795+
bridge->loop();
796+
#endif
797+
768798
mesh::Mesh::loop();
769799

770800
if (next_flood_advert && millisHasNowPassed(next_flood_advert)) {
@@ -813,6 +843,10 @@ void setup() {
813843
Serial.begin(115200);
814844
delay(1000);
815845

846+
#ifdef WITH_BRIDGE
847+
bridge->begin();
848+
#endif
849+
816850
board.begin();
817851

818852
#ifdef DISPLAY_CLASS
@@ -824,7 +858,9 @@ void setup() {
824858
}
825859
#endif
826860

827-
if (!radio_init()) { halt(); }
861+
if (!radio_init()) {
862+
halt();
863+
}
828864

829865
fast_rng.begin(radio_get_rng_seed());
830866

platformio.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ build_src_filter =
4747
+<*.cpp>
4848
+<helpers/*.cpp>
4949
+<helpers/radiolib/*.cpp>
50+
+<helpers/bridges/BridgeBase.cpp>
5051
+<helpers/ui/MomentaryButton.cpp>
5152

5253
; ----------------- ESP32 ---------------------

src/helpers/AbstractBridge.h

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#pragma once
2+
3+
#include <Mesh.h>
4+
5+
class AbstractBridge {
6+
public:
7+
virtual ~AbstractBridge() {}
8+
9+
/**
10+
* @brief Initializes the bridge.
11+
*/
12+
virtual void begin() = 0;
13+
14+
/**
15+
* @brief A method to be called on every main loop iteration.
16+
* Used for tasks like checking for incoming data.
17+
*/
18+
virtual void loop() = 0;
19+
20+
/**
21+
* @brief A callback that is triggered when the mesh transmits a packet.
22+
* The bridge can use this to forward the packet.
23+
*
24+
* @param packet The packet that was transmitted.
25+
*/
26+
virtual void onPacketTransmitted(mesh::Packet* packet) = 0;
27+
28+
/**
29+
* @brief Processes a received packet from the bridge's medium.
30+
*
31+
* @param packet The packet that was received.
32+
*/
33+
virtual void onPacketReceived(mesh::Packet* packet) = 0;
34+
};

src/helpers/bridges/BridgeBase.cpp

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#include "BridgeBase.h"
2+
3+
#include <Arduino.h>
4+
5+
const char *BridgeBase::getLogDateTime() {
6+
static char tmp[32];
7+
uint32_t now = _rtc->getCurrentTime();
8+
DateTime dt = DateTime(now);
9+
sprintf(tmp, "%02d:%02d:%02d - %d/%d/%d U", dt.hour(), dt.minute(), dt.second(), dt.day(), dt.month(),
10+
dt.year());
11+
return tmp;
12+
}
13+
14+
uint16_t BridgeBase::fletcher16(const uint8_t *data, size_t len) {
15+
uint8_t sum1 = 0, sum2 = 0;
16+
17+
for (size_t i = 0; i < len; i++) {
18+
sum1 = (sum1 + data[i]) % 255;
19+
sum2 = (sum2 + sum1) % 255;
20+
}
21+
22+
return (sum2 << 8) | sum1;
23+
}
24+
25+
bool BridgeBase::validateChecksum(const uint8_t *data, size_t len, uint16_t received_checksum) {
26+
uint16_t calculated_checksum = fletcher16(data, len);
27+
return received_checksum == calculated_checksum;
28+
}
29+
30+
void BridgeBase::handleReceivedPacket(mesh::Packet *packet) {
31+
if (!_seen_packets.hasSeen(packet)) {
32+
_mgr->queueInbound(packet, millis() + BRIDGE_DELAY);
33+
} else {
34+
_mgr->free(packet);
35+
}
36+
}

src/helpers/bridges/BridgeBase.h

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
#pragma once
2+
3+
#include "helpers/AbstractBridge.h"
4+
#include "helpers/SimpleMeshTables.h"
5+
6+
#include <RTClib.h>
7+
8+
/**
9+
* @brief Base class implementing common bridge functionality
10+
*
11+
* This class provides common functionality used by different bridge implementations
12+
* like packet tracking, checksum calculation, timestamping, and duplicate detection.
13+
*
14+
* Features:
15+
* - Fletcher-16 checksum calculation for data integrity
16+
* - Packet duplicate detection using SimpleMeshTables
17+
* - Common timestamp formatting for debug logging
18+
* - Shared packet management and queuing logic
19+
*/
20+
class BridgeBase : public AbstractBridge {
21+
public:
22+
virtual ~BridgeBase() = default;
23+
24+
/**
25+
* @brief Common magic number used by all bridge implementations for packet identification
26+
*
27+
* This magic number is placed at the beginning of bridge packets to identify
28+
* them as mesh bridge packets and provide frame synchronization.
29+
*/
30+
static constexpr uint16_t BRIDGE_PACKET_MAGIC = 0xC03E;
31+
32+
/**
33+
* @brief Common field sizes used by bridge implementations
34+
*
35+
* These constants define the size of common packet fields used across bridges.
36+
* BRIDGE_MAGIC_SIZE is used by all bridges for packet identification.
37+
* BRIDGE_LENGTH_SIZE is used by bridges that need explicit length fields (like RS232).
38+
* BRIDGE_CHECKSUM_SIZE is used by all bridges for Fletcher-16 checksums.
39+
*/
40+
static constexpr uint16_t BRIDGE_MAGIC_SIZE = sizeof(BRIDGE_PACKET_MAGIC);
41+
static constexpr uint16_t BRIDGE_LENGTH_SIZE = sizeof(uint16_t);
42+
static constexpr uint16_t BRIDGE_CHECKSUM_SIZE = sizeof(uint16_t);
43+
44+
/**
45+
* @brief Default delay in milliseconds for scheduling inbound packet processing
46+
*
47+
* It provides a buffer to prevent immediate processing conflicts in the mesh network.
48+
* Used in handleReceivedPacket() as: millis() + BRIDGE_DELAY
49+
*/
50+
static constexpr uint16_t BRIDGE_DELAY = 500; // TODO: maybe too high ?
51+
52+
protected:
53+
/** Packet manager for allocating and queuing mesh packets */
54+
mesh::PacketManager *_mgr;
55+
56+
/** RTC clock for timestamping debug messages */
57+
mesh::RTCClock *_rtc;
58+
59+
/** Tracks seen packets to prevent loops in broadcast communications */
60+
SimpleMeshTables _seen_packets;
61+
62+
/**
63+
* @brief Constructs a BridgeBase instance
64+
*
65+
* @param mgr PacketManager for allocating and queuing packets
66+
* @param rtc RTCClock for timestamping debug messages
67+
*/
68+
BridgeBase(mesh::PacketManager *mgr, mesh::RTCClock *rtc) : _mgr(mgr), _rtc(rtc) {}
69+
70+
/**
71+
* @brief Gets formatted date/time string for logging
72+
*
73+
* Format: "HH:MM:SS - DD/MM/YYYY U"
74+
*
75+
* @return Formatted date/time string
76+
*/
77+
const char *getLogDateTime();
78+
79+
/**
80+
* @brief Calculate Fletcher-16 checksum
81+
*
82+
* Based on: https://en.wikipedia.org/wiki/Fletcher%27s_checksum
83+
* Used to verify data integrity of received packets
84+
*
85+
* @param data Pointer to data to calculate checksum for
86+
* @param len Length of data in bytes
87+
* @return Calculated Fletcher-16 checksum
88+
*/
89+
static uint16_t fletcher16(const uint8_t *data, size_t len);
90+
91+
/**
92+
* @brief Validate received checksum against calculated checksum
93+
*
94+
* @param data Pointer to data to validate
95+
* @param len Length of data in bytes
96+
* @param received_checksum Checksum received with data
97+
* @return true if checksum is valid, false otherwise
98+
*/
99+
bool validateChecksum(const uint8_t *data, size_t len, uint16_t received_checksum);
100+
101+
/**
102+
* @brief Common packet handling for received packets
103+
*
104+
* Implements the standard pattern used by all bridges:
105+
* - Check if packet was seen before using _seen_packets.hasSeen()
106+
* - Queue packet for mesh processing if not seen before
107+
* - Free packet if already seen to prevent duplicates
108+
*
109+
* @param packet The received mesh packet
110+
*/
111+
void handleReceivedPacket(mesh::Packet *packet);
112+
};

0 commit comments

Comments
 (0)