LoRa S.A.O - L.E.M

By

Custom LoRa Meshtastic SAO, ESP32-C3 + E22-900M22S (SX1262)

L.E.M v1
L.E.M v1

What it is

L.E.M (Low Earth Mesh) is a SAO ("Shitty Add-On", the small 2×3 connector you'll find on hacker conference badges) that packs a real Meshtastic radio node, a long-range, encrypted LoRa mesh network with no infrastructure (no cell tower, no wifi, just LoRa relayed peer-to-peer from node to node).

Two ways to use it:

  • Standalone node: a plain Meshtastic node on this hardware, configured from the Meshtastic app over Bluetooth. Nothing else.

  • Badge SAO: plugged into a host badge (here, the NorthSec 2026 conference badge) through the SAO connector, it exposes live mesh state (node count, packets relayed, RSSI/SNR, last received message) and the badge can send messages through it. The badge adds a full e-ink UI on top (dashboard, messaging, node tracking, etc.)

The firmware is a fork of Meshtastic, not a rewrite; only a thin layer (LemSaoModule) was added SAO-side to expose this protocol to the badge.

Technical details

Hardware (body v2):

   
MCU ESP32-C3 Super Mini (native USB-C, onboard LDO)
Radio E22-900M22S, SX1262 module, 915 MHz, external PA/LNA, U.FL
Connector SAO 2×3, 2.54mm, shrouded (SAO spec v1.69bis)
Power 3V3 via the SAO connector or USB-C
Extras 4 wing connectors (+3V3/GND) for decorative LEDs

Two 10k I2C pull-ups were added in v2 (SCL on GPIO2, a strapping pin, this secures the boot) plus a 10µF bulk cap on the E22's VCC (anti brown-out on TX).

Why a custom build instead of the Meshtastic web flasher: the radio wiring is non-standard (a discrete E22 on a C3, not an off-the-shelf board from the official catalog), so the pins are baked into variant.h at compile time; no pre-built firmware from the catalog can match it.

SAO ↔ badge link: unlike the usual I2C convention for SAOs, this one runs UART over the connector's two signal pins (115200 baud, a full read block ~80 bytes takes ~7-8ms, well under the badge's 3Hz poll rate). Custom framed protocol (0xAA request / 0x55 response, CRC8), with a register map exposing mesh state for reading (node count, RSSI/SNR, last message, role/hop of the last sender...) and a write buffer to send a message. Full details live in the repo's register map doc.

Precautions

  • Antenna required before any transmission. The E22 has an external PA: transmitting with no antenna plugged into the U.FL can damage it.

  • Never plug the SAO and USB-C at the same time. Both power the board, there's no guarantee on behavior if both sources are active.

  • GPIO2, GPIO8, and GPIO9 are strapping pins on the C3; never route an external signal onto them with an uncertain reset-time state. A v1 board ended up stuck in download mode because of this (GPIO9 held low by an external signal).

  • Radio region = US (915MHz), regardless of mode (standalone or SAO): that's the band this hardware is wired/tested for.

  • The R1 pull-up (v2, GPIO2/SCL) is what fixed cold-boot reliability; if a v2 board ever becomes flaky on boot again after an unplug/replug, that's the first thing to check.

L.E.M v2
L.E.M v2

How to flash: standalone SAO (autonomous node)

Prerequisites (Windows): Git, VS Code + the PlatformIO IDE extension (bundles its own Python, nothing else to install), and ideally Microsoft's Serial Monitor extension (PlatformIO's built-in monitor loops on ClearCommError against the C3's native USB-JTAG controller).

  1. Clone vanilla Meshtastic firmware (upstream, not the fork):

    git clone --recurse-submodules https://github.com/meshtastic/firmware.git
    cd firmware
    git submodule update --init
  2. Copy the variant/lem/ folder from the LowEarthMesh repo into variants/esp32c3/lem/ in the firmware tree. This folder holds two files:

    • platformio.ini: the new [env:lem-esp32c3-sx1262] environment, identity PRIVATE_HW (no official model), and the build flags specific to this wiring (no screen, native USB CDC, etc.)

    • variant.h: defines SPI, the SX1262's control pins (watch for the GPIO9→GPIO0 bodge on BUSY), the E22's TCXO voltage, and the Super Mini's onboard LED.

  3. Build (from the PlatformIO Core CLI terminal, not a plain PowerShell: pio isn't on PATH otherwise):

    pio run -e lem-esp32c3-sx1262

    First build: 20-40 min (toolchain download). Cached after that. Only *** Error or FAILED lines matter; ignore warning: lines.

  4. Connect over USB-C (never with the SAO plugged in at the same time), then flash:

    pio run -e lem-esp32c3-sx1262 -t upload --upload-port COM7

    If it hangs on Connecting....____: hold BOOT, tap RST, release BOOT, retry (auto-reset is unreliable over native USB with CDC_ON_BOOT).

  5. Check at boot (via the serial monitor, 115200 baud): a SX1262 init result 0 or equivalent confirms SPI/radio are good.

  6. Configure the network over Bluetooth via the Meshtastic app (see Setting up a node below).

Integrating the NorthSec 2026 badge (SAO mode)

This path uses the dedicated Meshtastic fork (variant + LemSaoModule already in the tree; no need to copy variant/lem/ by hand):

L.E.M v1
L.E.M v1
git clone --recurse-submodules -b develop https://github.com/AlrikRr/Meshtastic-Firmware.git
cd Meshtastic-Firmware
git submodule update --init

Open the folder in VS Code (the one with the root platformio.ini), then build/flash exactly as above with the lem-esp32c3-sx1262 environment; nothing else to configure.

Once flashed, this SAO talks over UART to the badge firmware (branch feature-lem-sao of the badge-2026 fork), which continuously reads mesh state and adds on top of it:

  • Dashboard: node count, packets relayed, RSSI/SNR, last message, the docked SAO's own name.

  • Messaging: last received text (DM or broadcast), with a dedicated LED notification on DMs.

  • SIGINT Constellation + Target Lock: every node heard gets classified (role + hop distance), lock onto one and a NeoPixel radar pulse gets faster/redder as its signal strengthens.

  • LemDex: a persistent (NVS-backed) collection of every node ever discovered, "tactical space RPG" style, browsable as filterable cards.

  • Orbital Docking: two badges bumped together over NFC exchange their SAO's public key/contact info directly, without waiting on mesh NodeInfo propagation.

  • Bluetooth PIN relay: the SAO has no screen; the badge reads the SAO's BLE pairing PIN off a dedicated register and displays it.

  • Sleep / dock-disconnect feedback: e-ink + LED feedback on dock/pull, a long-press standby mode with LEDs and screen off.

All of this lives badge-side; the SAO firmware stays deliberately lean, it only exposes the register map.

Setting up a node (runtime config)

No screen on this hardware: all configuration happens through the Meshtastic app over Bluetooth (BLE).

  • Region: US (915MHz), required, regardless of mode.
  • Role: ROUTER for pairing with the badge (SAO mode). In standalone mode, any role that fits your deployment.
  • Bluetooth pairing PIN:
    • SAO mode: LemSaoModule forces a RANDOM_PIN on every boot (in RAM only). Since the SAO has no screen, the docked badge reads it back (BLE_PIN register) and displays it. Nothing to configure on the SAO side.

    • Standalone mode: with no screen, Meshtastic falls back to its default fixed PIN (123456) until changed, either at runtime (meshtastic --set bluetooth.fixed_pin 654321 or via the app), or at compile time to flash a batch of boards with a set default PIN (userPrefs.jsonc, key USERPREFS_FIXED_BLUETOOTH).

Resource URL
Build firmware from source (official) https://meshtastic.org/docs/development/firmware/build/
Flashing firmware (official) https://meshtastic.org/docs/getting-started/flashing-firmware/
Bluetooth settings (official) https://meshtastic.org/docs/configuration/radio/bluetooth/
Meshtastic firmware repo (upstream) https://github.com/meshtastic/firmware
userPrefs.jsonc (reference) https://github.com/meshtastic/firmware/blob/master/userPrefs.jsonc
SAO firmware fork (variant + LemSaoModule) https://github.com/AlrikRr/Meshtastic-Firmware
LowEarthMesh repo (BOM, pinout, register map) https://github.com/NetRunSecurity/LowEarthMesh
NorthSec 2026 badge fork (feature-lem-sao) https://github.com/AlrikRr/badge-2026