This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
LoRaWAN-over-Meshtastic (LWoM) tunnels LoRaWAN Class A/C traffic over Meshtastic transport, allowing devices out of gateway range to reach a LoRaWAN Network Server (LNS) without modifying the higher-level device implementation. The first implementation targets Arduino LMIC.
The device uses a single LoRa radio with a Meshtastic protocol shim between the LMIC MAC and the existing radio driver. A Linux bridge connects to a standard Meshtastic node and forwards to the LNS.
LMIC MAC (LoRaWAN state machine, crypto, join)
↓ os_radio_v2()
radio_meshtastic.c (shim layer)
↓ wraps/unwraps PHYPayload in Meshtastic framing
↓ manages minimal Meshtastic protocol state
↓ switches radio to Meshtastic modulation params
radio_sx127x.c / radio_sx126x.c (existing, unmodified)
↓ SPI
LoRa radio hardware
Standard Meshtastic node
↓ USB/Serial
Linux bridge (Python)
↓ Semtech UDP
The Things Stack (TTN/TTS) or ChirpStack
- Same radio: Device uses its own LoRa radio for Meshtastic transport (not an external Meshtastic node)
- Radio shim:
radio_meshtastic.cimplementsos_radio_v2(), wraps PHYPayload in Meshtastic protocol, calls through to real radio driver - Clean-room Meshtastic: Minimal protocol reimplementation (not a fork of Meshtastic firmware); lives in LMIC repo at
src/meshtastic/ - Payload cap: Meshtastic
Data.payload≤ 233 B → LoRaWAN FRMPayload ≤ 220 B (no FOpts) - Soft RX windows: RX1 delay 5-10 s with RX2 fallback (not strict 1 s/2 s); requires runtime timing overrides in LMIC
- ADR disabled: No RF metrics available through Meshtastic; bridge fabricates RF metadata for LNS
- Class A and Class C: Both supported (LMIC v6.0.1 has Class C)
All device-side code lives in the Arduino LMIC repo. Configuration follows the existing lmic_project_config.h pattern:
#define CFG_us915 1
#define CFG_meshtastic_transport 1 // enables the Meshtastic shim
#define CFG_sx1276_radio_hw 1 // actual radio chip
#define LMIC_ENABLE_class_c 1
#define LMIC_ENABLE_timing_overrides 1MCCI BSP boards get a menu item for transport selection. Non-MCCI platforms (ESP32, etc.) use lmic_project_config.h directly.
Meshtastic channel encryption uses AES-256-CTR. The implementation extends the LMIC Secure Element API with aes256ctr methods, using the same driver dispatch pattern. Software fallback uses BearSSL aes_ct (constant-time bitsliced AES, MIT license). Platform-specific SE drivers can use hardware crypto (ESP32, nRF52).
- The Things Stack (TTN/TTS) preferred
- Semtech UDP protocol first, LoRa Basics Station later
- ChirpStack acceptable for local testing
- Private portnum (256-511 range) for LoRaWAN traffic
- Dedicated channel key (AES-256-CTR encrypted)
- Minimal implementation: 16-byte header, Data protobuf (hand-coded, no nanopb), CAD/CSMA before TX
- No node discovery, ack handling, multi-channel, or Bluetooth/USB client API
- Keep hop limit minimal
See docs/lwom-implementation-plan.md for the full plan.
- Phase 0: Runtime timing overrides in LMIC (prerequisite)
- Phase 1: Meshtastic-only POC (shim, protocol, bridge, example sketch)
- Phase 2: Dual-mode runtime switching between LoRaWAN-direct and Meshtastic
- GitHub-ready Markdown with Mermaid diagrams
- Use quoted labels in Mermaid nodes, avoid HTML entities
- Keep region details (caps, timing) only in
docs/lwom-implementation-plan.md, not README
All markdown files must pass markdownlint. Node/npx is not globally
available on Windows; run via WSL using the project's local install:
WSLPATH=$(wsl -d Ubuntu-24.04 wslpath "$(pwd -W)" 2>/dev/null | tr -d '\r\0')
wsl -d Ubuntu-24.04 -- bash -c "cd '$WSLPATH' && npx markdownlint <file.md>"MD013 (line length) is disabled project-wide via inline comments.
- No trailing whitespace in
.mdfiles - Ask for user approval before adding markdownlint disable annotations
- When using markdownlint-disable, add separate comment lines explaining each rule:
<!-- markdownlint-disable MD013 MD041 -->
<!-- MD013: allow long lines; URLs exceed 80 chars -->
<!-- MD041: allow non-heading first line; resume reminder needed -->When suppressing any linter warning, always include an explanatory comment:
- Format:
# noqa: CODE -- what is allowed; why it's needed - Purpose: Future maintainers should understand both what and why
All source files must have a standard header block:
/*******************************************************************************
*
* Name: filename.cpp
*
* Function:
* Brief description of the module's purpose
*
* Copyright notice and license:
* See LICENSE
*
* Author:
* Terry Moore
*
******************************************************************************/##############################################################################
#
# Name: filename.py
#
# Function:
# Brief description of the module's purpose
#
# Copyright notice and license:
# See LICENSE
#
# Author:
# Terry Moore
#
##############################################################################The README.md should end with a ## Meta section containing:
- Contributors: List of contributors
- Support: Link to thethings.nyc with message: "If you find this helpful, please support The Things Network New York by joining, participating, or donating."
After cloning, configure git hooks:
git config core.hooksPath .githooks