Companion-computer firmware and a ground-control station (GCS) that carry encrypted MAVLink telemetry and a small command set over a Meshtastic mesh.
Repository: github.com/AkitaEngineering/MeshSwarm. Previously published as Meshtastic-Integration-for-DroneBridge32-Swarm.
This is a control/telemetry link, not a full autopilot. Flight mode still belongs to the flight controller. Bench-test with props off before any flight.
- AES-GCM encrypted telemetry (position, altitude, attitude, battery) and commands (RTL, land, emergency land, counter sync, heartbeat). Message type is bound as GCM AAD (0.2.0+; GCS and firmware must match)
- Replay protection with persisted sequence numbers
- Signed AES-key provisioning on the ESP32 (
SETKEYSIG) - Lost-link, never-acquired-GCS (when FC reports armed), GPS-loss, low-battery, and circular geofence RTL, with MAVLink
COMMAND_ACKretries - Web GCS (Waitress) with live map, command ACKs, API token, serial reconnect, and JSONL telemetry log
- In-process fake radio (
MESHTASTIC_FAKE=1) for app QA without hardware
GCS (Python) --USB protobuf--> Ground Meshtastic node
|
LoRa mesh
|
ESP32 companion --UART1 PROTO--> Drone Meshtastic node
|
UART2 MAVLink
|
Flight controller
Swarm payloads are an inner frame (0xDB 0x32 + type + length + AES-GCM blob + CRC16) carried on Meshtastic PRIVATE_APP (256). Configure the drone Meshtastic Serial Module mode to PROTO so UART1 is the client API (0x94 0xC3 ToRadio/FromRadio).
For Serial Module SIMPLE mode, compile firmware with -DMESH_SERIAL_SIMPLE and run the GCS with MESHTASTIC_PORTNUM=64 (SERIAL_APP). Receive still accepts both 64 and 256.
- Ground Control Station — Waitress/Flask app (
gcs_app.py) plus Leaflet UI. - ESP32 firmware — ESP-IDF FreeRTOS node in
main/(Meshtastic codec, MAVLink heartbeat/commands, fail-safes).
git clone https://github.com/AkitaEngineering/MeshSwarm.git
cd MeshSwarm-
Install ESP-IDF v5.x (v5.5 is the version this tree is built against).
-
Load the IDF environment so
idf.py, IDF CMake 3.30.2, Ninja 1.12.1, andxtensa-esp-elf-gccare onPATH. Distro CMake 4.x andarm-none-eabi-gccwill not configure this project.. $HOME/esp/esp-idf/export.sh # bash source $HOME/esp/esp-idf/export.fish # fish
If
cmake --versionstill reports 4.x, install the IDF copies:python $IDF_PATH/tools/idf_tools.py install cmake ninja, then sourceexportagain. -
From this repository:
idf.py set-target esp32thenidf.py buildandidf.py flash monitor. -
Wire MAVLink TX/RX to UART2 (pins 17/16) and Meshtastic UART to UART1 (pins 4/5).
-
On the drone Meshtastic node enable the Serial Module in PROTO mode at 115200 8N1.
No external MAVLink component is required; the firmware includes a minimal encoder/decoder for HEARTBEAT, COMMAND_LONG, COMMAND_ACK, GLOBAL_POSITION_INT, SYS_STATUS, and ATTITUDE. The flight controller must emit HEARTBEAT so the companion can see the armed flag.
pip install -r requirements.txt
python gcs_app.pyOpen http://127.0.0.1:5000. Leaflet assets are served locally; OSM map tiles still need network. The GCS process is Waitress, not the Flask development server.
App-level QA without radios:
MESHTASTIC_FAKE=1 MESHTASTIC_API_TOKEN=qa-token python gcs_app.py- Drone ID: default 1. USB console
SETID:2(persisted in NVS). Rebuild with-DDRONE_ID=nfor a compile-time default. - AES key (GCS), in order:
MESHTASTIC_AES_KEY_FILE(32 hex chars),MESHTASTIC_AES_KEY, system keyring, compiled NIST test key (dev only). Optional per-drone keys:MESHTASTIC_AES_KEY_<id>(1–254). Each aircraft still holds one NVS key; set that drone’s GCS env var to the same bytes. Broadcast (255) always uses the swarm default key, so uniquely keyed aircraft will ignore swarm-wide commands. SetMESHTASTIC_PRODUCTION=1orMESHTASTIC_REQUIRE_KEY=1to refuse the fallback. A configured file or env var that is missing or malformed is an error; it will not silently use the test key. - AES key (MCU):
SETKEYSIG:<32-hex>:<ecdsa-der-hex>generated withpython scripts/provision_key.py --hex <32 hex chars> --sign-pem /path/to/provisioning_private.pem. UnsignedSETKEY:is compiled out unlessALLOW_INSECURE_SETKEY. - Provisioning public key: replace
provisioning_pubkey.handscripts/provisioning_public.pembefore deployment. Do not commit production private keys. - API token:
MESHTASTIC_API_TOKEN. The UI sendsAuthorization: Bearer …from the header field (stored in browser localStorage). Required for/api/controland/api/telemetrywhen set./api/statusthen hides live counts until the token is sent. - Bind address:
MESHTASTIC_GCS_HOST/MESHTASTIC_GCS_PORT(default127.0.0.1:5000). Binding beyond localhost requiresMESHTASTIC_API_TOKENand a non-default AES key. - Serial port:
MESHTASTIC_SERIAL_PORTif more than one Meshtastic device is attached. The GCS reconnects with backoff if the serial link drops. - Mesh port:
MESHTASTIC_PORTNUM(default 256). - Heartbeat:
MESHTASTIC_HEARTBEAT_INTERVALseconds (default 15;0disables). Aircraft RTL afterLOST_LINK_TIMEOUT_MS(default 30000) without a valid command/heartbeat. If the FC reports armed and no GCS packet is ever seen, the same timeout triggers RTL. After a GPS fix is lost forGPS_LOSS_TIMEOUT_MS(default 15000) while armed, the aircraft RTLs. - Control rate limit:
MESHTASTIC_CONTROL_RATEcommands perMESHTASTIC_CONTROL_RATE_WINDOWseconds (default 8 / 2). Heartbeats are not limited.0disables. - Command ACK wait:
MESHTASTIC_ACK_TIMEOUTseconds (default 5). - Geofence (GCS UI):
MESHTASTIC_GEOFENCE_LAT,MESHTASTIC_GEOFENCE_LON,MESHTASTIC_GEOFENCE_RADIUS_M. - Geofence (aircraft RTL): USB console
SETFENCE:<lat>:<lon>:<radius_m>(NVS). Radius0disables. - Telemetry log:
MESHTASTIC_TELEMETRY_LOG(defaultdrone_telemetry.jsonl). SetMESHTASTIC_TELEMETRY_LOG_DISABLE=1to turn off. - Replay / sync: sequence files
.control_seqon the GCS and NVSctrl_seqon the drone. If the GCS counter is behind, use Sync Counters (command 4). Sync is accepted even when the GCS seq is stale; the ACK carries the drone’s last seq.
| ID | Name | Aircraft action |
|---|---|---|
| 1 | RTL | MAV_CMD_NAV_RETURN_TO_LAUNCH |
| 2 | Land | MAV_CMD_NAV_LAND |
| 3 | Emergency land | MAV_CMD_NAV_LAND with confirmation=1, retried until COMMAND_ACK |
| 4 | Sync | report/advance control counter; no flight-mode change |
| 5 | Heartbeat | link keepalive (GCS sends this automatically) |
Broadcast drone id 255 addresses the whole swarm.
The GCS refuses several unsafe combinations on startup:
MESHTASTIC_PRODUCTION=1refuses the compiled test AES key,MESHTASTIC_FAKE=1, and a missing API token- Binding
MESHTASTIC_GCS_HOSTbeyond localhost refuses a missing API token and the compiled test AES key - A set-but-invalid
MESHTASTIC_AES_KEY/MESHTASTIC_AES_KEY_FILEis an error (no silent fallback to the test key)
Operator steps:
pip install -r requirements-dev.txt && flake8 --jobs=1 . && mypy --ignore-missing-imports . && pytest -q && make -C tests/hostidf.py buildfrom a clean checkout with ESP-IDF installed. Do not compile withALLOW_INSECURE_DEFAULT_KEYorALLOW_INSECURE_SETKEY. This tree does not enable ESP secure boot or flash encryption; add those with your own signing keys before fielded hardware.- Replace
provisioning_pubkey.handscripts/provisioning_public.pem; keep the matching private key offline and never commit it. The repo ships a test provisioning pubkey and will log a warning if it is still in the firmware. - Provision a non-default AES key on the GCS and every drone (
SETKEYSIG); setMESHTASTIC_PRODUCTION=1. Optionally setMESHTASTIC_AES_KEY_<id>for per-drone keys. - Set
MESHTASTIC_API_TOKEN(required for production and for any non-localhost bind) - Set unique drone IDs, aircraft geofence (
SETFENCE), and lost-link timeout for the site - Confirm the flight controller’s own radio-loss failsafe is enabled. MeshSwarm RTL is a companion-computer command; it is not a substitute for the FC failsafe.
- Bench-test command ACKs (including FC
COMMAND_ACK), replay rejection, radio-out RTL, never-GCS-while-armed RTL, GPS-loss RTL, low battery RTL, and geofence RTL with props off
Flash GCS 0.2.0 and firmware 0.2.0 together. AES-GCM AAD is a breaking wire change versus 0.1.x.
- Python:
pytest -q(framing, crypto/AAD, GCS routes, fake radio, geofence flags, logging) - Host firmware:
make -C tests/host(frame CRC, MAVLink round-trip, failsafe decision table) - CI also builds ESP32 firmware with ESP-IDF v5.5
They do not replace a hardware-in-the-loop pass.
Provided as-is, without warranty. You are responsible for airframe fail-safes, legal operation, and not flying an untested link.