Skip to content

Add Heltec Mesh Node T114 support (nRF52840 + SX1262) - #107

Open
Giak88 wants to merge 11 commits into
attermann:masterfrom
Giak88:master
Open

Add Heltec Mesh Node T114 support (nRF52840 + SX1262)#107
Giak88 wants to merge 11 commits into
attermann:masterfrom
Giak88:master

Conversation

@Giak88

@Giak88 Giak88 commented Aug 10, 2026

Copy link
Copy Markdown

Adds support for the Heltec Mesh Node T114 (nRF52840 + SX1262), tested
standalone as a Reticulum transport node — both via USB and running on
battery alone.

Verified on hardware

  • TCXO enabled at 1.8V via DIO3 — required, the radio does not transmit
    or receive usably without it (enableTCXO() in sx126x.cpp)
  • SPI/radio pin mapping (via Nateyo/Heltec-T114-PlatformIO-Base variant)
  • Standalone boot on battery, without a USB host attached — required
    excluding the board from the unconditional while (!Serial); in
    RNode_Firmware.ino (same fix already applied to RAK4631, RNode NG22,
    XIAO nRF52840)
  • Transport/announce forwarding, confirmed via RX→TX hop increment and
    non-zero paths/pin/pout in periodic stats

Not independently verified

  • EEPROM_SIZE 296, inherited from the XIAO block by analogy, not
    measured against T114's actual InternalFS constraints
  • Model code provisioning currently borrows RAK4631's codes (0x11/0x12)
    as a workaround — the T114 has no dedicated model identity yet

Known limitation (not board-specific)

Direct LXMF message reception over a Link currently fails — links
establish correctly (~180ms) and Resource chunks arrive at the radio
layer, but the receive-side Resource protocol (Resource::accept(),
proof generation) is not implemented in the underlying microReticulum
library. Filed upstream as attermann/microReticulum#77. Transport and
forwarding are unaffected and fully functional.

Also fixes (unrelated to T114, hit while building from clean checkout)

  • Missing Crypto and ArduinoJson lib_deps declarations

varna9000 and others added 11 commits February 10, 2026 17:22
Add LXMF-based GPIO control for RNode hardware
- Add battery telemetry in Sideband Telemeter format (SID_TIME + SID_BATTERY)
  attached to every LXMF reply, so Sideband natively displays battery data
- Add BATTERY and ANNOUNCE commands, remove GPIO pin control from XIAO build
- Fix XIAO nRF52840 battery: correct voltage divider (2.957x), hardware
  charging detection via BQ25101 /CHG pin + NRF_POWER USB register
- Add LittleFS corruption guard for nRF52840 InternalFS
- Change default LXMF node name to RTransport-XXXX (random per compile)
- Make transport announce interval runtime-configurable
- Fix flash script: auto-activate venv, relaxed device check for RNS transport
- Add msgpack helpers (pack_bool, pack_nil, pack_fixint, pack_float32, pack_uint32)
- Enable HAS_GPIO_CONTROL for xiao_nrf52840 in platformio.ini
Board support for Seeed XIAO ESP32-S3 + Wio-SX1262 Meshtastic Kit
(B2B connector variant):
- New board definition BOARD_XIAO_ESP32S3 (0x53) with B2B pin mapping
  (CS=GPIO41, RST=GPIO42, DIO1=GPIO39, BUSY=GPIO40, RXEN=GPIO38)
- Product code PRODUCT_XIAO_ESP32S3 (0x12), model MODEL_13 (0x13)
- PlatformIO env with 8MB flash, default_8MB partition table
- Custom SPI init in sx126x.cpp for ESP32-S3 pin assignment
- TCXO 1.8V configuration for Wio-SX1262
- Flash script with esptool USB reset, erase, provision, TNC config
- LED functions and EEPROM validation entries for the new board
- IS_ESP32S3 flag used for WDT header selection (hal/wdt_hal.h)

Fix LittleFS filename length causing transport cache failures:
- ESP32 LittleFS has an effective filename limit well below the
  configured CONFIG_LITTLEFS_OBJ_NAME_LEN=64. Cache filenames
  (SHA256 hex hashes, 64 chars) exceeded this limit, causing all
  packet cache writes to silently fail with "no permits for creation".
- Set FS_NAME_MAX=32 to truncate cache filenames to 32 chars (128-bit
  hash prefix), which is still collision-proof for cache purposes.
- This was the root cause of transport nodes failing to forward
  non-announce packets: without cached announce data, the transport
  node couldn't resolve paths or answer path requests from peers.
- Added FS.mkdir("/cache") call during init to ensure the cache
  directory exists on fresh filesystems.

Debug transport mode (compile-time flag):
- Added DEBUG_TRANSPORT flag that forces RNS logs to serial even
  when a KISS client is connected, and sets log level to TRACE.
- Enables full visibility into transport forwarding decisions.

Test tooling:
- test_transport.py: Python script that injects a forced transport
  path and sends a HEADER_2 link request through the transport node
  to verify forwarding works end-to-end.
The PlatformIO mklittlefs tool is compiled with LFS_NAME_MAX=32,
so any filesystem image it creates has name_max=32 baked into the
superblock. When the ESP32 runtime mounts this filesystem, it
reads the superblock and downgrades its own name_max from 255 to
32, even though the runtime library supports 255-char filenames.

This caused all cache file writes to fail silently because the
SHA256 hex hash filenames are 64 characters long.

Fix: on boot, test if a 64-char filename can be created. If not,
reformat the filesystem (which creates a new superblock with
name_max=255 from the runtime default), then restore identity
files from the pre-reformat read. Raise FS_NAME_MAX from 32 to
64 to use full SHA256 hashes without truncation.
A brownout during a flash write could corrupt LittleFS metadata at a
level below the superblock. Adafruit_LittleFS::begin()'s auto-format
recovered only mount failures, so subsequent file ops tripped an
assertion in lfs.c:1144 (head block pointer out of range) and abort()ed
into a permanent ~3s reboot loop.

Recovery now layered:
- Build with -DLFS_NO_ASSERT so LFS returns LFS_ERR_CORRUPT instead of
  abort()ing on internal sanity checks
- eeprom_begin() split into mount + eeprom_open_files() steps; either
  failing triggers InternalFS.format() + retry, with all file handles
  closed on the failure path so eeprom_read() is a safe no-op
- FileSystem::init() (RNS side) mirrors the format-and-retry on the
  InternalFS mount path
- RNS startup, identity-restore, and /destination_table read are gated
  on fs_ready from filesystem.init()
- Serial.flush() after each early boot print so a future failure is
  pinpointable from the serial log
- Identity keys preserved via the existing raw-flash backup at 0xEC000,
  outside the LittleFS region

Also includes accumulated pre-existing changes: no-activity watchdog,
GPIO control over LXMF, XIAO ESP32-S3 board support tweaks, local
microReticulum library copy under lib/, telemetry destination removal,
and CUSTOM_BOARDS.md updates.
…ial)

Without this, the T114 hung forever in setup() waiting for a USB host
that never arrives when running on battery alone. RAK4631, RNode NG22
and XIAO nRF52840 were already excluded for the same reason.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants