Skip to content

Firmware Internals

Internal architecture of the MeshCore firmware. Covers the layered design, major source components, supported hardware, storage, and debugging.


Layered Architecture

MeshCore is structured as a clean stack of layers:

┌──────────────────────────────────────────────────┐
│  User Interface Layer                            │
│  Mobile app (iOS/Android), web app, CLI terminal │
└───────────────────┬──────────────────────────────┘
                    │ BLE / USB Serial / WiFi TCP / ESP-NOW
┌───────────────────▼──────────────────────────────┐
│  Application Firmware                            │
│  companion_radio | simple_repeater               │
│  room_server | sensor | kiss_modem               │
└───────────────────┬──────────────────────────────┘
                    │ Virtual callbacks (onPeerDataRecv, onAdvertRecv, etc.)
┌───────────────────▼──────────────────────────────┐
│  Mesh Networking Core                            │
│  Mesh.cpp — routing, duplicate suppression       │
│  Dispatcher.cpp — MAC layer, CAD, queuing        │
└───────────────────┬──────────────────────────────┘
                    │ Radio hardware abstraction
┌───────────────────▼──────────────────────────────┐
│  Hardware Abstraction                            │
│  RadioLib wrappers (SX1262, SX1276, LR1110, etc.)│
└───────────────────┬──────────────────────────────┘
                    │ SPI
┌───────────────────▼──────────────────────────────┐
│  Physical Radio                                  │
│  SX1262 / SX1276 / LR1110 / LLCC68 / STM32WL    │
└──────────────────────────────────────────────────┘

Source Tree

src/
├── MeshCore.h              All global constants (key sizes, packet limits)
├── Packet.h / .cpp         Packet class — header, path, payload, serialization
├── Mesh.h / .cpp           Core routing — onRecvPacket, flood, direct, ACK handling
├── Dispatcher.h / .cpp     Radio MAC layer — CAD, queuing, airtime budget
├── Identity.h / .cpp       Ed25519 identity, ECDH key exchange
├── Utils.h / .cpp          AES-128-ECB, HMAC-SHA256, SHA256, hex utilities
└── helpers/
    ├── BaseChatMesh.h/.cpp         Companion radio base (sendMessage, path caching)
    ├── ContactInfo.h               Contact struct (path, keys, GPS, timestamps)
    ├── ChannelDetails.h            Channel wrapper (GroupChannel + name)
    ├── AdvertDataHelpers.h/.cpp    Advertisement builder/parser
    ├── TxtDataHelpers.h            Text message type constants
    ├── ArduinoSerialInterface.h    USB/UART serial bridge to app
    ├── BaseSerialInterface.h       Abstract interface (MAX_FRAME_SIZE=172)
    ├── CommonCLI.h/.cpp            Text CLI commands, NodePrefs struct
    ├── ClientACL.h/.cpp            Room server access control (4 permission levels)
    ├── IdentityStore.h/.cpp        Key storage abstraction across filesystems
    ├── SimpleMeshTables.h/.cpp     Duplicate suppression (64 ACK, 128 packet hashes)
    ├── StaticPoolPacketManager.h   Pre-allocated packet pool (no malloc at runtime)
    ├── TransportKeyStore.h/.cpp    16-entry transport key cache
    ├── RegionMap.h/.cpp            Region hierarchy (DENY_FLOOD/DENY_DIRECT flags)
    ├── SensorManager.h             Sensor abstraction layer
    ├── bridges/                    AbstractBridge + concrete bridge implementations
    ├── radiolib/                   RadioLib wrappers per chip (SX1262, SX1276, etc.)
    ├── esp32/                      ESP32-specific board support
    ├── nrf52/                      nRF52-specific board support
    ├── stm32/                      STM32-specific board support
    └── ui/                         Display UI helpers

examples/
├── companion_radio/        BLE/USB companion firmware (MyMesh.h/.cpp)
├── simple_repeater/        Repeater firmware
├── simple_room_server/     Room server firmware
├── simple_secure_chat/     Terminal chat firmware
├── simple_sensor/          Sensor node firmware
└── kiss_modem/             KISS TNC firmware

variants/
    67+ hardware-specific platformio.ini + target.cpp configurations

Core Components

Mesh.cpp — Routing Core

The heart of MeshCore. Handles all received packets via onRecvPacket(): - Validates header and packet bounds - Queries SimpleMeshTables for duplicates — drops seen packets - Dispatches to handlers: onFloodRecv(), onDirectRecv(), onAdvertRecv(), etc. - Implements sendFlood() and sendDirect() for outgoing messages - Manages path caching: onContactPathRecv() stores returned routes - Handles resetPathTo() when ACKs are not received

Dispatcher.cpp — MAC Layer

Manages radio access between the core and physical hardware: - Maintains a priority queue of outgoing packets - Executes Channel Activity Detection (CAD) before transmitting - Enforces airtime budget (33.3% duty cycle by default) - Tracks noise floor via periodic RSSI sampling - Watchdog timers for stuck-TX and stuck-CAD conditions - Calculates flood retransmission delays based on SNR score

SimpleMeshTables.cpp — Duplicate Suppression

Two cyclic ring-buffer tables: - ACK table: 64 × uint32_t entries (ACK CRCs) - Packet hash table: 128 × 8-byte entries (packet content hashes)

Both use linear scan for hasSeen() and wrap-around overwrite for new entries. Zero dynamic allocation.

Identity.cpp — Cryptographic Identity

  • Ed25519 keypair generation from hardware RNG seed
  • calcSharedSecret(): converts Ed25519 keys to X25519 curve for ECDH
  • Caches shared secrets in ContactInfo.shared_secret[] to avoid repeated computation
  • Advertisement signing and verification

Utils.cpp — Crypto Primitives

Low-level cryptographic building blocks: - AES-128-ECB encrypt/decrypt (16-byte blocks, zero-padded) - HMAC-SHA256 (used for MAC generation and verification) - SHA256 (used for ACK CRC and packet hashing) - encryptThenMAC(): combined AES-ECB + 2-byte HMAC - Hex encoding/decoding utilities


Application Firmware Types

companion_radio

  • Based on BaseChatMesh : Mesh
  • Does not forward packets (allowPacketForward() = false)
  • Stores up to 350 contacts (ESP32-S3) or 160 contacts (ESP32/nRF52)
  • Maintains offline message queue (up to 256 messages)
  • Maintains up to 40 group channels
  • Exposes BLE/USB/WiFi/ESP-NOW interface to companion app
  • 16-entry LRU advertisement path cache

simple_repeater

  • Based on SimpleRepeater : Mesh
  • Forwards eligible packets (allowPacketForward() = true)
  • No contact storage, no message queue
  • Maintains neighbour table (up to 50 entries)
  • Managed entirely via USB serial CLI or remote mesh CLI commands
  • Configurable flood_max hop limit, power save mode, airtime factor

simple_room_server

  • Based on SimpleRoomServer : Mesh
  • Store-and-forward message board
  • Up to 32 unsynced posts per board, max 151 characters each
  • 4-level role-based access control (Guest / Read-only / Read-write / Admin)
  • Client login via anonymous ECDH (prevents identity linkage)
  • 300 ms delay between sequential client responses

simple_sensor

  • Transmits telemetry only; does not forward
  • Reports GPS position via ADV_LATLON_MASK in advertisements
  • Sensor data encoded in CayenneLPP format
  • KISS modem interface for external sensor data ingestion

kiss_modem

  • Standard KISS TNC interface over serial (115200 baud 8N1)
  • Enables integration with APRS software and other AX.25 tools
  • Maximum unescaped frame: 512 bytes; data payload: 255 bytes
  • SetHardware extensions expose MeshCore-specific features:
  • Ed25519 signing (start/data/finish flow)
  • AES-128-CBC encryption
  • SHA-256 hashing
  • Radio configuration
  • Telemetry (RSSI, SNR, airtime, noise floor)

Supported MCUs

MCU Flash RAM Max Contacts Primary Use
ESP32-S3 16 MB 512 KB 350 Companion, repeater
ESP32 4 MB 520 KB 160 Companion, repeater
nRF52840 1 MB 256 KB 160 Companion (BLE-native)
RP2040 2 MB 264 KB N/A Repeater, sensor
STM32WL 256 KB 64 KB N/A Repeater (integrated LoRa)

