BLE admin utility for provisioning Meshtastic nodes (Heltec V4 / firmware 2.7.x).
Uses official Meshtastic protobuf schemas. No manual encoding.
pip install bleak meshtasticPython 3.9+. macOS or Linux. Bluetooth must be on.
# Scan, pick device, enter menu
python3 meshtastic_admin.py
# Skip scan — connect directly by BLE address
python3 meshtastic_admin.py --addr 12345678-ABCD-EF00-1234-123456789000
# Full debug output (BLE, protobuf, drain detail)
python3 meshtastic_admin.py --debug
# Scan only — list devices and exit
python3 meshtastic_admin.py --scanOn launch the tool scans for 10 seconds and lists all Meshtastic devices found, sorted by signal strength. Select by number or supply --addr to skip the scan.
1) Read config → file
2) Default setup (full interactive)
3) Quick setup from file
4) Set owner only
5) Set channels only
6) Set device config
7) Set position config
8) Set bluetooth + lora
9) Read node keys → file
10) Write node keys from file
11) Factory reset config (preserves keys)
12) Factory reset full (wipes everything)
q) Quit
All write operations:
- Compare desired value against current device state before writing — skip if no change
- Reconnect and verify after every section that causes a reboot
- Print per-field
[verify ...]lines with explicitMISMATCHwarnings
Connects, drains config, prints it, saves to JSON. No writes.
Output file: {long_name}_config_{YYYYMMDD_HHMMSS}.json
Contains: identity, LoRa, device, position, bluetooth, all active channels with PSK (base64 + display fingerprint). Does not contain node crypto keys.
Full walk-through of every configurable section. Connects and drains first to read current values, then disconnects while collecting input (BLE is not held open during prompts). Reconnects only when ready to write.
Each prompt shows the current device value in brackets — press Enter to keep it.
Sections in order:
Identity — long name, short name (4 char max)
Channels — number of active channels (1–8), then per-channel:
- Name
- PSK (see Channel PSK below)
- Position precision (0–32; 32 = full precision on CH0)
LoRa — region, modem preset, TX power (dBm), hop limit
Device — role (CLIENT / ROUTER / ROUTER_CLIENT / REPEATER), NodeInfo broadcast interval (s)
Position — GPS mode, GPS update interval, position broadcast interval, smart positioning on/off, smart min interval (s), smart min distance (m)
Bluetooth — enabled, pairing mode (RANDOM_PIN / FIXED_PIN / NO_PIN)
After all prompts a summary is shown. Confirm with y to proceed.
Write order (minimises reboots):
set_owner— no rebootset_channel × N— no rebootset_config(device)→ reboot → reconnect → verifyset_config(position)→ reboot → reconnect → verifyset_config(bluetooth)→ reboot → reconnect → verifyset_config(lora)→ reboot → reconnect → verify (always last)
Sections with no change are skipped entirely.
Load any config JSON (from any node — different node is fine). After file selection the tool connects to the radio, drains its current identity, and shows a side-by-side comparison:
── IDENTITY COMPARISON ──
RADIO FILE
Node ID !abcd1234 !dcba4321
Long name mesh1234_john mesh4321_bobb
Short name JOHN BOBB
Prompts for the names to apply (defaults to file values). All other settings (channels, LoRa, device, position, bluetooth) are taken from the file without further prompts. Same write order and diff logic as menu 2.
File browser — see File Browser.
Prompts for long name and short name. Writes set_owner if changed. No reboot. Reconnects and verifies.
Enter channel indices one at a time (0–7, blank to finish). For each channel: name, PSK, position precision. Writes only changed channels. No reboot. Reconnects and verifies.
Role and NodeInfo broadcast interval. Triggers reboot. Reconnects and verifies.
GPS mode, GPS update interval, position broadcast interval, smart positioning settings. Triggers reboot. Reconnects and verifies.
Bluetooth (enabled, pairing mode) then LoRa (region, modem preset, TX power, hop limit). Each triggers a separate reboot if changed. LoRa always written last. Reconnects and verifies after each.
Reads the security config (public key, private key, admin key) and saves to a separate JSON file. Private key is not printed to the terminal.
Output file: {long_name}_keys_{YYYYMMDD_HHMMSS}.json
Loads a _keys_ file, shows the public key, confirms before writing. Skips if keys already match. Triggers reboot. Reconnects and verifies public key.
Use case: restore a node after factory reset, or clone keys to a replacement unit.
File browser — filters to _keys_ files only. See File Browser.
Resets all configuration to firmware defaults. Preserves BLE pairing and node crypto keys. Requires typing RESET to confirm.
Wipes everything including BLE pairing and node crypto keys. The device becomes a new unconfigured node with a new identity. Requires typing WIPE to confirm.
Channel 0 (PRIMARY):
- PUBLIC — uses
AQ==(Meshtastic default public mesh key) - PRIVATE — enter a passphrase; PSK =
SHA-256("{name}{passphrase}{name}")
Channels 1–7 (SECONDARY):
- Shared passphrase — same passphrase for all secondary channels, each derives its own PSK using its channel name
- Custom passphrase — per-channel passphrase
- PUBLIC —
AQ== - Keep current — press Enter
PSK derivation formula: SHA-256("{channel_name}{passphrase}{channel_name}")
The 8-character fingerprint shown (e.g. 991122aa) is SHA-256(psk).hex()[:8] — useful for confirming two nodes share the same key without exposing the key itself.
Menus 3 and 10 open an interactive file browser instead of a raw path prompt.
SELECT CONFIG FILE
Directory: /Users/you/lora
1) BOBB_4321_config_20260329_135011.json 2847B 2026-03-29 13:50 !abcd1234 2026-03-29T13:50
2) JOHN_1234_config_20260329_143154.json 1903B 2026-03-29 14:31 !dcba4321 2026-03-29T14:31
3) JOHN_1234_keys_20260329_140000.json 983B 2026-03-29 14:00 !dcba4321 2026-03-29T14:00 🔑
Enter number, path, or 'd <path>' to change directory (q=cancel)
>
- Enter a number to select
- Enter a path or filename to select directly
d ~/Desktopto change directoryqor Enter to cancel
Menu 10 filters to files with _keys_ in the name. Menu 3 shows all .json files. Each row peeks inside the file to show node ID, save timestamp, and 🔑 if it contains keys.
Standard config file (no keys):
{
"_meta": {
"tool": "meshtastic_admin",
"version": "2",
"saved": "2026-03-29T14:51:36",
"node_id": "!abcd1234",
"include_keys": false
},
"identity": {
"node_id": "!abcd1234",
"long_name": "mesh1234_john",
"short_name": "JOHN",
"hw_model": "HELTEC_V4",
"firmware_version": "2.7.15.567b8ea"
},
"lora": {
"region": "US",
"modem_preset": "MEDIUM_FAST",
"tx_power": 20,
"hop_limit": 3,
...
},
"channels": [
{
"index": 0,
"name": "General",
"role": "PRIMARY",
"psk_b64": "base64-encoded-32-bytes",
"psk_display": "AES-256 (12345678)",
"position_precision": 32
},
...
]
}Files are human-readable JSON. psk_b64 is the raw PSK bytes in base64 — this is what gets written to the device. psk_display is informational only.
| Setting | Value |
|---|---|
| Region | US |
| Modem preset | MEDIUM_FAST |
| TX power | 20 dBm |
| Hop limit | 3 |
| Role | CLIENT |
| NodeInfo interval | 10800 s (3 h) |
| GPS mode | ENABLED |
| GPS update interval | 60 s |
| Position broadcast | 3600 s (1 h) |
| Smart positioning | enabled, 30 s / 50 m |
| Bluetooth | enabled, RANDOM_PIN |
Standard 4-channel layout:
| CH | Role | Name | PSK | Position precision |
|---|---|---|---|---|
| 0 | PRIMARY | General | derived | 32 (full) |
| 1 | SECONDARY | Priority | derived | 0 |
| 2 | SECONDARY | Immediate | derived | 0 |
| 3 | SECONDARY | waypoints | derived | 0 |
All channels use a shared fleet passphrase. Each derives its PSK independently using its channel name.
- Pair the node via BLE (macOS: System Settings → Bluetooth, or iOS)
- Run the tool, select the node
- Menu 2 (Default setup) — walk through all settings
- When prompted for channels, enter the fleet passphrase
- Confirm and let the tool write and verify
- Menu 1 to save the final config to file as a record
To clone config from an existing node to a new one:
- Menu 1 on the source node — save config file
- Pair the new node
- Menu 3 on the new node — load the saved file, confirm the new node's name
Some config sections require a firmware reboot to take effect:
| Section | Reboot |
|---|---|
set_owner |
No |
set_channel |
No (if LoRa unchanged) |
set_config(device) |
Yes |
set_config(position) |
Yes |
set_config(bluetooth) |
Yes |
set_config(lora) |
Yes — always last |
After each reboot the tool waits 7 seconds, then reconnects (with a 5-second delay) and performs a full config drain to verify the written values. Each field is checked individually with a [verify ...] line. Mismatches print a ⚠ MISMATCH warning.
--debug enables four log channels:
| Tag | Content |
|---|---|
[BLE] |
Connect, disconnect, FROMNUM notifications, MTU |
[PROTO] |
Encoded wire bytes for each ToRadio write |
[DRAIN] |
Every FromRadio message with variant and size |
[ADMIN] |
Every AdminMessage sent with field values |
Without --debug only [ADMIN] is shown, plus user-facing status lines.
- Tested on Heltec V4, firmware 2.7.15
- BLE MTU negotiated automatically (515 bytes observed)
- FROMNUM notify subscription used to detect config drain start; fallback to polling if notify does not fire within 2 s (normal on V4)
- Reboot detection via FROMNUM value decrease, guarded against false trigger during initial drain