Fix TCP receive: path table update + interface naming + 10Mbps bitrate
- Fix path table insert bug: C++ map::insert() silently fails when key exists (unlike Python dict[key]=value). Changed to erase()+insert() so updated paths (e.g. local TCP replacing stale LoRa) actually take effect. - Add name parameter to TcpInterface constructor to give each instance a unique identity hash, fixing map collision between backbone and local TCP server interfaces. - Set TCP interface bitrate to 10 Mbps (was 500 bps) so Transport correctly prefers TCP paths over LoRa when both exist. - Add PRG button hold >5s white screen indicator for config portal. - Boundary mode cull_path_table: evict backbone paths first, preserving local paths needed for inbound routing.
This commit is contained in:
@@ -1196,7 +1196,14 @@ bool epd_blanked = false;
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef BOUNDARY_MODE
|
||||||
|
extern bool display_lock_white;
|
||||||
|
#endif
|
||||||
|
|
||||||
void update_display(bool blank = false) {
|
void update_display(bool blank = false) {
|
||||||
|
#ifdef BOUNDARY_MODE
|
||||||
|
if (display_lock_white) return;
|
||||||
|
#endif
|
||||||
display_updating = true;
|
display_updating = true;
|
||||||
if (blank == true) {
|
if (blank == true) {
|
||||||
last_disp_update = millis()-disp_update_interval-1;
|
last_disp_update = millis()-disp_update_interval-1;
|
||||||
|
|||||||
19
Input.h
19
Input.h
@@ -31,6 +31,7 @@
|
|||||||
|
|
||||||
int button_events = EVENT_CLICKS;
|
int button_events = EVENT_CLICKS;
|
||||||
int button_state = RELEASED;
|
int button_state = RELEASED;
|
||||||
|
bool display_lock_white = false;
|
||||||
int debounce_state = button_state;
|
int debounce_state = button_state;
|
||||||
unsigned long button_debounce_last = 0;
|
unsigned long button_debounce_last = 0;
|
||||||
unsigned long button_debounce_delay = 25;
|
unsigned long button_debounce_delay = 25;
|
||||||
@@ -82,6 +83,24 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Live hold indicator: turn display white when held >5s ──
|
||||||
|
#ifdef BOUNDARY_MODE
|
||||||
|
{
|
||||||
|
if (button_state == PRESSED && button_down_last > 0) {
|
||||||
|
unsigned long held = millis() - button_down_last;
|
||||||
|
if (held > 5000 && !display_lock_white) {
|
||||||
|
display_lock_white = true;
|
||||||
|
#if HAS_DISPLAY
|
||||||
|
if (disp_ready) {
|
||||||
|
display.fillScreen(SSD1306_WHITE);
|
||||||
|
display.display();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool button_pressed() {
|
bool button_pressed() {
|
||||||
|
|||||||
35
README.md
Normal file → Executable file
35
README.md
Normal file → Executable file
@@ -3,16 +3,19 @@
|
|||||||
A custom firmware for the **Heltec WiFi LoRa 32 V4** (ESP32-S3 + SX1262) that operates as a **Boundary Node** — bridging a local LoRa radio network with a remote TCP/IP backbone (such as [rmap.world](https://rmap.world)) over WiFi.
|
A custom firmware for the **Heltec WiFi LoRa 32 V4** (ESP32-S3 + SX1262) that operates as a **Boundary Node** — bridging a local LoRa radio network with a remote TCP/IP backbone (such as [rmap.world](https://rmap.world)) over WiFi.
|
||||||
|
|
||||||
```
|
```
|
||||||
Android / Sideband Remote
|
Android / Sideband Remote
|
||||||
┌──────────┐ ┌──────────────┐ WiFi Reticulum
|
┌──────────┐ ┌────────────┐ Reticulum
|
||||||
│ Sideband │◄── BT ──►│ RNode (V4) │◄── TCP ──────────► Backbone
|
│ Sideband │◄── BT ──►│ RNode (BT) │ Backbone
|
||||||
│ App │ │ Boundary Mode│ ▲ (rnsd /
|
│ App │ └─────┬──────┘ (rnsd /
|
||||||
└──────────┘ └──────┬───────┘ │ rmap.world)
|
└──────────┘ │ rmap.world)
|
||||||
│ ┌───┴───┐
|
LoRa Radio ▲
|
||||||
LoRa Radio │ Router │
|
│ ┌──────────────┐ WiFi │
|
||||||
│ └───────┘
|
◄── RF mesh ──────►│ RNodeTHV4 │◄─TCP──┘
|
||||||
◄── RF mesh ──►
|
│ │ Boundary Node│ ▲
|
||||||
Other RNodes
|
Other RNodes └──────────────┘ │
|
||||||
|
┌───┴───┐
|
||||||
|
│ Router │
|
||||||
|
└───────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
Built on [microReticulum](https://github.com/attermann/microReticulum) (a C++ port of the [Reticulum](https://reticulum.network/) network stack) and the [RNode firmware](https://github.com/markqvist/RNode_Firmware) by Mark Qvist.
|
Built on [microReticulum](https://github.com/attermann/microReticulum) (a C++ port of the [Reticulum](https://reticulum.network/) network stack) and the [RNode firmware](https://github.com/markqvist/RNode_Firmware) by Mark Qvist.
|
||||||
@@ -70,7 +73,7 @@ The config portal activates automatically on:
|
|||||||
- **First boot** — when no saved configuration exists
|
- **First boot** — when no saved configuration exists
|
||||||
- **Button hold >5 seconds** — hold the PRG button for 5+ seconds, the device reboots into config mode
|
- **Button hold >5 seconds** — hold the PRG button for 5+ seconds, the device reboots into config mode
|
||||||
|
|
||||||
When active, the device creates a WiFi access point named **`RNode-Boundary-Setup`** (open network). Connect to it and browse to `http://192.168.4.1`.
|
When active, the device creates a WiFi access point named **`RNode-Boundary-Setup`** (open network). A captive portal should appear automatically when you connect; if not, browse to `http://192.168.4.1`.
|
||||||
|
|
||||||
### Config Page Options
|
### Config Page Options
|
||||||
|
|
||||||
@@ -103,7 +106,7 @@ The web form has four sections:
|
|||||||
| **Bandwidth** | 7.8 kHz – 500 kHz (typically `125 kHz`) |
|
| **Bandwidth** | 7.8 kHz – 500 kHz (typically `125 kHz`) |
|
||||||
| **Spreading Factor** | SF6 – SF12 (typically `SF7` for backbone, `SF10` for long range) |
|
| **Spreading Factor** | SF6 – SF12 (typically `SF7` for backbone, `SF10` for long range) |
|
||||||
| **Coding Rate** | 4/5 – 4/8 |
|
| **Coding Rate** | 4/5 – 4/8 |
|
||||||
| **TX Power** | 2 – 22 dBm |
|
| **TX Power** | 2 – 28 dBm |
|
||||||
|
|
||||||
After saving, the device reboots with the new configuration applied.
|
After saving, the device reboots with the new configuration applied.
|
||||||
|
|
||||||
@@ -141,7 +144,7 @@ The 128×64 OLED is split into two panels:
|
|||||||
|
|
||||||
## Interface Modes
|
## Interface Modes
|
||||||
|
|
||||||
The firmware runs **two RNS interfaces** simultaneously, using different interface modes to control announce propagation and routing behavior:
|
The firmware runs up to **three RNS interfaces** simultaneously, using different interface modes to control announce propagation and routing behavior:
|
||||||
|
|
||||||
### LoRa Interface — `MODE_ACCESS_POINT`
|
### LoRa Interface — `MODE_ACCESS_POINT`
|
||||||
|
|
||||||
@@ -152,10 +155,10 @@ The LoRa radio operates in **Access Point mode**. In Reticulum, this means:
|
|||||||
|
|
||||||
### TCP Backbone Interface — `MODE_BOUNDARY`
|
### TCP Backbone Interface — `MODE_BOUNDARY`
|
||||||
|
|
||||||
The TCP backbone connection uses a custom **Boundary mode** (`0x20`), a new interface mode added to microReticulum for this firmware. Boundary mode means:
|
The TCP backbone connection uses `MODE_BOUNDARY` (`0x20`), a custom implementation of the Reticulum boundary concept adapted for the memory-constrained ESP32 environment. In this implementation, boundary mode means:
|
||||||
- Incoming announces from the backbone are received and cached, but **not stored in the path table by default** — only stored when specifically requested via a path request from a local LoRa node
|
- Incoming announces from the backbone are received and cached, but **not stored in the path table by default** — only stored when specifically requested via a path request from a local LoRa node
|
||||||
- This prevents the path table (limited to 48 entries on ESP32) from being overwhelmed by thousands of backbone destinations
|
- This prevents the path table (limited to 48 entries on ESP32) from being overwhelmed by thousands of backbone destinations
|
||||||
- When the path table needs to be culled, **Boundary-mode paths are evicted first**, preserving locally-needed LoRa paths
|
- When the path table needs to be culled, **boundary-mode paths are evicted first**, preserving locally-needed LoRa paths
|
||||||
|
|
||||||
### Optional Local TCP Server — `MODE_ACCESS_POINT`
|
### Optional Local TCP Server — `MODE_ACCESS_POINT`
|
||||||
|
|
||||||
@@ -212,6 +215,8 @@ The original microReticulum `get_cached_packet()` function called `update_hash()
|
|||||||
|
|
||||||
This was changed to call `unpack()` instead, which parses all packet fields AND computes the hash. Without this fix, path responses contained empty destination hashes and were silently dropped by LoRa nodes.
|
This was changed to call `unpack()` instead, which parses all packet fields AND computes the hash. Without this fix, path responses contained empty destination hashes and were silently dropped by LoRa nodes.
|
||||||
|
|
||||||
|
> **Note:** `unpack()` only parses the plaintext routing envelope (destination hash, flags, hops, transport headers). It does not decrypt the end-to-end encrypted payload. Every Reticulum transport node performs equivalent header parsing during normal routing — this is standard behavior, not a security concern.
|
||||||
|
|
||||||
## Connecting to the Backbone
|
## Connecting to the Backbone
|
||||||
|
|
||||||
### Example: Connect to rmap.world
|
### Example: Connect to rmap.world
|
||||||
|
|||||||
@@ -472,6 +472,9 @@ void setup() {
|
|||||||
// Load LoRa config from EEPROM so the portal can show current values
|
// Load LoRa config from EEPROM so the portal can show current values
|
||||||
eeprom_conf_load();
|
eeprom_conf_load();
|
||||||
|
|
||||||
|
// Load boundary config so the portal can show current/default values
|
||||||
|
boundary_load_config();
|
||||||
|
|
||||||
// Enter config mode if: first boot with no config, OR button-triggered reboot
|
// Enter config mode if: first boot with no config, OR button-triggered reboot
|
||||||
bool need_config = boundary_needs_config();
|
bool need_config = boundary_needs_config();
|
||||||
bool config_requested = (boundary_config_request == BOUNDARY_CONFIG_MAGIC);
|
bool config_requested = (boundary_config_request == BOUNDARY_CONFIG_MAGIC);
|
||||||
@@ -695,7 +698,8 @@ void setup() {
|
|||||||
TCP_IF_MODE_SERVER,
|
TCP_IF_MODE_SERVER,
|
||||||
boundary_state.ap_tcp_port,
|
boundary_state.ap_tcp_port,
|
||||||
"", // no target host for server mode
|
"", // no target host for server mode
|
||||||
0
|
0,
|
||||||
|
"LocalTcpInterface"
|
||||||
);
|
);
|
||||||
local_tcp_rns_interface = local_tcp_interface_ptr;
|
local_tcp_rns_interface = local_tcp_interface_ptr;
|
||||||
local_tcp_rns_interface.mode(RNS::Type::Interface::MODE_ACCESS_POINT);
|
local_tcp_rns_interface.mode(RNS::Type::Interface::MODE_ACCESS_POINT);
|
||||||
|
|||||||
@@ -60,8 +60,9 @@ struct TcpClient {
|
|||||||
class TcpInterface : public RNS::InterfaceImpl {
|
class TcpInterface : public RNS::InterfaceImpl {
|
||||||
public:
|
public:
|
||||||
TcpInterface(TcpIfMode mode, uint16_t port = TCP_IF_DEFAULT_PORT,
|
TcpInterface(TcpIfMode mode, uint16_t port = TCP_IF_DEFAULT_PORT,
|
||||||
const char* target_host = nullptr, uint16_t target_port = 0)
|
const char* target_host = nullptr, uint16_t target_port = 0,
|
||||||
: RNS::InterfaceImpl("TcpInterface"),
|
const char* name = "TcpInterface")
|
||||||
|
: RNS::InterfaceImpl(name),
|
||||||
_mode(mode),
|
_mode(mode),
|
||||||
_port(port),
|
_port(port),
|
||||||
_target_port(target_port),
|
_target_port(target_port),
|
||||||
@@ -77,11 +78,11 @@ public:
|
|||||||
_IN = true;
|
_IN = true;
|
||||||
_OUT = true;
|
_OUT = true;
|
||||||
_HW_MTU = TCP_IF_HW_MTU;
|
_HW_MTU = TCP_IF_HW_MTU;
|
||||||
// Report low bitrate + small announce_cap so that Transport
|
// TCP links are effectively 10 Mbps+. Setting a realistic
|
||||||
// rate-limits announce forwarding through this interface.
|
// bitrate lets Transport prefer TCP paths over LoRa when
|
||||||
// Without this the backbone floods the ESP32 with announces.
|
// both exist for the same destination.
|
||||||
// 500 bps ≈ LoRa-class throughput; announce_cap = 2% max bandwidth.
|
// announce_cap = 2% keeps backbone announce flooding in check.
|
||||||
_bitrate = 500;
|
_bitrate = 10000000;
|
||||||
_announce_cap = 2.0;
|
_announce_cap = 2.0;
|
||||||
if (target_host != nullptr) {
|
if (target_host != nullptr) {
|
||||||
strncpy(_target_host, target_host, sizeof(_target_host) - 1);
|
strncpy(_target_host, target_host, sizeof(_target_host) - 1);
|
||||||
|
|||||||
Reference in New Issue
Block a user