Memory footprint (ESP32): approximately 200–300 KB flash, 40–60 KB RAM.

No dynamic allocation at runtime: all buffers are compile-time fixed via StaticPoolPacketManager and fixed-size arrays. No malloc() calls during operation.


Persistent Storage

Key and configuration files are stored on the device filesystem:

Platform Filesystem Key Files
ESP32 SPIFFS /_main.id, /new_prefs, /contacts3, /channels2
nRF52 InternalFS Same naming
RP2040 LittleFS Same naming
STM32 InternalFS Same naming

/_main.id: 64-byte Ed25519 private key + 32-byte public key.

/new_prefs: NodePrefs struct (radio frequency, BW, SF, CR, TX power, flood_max, etc.)

/contacts3: Serialized contact database (keys, cached paths, timestamps, names).

/channels2: Group channel definitions (hash + secret + name).

Lazy write: Contact changes are batched by a 5-second dirty timer to minimize flash wear cycles.

The IdentityStore class provides an abstraction layer so the same code path works across all filesystem types.


NodePrefs — Configuration Structure

The NodePrefs struct contains all persistent node configuration:

Field Type Description
freq float Radio frequency in MHz
bw float LoRa bandwidth in kHz
sf uint8_t Spreading factor (7–12)
cr uint8_t Coding rate (5–8 = ⅘ to 4/8)
tx_power_dbm int8_t Transmit power in dBm
flood_max uint8_t Maximum hops for flood forwarding (0–64)
disable_fwd bool Disable all packet forwarding
powersaving_enabled bool Enable sleep mode between transmissions
agc_reset_interval uint8_t AGC reset interval (stored as seconds/4)
bridge_* various Bridge configuration (type, delay, secret)
advert_loc_policy uint8_t GPS in adverts: none / current / stored

Configure via CLI: set radio <freq> <bw> <sf> <cr>, set flood.max <n>, etc.


Bridge Architecture

AbstractBridge allows MeshCore to interconnect with external networks:

Bridge Type Description
MQTT Forward mesh messages to/from an MQTT broker (meshcore-mqtt project)
Serial Raw serial bridge for integration with other systems
ESP-NOW Wi-Fi inter-mesh links between ESP32 nodes without infrastructure

Bridge configuration in NodePrefs includes: type/enable, 500 ms default delay, source selection, baud rate, ESP-NOW channel number, and a 16-byte bridge secret.


Debugging

Serial Console

All firmware variants expose a serial CLI at 115200 baud 8N1. Connect via USB and use any terminal emulator.

Useful commands:

status          — show node identity, uptime, battery
stats           — packet counts, airtime, error flags
contacts        — list known contacts
mesh            — show neighbour table (repeater only)
set             — configure parameters (use 'set ?' for help)
reboot          — restart firmware

Stats and Error Flags

RepeaterStats tracks: - Battery voltage - Queue length - Noise floor (dBm) - Last RSSI/SNR - Flood/direct send and receive counts - TX/RX airtime (ms) - Uptime (seconds) - Duplicate packet count - Error event flags

The err_flags byte in stats frames encodes fault conditions (stuck radio, CAD timeout, queue overflow, etc.).

Common Issues

Symptom Likely Cause
No packets received Frequency or BW mismatch with network
High duplicate count Overlapping repeater coverage, flood_max too high
ACK timeouts, path resets Marginal links, node moved, topology changed
CAD timeout errors RF interference, radio chip lockup (fixed by reboot or AGC reset)
Queue overflow Burst traffic exceeding airtime budget
High noise floor Near interference source (WiFi, other LoRa deployments)

SNR Interpretation

SNR is stored as int8_t in quarter-dB units throughout the firmware. Always divide by 4.0 to get dBm float.

Minimum decodable SNR per spreading factor:

SF Min SNR
SF7 −7.5 dB
SF8 −10.0 dB
SF9 −12.5 dB
SF10 −15.0 dB
SF11 −17.5 dB
SF12 −20.0 dB