Skip to content
129 changes: 121 additions & 8 deletions examples/simple_repeater/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@

/* ------------------------------ Code -------------------------------- */

#ifdef BRIDGE_OVER_SERIAL
#define SERIAL_PKT_MAGIC 0xcafe
#endif

#define REQ_TYPE_GET_STATUS 0x01 // same as _GET_STATS
#define REQ_TYPE_KEEP_ALIVE 0x02
#define REQ_TYPE_GET_TELEMETRY_DATA 0x03
Expand Down Expand Up @@ -252,6 +256,89 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
#endif
}

#ifdef BRIDGE_OVER_SERIAL
struct SerialPacket {
uint16_t magic, len, crc;
uint8_t payload[MAX_TRANS_UNIT];
SerialPacket() : magic(SERIAL_PKT_MAGIC), len(0), crc(0) {}
};

// Fletcher-16
// https://en.wikipedia.org/wiki/Fletcher%27s_checksum
inline static uint16_t fletcher16(const uint8_t *bytes, const size_t len) {
uint8_t sum1 = 0, sum2 = 0;

for (size_t i = 0; i < len; i++) {
sum1 = (sum1 + bytes[i]) % 255;
sum2 = (sum2 + sum1) % 255;
}

return (sum2 << 8) | sum1;
};

inline void serialBridgeSendPkt(const mesh::Packet *pkt) {
SerialPacket spkt;
spkt.len = pkt->writeTo(spkt.payload);
spkt.crc = fletcher16(spkt.payload, spkt.len);
BRIDGE_OVER_SERIAL.write((uint8_t *)&spkt, sizeof(SerialPacket));

#if MESH_PACKET_LOGGING
Serial.printf("%s: BRIDGE: TX, len=%d crc=0x%04x\n", getLogDateTime(), spkt.len, spkt.crc);
#endif
}

inline void serialBridgeReceivePkt() {
static constexpr uint16_t size = sizeof(SerialPacket) + 1;
static uint8_t buffer[size];
static uint16_t tail = 0;

while (BRIDGE_OVER_SERIAL.available()) {
buffer[tail] = (uint8_t)BRIDGE_OVER_SERIAL.read();
MESH_DEBUG_PRINT("%02x ", buffer[tail]);
tail = (tail + 1) % size;

// Check for complete packet by looking back to where the magic number should be
const uint16_t head = (tail - sizeof(SerialPacket) + size) % size;
if ((buffer[head] | (buffer[(head + 1) % size] << 8)) != SERIAL_PKT_MAGIC) {
return;
}

uint8_t bytes[MAX_TRANS_UNIT];
const uint16_t len = buffer[(head + 2) % size] | (buffer[(head + 3) % size] << 8);

if (len == 0 || len > sizeof(bytes)) {
MESH_DEBUG_PRINTLN("%s: BRIDGE: RX, invalid packet len", getLogDateTime());
return;
}

for (size_t i = 0; i < len; i++) {
bytes[i] = buffer[(head + 6 + i) % size];
}

const uint16_t crc = buffer[(head + 4) % size] | (buffer[(head + 5) % size] << 8);
const uint16_t f16 = fletcher16(bytes, len);

#if MESH_PACKET_LOGGING
Serial.printf("%s: BRIDGE: RX, len=%d crc=0x%04x\n", getLogDateTime(), len, crc);
#endif

if ((f16 != crc)) {
MESH_DEBUG_PRINTLN("%s: BRIDGE: RX, invalid packet checksum", getLogDateTime());
return;
}

mesh::Packet *pkt = _mgr->allocNew();
if (pkt == NULL) {
MESH_DEBUG_PRINTLN("%s: BRIDGE: RX, no unused packets available", getLogDateTime());
return;
}

pkt->readFrom(bytes, len);
_mgr->queueInbound(pkt, futureMillis(0));
}
}
#endif

