v1.0.5: Heltec V3 support, heap stability fix, TCP reconnection improvements
- Add Heltec WiFi LoRa 32 V3 board support (8MB flash, 8MB PSRAM) - New heltec_V3_boundary build environment in platformio.ini - Board auto-detection in flash.py (8MB=V3, 16MB=V4) - V3 board definition in Boards.h - Fix heap exhaustion causing watchdog reboots every ~70 min - Lower boundary_mentioned_addresses cap from 512 to 200 - Heap now stable at ~38KB free (was draining to 0) - TCP reconnection improvements in TcpInterface.h - SO_LINGER(0) for clean socket teardown - 10-minute read timeout prevents zombie connections - Defensive client cleanup on accept - Add heap telemetry instrumentation (HEAP-TEL) for monitoring - Add level guards on TRACE/DEBUG macros in Log.h - Update README for dual V3/V4 board support
This commit is contained in:
4
Boards.h
4
Boards.h
@@ -349,7 +349,11 @@
|
||||
#define HAS_DISPLAY true
|
||||
#define HAS_WIFI true
|
||||
#define HAS_BLUETOOTH false
|
||||
#ifdef BOUNDARY_MODE
|
||||
#define HAS_BLE false
|
||||
#else
|
||||
#define HAS_BLE true
|
||||
#endif
|
||||
#define HAS_PMU true
|
||||
#define HAS_CONSOLE true
|
||||
#define HAS_EEPROM true
|
||||
|
||||
50
README.md
50
README.md
@@ -1,6 +1,6 @@
|
||||
# RNodeTHV4 — Reticulum Boundary Node for Heltec WiFi LoRa 32 V4
|
||||
# RNodeTHV4 — Reticulum Boundary Node for Heltec WiFi LoRa 32 V3 / V4
|
||||
|
||||
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 V3** and **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
|
||||
@@ -28,18 +28,22 @@ Built on [microReticulum](https://github.com/attermann/microReticulum) (a C++ po
|
||||
- **Optional local TCP server** — serve local devices on your WiFi in addition to the backbone connection
|
||||
- **Automatic reconnection** — WiFi and TCP connections recover from drops with exponential backoff
|
||||
- **ESP32 memory-optimized** — table sizes, timeouts, and caching tuned for the constrained MCU environment
|
||||
- **Dual board support** — supports both Heltec V3 (8MB flash, 8MB PSRAM) and V4 (16MB flash, 2MB PSRAM) with automatic board detection
|
||||
|
||||
## Hardware
|
||||
|
||||
The **Heltec WiFi LoRa 32 V4** was chosen because it ships standard with **2 MB PSRAM** and **16 MB flash** — enough headroom for the microReticulum transport tables, packet caching to flash storage, and the web-based configuration portal. Many other LoRa dev boards come with only 4–8 MB flash and no PSRAM, which would require significant compromises to the boundary node's caching and routing capabilities.
|
||||
Both the **Heltec WiFi LoRa 32 V3** and **V4** are supported. These boards were chosen because they ship with PSRAM and ample flash — enough headroom for the microReticulum transport tables, packet caching to flash storage, and the web-based configuration portal. Many other LoRa dev boards come with only 4 MB flash and no PSRAM, which would require significant compromises to the boundary node's caching and routing capabilities.
|
||||
|
||||
| Component | Spec |
|
||||
|-----------|------|
|
||||
| **Board** | Heltec WiFi LoRa 32 V4 |
|
||||
| **MCU** | ESP32-S3, 2MB PSRAM, 16MB Flash |
|
||||
| **Radio** | SX1262 + GC1109 PA (up to 28 dBm) |
|
||||
| **Display** | SSD1306 OLED 128×64 |
|
||||
| **WiFi** | 2.4 GHz 802.11 b/g/n |
|
||||
| Component | Heltec V3 | Heltec V4 |
|
||||
|-----------|-----------|----------|
|
||||
| **MCU** | ESP32-S3 | ESP32-S3 |
|
||||
| **Flash** | 8 MB | 16 MB |
|
||||
| **PSRAM** | 8 MB (QSPI) | 2 MB (QSPI) |
|
||||
| **Radio** | SX1262 | SX1262 + GC1109 PA |
|
||||
| **TX Power** | Up to 22 dBm | Up to 28 dBm |
|
||||
| **Display** | SSD1306 OLED 128×64 | SSD1306 OLED 128×64 |
|
||||
| **WiFi** | 2.4 GHz 802.11 b/g/n | 2.4 GHz 802.11 b/g/n |
|
||||
| **USB** | Native USB CDC | Native USB CDC |
|
||||
|
||||
## Quick Start
|
||||
|
||||
@@ -56,13 +60,18 @@ git clone https://github.com/jrl290/RNodeTHV4.git
|
||||
cd RNodeTHV4
|
||||
|
||||
# Download latest firmware from GitHub Releases and flash
|
||||
# (auto-detects V3 vs V4 from flash size)
|
||||
python flash.py --download
|
||||
|
||||
# Or specify board explicitly
|
||||
python flash.py --download --board v3
|
||||
python flash.py --download --board v4
|
||||
|
||||
# Or flash a local binary
|
||||
python flash.py --file rnodethv4_firmware.bin
|
||||
```
|
||||
|
||||
The flash utility will list all available serial ports and prompt you to choose one. If no ports are detected, you may need to hold the **BOOT** button while pressing **RESET** to enter download mode.
|
||||
The flash utility auto-detects whether a V3 or V4 is connected by querying the flash size (8MB = V3, 16MB = V4). You can override with `--board v3` or `--board v4`. It will list all available serial ports and prompt you to choose one. If no ports are detected, you may need to hold the **BOOT** button while pressing **RESET** to enter download mode.
|
||||
|
||||
### Option B: Build from Source (PlatformIO)
|
||||
|
||||
@@ -74,15 +83,18 @@ For development or customization:
|
||||
git clone https://github.com/jrl290/RNodeTHV4.git
|
||||
cd RNodeTHV4
|
||||
|
||||
# Build
|
||||
# Build for V4
|
||||
pio run -e heltec_V4_boundary
|
||||
|
||||
# Build for V3
|
||||
pio run -e heltec_V3_boundary
|
||||
|
||||
# Flash (via PlatformIO)
|
||||
pio run -e heltec_V4_boundary -t upload
|
||||
|
||||
# Or create a merged binary and flash with the utility
|
||||
python flash.py --merge-only # creates rnodethv4_firmware.bin
|
||||
python flash.py # flash it
|
||||
python flash.py --merge-only # creates merged firmware bin
|
||||
python flash.py # flash it (auto-detects board)
|
||||
|
||||
# Monitor serial output (optional)
|
||||
pio device monitor -e heltec_V4_boundary
|
||||
@@ -326,8 +338,8 @@ Set the boundary node's **Local TCP Server** to **Enabled** (port 4242).
|
||||
| `TcpInterface.h` | TCP interface for both backbone and local server (implements `RNS::InterfaceImpl`) with HDLC framing, unique naming, and 10 Mbps bitrate |
|
||||
| `Display.h` | OLED display layout — boundary-specific status page |
|
||||
| `flash.py` | Flash utility — list serial ports, download from GitHub, merge & flash firmware |
|
||||
| `Boards.h` | Board variant definition for `heltec32v4_boundary` |
|
||||
| `platformio.ini` | Build targets: `heltec_V4_boundary` and `heltec_V4_boundary-local` |
|
||||
| `Boards.h` | Board variant definitions for V3 and V4 |
|
||||
| `platformio.ini` | Build targets: `heltec_V3_boundary`, `heltec_V4_boundary`, and `heltec_V4_boundary-local` |
|
||||
|
||||
### Library Patches
|
||||
|
||||
@@ -340,12 +352,12 @@ The firmware depends on [microReticulum](https://github.com/attermann/microRetic
|
||||
| `Identity.cpp` | `_known_destinations_maxsize` = 24, `cull_known_destinations()` |
|
||||
| `Type.h` | `MODE_BOUNDARY` = 0x20, reduced `MAX_QUEUED_ANNOUNCES`, `MAX_RECEIPTS`, shorter timeouts |
|
||||
|
||||
### Memory Usage (typical)
|
||||
### Memory Usage (typical, V4)
|
||||
|
||||
| Resource | Used | Available |
|
||||
|----------|------|-----------|
|
||||
|----------|------|----------|
|
||||
| RAM | ~21.7% | 320 KB |
|
||||
| Flash | ~18.1% | 16 MB |
|
||||
| Flash | ~18.4% | 16 MB |
|
||||
| PSRAM | Dynamic | 2 MB |
|
||||
|
||||
## License
|
||||
|
||||
@@ -635,7 +635,8 @@ void setup() {
|
||||
RNS::Transport::set_transmit_packet_callback(on_transmit_packet);
|
||||
|
||||
Serial.write("Starting RNS...\r\n");
|
||||
RNS::loglevel(RNS::LOG_TRACE);
|
||||
RNS::loglevel(RNS::LOG_VERBOSE);
|
||||
//RNS::loglevel(RNS::LOG_TRACE);
|
||||
//RNS::loglevel(RNS::LOG_MEM);
|
||||
|
||||
HEAD("Registering LoRA Interface...", RNS::LOG_TRACE);
|
||||
@@ -701,6 +702,9 @@ void setup() {
|
||||
0,
|
||||
"LocalTcpInterface"
|
||||
);
|
||||
// rnsd can be quiet for long stretches — use 10 min timeout
|
||||
// to prevent unnecessary reconnection cycles that leak lwIP memory
|
||||
local_tcp_interface_ptr->setReadTimeout(600000);
|
||||
local_tcp_rns_interface = local_tcp_interface_ptr;
|
||||
local_tcp_rns_interface.mode(RNS::Type::Interface::MODE_ACCESS_POINT);
|
||||
RNS::Transport::register_interface(local_tcp_rns_interface);
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#ifdef BOUNDARY_MODE
|
||||
|
||||
#include <WiFi.h>
|
||||
#include <lwip/sockets.h> // SO_LINGER — force RST to free lwIP PCBs immediately
|
||||
#include <Interface.h>
|
||||
#include <Transport.h>
|
||||
#include <Bytes.h>
|
||||
@@ -75,6 +76,7 @@ public:
|
||||
_last_reconnect(0),
|
||||
_last_keepalive(0),
|
||||
_reconnect_interval(TCP_IF_RECONNECT_MIN),
|
||||
_read_timeout(TCP_IF_READ_TIMEOUT),
|
||||
_resolved_ip((uint32_t)0),
|
||||
_consecutive_failures(0),
|
||||
_started(false)
|
||||
@@ -128,7 +130,16 @@ public:
|
||||
void stop() {
|
||||
for (int i = 0; i < TCP_IF_MAX_CLIENTS; i++) {
|
||||
if (_clients[i].active) {
|
||||
// Force RST to free lwIP PCBs immediately (no TIME_WAIT)
|
||||
int fd = _clients[i].client.fd();
|
||||
if (fd >= 0) {
|
||||
struct linger lin;
|
||||
lin.l_onoff = 1;
|
||||
lin.l_linger = 0;
|
||||
setsockopt(fd, SOL_SOCKET, SO_LINGER, &lin, sizeof(lin));
|
||||
}
|
||||
_clients[i].client.stop();
|
||||
_clients[i].client = WiFiClient();
|
||||
_clients[i].active = false;
|
||||
}
|
||||
}
|
||||
@@ -185,25 +196,15 @@ public:
|
||||
if (!_clients[i].active) continue;
|
||||
|
||||
if (!_clients[i].client.connected()) {
|
||||
Serial.printf("[TcpIF] Client %d disconnected\r\n", i);
|
||||
_clients[i].client.stop();
|
||||
_clients[i].active = false;
|
||||
_clients[i].in_frame = false;
|
||||
_clients[i].escape = false;
|
||||
_clients[i].rxlen = 0;
|
||||
_num_clients--;
|
||||
_cleanup_client(i, "disconnected");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check read timeout
|
||||
if (_clients[i].last_activity > 0 &&
|
||||
(millis() - _clients[i].last_activity) > TCP_IF_READ_TIMEOUT) {
|
||||
Serial.printf("[TcpIF] Client %d read timeout\r\n", i);
|
||||
_clients[i].client.stop();
|
||||
_clients[i].active = false;
|
||||
_clients[i].in_frame = false;
|
||||
_clients[i].rxlen = 0;
|
||||
_num_clients--;
|
||||
// Check read timeout (0 = disabled)
|
||||
if (_read_timeout > 0 &&
|
||||
_clients[i].last_activity > 0 &&
|
||||
(millis() - _clients[i].last_activity) > _read_timeout) {
|
||||
_cleanup_client(i, "read timeout");
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -220,6 +221,7 @@ public:
|
||||
int clientCount() const { return _num_clients; }
|
||||
bool isStarted() const { return _started; }
|
||||
bool isConnected() const { return _num_clients > 0; }
|
||||
void setReadTimeout(uint32_t timeout_ms) { _read_timeout = timeout_ms; }
|
||||
|
||||
protected:
|
||||
// ─── RNS InterfaceImpl: outgoing packet from RNS Transport ───────────────
|
||||
@@ -248,12 +250,7 @@ protected:
|
||||
if (_clients[i].active && _clients[i].client.connected()) {
|
||||
size_t written = _clients[i].client.write(frame_buf, flen);
|
||||
if (written == 0) {
|
||||
Serial.printf("[TcpIF] Write failed on client %d, dropping\r\n", i);
|
||||
_clients[i].client.stop();
|
||||
_clients[i].active = false;
|
||||
_clients[i].in_frame = false;
|
||||
_clients[i].rxlen = 0;
|
||||
_num_clients--;
|
||||
_cleanup_client(i, "write failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -270,6 +267,38 @@ protected:
|
||||
}
|
||||
|
||||
private:
|
||||
// ─── Cleanup a client slot, freeing all lwIP resources ───────────────────
|
||||
void _cleanup_client(int idx, const char* reason) {
|
||||
TcpClient& c = _clients[idx];
|
||||
if (!c.active) return;
|
||||
|
||||
uint32_t heap_before = ESP.getFreeHeap();
|
||||
|
||||
// Set SO_LINGER with timeout 0: forces RST instead of FIN,
|
||||
// which skips TIME_WAIT and immediately frees the lwIP PCB
|
||||
// and all associated TCP send/receive buffers (~2-4 KB each).
|
||||
int fd = c.client.fd();
|
||||
if (fd >= 0) {
|
||||
struct linger lin;
|
||||
lin.l_onoff = 1;
|
||||
lin.l_linger = 0;
|
||||
setsockopt(fd, SOL_SOCKET, SO_LINGER, &lin, sizeof(lin));
|
||||
}
|
||||
|
||||
c.client.stop();
|
||||
c.client = WiFiClient(); // Release any residual shared_ptr state
|
||||
c.active = false;
|
||||
c.in_frame = false;
|
||||
c.escape = false;
|
||||
c.rxlen = 0;
|
||||
_num_clients--;
|
||||
|
||||
uint32_t heap_after = ESP.getFreeHeap();
|
||||
Serial.printf("[TcpIF] Client %d %s (heap: %u -> %u, delta: %+d)\r\n",
|
||||
idx, reason, heap_before, heap_after,
|
||||
(int)(heap_after - heap_before));
|
||||
}
|
||||
|
||||
// ─── HDLC byte-level deframing ──────────────────────────────────────────
|
||||
void _hdlc_deframe(int idx, uint8_t byte) {
|
||||
TcpClient& c = _clients[idx];
|
||||
@@ -306,6 +335,18 @@ private:
|
||||
// Find a free slot
|
||||
for (int i = 0; i < TCP_IF_MAX_CLIENTS; i++) {
|
||||
if (!_clients[i].active) {
|
||||
// Defensive: force-release any residual lwIP resources in this slot
|
||||
// before assigning the new client (prevents PCB/buffer leaks)
|
||||
int fd = _clients[i].client.fd();
|
||||
if (fd >= 0) {
|
||||
struct linger lin;
|
||||
lin.l_onoff = 1;
|
||||
lin.l_linger = 0;
|
||||
setsockopt(fd, SOL_SOCKET, SO_LINGER, &lin, sizeof(lin));
|
||||
_clients[i].client.stop();
|
||||
}
|
||||
_clients[i].client = WiFiClient(); // Reset to clean state
|
||||
|
||||
_clients[i].client = newClient;
|
||||
_clients[i].client.setNoDelay(true);
|
||||
_clients[i].client.setTimeout(TCP_IF_WRITE_TIMEOUT / 1000);
|
||||
@@ -399,6 +440,7 @@ private:
|
||||
uint32_t _last_reconnect;
|
||||
uint32_t _last_keepalive;
|
||||
uint32_t _reconnect_interval;
|
||||
uint32_t _read_timeout;
|
||||
IPAddress _resolved_ip;
|
||||
uint16_t _consecutive_failures;
|
||||
bool _started;
|
||||
|
||||
294
flash.py
294
flash.py
@@ -2,16 +2,19 @@
|
||||
"""
|
||||
RNodeTHV4 Flash Utility
|
||||
|
||||
Flash the RNodeTHV4 boundary node firmware to a Heltec WiFi LoRa 32 V4.
|
||||
Flash the RNodeTHV4 boundary node firmware to a Heltec WiFi LoRa 32 V3 or V4.
|
||||
No PlatformIO required — just Python 3 and a USB cable.
|
||||
|
||||
Default mode flashes only the app partition (0x10000), preserving
|
||||
bootloader, partition table, NVS, and EEPROM settings.
|
||||
|
||||
Usage:
|
||||
# Update firmware (preserves WiFi/boundary settings)
|
||||
# Update firmware — V4 (default)
|
||||
python flash.py
|
||||
|
||||
# Update firmware — V3
|
||||
python flash.py --board v3
|
||||
|
||||
# Full flash with merged binary (overwrites everything)
|
||||
python flash.py --full
|
||||
|
||||
@@ -43,9 +46,6 @@ import time
|
||||
CHIP = "esp32s3"
|
||||
FLASH_MODE = "qio"
|
||||
FLASH_FREQ = "80m"
|
||||
FLASH_SIZE = "16MB"
|
||||
BAUD_RATE = "921600"
|
||||
MERGED_FILENAME = "rnodethv4_firmware.bin"
|
||||
GITHUB_REPO = "jrl290/RNodeTHV4"
|
||||
|
||||
# Flash addresses for ESP32-S3 Arduino framework
|
||||
@@ -54,11 +54,61 @@ PARTITIONS_ADDR = 0x8000
|
||||
BOOT_APP0_ADDR = 0xe000
|
||||
APP_ADDR = 0x10000
|
||||
|
||||
# PlatformIO build output paths (relative to project root)
|
||||
BUILD_DIR = ".pio/build/heltec_V4_boundary"
|
||||
BOOTLOADER_BIN = os.path.join(BUILD_DIR, "bootloader.bin")
|
||||
PARTITIONS_BIN = os.path.join(BUILD_DIR, "partitions.bin")
|
||||
FIRMWARE_BIN = os.path.join(BUILD_DIR, "rnode_firmware_heltec32v4_boundary.bin")
|
||||
# ── Board profiles ─────────────────────────────────────────────────────────────
|
||||
# Each board defines its PIO env, flash size, baud rate, firmware binary name,
|
||||
# and merged binary name.
|
||||
|
||||
BOARD_PROFILES = {
|
||||
"v4": {
|
||||
"name": "Heltec WiFi LoRa 32 V4",
|
||||
"pio_env": "heltec_V4_boundary",
|
||||
"build_dir": ".pio/build/heltec_V4_boundary",
|
||||
"firmware_bin": "rnode_firmware_heltec32v4_boundary.bin",
|
||||
"merged_filename": "rnodethv4_firmware.bin",
|
||||
"flash_size": "16MB",
|
||||
"baud_rate": "921600",
|
||||
},
|
||||
"v3": {
|
||||
"name": "Heltec WiFi LoRa 32 V3",
|
||||
"pio_env": "heltec_V3_boundary",
|
||||
"build_dir": ".pio/build/heltec_V3_boundary",
|
||||
"firmware_bin": "rnode_firmware_heltec32v3.bin",
|
||||
"merged_filename": "rnodethv3_firmware.bin",
|
||||
"flash_size": "8MB",
|
||||
"baud_rate": "460800",
|
||||
},
|
||||
}
|
||||
DEFAULT_BOARD = "v4"
|
||||
|
||||
# Active board profile (set in main() from --board arg)
|
||||
_board = None
|
||||
|
||||
def board_profile():
|
||||
return BOARD_PROFILES[_board or DEFAULT_BOARD]
|
||||
|
||||
def BUILD_DIR():
|
||||
return board_profile()["build_dir"]
|
||||
|
||||
def BOOTLOADER_BIN():
|
||||
return os.path.join(BUILD_DIR(), "bootloader.bin")
|
||||
|
||||
def PARTITIONS_BIN():
|
||||
return os.path.join(BUILD_DIR(), "partitions.bin")
|
||||
|
||||
def FIRMWARE_BIN():
|
||||
return os.path.join(BUILD_DIR(), board_profile()["firmware_bin"])
|
||||
|
||||
def FLASH_SIZE():
|
||||
return board_profile()["flash_size"]
|
||||
|
||||
def BAUD_RATE():
|
||||
return board_profile()["baud_rate"]
|
||||
|
||||
def MERGED_FILENAME():
|
||||
return board_profile()["merged_filename"]
|
||||
|
||||
def PIO_ENV():
|
||||
return board_profile()["pio_env"]
|
||||
|
||||
# ESP32 partition table magic bytes (first two bytes of a partition table entry)
|
||||
PARTITION_TABLE_MAGIC = b'\xaa\x50'
|
||||
@@ -94,6 +144,16 @@ def _find_in_platformio_or_release(build_path, release_name):
|
||||
|
||||
return None
|
||||
|
||||
# Forward-compatible aliases (these are now functions, not constants)
|
||||
def _bootloader_bin():
|
||||
return BOOTLOADER_BIN()
|
||||
|
||||
def _partitions_bin():
|
||||
return PARTITIONS_BIN()
|
||||
|
||||
def _firmware_bin():
|
||||
return FIRMWARE_BIN()
|
||||
|
||||
|
||||
def find_boot_app0():
|
||||
"""Find boot_app0.bin from PlatformIO framework packages.
|
||||
@@ -126,16 +186,77 @@ def find_boot_app0():
|
||||
|
||||
def find_bootloader():
|
||||
"""Find bootloader.bin from PlatformIO build output or Release/ bundle."""
|
||||
return _find_in_platformio_or_release(BOOTLOADER_BIN, "bootloader.bin")
|
||||
return _find_in_platformio_or_release(BOOTLOADER_BIN(), "bootloader.bin")
|
||||
|
||||
|
||||
def find_partitions():
|
||||
"""Find partitions.bin from PlatformIO build output or Release/ bundle."""
|
||||
return _find_in_platformio_or_release(PARTITIONS_BIN, "partitions.bin")
|
||||
return _find_in_platformio_or_release(PARTITIONS_BIN(), "partitions.bin")
|
||||
|
||||
|
||||
BOOT_APP0_BIN = find_boot_app0()
|
||||
|
||||
# ── Board auto-detection ───────────────────────────────────────────────────────
|
||||
|
||||
# Map detected flash sizes to board keys
|
||||
_FLASH_SIZE_TO_BOARD = {
|
||||
"16MB": "v4",
|
||||
"8MB": "v3",
|
||||
}
|
||||
|
||||
def detect_board(port, esptool_cmd):
|
||||
"""Auto-detect which Heltec board is connected by querying flash size.
|
||||
|
||||
Runs ``esptool.py flash_id`` and parses the output for:
|
||||
- Detected flash size (16MB → V4, 8MB → V3)
|
||||
- Chip type (ESP32-S3 expected)
|
||||
- Features (PSRAM size, WiFi, BLE)
|
||||
|
||||
Returns a tuple (board_key, info_dict) on success, or (None, reason) on
|
||||
failure. ``board_key`` is "v3" or "v4".
|
||||
"""
|
||||
cmd = esptool_cmd + ["--port", port, "flash_id"]
|
||||
try:
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, timeout=15)
|
||||
except subprocess.TimeoutExpired:
|
||||
return None, "esptool timed out (device not responding?)"
|
||||
except Exception as e:
|
||||
return None, str(e)
|
||||
|
||||
output = result.stdout + result.stderr
|
||||
if result.returncode != 0:
|
||||
return None, f"esptool flash_id failed:\n{output.strip()}"
|
||||
|
||||
# Parse key fields
|
||||
info = {}
|
||||
for line in output.splitlines():
|
||||
line = line.strip()
|
||||
if line.startswith("Chip is "):
|
||||
info["chip"] = line[len("Chip is "):]
|
||||
elif line.startswith("Features:"):
|
||||
info["features"] = line[len("Features:"):].strip()
|
||||
elif line.startswith("Detected flash size:"):
|
||||
info["flash_size"] = line.split(":")[-1].strip()
|
||||
elif line.startswith("MAC:"):
|
||||
info["mac"] = line.split(":")[-5:] # last 5 colon-groups
|
||||
info["mac"] = line[len("MAC:"):].strip()
|
||||
elif line.startswith("Crystal is"):
|
||||
info["crystal"] = line[len("Crystal is"):].strip()
|
||||
|
||||
flash_size = info.get("flash_size")
|
||||
if not flash_size:
|
||||
return None, f"Could not parse flash size from esptool output:\n{output.strip()}"
|
||||
|
||||
board_key = _FLASH_SIZE_TO_BOARD.get(flash_size)
|
||||
if not board_key:
|
||||
return None, (
|
||||
f"Unknown flash size '{flash_size}' — expected 16MB (V4) or 8MB (V3).\n"
|
||||
f"Use --board v3 or --board v4 to specify manually."
|
||||
)
|
||||
|
||||
return board_key, info
|
||||
|
||||
|
||||
# ── Helpers ────────────────────────────────────────────────────────────────────
|
||||
|
||||
def find_esptool():
|
||||
@@ -259,16 +380,16 @@ def download_firmware(dest_path):
|
||||
# Find the merged firmware asset
|
||||
asset_url = None
|
||||
for asset in release.get("assets", []):
|
||||
if asset["name"] == MERGED_FILENAME:
|
||||
if asset["name"] == MERGED_FILENAME():
|
||||
asset_url = asset["browser_download_url"]
|
||||
break
|
||||
|
||||
if not asset_url:
|
||||
print(f"Error: '{MERGED_FILENAME}' not found in latest release ({release.get('tag_name', '?')}).")
|
||||
print(f"Error: '{MERGED_FILENAME()}' not found in latest release ({release.get('tag_name', '?')}).")
|
||||
print("Available assets:", [a["name"] for a in release.get("assets", [])])
|
||||
return False
|
||||
|
||||
print(f"Downloading {release['tag_name']} / {MERGED_FILENAME}...")
|
||||
print(f"Downloading {release['tag_name']} / {MERGED_FILENAME()}...")
|
||||
try:
|
||||
urlretrieve(asset_url, dest_path)
|
||||
except Exception as e:
|
||||
@@ -293,7 +414,7 @@ def _do_merge(output_path, esptool_cmd, bootloader, partitions, boot_app0, firmw
|
||||
"merge_bin",
|
||||
"--flash_mode", FLASH_MODE,
|
||||
"--flash_freq", FLASH_FREQ,
|
||||
"--flash_size", FLASH_SIZE,
|
||||
"--flash_size", FLASH_SIZE(),
|
||||
"-o", output_path,
|
||||
f"0x{BOOTLOADER_ADDR:x}", bootloader,
|
||||
f"0x{PARTITIONS_ADDR:x}", partitions,
|
||||
@@ -321,11 +442,11 @@ def merge_firmware(output_path, esptool_cmd):
|
||||
bootloader = find_bootloader()
|
||||
partitions = find_partitions()
|
||||
boot_app0 = BOOT_APP0_BIN
|
||||
firmware = FIRMWARE_BIN
|
||||
firmware = FIRMWARE_BIN()
|
||||
|
||||
missing = []
|
||||
if not bootloader: missing.append(("bootloader", BOOTLOADER_BIN))
|
||||
if not partitions: missing.append(("partitions", PARTITIONS_BIN))
|
||||
if not bootloader: missing.append(("bootloader", BOOTLOADER_BIN()))
|
||||
if not partitions: missing.append(("partitions", PARTITIONS_BIN()))
|
||||
if not boot_app0: missing.append(("boot_app0", "(not found)"))
|
||||
if not os.path.isfile(firmware):
|
||||
missing.append(("firmware", firmware))
|
||||
@@ -333,7 +454,7 @@ def merge_firmware(output_path, esptool_cmd):
|
||||
if missing:
|
||||
for name, path in missing:
|
||||
print(f"Error: {name} not found: {path}")
|
||||
print("Run 'pio run -e heltec_V4_boundary' to build first.")
|
||||
print(f"Run 'pio run -e {PIO_ENV()}' to build first.")
|
||||
return False
|
||||
|
||||
return _do_merge(output_path, esptool_cmd, bootloader, partitions, boot_app0, firmware)
|
||||
@@ -360,7 +481,7 @@ def auto_merge_app_binary(app_binary_path, esptool_cmd):
|
||||
if missing:
|
||||
print(f"Cannot auto-merge: missing {', '.join(missing)}")
|
||||
print("Place them in the Release/ folder alongside flash.py, or")
|
||||
print("build with PlatformIO: pio run -e heltec_V4_boundary")
|
||||
print(f"build with PlatformIO: pio run -e {PIO_ENV()}")
|
||||
return None
|
||||
|
||||
# Create merged binary next to the app binary
|
||||
@@ -406,10 +527,13 @@ def reset_to_bootloader(port):
|
||||
return True
|
||||
|
||||
|
||||
def flash_firmware(firmware_path, port, esptool_cmd, baud=BAUD_RATE):
|
||||
def flash_firmware(firmware_path, port, esptool_cmd, baud=None):
|
||||
"""Flash firmware to the device."""
|
||||
if baud is None:
|
||||
baud = BAUD_RATE()
|
||||
flash_size = FLASH_SIZE()
|
||||
print(f"\nFlashing {firmware_path} to {port}...")
|
||||
print(f" Chip: {CHIP} Baud: {baud} Flash: {FLASH_SIZE}\n")
|
||||
print(f" Chip: {CHIP} Baud: {baud} Flash: {flash_size}\n")
|
||||
|
||||
# Determine if this is a merged binary (flash at 0x0) or app-only (flash at 0x10000)
|
||||
is_merged = is_merged_binary(firmware_path)
|
||||
@@ -431,7 +555,7 @@ def flash_firmware(firmware_path, port, esptool_cmd, baud=BAUD_RATE):
|
||||
"-z",
|
||||
"--flash_mode", FLASH_MODE,
|
||||
"--flash_freq", FLASH_FREQ,
|
||||
"--flash_size", FLASH_SIZE,
|
||||
"--flash_size", flash_size,
|
||||
flash_addr, firmware_path,
|
||||
]
|
||||
|
||||
@@ -443,12 +567,14 @@ def flash_firmware(firmware_path, port, esptool_cmd, baud=BAUD_RATE):
|
||||
# ── Main ───────────────────────────────────────────────────────────────────────
|
||||
|
||||
def main():
|
||||
global _board
|
||||
parser = argparse.ArgumentParser(
|
||||
description="RNodeTHV4 Flash Utility — flash boundary node firmware to Heltec V4",
|
||||
description="RNodeTHV4 Flash Utility — flash boundary node firmware to Heltec V3/V4",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
Examples:
|
||||
python flash.py # App-only update (preserves settings)
|
||||
python flash.py # App-only update, V4 (default)
|
||||
python flash.py --board v3 # App-only update, V3
|
||||
python flash.py --full # Full flash with merged binary
|
||||
python flash.py --download # Download latest release and flash
|
||||
python flash.py --file firmware.bin # Flash a specific file
|
||||
@@ -457,9 +583,12 @@ Examples:
|
||||
python flash.py --erase --full # Erase flash, then full flash
|
||||
""",
|
||||
)
|
||||
parser.add_argument("--board", choices=["v3", "v4"], default=None,
|
||||
help="Target board: v3 (Heltec V3) or v4 (Heltec V4). "
|
||||
"Auto-detected from connected device if omitted.")
|
||||
parser.add_argument("--file", "-f", help="Path to firmware binary to flash")
|
||||
parser.add_argument("--port", "-p", help="Serial port (auto-detected if omitted)")
|
||||
parser.add_argument("--baud", "-b", default=BAUD_RATE, help=f"Baud rate (default: {BAUD_RATE})")
|
||||
parser.add_argument("--baud", "-b", default=None, help="Baud rate (board-specific default)")
|
||||
parser.add_argument("--download", "-d", action="store_true",
|
||||
help="Download latest firmware from GitHub Releases")
|
||||
parser.add_argument("--merge-only", action="store_true",
|
||||
@@ -470,20 +599,58 @@ Examples:
|
||||
help="Erase entire flash before writing (implies --full)")
|
||||
|
||||
args = parser.parse_args()
|
||||
baud = args.baud
|
||||
|
||||
print("╔══════════════════════════════════════════╗")
|
||||
print("║ RNodeTHV4 Flash Utility ║")
|
||||
print("║ Heltec WiFi LoRa 32 V4 Boundary Node ║")
|
||||
print("╚══════════════════════════════════════════╝")
|
||||
print()
|
||||
|
||||
# Find esptool
|
||||
# Find esptool early — needed for both auto-detect and flashing
|
||||
esptool_cmd = find_esptool()
|
||||
if not esptool_cmd:
|
||||
print("Error: esptool not found!")
|
||||
print("Install it with: pip install esptool")
|
||||
sys.exit(1)
|
||||
|
||||
# ── Board detection ─────────────────────────────────────────────────
|
||||
detected_info = None
|
||||
_early_port = None
|
||||
|
||||
if args.board:
|
||||
# Explicit board — no detection needed
|
||||
_board = args.board
|
||||
elif args.merge_only:
|
||||
# No device needed for merge — fall back to default
|
||||
_board = DEFAULT_BOARD
|
||||
print(f"(No --board specified; defaulting to {DEFAULT_BOARD} for merge)")
|
||||
else:
|
||||
# Auto-detect from connected device
|
||||
_early_port = args.port or find_serial_port()
|
||||
if not _early_port:
|
||||
print("No serial port detected and no --board specified.")
|
||||
print(f"Defaulting to {DEFAULT_BOARD}. Specify with --board v3 or --board v4.")
|
||||
_board = DEFAULT_BOARD
|
||||
else:
|
||||
print(f"Detecting board on {_early_port}...")
|
||||
board_key, info = detect_board(_early_port, esptool_cmd)
|
||||
if board_key:
|
||||
_board = board_key
|
||||
detected_info = info
|
||||
print(f" Chip: {info.get('chip', '?')}")
|
||||
print(f" Flash: {info.get('flash_size', '?')}")
|
||||
print(f" Features: {info.get('features', '?')}")
|
||||
print(f" MAC: {info.get('mac', '?')}")
|
||||
print(f" → Detected: {BOARD_PROFILES[board_key]['name']}")
|
||||
else:
|
||||
reason = info # info is the error reason when board_key is None
|
||||
print(f" Auto-detect failed: {reason}")
|
||||
print(f" Defaulting to {DEFAULT_BOARD}. Specify with --board v3 or --board v4.")
|
||||
_board = DEFAULT_BOARD
|
||||
|
||||
baud = args.baud or BAUD_RATE()
|
||||
bp = board_profile()
|
||||
|
||||
print()
|
||||
print("╔══════════════════════════════════════════╗")
|
||||
print("║ RNodeTHV4 Flash Utility ║")
|
||||
print(f"║ {bp['name']:^40s} ║")
|
||||
print("╚══════════════════════════════════════════╝")
|
||||
print()
|
||||
print(f"Using esptool: {' '.join(esptool_cmd)}")
|
||||
|
||||
# --erase implies --full (after erase, device needs bootloader + partitions)
|
||||
@@ -492,6 +659,9 @@ Examples:
|
||||
|
||||
# Determine firmware file
|
||||
firmware_path = None
|
||||
merged_fn = MERGED_FILENAME()
|
||||
firmware_bin = FIRMWARE_BIN()
|
||||
pio_env = PIO_ENV()
|
||||
|
||||
if args.file:
|
||||
firmware_path = args.file
|
||||
@@ -500,69 +670,69 @@ Examples:
|
||||
sys.exit(1)
|
||||
|
||||
elif args.download:
|
||||
firmware_path = MERGED_FILENAME
|
||||
firmware_path = merged_fn
|
||||
if not download_firmware(firmware_path):
|
||||
sys.exit(1)
|
||||
|
||||
elif args.merge_only:
|
||||
if merge_firmware(MERGED_FILENAME, esptool_cmd):
|
||||
print(f"\nDone! Flash with: python flash.py --file {MERGED_FILENAME}")
|
||||
if merge_firmware(merged_fn, esptool_cmd):
|
||||
print(f"\nDone! Flash with: python flash.py --board {_board} --file {merged_fn}")
|
||||
else:
|
||||
sys.exit(1)
|
||||
return
|
||||
|
||||
elif args.full:
|
||||
# Full flash: use or create merged binary
|
||||
if os.path.isfile(FIRMWARE_BIN):
|
||||
if os.path.isfile(firmware_bin):
|
||||
# Build exists — (re-)merge
|
||||
if os.path.isfile(MERGED_FILENAME):
|
||||
build_time = os.path.getmtime(FIRMWARE_BIN)
|
||||
merge_time = os.path.getmtime(MERGED_FILENAME)
|
||||
if os.path.isfile(merged_fn):
|
||||
build_time = os.path.getmtime(firmware_bin)
|
||||
merge_time = os.path.getmtime(merged_fn)
|
||||
if build_time > merge_time:
|
||||
print("Build output is newer than merged binary, re-merging...")
|
||||
if not merge_firmware(MERGED_FILENAME, esptool_cmd):
|
||||
if not merge_firmware(merged_fn, esptool_cmd):
|
||||
sys.exit(1)
|
||||
else:
|
||||
print("Creating merged binary from PlatformIO build output...")
|
||||
if not merge_firmware(MERGED_FILENAME, esptool_cmd):
|
||||
if not merge_firmware(merged_fn, esptool_cmd):
|
||||
sys.exit(1)
|
||||
firmware_path = MERGED_FILENAME
|
||||
elif os.path.isfile(MERGED_FILENAME):
|
||||
firmware_path = MERGED_FILENAME
|
||||
firmware_path = merged_fn
|
||||
elif os.path.isfile(merged_fn):
|
||||
firmware_path = merged_fn
|
||||
else:
|
||||
print("No firmware found for full flash!")
|
||||
print()
|
||||
print("Options:")
|
||||
print(" 1. Build with PlatformIO first: pio run -e heltec_V4_boundary")
|
||||
print(" 2. Download from GitHub: python flash.py --download")
|
||||
print(" 3. Specify a file: python flash.py --file <path>")
|
||||
print(f" 1. Build with PlatformIO first: pio run -e {pio_env}")
|
||||
print(f" 2. Download from GitHub: python flash.py --board {_board} --download")
|
||||
print(f" 3. Specify a file: python flash.py --board {_board} --file <path>")
|
||||
sys.exit(1)
|
||||
|
||||
else:
|
||||
# Default: app-only flash (preserves settings)
|
||||
if os.path.isfile(FIRMWARE_BIN):
|
||||
firmware_path = FIRMWARE_BIN
|
||||
if os.path.isfile(firmware_bin):
|
||||
firmware_path = firmware_bin
|
||||
print(f"App-only update (preserves WiFi/boundary settings)")
|
||||
print(f" Use --full for a complete flash, or --erase for recovery.")
|
||||
elif os.path.isfile(MERGED_FILENAME):
|
||||
firmware_path = MERGED_FILENAME
|
||||
print(f"No build output found, using merged binary: {MERGED_FILENAME}")
|
||||
elif os.path.isfile(merged_fn):
|
||||
firmware_path = merged_fn
|
||||
print(f"No build output found, using merged binary: {merged_fn}")
|
||||
print(f" Note: merged binary will overwrite bootloader + partitions.")
|
||||
else:
|
||||
print("No firmware found!")
|
||||
print()
|
||||
print("Options:")
|
||||
print(" 1. Build with PlatformIO first: pio run -e heltec_V4_boundary")
|
||||
print(" 2. Download from GitHub: python flash.py --download")
|
||||
print(" 3. Specify a file: python flash.py --file <path>")
|
||||
print(f" 1. Build with PlatformIO first: pio run -e {pio_env}")
|
||||
print(f" 2. Download from GitHub: python flash.py --board {_board} --download")
|
||||
print(f" 3. Specify a file: python flash.py --board {_board} --file <path>")
|
||||
sys.exit(1)
|
||||
|
||||
# Flash
|
||||
port = args.port or find_serial_port()
|
||||
# Flash — reuse early-detected port if available
|
||||
port = args.port or _early_port or find_serial_port()
|
||||
if not port:
|
||||
print("\nError: No serial port detected!")
|
||||
print("Connect your Heltec V4 via USB and try again,")
|
||||
print("or specify manually: python flash.py --port /dev/ttyACM0")
|
||||
print(f"Connect your {bp['name']} via USB and try again,")
|
||||
print(f"or specify manually: python flash.py --board {_board} --port /dev/ttyACM0")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"\nSerial port: {port}")
|
||||
|
||||
@@ -25,10 +25,10 @@
|
||||
#define VERBOSE(msg) (RNS::verbose(msg))
|
||||
#define VERBOSEF(msg, ...) (RNS::verbosef(msg, __VA_ARGS__))
|
||||
#ifndef NDEBUG
|
||||
#define DEBUG(msg) (RNS::debug(msg))
|
||||
#define DEBUGF(msg, ...) (RNS::debugf(msg, __VA_ARGS__))
|
||||
#define TRACE(msg) (RNS::trace(msg))
|
||||
#define TRACEF(msg, ...) (RNS::tracef(msg, __VA_ARGS__))
|
||||
#define DEBUG(msg) do { if (RNS::loglevel() >= RNS::LOG_DEBUG) RNS::debug(msg); } while(0)
|
||||
#define DEBUGF(msg, ...) do { if (RNS::loglevel() >= RNS::LOG_DEBUG) RNS::debugf(msg, __VA_ARGS__); } while(0)
|
||||
#define TRACE(msg) do { if (RNS::loglevel() >= RNS::LOG_TRACE) RNS::trace(msg); } while(0)
|
||||
#define TRACEF(msg, ...) do { if (RNS::loglevel() >= RNS::LOG_TRACE) RNS::tracef(msg, __VA_ARGS__); } while(0)
|
||||
#if defined(RNS_MEM_LOG)
|
||||
#define MEM(msg) (RNS::mem(msg))
|
||||
#define MEMF(msg, ...) (RNS::memf(msg, __VA_ARGS__))
|
||||
|
||||
@@ -104,6 +104,7 @@ using namespace RNS::Utilities;
|
||||
static std::set<Bytes> _boundary_local_addresses;
|
||||
// BOUNDARY MODE Whitelist 2: addresses mentioned in packets from local devices
|
||||
static std::set<Bytes> _boundary_mentioned_addresses;
|
||||
static const uint16_t _boundary_maxsize = 200;
|
||||
|
||||
// BOUNDARY MODE: Check if an interface is the backbone
|
||||
static bool is_backbone_interface(const Interface& iface) {
|
||||
@@ -256,6 +257,9 @@ static bool is_backbone_interface(const Interface& iface) {
|
||||
/*static*/ void Transport::jobs() {
|
||||
//TRACE("Transport::jobs()");
|
||||
|
||||
// Heap telemetry: snapshot at jobs entry
|
||||
size_t _jobs_heap_entry = OS::heap_available();
|
||||
|
||||
std::vector<Packet> outgoing;
|
||||
std::set<Bytes> path_requests;
|
||||
int count;
|
||||
@@ -443,6 +447,15 @@ static bool is_backbone_interface(const Interface& iface) {
|
||||
_packet_hashlist.erase(_packet_hashlist.begin(), iter);
|
||||
}
|
||||
|
||||
#ifdef BOUNDARY_MODE
|
||||
// Cull the boundary mentioned addresses if it has reached its max size
|
||||
if (_boundary_mentioned_addresses.size() > _boundary_maxsize) {
|
||||
std::set<Bytes>::iterator iter = _boundary_mentioned_addresses.begin();
|
||||
std::advance(iter, _boundary_mentioned_addresses.size() - _boundary_maxsize);
|
||||
_boundary_mentioned_addresses.erase(_boundary_mentioned_addresses.begin(), iter);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Cull the path request tags list if it has reached its max size
|
||||
if (_discovery_pr_tags.size() > _max_pr_tags) {
|
||||
std::set<Bytes>::iterator iter = _discovery_pr_tags.begin();
|
||||
@@ -656,6 +669,15 @@ static bool is_backbone_interface(const Interface& iface) {
|
||||
|
||||
_jobs_running = false;
|
||||
|
||||
// Heap telemetry: snapshot at jobs exit
|
||||
{
|
||||
size_t _jobs_heap_exit = OS::heap_available();
|
||||
int _jobs_delta = (int)_jobs_heap_exit - (int)_jobs_heap_entry;
|
||||
if (_jobs_delta < -64 || _jobs_delta > 64) {
|
||||
VERBOSEF("[HEAP-TEL] jobs: %d bytes (heap=%u)", _jobs_delta, (uint32_t)_jobs_heap_exit);
|
||||
}
|
||||
}
|
||||
|
||||
// CBA send announce retransmission packets
|
||||
for (auto& packet : outgoing) {
|
||||
packet.send();
|
||||
@@ -1201,6 +1223,9 @@ static bool is_backbone_interface(const Interface& iface) {
|
||||
/*static*/ void Transport::inbound(const Bytes& raw, const Interface& interface /*= {Type::NONE}*/) {
|
||||
TRACEF("Transport::inbound: received %d bytes", raw.size());
|
||||
++_packets_received;
|
||||
|
||||
// Heap telemetry: snapshot at entry
|
||||
size_t _heap_at_entry = OS::heap_available();
|
||||
// CBA
|
||||
if (_callbacks._receive_packet) {
|
||||
try {
|
||||
@@ -1440,6 +1465,15 @@ static bool is_backbone_interface(const Interface& iface) {
|
||||
}
|
||||
#endif
|
||||
|
||||
// Heap telemetry: snapshot after boundary filter
|
||||
{
|
||||
size_t _heap_after_boundary = OS::heap_available();
|
||||
int _boundary_delta = (int)_heap_after_boundary - (int)_heap_at_entry;
|
||||
if (_boundary_delta < -64) {
|
||||
VERBOSEF("[HEAP-TEL] boundary: %d bytes (bma=%u phl=%u)", _boundary_delta, _boundary_mentioned_addresses.size(), _packet_hashlist.size());
|
||||
}
|
||||
}
|
||||
|
||||
// CBA ACCUMULATES
|
||||
_packet_hashlist.insert(packet.packet_hash());
|
||||
cache_packet(packet);
|
||||
@@ -2642,6 +2676,21 @@ static bool is_backbone_interface(const Interface& iface) {
|
||||
}
|
||||
}
|
||||
|
||||
// Heap telemetry: snapshot at exit
|
||||
{
|
||||
size_t _heap_at_exit = OS::heap_available();
|
||||
int _inbound_delta = (int)_heap_at_exit - (int)_heap_at_entry;
|
||||
// Log every 100th packet or when delta exceeds threshold
|
||||
static uint32_t _tel_pkt_count = 0;
|
||||
++_tel_pkt_count;
|
||||
if (_inbound_delta < -64 || (_tel_pkt_count % 100 == 0)) {
|
||||
VERBOSEF("[HEAP-TEL] inbound: %d bytes (heap=%u pin=%u bma=%u phl=%u lt=%u revr=%u)",
|
||||
_inbound_delta, (uint32_t)_heap_at_exit, _packets_received,
|
||||
_boundary_mentioned_addresses.size(), _packet_hashlist.size(),
|
||||
_link_table.size(), _reverse_table.size());
|
||||
}
|
||||
}
|
||||
|
||||
_jobs_locked = false;
|
||||
}
|
||||
|
||||
@@ -4249,6 +4298,7 @@ TRACE("Transport::write_path_table: buffer size " + std::to_string(Persistence::
|
||||
interface_announces += interface.announce_queue().size();
|
||||
}
|
||||
VERBOSEF("phl: %u rcp: %u lt: %u pl: %u al: %u tun: %u", _packet_hashlist.size(), _receipts.size(), _link_table.size(), _pending_links.size(), _active_links.size(), _tunnels.size());
|
||||
VERBOSEF("bla: %u bma: %u", _boundary_local_addresses.size(), _boundary_mentioned_addresses.size());
|
||||
VERBOSEF("pin: %u pout: %u padd: %u dpr: %u ikd: %u ia: %u\r\n", _packets_received, _packets_sent, _destinations_added, destination_path_responses, Identity::_known_destinations.size(), interface_announces);
|
||||
|
||||
_last_memory = memory;
|
||||
|
||||
@@ -309,6 +309,37 @@ lib_deps =
|
||||
${env.lib_deps}
|
||||
XPowersLib@^0.2.1
|
||||
|
||||
[env:heltec_V3_boundary]
|
||||
platform = espressif32
|
||||
board = heltec_wifi_lora_32_V3
|
||||
custom_variant = heltec32v3
|
||||
board_build.filesystem = littlefs
|
||||
; Flash / memory layout for 8MB flash + 8MB PSRAM (QSPI)
|
||||
board_upload.flash_size = 8MB
|
||||
board_upload.maximum_size = 8388608
|
||||
board_build.partitions = default_8MB.csv
|
||||
board_build.flash_mode = qio
|
||||
board_build.psram_type = qio
|
||||
board_build.arduino.memory_type = qio_qspi
|
||||
monitor_speed = 115200
|
||||
build_flags =
|
||||
${env.build_flags}
|
||||
-DBOARD_MODEL=BOARD_HELTEC32_V3
|
||||
-DBOARD_HAS_PSRAM=1
|
||||
-DBOUNDARY_MODE
|
||||
;-DNDEBUG
|
||||
-DRNS_USE_TLSF=1
|
||||
-DRNS_USE_ALLOCATOR=1
|
||||
; --- Boundary mode defaults (override via EEPROM at runtime) ---
|
||||
; TCP server mode (0=server, 1=client)
|
||||
-DBOUNDARY_TCP_MODE=0
|
||||
; TCP listen/connect port
|
||||
-DBOUNDARY_TCP_PORT=4242
|
||||
lib_deps =
|
||||
${env.lib_deps}
|
||||
XPowersLib@^0.2.1
|
||||
monitor_filters = esp32_exception_decoder
|
||||
|
||||
[env:heltec_wifi_lora_32_V4]
|
||||
platform = espressif32
|
||||
board = esp32-s3-devkitc-1
|
||||
@@ -342,7 +373,7 @@ build_flags =
|
||||
-DARDUINO_USB_CDC_ON_BOOT=1
|
||||
-DBOARD_HAS_PSRAM=1
|
||||
-DBOUNDARY_MODE
|
||||
-DNDEBUG
|
||||
;-DNDEBUG
|
||||
-DRNS_USE_TLSF=1
|
||||
-DRNS_USE_ALLOCATOR=1
|
||||
; --- Boundary mode defaults (override via EEPROM at runtime) ---
|
||||
|
||||
Reference in New Issue
Block a user