protected:
float getAirtimeBudgetFactor() const override {
return _prefs.airtime_factor;
Expand Down Expand Up @@ -300,6 +387,11 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
}
}
void logTx(mesh::Packet* pkt, int len) override {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pretty sure this should be logRx()

You want to send received packets over to the other side of the bridge.
If you send transmitted packets, you will also be sending back packets that came FROM the other side of bridge.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we use logRx() instead of logTx() then packets originated ON the bridge itself will not be sent over the bridge (adverts etc). So it makes impossible to manage bridge A from bridge B. Using logTx() we get duplicated packets on the serial line, but the hashing mechanism will prevent them from being transmitted over RF.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exactly what I've noticed ... and why I used logtx yesterday

#ifdef BRIDGE_OVER_SERIAL
if (!pkt->isMarkedDoNotRetransmit()) {
serialBridgeSendPkt(pkt);
}
#endif
if (_logging) {
File f = openAppend(PACKET_LOG_FILE);
if (f) {
Expand Down Expand Up @@ -364,9 +456,9 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
} else if (strcmp((char *) &data[4], _prefs.guest_password) == 0) { // check guest password
is_admin = false;
} else {
#if MESH_DEBUG
#if MESH_DEBUG
MESH_DEBUG_PRINTLN("Invalid password: %s", &data[4]);
#endif
#endif
return;
}

Expand All @@ -384,15 +476,15 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {

uint32_t now = getRTCClock()->getCurrentTimeUnique();
memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp
#if 0
#if 0
memcpy(&reply_data[4], "OK", 2); // legacy response
#else
#else
reply_data[4] = RESP_SERVER_LOGIN_OK;
reply_data[5] = 0; // NEW: recommended keep-alive interval (secs / 16)
reply_data[6] = is_admin ? 1 : 0;
reply_data[7] = 0; // FUTURE: reserved
getRNG()->random(&reply_data[8], 4); // random blob to help packet-hash uniqueness
#endif
#endif

if (packet->isRouteFlood()) {
// let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response
Expand Down Expand Up @@ -570,9 +662,9 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
set_radio_at = revert_radio_at = 0;
_logging = false;

#if MAX_NEIGHBOURS
#if MAX_NEIGHBOURS
memset(neighbours, 0, sizeof(neighbours));
#endif
#endif

// defaults
memset(&_prefs, 0, sizeof(_prefs));
Expand Down Expand Up @@ -740,6 +832,10 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
}

void loop() {
#ifdef BRIDGE_OVER_SERIAL
serialBridgeReceivePkt();
#endif

mesh::Mesh::loop();

if (next_flood_advert && millisHasNowPassed(next_flood_advert)) {
Expand Down Expand Up @@ -788,6 +884,21 @@ void setup() {
Serial.begin(115200);
delay(1000);

#ifdef BRIDGE_OVER_SERIAL
#if defined(ESP32)
BRIDGE_OVER_SERIAL.setPins(BRIDGE_OVER_SERIAL_RX, BRIDGE_OVER_SERIAL_TX);
#elif defined(RP2040_PLATFORM)
BRIDGE_OVER_SERIAL.setRX(BRIDGE_OVER_SERIAL_RX);
BRIDGE_OVER_SERIAL.setTX(BRIDGE_OVER_SERIAL_TX);
#elif defined(STM32_PLATFORM)
BRIDGE_OVER_SERIAL.setRx(BRIDGE_OVER_SERIAL_RX);
BRIDGE_OVER_SERIAL.setTx(BRIDGE_OVER_SERIAL_TX);
#else
#error SerialBridge was not tested on the current platform
#endif
BRIDGE_OVER_SERIAL.begin(115200);
#endif

board.begin();

#ifdef DISPLAY_CLASS
Expand All @@ -798,7 +909,9 @@ void setup() {
}
#endif

if (!radio_init()) { halt(); }
if (!radio_init()) {
halt();
}

fast_rng.begin(radio_get_rng_seed());

Expand Down
22 changes: 22 additions & 0 deletions variants/heltec_v3/platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,28 @@ lib_deps =
${esp32_ota.lib_deps}
bakercp/CRC32 @ ^2.0.0

[env:Heltec_v3_Bridge]
extends = Heltec_lora32_v3
build_flags =
${Heltec_lora32_v3.build_flags}
-D DISPLAY_CLASS=SSD1306Display
-D ADVERT_NAME='"Heltec Bridge"'
-D ADVERT_LAT=0.0
-D ADVERT_LON=0.0
-D ADMIN_PASSWORD='"password"'
-D MAX_NEIGHBOURS=8
-D BRIDGE_OVER_SERIAL=Serial2
-D BRIDGE_OVER_SERIAL_RX=5
-D BRIDGE_OVER_SERIAL_TX=6
-D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
build_src_filter = ${Heltec_lora32_v3.build_src_filter}
+<helpers/ui/SSD1306Display.cpp>
+<../examples/simple_repeater>
lib_deps =
${Heltec_lora32_v3.lib_deps}
${esp32_ota.lib_deps}

[env:Heltec_v3_room_server]
extends = Heltec_lora32_v3
build_flags =
Expand Down
22 changes: 22 additions & 0 deletions variants/lilygo_tlora_v2_1/platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,28 @@ lib_deps =
${LilyGo_TLora_V2_1_1_6.lib_deps}
${esp32_ota.lib_deps}

[env:LilyGo_TLora_V2_1_1_6_Bridge]
extends = LilyGo_TLora_V2_1_1_6
build_src_filter = ${LilyGo_TLora_V2_1_1_6.build_src_filter}
+<helpers/ui/SSD1306Display.cpp>
+<../examples/simple_repeater>
build_flags =
${LilyGo_TLora_V2_1_1_6.build_flags}
-D ADVERT_NAME='"TLora-V2.1-1.6 Bridge"'
-D ADVERT_LAT=0.0
-D ADVERT_LON=0.0
-D ADMIN_PASSWORD='"password"'
-D MAX_NEIGHBOURS=8
-D BRIDGE_OVER_SERIAL=Serial2
-D BRIDGE_OVER_SERIAL_RX=34
-D BRIDGE_OVER_SERIAL_TX=25
-D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
; -D CORE_DEBUG_LEVEL=3
lib_deps =
${LilyGo_TLora_V2_1_1_6.lib_deps}
${esp32_ota.lib_deps}

[env:LilyGo_TLora_V2_1_1_6_terminal_chat]
extends = LilyGo_TLora_V2_1_1_6
build_flags =
Expand Down
16 changes: 16 additions & 0 deletions variants/waveshare_rp2040_lora/platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,22 @@ build_flags = ${waveshare_rp2040_lora.build_flags}
build_src_filter = ${waveshare_rp2040_lora.build_src_filter}
+<../examples/simple_repeater>

[env:waveshare_rp2040_lora_Bridge]
extends = waveshare_rp2040_lora
build_flags = ${waveshare_rp2040_lora.build_flags}
-D ADVERT_NAME='"RP2040-LoRa Bridge"'
-D ADVERT_LAT=0.0
-D ADVERT_LON=0.0
-D ADMIN_PASSWORD='"password"'
-D MAX_NEIGHBOURS=8
-D BRIDGE_OVER_SERIAL=Serial2
-D BRIDGE_OVER_SERIAL_RX=9
-D BRIDGE_OVER_SERIAL_TX=8
-D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
build_src_filter = ${waveshare_rp2040_lora.build_src_filter}
+<../examples/simple_repeater>

[env:waveshare_rp2040_lora_room_server]
extends = waveshare_rp2040_lora
build_flags = ${waveshare_rp2040_lora.build_flags}
Expand Down