Meshtastic
Everything about the Meshtastic protocol: how it works, how to set it up, and technical details.
- 📖 Start Here — Meshtastic Guide
- How Meshtastic Works
- Meshtastic Protocol Overview
- Meshtastic Flooding Mesh Protocol Explained
- Meshtastic Node IDs, Addresses, and Naming
- Setting Up Meshtastic
- Meshtastic Setup Guide
- Initial Node Configuration Checklist
- Advanced Configuration for Infrastructure Nodes
- Device Roles — All 12
- Channels & Encryption
- Telemetry & Monitoring
- Meshtastic CLI Reference
- MQTT & Internet Gateway
- Meshtastic MQTT Setup
- Building a Meshtastic Internet Gateway
- MQTT Topic Structure and Packet Format
- Preventing MQTT Message Loops
- Advanced Features
- Meshtastic Web Interface and Remote Access
- Meshtastic Module Configuration Reference
- Traceroute and Path Diagnostics
- Neighbor Info and Signal Mapping
- Integrations & Automation
- Meshtastic APRS Gateway
- Meshtastic Python Scripting
- Home Assistant Integration via MQTT
- Node-RED Flows for Mesh Automation
- Security & Privacy
- Channel Management
- Understanding Channels and PSKs
- Sharing Channels via QR Code
- Channel Strategy for Community Networks
- Network Diagnostics and Health Monitoring
- Reading Network Statistics in the Meshtastic App
- Using the Meshtastic Python CLI for Diagnostics
- Mesh Topology and Path Analysis
- Common Network Problems and Solutions
- Building a Meshtastic Network Map
- Meshtastic Python API
- Getting Started with the Meshtastic Python Library
- Automating Meshtastic: Practical Scripts
- Meshtastic Python API Reference
- Meshtastic Modules and Plugins
- Telemetry Module: Device, Environment, and Power
- Store and Forward Module
- Range Test Module
- External Notification and Canned Messages
- Serial, MQTT, and Ambient Light Modules
- Encryption and Privacy
- Meshtastic Configuration Reference
- Meshtastic Apps and Interfaces
- Power Configuration
- LoRa Radio Configuration
📖 Start Here — Meshtastic Guide
Meshtastic is the most widely deployed LoRa mesh platform - it has the largest community, the most hardware support, and excellent mobile apps for Android and iOS. It uses flood-based mesh routing and is ideal for both personal use and community networks.
🚀 New to Meshtastic? Start Here
- Meshtastic Setup Guide - First-time setup walkthrough
- Initial Node Configuration Checklist - Everything to configure before going live
- Meshtastic Android App Overview or iOS App Overview
- Understanding What You're Seeing in the App
📚 What's In This Book
How Meshtastic Works
- Meshtastic Flooding Mesh Protocol Explained
- Node IDs, Addresses, and Naming
- How Meshtastic Channels Work
Device Roles
- Personal Device Roles - CLIENT, CLIENT_MUTE, CLIENT_HIDDEN
- Infrastructure Roles: ROUTER and REPEATER
- Specialized Roles
Configuration Reference
- LoRa Settings Reference - Every radio setting explained
- Choosing the Right Modem Preset - LongFast vs LongSlow vs ShortFast, etc.
- Device Configuration Settings
- Power Configuration Settings Reference
- Advanced Configuration for Infrastructure Nodes
Channels and Encryption
- Understanding Channels and PSKs
- Creating Private Channels
- Channel Strategy for Community Networks
- PKC Direct Messaging (v2.5+) - End-to-end encrypted DMs
- Channel Number Selection Guide
MQTT and Internet Connectivity
- Meshtastic MQTT Setup
- MQTT Topic Structure and Packet Format
- Preventing MQTT Message Loops
- Building a Meshtastic Internet Gateway
Modules and Integrations
- Telemetry Module: Device, Environment, and Power
- Store and Forward Module
- Home Assistant Integration via MQTT
- Node-RED Flows for Mesh Automation
- Meshtastic Python Scripting
Network Diagnostics
- Traceroute and Path Diagnostics
- Neighbor Info and Signal Mapping
- Building a Meshtastic Network Map
- Common Network Problems and Solutions
➡️ Related Books
- Meshtastic Repeaters - Setting up ROUTER and REPEATER nodes
- Room Servers & Gateways - MQTT gateways and internet bridges
- Hardware Guide - Choosing and buying hardware
- Solar & Power Systems - Powering your nodes
How Meshtastic Works
The Meshtastic protocol, routing, and architecture explained.
Meshtastic Protocol Overview
Meshtastic is an open-source mesh networking project built on LoRa radio hardware. It has a large, active global community and is one of the most widely deployed LoRa mesh platforms available.
How Meshtastic routes messages
Meshtastic uses a flooding approach to message delivery. When a node sends a message, nearby nodes rebroadcast it, and those nodes rebroadcast to others, until the message has propagated through the reachable network. A hop limit controls how many times a message can be relayed.
This approach is simple and effective for small networks, but it is best-effort - there is no guaranteed delivery. Confirm critical messages with an explicit ACK or acknowledgement. On larger, denser networks, the rebroadcast traffic can create congestion. Meshtastic has introduced optimizations over time (such as managed flooding) to reduce unnecessary retransmissions.
Node roles
Meshtastic devices can be assigned different roles that control how they participate in the network:
- Client - standard personal device paired with a phone via Bluetooth
- Client Mute - receives but does not retransmit (reduces traffic in dense areas)
- Router - infrastructure node that always rebroadcasts each packet once with prioritized routing; visible in the node list. Power Saving is enabled by default (ESP32)
- Repeater - dedicated relay node, does not appear in node lists. Note: REPEATER was deprecated as of firmware 2.7.11; prefer ROUTER or ROUTER_LATE for new infrastructure
- Tracker - broadcasts GPS position frequently
- Sensor - designed for environmental sensor data reporting
Note: the older Router Client (ROUTER_CLIENT) role was deprecated in firmware 2.3.15 and is no longer a current role. For infrastructure, use ROUTER or ROUTER_LATE; for ordinary nodes, use CLIENT.
Channels and encryption
Meshtastic uses a channel-based system. Each channel has a name and a pre-shared key (PSK). Devices on the same channel with the same key can communicate. Channels are encrypted with AES-CTR; the PSK may be 16 bytes (AES-128) or 32 bytes (AES-256). The default public channel uses a well-known 1-byte key (AQ==), which is effectively AES-128, making messages readable by any Meshtastic node - it is not private. Private channels use custom keys shared only among trusted users.
Technical specifications
| Parameter | Value |
|---|---|
| Frequency (US/Canada) | 902-928 MHz ISM band (commonly called the "915 MHz" band); license-free under FCC Part 15 / ISED RSS-247. Meshtastic uses defined frequency slots within the band |
| Modulation | LoRa (Long Range) |
| Channel encryption | AES-CTR; default public channel uses a 128-bit key (the public AQ== key, so it provides no confidentiality). AES-256 available when a 32-byte PSK is set |
| Routing | Managed flooding with hop limit |
| GPS support | Optional (device-dependent) |
| Protocol | Open source, Protobuf-based |
Meshtastic Flooding Mesh Protocol Explained
Understanding how Meshtastic actually routes messages helps you make better configuration decisions and troubleshoot problems faster.
Flooding: The Core Mechanism
Meshtastic uses a flooding broadcast approach to message delivery. When you send a message:
- Your node broadcasts the packet over LoRa
- Every node that receives it checks whether it has seen this packet before (via a packet ID hash)
- If not seen before: the node rebroadcasts the packet (with hop count decremented by 1)
- If seen before, or if hop count = 0: the node discards the packet
This is called "flooding" because the packet spreads outward like a flood, covering every node in range up to the hop limit. Every node participates in delivery - there's no designated router.
Hop Count and Hop Limit
Every Meshtastic packet carries a hop count. When a packet is first sent, it starts at the configured hop_limit (default: 3). Each time a node rebroadcasts the packet, it decrements the hop count. When hop count reaches zero, nodes still receive but don't rebroadcast.
This means a hop_limit of 3 allows a message to be relayed by up to 3 intermediate nodes, reaching a destination up to 3 "hops" away from the source (source → relay 1 → relay 2 → relay 3/destination).
# Check current hop limit:
meshtastic --get lora.hop_limit
# Change hop limit (3 is default, 7 is maximum):
meshtastic --set lora.hop_limit 5
Duplicate Suppression
Without duplicate suppression, flooding would create infinite loops - every node would keep rebroadcasting forever. Meshtastic prevents this by:
- Maintaining a "recently seen" packet ID cache (stored in RAM)
- Each packet is identified by the sender NodeID plus a 32-bit packet ID; a node treats a (sender, packet-ID) pair it has recently seen as a duplicate
- If a node has seen a packet ID recently, it discards the duplicate without rebroadcasting
Addressing: Broadcast vs Direct
Meshtastic packets can be addressed two ways:
- Broadcast (channel messages) - Addressed to all nodes (0xFFFFFFFF). Every node in the network receives and can read the message.
- Direct (DM messages) - Addressed to a specific node ID. On firmware v2.5+, DMs use per-recipient public-key cryptography (PKC), so only the destination node can read the content. On older firmware a DM is encrypted only with the channel key, so anyone holding that channel's PSK can read it - it is not truly private one-to-one. Either way, intermediate nodes still rebroadcast the packet (they just may not be able to read it).
Why Flooding Works Well for Small Networks
For networks up to ~50-100 nodes, flooding is remarkably effective:
- No routing tables to maintain or synchronize
- Extremely simple - even new, unconfigured nodes automatically participate in delivery
- Redundant paths - if one relay node is down, another may bridge the gap
- Works well in dynamic environments where nodes join and leave frequently
Flooding's Limitations in Large Networks
As networks grow, flooding creates a "broadcast storm" problem:
- One broadcast can trigger a rebroadcast from many nodes - up to roughly one per node within hop range that does not hear an earlier rebroadcast first. Managed flooding (listen-before-rebroadcast, SNR-based contention) suppresses many of these, but in dense meshes the cumulative retransmissions can still saturate the channel.
- Channel utilization increases with each new node; at high utilization, packets collide and delivery rates drop
- A 200-node network with active users can saturate the LoRa channel, causing significant packet loss
This is why MeshCore's path-based routing scales better for large networks - but for most community networks under 100 active nodes, Meshtastic flooding works well.
Meshtastic Node IDs, Addresses, and Naming
Understanding how Meshtastic nodes are identified helps you interpret the node list, configure direct messaging, and troubleshoot network issues.
Node ID Format
Every Meshtastic node has a 32-bit node ID derived by default from its hardware MAC address. It is not guaranteed to be globally unique. The ID is displayed in several formats:
- Decimal: 2882343476 (rarely used in user interfaces)
- Hex with prefix: !abcd1234 (most common display format; the "!" prefix denotes a node ID)
- Short ID: The last 4 hex digits, e.g., "1234" - used as the default short name
The node ID is normally fixed (MAC-derived) and is effectively permanent for typical users, but it is not strictly immutable: it can change on certain firmware events and can be overridden in firmware or config on some boards.
Long Name and Short Name
In addition to the hardware node ID, each node has two user-configurable names:
- Long name - Up to 40 bytes (about 37-39 ASCII characters; fewer if multibyte/UTF-8 or emoji are used, since those count as multiple bytes). Used in the full node list. Example: "Portland-Burnside-Bridge-Repeater" or "KG7XYZ Mobile"
- Short name - Up to 4 characters. Displayed on the map and in compact node list views. Example: "PDX1", "K7AB"
# Set long name:
meshtastic --set-owner "Your Long Name Here"
# Set short name:
meshtastic --set-owner-short "SHT1"
Channel Name and Hash
Meshtastic channels have both a name and a PSK (encryption key). The channel name is displayed to users; the PSK is what actually controls which nodes can communicate on the channel. Two nodes can have different channel names but the same PSK - they'll still communicate (though the name mismatch may confuse users).
The "channel hash" is a short numeric identifier derived from the channel name AND the PSK, included in packet headers to quickly identify which channel a packet belongs to without decrypting the payload. Because the channel hash and the source/destination node IDs sit in the plaintext header, a passive RF observer can tell which channel a packet belongs to and who sent and received it without the PSK - only the message content is hidden.
How Nodes Discover Each Other
Meshtastic nodes don't have a directory or registration system. Discovery happens organically:
- Nodes periodically broadcast "NodeInfo" packets containing their node ID, name, hardware model, and (optionally) GPS position
- Any node that receives a NodeInfo packet adds the sender to its local node database
- NodeInfo packets propagate through the mesh via the same flooding mechanism as messages
- Nodes that haven't been heard from in a configurable time period are marked as "stale" in the node database
This means in a well-connected mesh, a new node will typically appear in all other nodes' lists within a few minutes of powering on, even if it's multiple hops away.
Setting Up Meshtastic
Step-by-step device configuration for Meshtastic.
Meshtastic Setup Guide
Getting started with Meshtastic is straightforward. The project has extensive documentation and a large community, making it one of the easiest LoRa platforms to get up and running.
What you need
- A Meshtastic-compatible LoRa device, fully charged
- A smartphone (Android or iOS) or a computer
- The Meshtastic app (free on Google Play and the App Store) or the web client at client.meshtastic.org
Step 1 - Flash firmware (if needed)
Many devices ship without Meshtastic pre-installed. Use the Meshtastic Web Flasher at flasher.meshtastic.org to install firmware directly from your browser - no special software required.
- Connect your device via USB
- Open flasher.meshtastic.org in Chrome or Edge
- Select your device type and click Flash
- Wait for the process to complete and reboot
Step 2 - Connect via app or web client
Open the Meshtastic app on your phone. The app will scan for nearby Bluetooth devices. Select your node, pair, and the app will show you the node configuration screen.
Alternatively, use the web client at client.meshtastic.org if you prefer a browser-based interface.
Step 3 - Configure region
This step is mandatory before the radio will transmit. In the app or web client:
- Go to Config → LoRa
- Set Region to the code that matches your country - US for the United States and Canada (both use the 902-928 MHz US915 band), EU_868 for most of Europe, and so on. See the region reference for other countries.
- Save and allow the device to reboot
Setting the wrong region transmits on the wrong frequencies, which may be illegal and will prevent you from connecting to other local nodes.
Step 4 - Set your node name
In Config → Device, set a recognizable long name and short name for your node. Other users on the network will see this name.
Step 5 - Test
Open the Messages tab and send a message on the default channel. Nearby Meshtastic nodes will receive it. You can also check the Nodes tab to see any other devices currently in range.
Note: the default channel is public - anyone running Meshtastic in range (or anyone watching the public MQTT broker, if a gateway uplinks it) can read it. It is fine for testing, but do not send anything sensitive on it. Create a private channel with its own key for private conversations.
Further configuration
Meshtastic has many configurable parameters covering radio settings, power management, display, telemetry, and more. The official Meshtastic documentation at meshtastic.org/docs is comprehensive and well-maintained - refer to it for anything beyond basic setup.
Initial Node Configuration Checklist
When you get a new Meshtastic node, running through a standard configuration checklist ensures it's properly set up for your network before deployment. This guide covers every setting that matters for a production deployment.
Step 1: Flash Latest Stable Firmware
Even brand-new hardware may ship with outdated firmware. Always flash the latest stable release before configuration:
- Visit flasher.meshtastic.org in Chrome or Edge
- Connect node via USB
- Select your board model exactly (wrong board = failed flash)
- Select "Latest Release" and click Flash
- Wait for flash to complete (1-3 minutes)
Step 2: Region Setting (Critical)
The region setting controls which frequency band the node uses. Transmitting on the wrong frequency is illegal and will cause your node to interfere with other services. The region setting is the master legal cap on both frequency and transmit power: selecting a region other than your own (for example a foreign region preset) can put your node outside your country's authorized band and power limits.
# For US users:
meshtastic --set lora.region US
# For Canada:
meshtastic --set lora.region US # Canada uses the same 902-928 MHz (US915) band as the US
# Check current setting:
meshtastic --get lora.region
The node will not transmit until a region is set. This is intentional - it prevents out-of-box interference.
Step 3: Set Your Identity
# Set your long name (visible to all nearby nodes):
meshtastic --set-owner "Your Name or Node Name"
# Set your short name (4 chars, shown on map):
meshtastic --set-owner-short "AB01"
Step 4: Configure Your Channel
To join an existing community network, the easiest and safest method is to scan their QR code (or open their shared channel URL) with the Meshtastic app, which imports the channel name and key automatically. If you configure the channel manually instead, you must use the community's real base64 PSK - the placeholder below is not valid base64 and typing it verbatim will error or set a garbage key:
# Set channel name and key (replace base64encodedkey== with your community's REAL base64 PSK):
meshtastic --ch-index 0 --ch-set name "CommunityName"
meshtastic --ch-index 0 --ch-set psk "base64encodedkey=="
# Alternatively, for a new/default key instead of a shared one:
# --ch-set psk none (no encryption)
# --ch-set psk random (generate a fresh random key)
# Verify channel settings:
meshtastic --info
Step 5: Set Device Role
# For a personal/portable node:
meshtastic --set device.role CLIENT
# For a fixed home station that also relays traffic, use CLIENT (smart relay) or ROUTER_LATE.
# ROUTER_CLIENT is deprecated and removed in current firmware - do not use it:
meshtastic --set device.role CLIENT
# For a dedicated infrastructure repeater:
meshtastic --set device.role ROUTER
Step 6: Configure GPS (if equipped)
# Enable GPS position broadcasting (current firmware uses the gps_mode enum;
# older firmware used the boolean position.gps_enabled):
meshtastic --set position.gps_mode ENABLED
# Set position broadcast interval (seconds):
# 900 = every 15 minutes (good for fixed nodes)
# 60 = every minute (for mobile use)
meshtastic --set position.position_broadcast_secs 900
# For fixed installations, set a fixed position instead of using GPS:
meshtastic --setlat 45.5051 --setlon -122.6750 --setalt 50
Step 7: Verify and Test
# Show full device info:
meshtastic --info
# Listen for incoming packets:
meshtastic --listen
# Send a test message:
meshtastic --sendtext "Test from [your name]"
Check that your node appears in the app's node list and that the channel matches your community's configuration. Send a test message and verify it's received by another node.
Advanced Configuration for Infrastructure Nodes
Infrastructure nodes (routers, backbone repeaters) require additional configuration beyond the defaults to operate efficiently and reliably in a production network.
Power Management
The ROUTER role automatically uses power-saving sleep on ESP32 boards (the LoRa radio stays in standby so it can wake on incoming packets) - this cannot be disabled. If you need BLE, WiFi, or serial to stay continuously available on an infrastructure node, use the CLIENT or ROUTER_LATE role instead. The settings below affect non-router nodes:
# Light-sleep tuning (note: power.is_power_saving false is already the
# default for non-router roles, but ROUTER force-enables sleep on ESP32):
meshtastic --set power.is_power_saving false
meshtastic --set power.ls_secs 0 # 0 selects the DEFAULT 5-minute light-sleep interval (it does NOT disable light sleep)
Telemetry Configuration
Infrastructure nodes can report their own health metrics to the network:
# Enable device telemetry (battery, voltage, uptime) - firmware default is 1800 s (30 min):
meshtastic --set telemetry.device_update_interval 1800 # every 30 min
# Enable environment telemetry (if sensor equipped):
meshtastic --set telemetry.environment_update_interval 1800
# Reduce position airtime on a fixed node by setting a long broadcast interval
# (setting 0 does NOT disable broadcasts):
meshtastic --set position.position_broadcast_secs 900
Hop Limit for Infrastructure Nodes
Meshtastic recommends leaving the hop limit at the default of 3 (maximum 7). Increasing it rarely improves coverage and significantly increases channel congestion; address coverage gaps with better-placed router nodes instead of more hops:
# Default is 3; only raise this if you genuinely need it:
meshtastic --set lora.hop_limit 3
# Note: Higher hop limit increases channel utilization and rarely helps coverage.
# Prefer adding well-placed routers over raising the hop count.
Ignore MQTT Setting
If your network has an MQTT gateway, setting lora.ignore_mqtt=true makes a node drop LoRa packets that entered the mesh via MQTT, which can reduce duplicate or looped traffic from the gateway:
# ignore_mqtt tells the node not to re-transmit over radio any packet that
# arrived from the internet (MQTT), which prevents echo loops:
meshtastic --set lora.ignore_mqtt true
Admin Channel Configuration
Set up a separate admin channel for network operations traffic, separate from the public community channel. The ops-channel PSK must be a cryptographically random 256-bit key generated by the app (use psk random), not a human-typed string:
# Add a second channel for admin use (--ch-add both creates and names it):
meshtastic --ch-add OpsNet
meshtastic --ch-index 1 --ch-set psk random # generates a secure random key; or supply a valid base64 32-byte key
meshtastic --ch-index 1 --ch-set uplink_enabled false # don't bridge to MQTT
meshtastic --ch-index 1 --ch-set downlink_enabled false
Configuration Backup
Always save configuration before deploying. A failed SD card or corrupted firmware update can lose your settings. The export is YAML, not JSON, and is restored with --configure:
# Export full config to YAML:
meshtastic --export-config > node-config-backup-$(date +%Y%m%d).yaml
# Restore from backup:
meshtastic --configure node-config-backup-20260101.yaml
Device Roles — All 12
Personal Device Roles
Personal roles are designed for individual users who want to send and receive messages. They vary in how aggressively the device relays messages for others.
Important: Your role choice affects everyone on the network. The Meshtastic project's guidance is clear: only assign Router or Repeater roles to nodes with genuinely excellent placement and reliable continuous power. Do not assign infrastructure roles to personal devices for convenience.
Client (Default)
Behavior: Sends and receives messages via the app. Rebroadcasts a packet (managed flood routing) only when no other node has already rebroadcast it after the node has heard it once - it does not pick a single "best-positioned" relay.
Use for: Most personal handhelds and portable nodes. This is the correct default for the majority of users.
Client Mute
Behavior: Fully participates in messaging but never relays packets for other nodes.
Use for: Dense environments (city centers, events, conferences) where the channel is congested and adding another relay would make things worse. Client Mute is underappreciated - in a high-density network, switching personal devices to Client Mute can dramatically reduce channel utilization without losing any personal messaging capability.
Client Hidden
Behavior: Suppresses periodic NodeInfo broadcasts. The node participates in messaging but does not regularly announce its existence, so it is less likely to appear in other users' node lists. (This role is firmware-version dependent.)
Use for: Privacy-conscious users who prefer not to be listed in others' node lists. This is not a covert or anti-detection mode - the node still transmits identifiable plaintext packet headers whenever it sends, so anyone monitoring RF can see the node when it transmits. It reduces directory visibility, not radio detectability, and metadata can still reveal the node.
Choosing the Right Personal Role
| Situation | Recommended Role |
|---|---|
| Default personal use | Client |
| Dense urban area or event with congestion | Client Mute |
| Reduce node-list visibility | Client Hidden |
Infrastructure Roles: Router and Repeater
Infrastructure roles are for fixed, well-placed nodes that genuinely improve mesh coverage for others. They should only be assigned to nodes that meet strict criteria. Misuse of these roles is a common cause of network congestion and poor performance.
Prerequisites for Any Infrastructure Role
- Excellent placement - hilltop, tower, rooftop, or other elevated location with line-of-sight coverage over a wide area
- Reliable continuous power - mains power, solar with battery backup, or other reliable source. An infrastructure node that goes offline unpredictably is worse than no node at all.
- Genuine improvement - the node must provide coverage that no other node provides, not just duplicate existing relay coverage
Router
Behavior: Always rebroadcasts each packet once, using prioritized routing. Visible in the node list and broadcasts its own position. On ESP32 boards the ROUTER role force-enables power-saving sleep and defaults BLE/WiFi/Serial off, but the node still appears in the node list. (Older firmware used an "aggressive/early" rebroadcast timing; current firmware no longer gives ROUTER a privileged fast contention window, so it does not "win" the relay race by speed.)
Use for: Primary hilltop/tower relays with the best possible placement. There should be few Router nodes on a regional network - one or two per coverage area at most.
Do not use for: Personal devices, nodes at ground level, nodes with intermittent power.
Router Late
Behavior: Always rebroadcasts each packet once, but only after all other nodes have had a chance to relay it. If another node has already relayed the packet, Router Late stays silent. If no other relay has forwarded the packet, Router Late steps in as backup coverage. (ROUTER_LATE is only available on newer firmware; on older builds it will not appear in the role list.)
Use for: Secondary or backup relay nodes that should only fill gaps. Useful in areas where primary coverage exists but is occasionally blocked or offline.
Repeater
Behavior: Silent relay. Does not appear in node lists and does not originate NodeInfo, position, or telemetry. Solely forwards packets, rebroadcasting each one once at the same elevated (prioritized) routing priority as Router. Maximally efficient - no overhead from presence or telemetry. Note: the REPEATER role is deprecated as of firmware 2.7.11; for most infrastructure nodes prefer ROUTER (or ROUTER_LATE).
Use for: Infrastructure relays where you want maximum forwarding efficiency with zero network overhead. Ideal for rooftop nodes in a managed deployment where you know the node is working from external monitoring rather than node-list visibility.
Caution: Because a Repeater is invisible in node lists, it can be difficult to diagnose remotely. Use an admin channel for remote configuration if deploying Repeater-role nodes.
Router vs. Repeater: Which to Choose?
| Attribute | Router | Router Late | Repeater |
|---|---|---|---|
| Visible in node list | Yes | Yes | No |
| Broadcasts position | Yes | Yes | No |
| Relay timing | Standard (prioritized routing) | Late (backup) | Standard (prioritized routing) |
| Network overhead | Moderate | Moderate | Minimal |
| Best for | Primary hilltop relay | Backup coverage | High-efficiency fixed relay |
Setting the Role via CLI
meshtastic --set device.role ROUTER
meshtastic --set device.role ROUTER_LATE
meshtastic --set device.role REPEATER
Role values are bare enum names (CLIENT, CLIENT_MUTE, CLIENT_HIDDEN, ROUTER, ROUTER_LATE, REPEATER, TRACKER, SENSOR, ...). There is no _ROLE suffix - ROUTER_ROLE, REPEATER_ROLE, etc. will fail with an invalid-value error. The old ROUTER_CLIENT role was retired in firmware 2.3.15.
Specialized Roles
Specialized roles optimize node behavior for specific use cases: tracking, sensing, and tactical operations. Each role adjusts transmission priority and sleep/retransmit behavior to match its intended function; telemetry cadence is configured separately in the Telemetry module.
Tracker
Behavior: Transmits high-priority location updates. Position packets from Tracker nodes are given higher relay priority, which improves but does not guarantee delivery under congestion.
Use for: Personnel or vehicle tracking where real-time position data is critical. Common in search-and-rescue, outdoor event management, and field team coordination.
Lost and Found
Behavior: Broadcasts its location as a text message to the default channel at regular intervals, allowing a lost asset or person to be located when the broadcast is received.
Sensor
Behavior: Sends sensor readings at defined intervals and deep-sleeps between reports to minimize power consumption. Optimized for battery-powered environmental monitors.
Use for: Temperature/humidity sensors, weather stations, soil moisture monitors, or any application where the node sends data periodically and does not need to be interactive between readings.
Configure the sensor reporting interval in the Telemetry module settings to match your battery budget.
TAK (Android Team Awareness Kit)
Behavior: Optimized for integration with ATAK (Android Team Awareness Kit) tactical software. Formats and prioritizes packets for compatibility with TAK workflows.
Use for: Public safety, military, or emergency management teams using ATAK for situational awareness. Requires ATAK installed on the controlling device.
TAK Tracker
Behavior: High-priority position tracking variant for ATAK. Combines the position prioritization of Tracker with TAK-specific packet formatting.
Use for: Individual team members in a TAK-based deployment where real-time position is critical to the tactical picture.
Roles at a Glance
| Role | Category | Key Behavior |
|---|---|---|
| Client | Personal | Default. Rebroadcasts via managed flooding; visible in the node list. |
| Client Mute | Personal | Never relays. For dense/congested areas. |
| Client Hidden | Personal | Broadcasts only as needed; not shown in the nodes list; for stealth/low-power deployments. |
| Tracker | Specialized | High-priority location updates. |
| Lost and Found | Specialized | Periodic location broadcast to the default channel (Regular power). |
| Sensor | Specialized | Sensor readings + deep sleep between reports. |
| TAK | Tactical | ATAK-optimized messaging. |
| TAK Tracker | Tactical | Priority tactical position for ATAK. |
| Router | Infrastructure | Always rebroadcasts once with prioritized routing; best on elevated/strategic sites; visible in the nodes list. |
| Router Late | Infrastructure | Backup relay; waits for others first. |
| Repeater | Infrastructure | Silent relay; no node list presence. Deprecated as of firmware 2.7.11 - use Router for new infrastructure. |
Channels & Encryption
How Meshtastic Channels Work
Meshtastic uses a channel system for message segmentation and encryption. Each node can have up to 8 channels simultaneously, each with its own name and (optionally) its own encryption key. Channel encryption uses AES-256-CTR keyed by the channel PSK; a channel can also have no key, in which case its traffic is unencrypted. See the PSK reference for key details.
Channel Structure
- Up to 8 channels per node, indexed 0 through 7
- Channel 0 is special - it is the primary channel (only one channel can be primary; channels 1-7 are secondary). Position updates and telemetry are broadcast on channel 0 by default.
- Each channel has:
- A name (displayed in the app)
- A pre-shared key (PSK) - the encryption key for that channel (a channel may also have no key, making it unencrypted)
- Optional uplink/downlink MQTT settings for internet bridging
The Default Public Channel
Out of the box, Meshtastic nodes are configured with:
- Channel name: LongFast (note: "LongFast" is really the name of the default modem preset; the firmware uses it as the default channel name too, but the preset and the channel name are distinct concepts)
- PSK:
AQ==- this is the single byte0x01(base64AQ==), which is firmware shorthand meaning "use the built-in default key." It is not itself the full key; it is an index that selects the publicly-known default key. Because that key is public, the default channel offers no privacy.
Any node using the default LongFast channel can communicate with any other node using the same channel - the encryption provides no privacy since the key is public. This is intentional: it allows strangers to discover and communicate across the mesh.
Channel URL Scheme
https://meshtastic.org/e/#CgUYAyIBAQ==
The hash after # is a base64-encoded channel configuration. To encode or decode channel configurations, use the tool at https://meshtastic.org/e/.
Sharing a channel URL (or its QR code) is the standard way to invite someone to a private channel - they scan or paste the URL and their node is automatically configured with the correct name and PSK. Important: the channel URL/QR contains the PSK in cleartext (base64) - the URL effectively is the key. Anyone who sees it can join (and decrypt) the channel. Share a private channel's URL only over a secure out-of-band path (in person, or an encrypted messenger such as Signal); never post a private channel URL in chat, email, a forum, or anywhere public.
MQTT Uplink and Downlink
Each channel can be individually configured to bridge traffic to/from an MQTT broker:
meshtastic --ch-index 0 --ch-set uplink_enabled true
meshtastic --ch-index 0 --ch-set downlink_enabled true
When uplink is enabled on a channel, packets on that channel are published to the MQTT broker. Downlink delivers MQTT messages back to the mesh. This is the basis of Meshtastic internet bridging and long-distance message delivery via the MQTT network. (Caution: enabling downlink on the default public channel lets anyone on the public broker inject messages into your local RF mesh - only enable downlink on channels you control and intend to bridge.)
Admin Channel
A designated private admin channel allows remote configuration of any node that shares the admin channel's PSK. Note: this shared-PSK admin channel is a legacy method; firmware 2.5+ uses public-key (PKC) remote administration - admin keys based on each node's X25519 public key - instead of a shared admin-channel PSK, and that is the preferred approach on modern firmware.
- Create a channel with a unique name (e.g.,
admin) and a strong, randomly generated PSK - Configure it as the admin channel on all nodes you want to remotely manage
- From any node with the admin channel, you can send configuration commands to remote nodes over the mesh - no physical access required
This is essential for maintaining remote or hard-to-reach infrastructure nodes.
Creating Private Channels
To communicate privately with a group, create a channel with a unique PSK known only to group members. Anyone without the PSK cannot decrypt messages on that channel - unless a gateway on that channel uplinks to MQTT without encryption_enabled, which republishes the traffic in cleartext to the broker. Channels are encrypted with AES-256-CTR when you use a full 32-byte (256-bit) PSK (a 16-byte key gives AES-128).
Via the App
- Open the Meshtastic app and go to Radio Config → Channels
- Select an unused channel slot (index 1 - 7; leave index 0 as the public primary unless you have a specific reason to change it)
- Set a channel name (e.g.,
TeamAlpha) - Tap Generate to create a random PSK, or enter a known PSK manually
- Save the channel
- Share the channel URL or QR code with group members out-of-band (signal, in person, etc.)
Via the CLI
Add a new channel (this creates an empty channel at the next free index; do not pass the name to --ch-add):
meshtastic --ch-add
Name the channel and set its PSK on that index. Use random to have the firmware generate a strong key, or supply your own base64 key string directly (there is no base64: prefix):
meshtastic --ch-index 1 --ch-set name TeamAlpha
meshtastic --ch-index 1 --ch-set psk random
Export the channel URL for sharing:
meshtastic --export-config
The config export includes channel URLs that can be shared with other users.
Security Considerations
- PSK distribution security: The security of a private channel is entirely dependent on how the PSK is distributed. Share it via an end-to-end encrypted channel (Signal, in person) - not via SMS or unencrypted email.
- The default LongFast channel is not private. All Meshtastic users can read it. Never send sensitive information on LongFast.
- MQTT uplink can leak even a private channel. If any gateway node on your private channel uplinks to an MQTT broker without
mqtt.encryption_enabledset, your channel's traffic is republished to the broker in cleartext - so "no PSK = can't read it" only holds for the RF mesh, not for an MQTT-connected mesh. - Channel names are not secret. Only the PSK encrypts message content. The channel name may be visible to other nodes in some circumstances.
- Changing the PSK: If a group member's device is lost or compromised, generate a new PSK and redistribute it to all remaining members. The compromised device will no longer be able to decrypt messages after the PSK change. Note there is no per-user revocation and no forward secrecy - rotating the key protects future traffic, but anyone who captured past ciphertext can still decrypt it with the old key.
Position and Telemetry Privacy
By default, position and telemetry are broadcast on channel 0 (the public primary channel). If you want location data to remain within your private group:
- The simplest, lowest-risk option is to disable position broadcasting entirely: Radio Config → Position → Position Broadcast Interval → 0. This keeps you connected to the public mesh while withholding your location.
- Alternatively, you can make your private channel the primary (index 0). Be aware of the tradeoff: putting a private channel at index 0 replaces the default public LongFast primary, which cuts your node off from the public mesh - you will no longer see or be reachable on the public network. Only do this if isolation from the public mesh is intended.
Telemetry & Monitoring
Built-in Telemetry Types
Meshtastic nodes broadcast telemetry data alongside messages. Understanding what each telemetry type reports helps you configure nodes correctly and interpret network monitoring data. Note that all telemetry below (battery, temperature, position, and the rest) is broadcast on the channel and, on any channel with an MQTT uplink, is republished to the broker - disable telemetry types you do not need on sensitive nodes.
Device Metrics
Broadcast by all nodes automatically. Includes:
- Battery voltage - raw voltage of the battery pack
- Battery percentage - estimated state of charge
- Air utilization TX (
airUtilTx) - rolling percentage of time this node's own radio spent transmitting, averaged over a moving window. This is a smoothed running metric, not an instantaneous reading, and should not be equated directly with a regulatory duty-cycle limit. - Channel utilization - percentage of time the channel was occupied by any transmission heard by this node (includes all other nodes' traffic)
Environment Telemetry (requires I2C sensor)
Available when a supported sensor board is connected via I2C:
- Temperature (°C or °F)
- Humidity (relative humidity %)
- Barometric pressure (hPa)
- Gas resistance - BME68x family (BME680 / BME688); a proxy for air quality / VOC concentration
Supported sensors include BME280, BME68x (BME680/BME688), SHT31, and others. Enable in app: Radio Config → Telemetry → Environment Telemetry.
Position
- GPS coordinates (latitude, longitude)
- Altitude (metres MSL)
- Speed (km/h)
- Heading (degrees true)
Position is broadcast on the primary channel. Disable or reduce frequency for privacy or power savings: Radio Config → Position → Position Broadcast Interval.
Power Metrics
For nodes with external power monitoring hardware:
- Voltage on external rails
- Current draw
Useful for solar-powered nodes where you want to monitor panel output and battery charge current over the mesh.
Configuring Telemetry Intervals via CLI
Set the device telemetry broadcast interval (in seconds). The firmware default is 1800 seconds (30 minutes):
meshtastic --set telemetry.device_update_interval 1800
The default is 1800 s (30 min). Reduce toward 900 (15 min) for more frequent updates - but note that shorter intervals increase channel utilization and airtime on a busy mesh - or increase to 3600 (1 hr) / 7200 (2 hr) or more to reduce channel load on congested networks.
Set environment telemetry interval:
meshtastic --set telemetry.environment_update_interval 1800
Enable/Disable via App
In the Meshtastic app: Radio Config → Telemetry. Each telemetry type (device, environment, power) can be independently enabled or disabled and its interval set.
Monitoring Channel Utilization
Channel utilization is the single most important metric for diagnosing a congested Meshtastic network. High channel utilization causes missed messages, failed relays, and poor network performance.
What Channel Utilization Measures
Channel utilization (reported as a percentage in device metrics telemetry) measures the fraction of time that the radio channel is occupied by any LoRa transmission audible to a given node, averaged over a rolling 1-minute window (the firmware sums on-air milliseconds across six 10-second sub-windows). This includes:
- The node's own transmissions
- Relay transmissions from neighboring nodes
- Position and telemetry broadcasts from all nearby nodes
The 25% Warning Threshold
A commonly used community rule of thumb is to treat 25% channel utilization as a warning threshold — it also matches the firmware's own behavior, which starts delaying transmissions above roughly 25% utilization (the app's green/orange band boundary). Above this level:
- Packet collision probability increases significantly
- Message delivery reliability decreases
- Effective network throughput drops despite higher raw utilization
Diagnosing High Channel Utilization
If channel utilization is above 25%, work through these checks:
- Count Router/Repeater nodes - Too many infrastructure-role nodes in one area creates excessive relay traffic. Audit whether all Router/Repeater nodes are genuinely needed.
- Check telemetry intervals - Frequent device metrics, position, or environment telemetry from many nodes adds up quickly. Increase intervals across the network.
- Identify high-traffic nodes - Look at air utilization (the TX percentage) in device metrics. A node with very high air utilization is generating a disproportionate share of traffic.
- Switch dense-area personal nodes to Client Mute - Each non-Mute client node attempts relay, multiplying traffic.
Switching to a Faster Modem Preset
LoRa modem presets trade range for throughput. A faster preset carries the same data in less airtime, directly reducing channel utilization:
meshtastic --set lora.modem_preset LONG_FAST # default; good range, moderate speed
meshtastic --set lora.modem_preset MEDIUM_FAST # shorter range, faster; less airtime
meshtastic --set lora.modem_preset SHORT_FAST # short range, maximum speed
Switching from LONG_FAST (SF11, ~1.07 kbps) to MEDIUM_FAST (SF9, ~3.52 kbps) increases the data rate roughly 3×, cutting per-packet airtime to roughly a third for the payload portion (less once fixed preamble/header overhead is included), substantially reducing channel utilization. The trade-off is reduced range. Important: changing the modem preset must be coordinated across the entire network — a single node on a different preset goes deaf to the rest of the mesh. Use this when you have good node density and congestion is the problem.
Viewing Channel Utilization
In the Meshtastic app: Node Details → Device Metrics shows the reported channel utilization from each node's perspective. Check several nodes across the network to understand the overall picture - a node at the center of a dense cluster will see higher utilization than one on the fringe.
Via CLI:
meshtastic --info
The output includes current channel utilization and air utilization for the connected node (under deviceMetrics.channelUtilization). Note that channel utilization is a live telemetry metric read via --info, not a config key — meshtastic --get channel_utilization will not return it.
Meshtastic CLI Reference
Meshtastic Python CLI Guide
The Meshtastic Python package provides both a command-line interface and a Python library for scripting. It is the primary tool for configuring nodes without the mobile app, backing up configurations, and automating tasks.
Installation
pip3 install --upgrade "meshtastic[cli]"
Requires Python 3.9 or newer (check the current meshtastic package requirements for the exact minimum). On Linux you may need to add your user to the dialout group to access serial ports without sudo:
sudo usermod -aG dialout $USER
Serial access: connect via USB at 115200 baud. The CLI auto-detects the port in most cases.
Getting Device Information
# Full device info: firmware, hardware, channel config, user info
meshtastic --info
# List all nodes heard by this device
meshtastic --nodes
Configuration Backup and Restore
# Export full configuration to YAML
meshtastic --export-config > config.yaml
# Restore configuration from YAML
meshtastic --configure config.yaml
Back up your configuration before any firmware update. The export includes all channel PSKs, radio settings, and module configuration. Because the exported YAML contains all channel PSKs in recoverable form, treat the backup file as secret — anyone with it can join and decrypt your private channels. Store it encrypted.
Setting Node Identity
# Set the node's display name
meshtastic --set-owner "MyName"
# Set a short name (4 chars, shown on small displays)
meshtastic --set-owner-short "MN01"
Radio Configuration
# Set LoRa region (required before use)
meshtastic --set lora.region US
# Set modem preset
meshtastic --set lora.modem_preset LONG_FAST
# Set TX power. 0 = use the region maximum (recommended); the firmware
# applies the correct per-region/per-board legal cap. 30 dBm (1 W) is the
# FCC Part 15 conducted ceiling for US 915 MHz, referenced to a 6 dBi
# antenna; with a higher-gain antenna you must reduce conducted power so
# EIRP stays compliant. Most hardware cannot reach 30 dBm regardless.
meshtastic --set lora.tx_power 0
# Set hop limit (default 3; max 7)
meshtastic --set lora.hop_limit 3
Device Role
# Set device role (use the bare enum value, with no _ROLE suffix)
meshtastic --set device.role CLIENT
meshtastic --set device.role CLIENT_MUTE
meshtastic --set device.role CLIENT_HIDDEN
meshtastic --set device.role TRACKER
meshtastic --set device.role LOST_AND_FOUND
meshtastic --set device.role SENSOR
meshtastic --set device.role TAK
meshtastic --set device.role TAK_TRACKER
meshtastic --set device.role ROUTER
meshtastic --set device.role ROUTER_LATE
meshtastic --set device.role REPEATER
Most ordinary nodes should be CLIENT. For fixed infrastructure use ROUTER (or ROUTER_LATE); REPEATER is deprecated as of firmware 2.7.11. There is no CLIENT_BASE role.
Channel Management
# Add a new channel (assigns next available index)
meshtastic --ch-add
# Name the channel you just added (e.g. index 1)
meshtastic --ch-index 1 --ch-set name MyPrivateChannel
# Enable MQTT uplink on channel 0
meshtastic --ch-index 0 --ch-set uplink_enabled true
# Enable MQTT downlink on channel 0
meshtastic --ch-index 0 --ch-set downlink_enabled true
# Set a custom PSK on channel 1. Use 'random' to generate one, or supply
# the raw base64 key string directly (no 'base64:' prefix).
meshtastic --ch-index 1 --ch-set psk random
Telemetry
# Set device metrics broadcast interval (seconds; default 1800 = 30 min)
meshtastic --set telemetry.device_update_interval 1800
# Set environment sensor interval (seconds)
meshtastic --set telemetry.environment_update_interval 1800
# Enable environment telemetry
meshtastic --set telemetry.environment_measurement_enabled true
Position
# Set fixed position (for nodes without GPS)
meshtastic --setlat 39.7392 --setlon -104.9903
# Set position broadcast interval (seconds; 0 = disable)
meshtastic --set position.position_broadcast_secs 3600
Sending Messages
# Send a message to all nodes on the primary channel
meshtastic --sendtext "Hello mesh"
# Send on a specific channel (index 1)
meshtastic --ch-index 1 --sendtext "Private message"
Serial Console (Advanced)
The Meshtastic device serial protocol uses length-delimited protobuf frames (ToRadio/FromRadio), not raw JSON commands — writing JSON bytes to the port will not configure the device. The CLI prints decoded packets as JSON to stdout for human reading only. For most users the Python CLI is the right tool; the snippet below is only a low-level illustration of opening the serial port:
import serial, json
ser = serial.Serial('/dev/ttyUSB0', 115200, timeout=1)
# Device control is via protobuf ToRadio frames; see the Meshtastic Python library source for details
Common CLI Patterns
# Full setup sequence for a new infrastructure node
meshtastic --set-owner "Tower-Node-01"
meshtastic --set lora.region US
meshtastic --set lora.modem_preset LONG_FAST
meshtastic --set device.role ROUTER
meshtastic --set lora.hop_limit 3
meshtastic --export-config > tower-node-01-config.yaml
Complete Meshtastic CLI Command Reference
The Meshtastic Python CLI provides the most comprehensive access to node configuration and data. This reference covers all major command categories.
Installation
pip3 install --upgrade "meshtastic[cli]"
# or for the latest pre-release version:
pip3 install --upgrade --pre "meshtastic[cli]"
Connection Options
# Auto-detect serial port (most common):
meshtastic --info
# Specify serial port (Windows):
meshtastic --port COM3 --info
# Specify serial port (Linux/Mac):
meshtastic --port /dev/ttyUSB0 --info
# Connect via TCP (WiFi-enabled nodes):
meshtastic --host 192.168.1.100 --info
# Connect via BLE. First scan to find the real device name/address:
meshtastic --ble-scan
# then connect using the name or address it reports:
meshtastic --ble "Meshtastic_ABCD" --info
Information and Status
# Show device info, config, and channels:
meshtastic --info
# Show node database (all known nodes):
meshtastic --nodes
# View channels: --info lists them; --qr-all shows channel URLs/QR codes:
meshtastic --qr-all
Configuration Get/Set
# Get a specific config value:
meshtastic --get device.role
meshtastic --get lora.hop_limit
meshtastic --get power.is_power_saving
# Set a config value (role values are bare enum names: CLIENT, ROUTER, ROUTER_LATE, CLIENT_MUTE, etc. - no _ROLE suffix):
meshtastic --set device.role ROUTER
meshtastic --set lora.hop_limit 5
meshtastic --set lora.region US
# Set multiple values at once:
meshtastic --set device.role ROUTER --set lora.hop_limit 5 --set power.is_power_saving false
Channel Management
# Show current channels:
meshtastic --info # includes channel list
# Set channel 0 (primary) name and key:
meshtastic --ch-index 0 --ch-set name "CommunityMesh"
meshtastic --ch-index 0 --ch-set psk "base64key=="
# Add a new channel:
meshtastic --ch-add
# Delete channel (specify index):
meshtastic --ch-del --ch-index 2
# Enable/disable MQTT uplink per channel:
meshtastic --ch-index 0 --ch-set uplink_enabled true
meshtastic --ch-index 0 --ch-set downlink_enabled true
Caution: Channel 0 is the default public channel. Enabling downlink_enabled on it lets anyone on the public MQTT broker inject messages into your local mesh; enabling uplink_enabled publishes that channel's traffic publicly. Note also that Meshtastic uploads packets to the broker unencrypted by default even on a channel with a PSK unless mqtt.encryption_enabled is set true. Only enable uplink/downlink on channels you intend to bridge.
Identity
# Set long name:
meshtastic --set-owner "Your Name"
# Set short name (4 chars max):
meshtastic --set-owner-short "AB01"
Position
# Set fixed GPS position (latitude and longitude):
meshtastic --setlat 45.5051 --setlon -122.6750
# Remove fixed position (use live GPS):
meshtastic --remove-position
The CLI sets a fixed position with --setlat and --setlon only; there is no --setalt flag. To set altitude as well, use the Python API (interface.localNode.setFixedPosition(lat, lon, alt)).
Messaging
# Send a text message to primary channel:
meshtastic --sendtext "Hello, mesh!"
# Send to a specific channel index:
meshtastic --ch-index 1 --sendtext "Admin message"
# Listen for incoming messages and packets (Ctrl+C to stop):
meshtastic --listen
Waypoints are created from the mobile/desktop apps. The CLI does not provide a --sendwaypoint / --waypoint-* set of flags as a documented way to send a waypoint.
Maintenance
# Export full config to file (output is YAML):
meshtastic --export-config > config.yaml
# Restore/apply config from a YAML file:
meshtastic --configure config.yaml
# Factory reset (clears all config and node DB):
meshtastic --factory-reset
# Reboot device:
meshtastic --reboot
There is no --import-config flag. The output of --export-config is YAML (not JSON), and it is applied back with --configure.
MQTT & Internet Gateway
Meshtastic MQTT Setup
MQTT lets a Meshtastic node forward all mesh traffic to the internet, making your local mesh visible on the network map, bridging messages to internet clients, and enabling monitoring and logging. This is what feeds online community maps (e.g. via the MapReport packet) that support Meshtastic map reporting.
Warning — read before enabling: By default, Meshtastic publishes packets to the MQTT broker UNENCRYPTED, even on channels that use a PSK, unless you set mqtt.encryption_enabled to true. Enabling MQTT uplink on the default LongFast channel publishes that traffic to the public broker where anyone can read it, because the default AQ== key is publicly known. Node positions are exposed regardless of PSK. Only uplink channels whose content you intend to be public, and never assume a PSK alone protects content sent over MQTT. The downlink/inject risk on public channels is covered below.
How MQTT works in Meshtastic
When MQTT is enabled on a node:
- Every mesh packet received by the node is forwarded to an MQTT broker over WiFi or TCP
- The MQTT broker stores and redistributes the messages to other subscribers
- The public Meshtastic MQTT broker (mqtt.meshtastic.org) shares filtered traffic — zero-hop only, a limited set of portnums, and reduced location precision — which feeds community maps. It is not an unrestricted public feed, but anything it does publish is visible to anyone subscribed.
- Optionally, messages from the internet (MQTT) can be injected back into the local radio mesh
Hardware requirement: Direct MQTT requires a WiFi-capable device (ESP32-based: Heltec V3/V4, T-Beam, etc.). nRF52840 devices (T-Echo, T-Deck, RAK4631) have no onboard WiFi and cannot connect to MQTT over their own network, but they can use the MQTT Client Proxy to relay MQTT through a connected phone running the app (note: JSON output is not supported on nRF52).
Connecting to the public Meshtastic MQTT broker
Via the Meshtastic app
- Go to Settings → Module Config → MQTT
- Enable MQTT: toggle ON
- MQTT Server Address:
mqtt.meshtastic.org - Username:
meshdev - Password:
large4cats - TLS Enabled: toggle ON (recommended)
- Map Reporting Enabled: toggle ON to publish a map report so your node appears on community maps that consume MapReport packets
- Save
Note on the public channel: the default LongFast channel uses the publicly known AQ== key and has no real authentication. If you leave uplink (and especially downlink) enabled on it, anyone on the public broker can see your traffic, and — if downlink is on — inject messages into your local mesh. See the downlink and security notes below before enabling.
Via CLI
meshtastic --set mqtt.enabled true
meshtastic --set mqtt.address mqtt.meshtastic.org
meshtastic --set mqtt.username meshdev
meshtastic --set mqtt.password large4cats
meshtastic --set mqtt.tls_enabled true
meshtastic --set mqtt.map_reporting_enabled true
Map reporting publishes a MapReport packet. Current firmware also exposes related sub-settings (for example position precision and the publish interval) under the mqtt.* namespace; check the field names in your installed firmware version, as they have changed over time.
Channel settings for MQTT
MQTT is enabled per channel. By default, the primary channel (channel 0) is configured to uplink to MQTT. Verify that your channel has Uplink Enabled set to ON:
meshtastic --ch-index 0 --ch-set uplink_enabled true
Important: A PSK on your channel does not mean MQTT uploads are encrypted. By default the gateway decrypts packets and uplinks them unencrypted to the broker, even on channels with a custom PSK. To keep message content encrypted on the broker you MUST set mqtt.encryption_enabled = true. Even with encryption enabled, packet metadata and node positions may still be exposed. With encryption enabled, community maps see only reduced-precision node positions and cannot read message content; the public server further filters location precision.
Downlink: receiving messages from the internet
Downlink allows messages published to MQTT to be injected into the local radio mesh - enabling internet-connected users to send messages that appear on mesh nodes in your area:
meshtastic --ch-index 0 --ch-set downlink_enabled true
Security note: Only enable downlink on channels with PSK authentication if you want to control who can inject messages into your local mesh. The public LongFast channel has no authentication - anyone on the public MQTT broker can inject messages into your mesh if downlink is enabled on the default channel.
Running a private MQTT broker
For a community or organizational network, run your own Mosquitto broker instead of using the public one:
# Install Mosquitto
sudo apt install mosquitto mosquitto-clients
# Basic config: /etc/mosquitto/mosquitto.conf
listener 1883 localhost # Local only (use nginx/TLS for external)
listener 8883 # TLS port for internet clients
cafile /path/to/ca.crt
certfile /path/to/server.crt
keyfile /path/to/server.key
allow_anonymous false
password_file /etc/mosquitto/passwd
Point your Meshtastic nodes to your broker's address instead of mqtt.meshtastic.org.
MQTT topic structure
Meshtastic publishes to topics of the form:
msh/{region}/2/e/{channel_name}/{node_id} # protobuf (encrypted/binary) topic
msh/{region}/2/json/{channel_name}/{node_id} # JSON topic (when JSON output is enabled)
Examples:
msh/US/2/json/LongFast/!abcd1234
msh/US/2/e/LongFast/!abcd1234
Subscribe to msh/US/# to receive all US region traffic. The 4th segment is the channel name; the packet type is not a topic segment. On msh/{region}/2/e/... the payload is a protobuf-encoded ServiceEnvelope; a JSON-encoded payload appears only on msh/{region}/2/json/... when JSON output is enabled, and the packet kind (nodeinfo, position, text, etc.) is carried in the JSON type field inside the payload.
Building a Meshtastic Internet Gateway
A Meshtastic internet gateway bridges local LoRa radio traffic to the internet and can serve as a powerful community infrastructure node. This guide covers setting up a dedicated gateway on a Raspberry Pi.
Gateway hardware options
| Option | Hardware | Pros | Cons |
|---|---|---|---|
| ESP32 node (simplest) | Heltec V3/V4, T-Beam | Single device, no Pi needed, compact | Single-channel, limited processing |
| Pi + LoRa hat | Raspberry Pi 4 + SX126x HAT | Full Linux environment via meshtasticd | More complex setup. Note: RAK2287/RAK5146 are LoRaWAN concentrators; Meshtastic on Linux (meshtasticd) uses a single-radio SX126x HAT, not a multi-channel LoRaWAN concentrator. |
| Pi + USB LoRa node | Raspberry Pi + RAK4631 USB | Easy setup, use standard Meshtastic firmware | Single channel only |
Option 1: Dedicated ESP32 gateway node
The simplest approach: configure a Heltec V3/V4 or T-Beam as a dedicated gateway. This node doesn't need to be a router/repeater - its primary job is bridging radio and MQTT.
- Flash with Meshtastic firmware (standard)
- Connect to your home or community WiFi:
meshtastic --set network.wifi_enabled true --set network.wifi_ssid "YourSSID" --set network.wifi_psk "YourPassword" - Configure MQTT as described in the MQTT Setup page
- Leave the role at the default
CLIENT. Do not set this gateway to ROUTER: on ESP32, ROUTER force-enables power-saving sleep and defaults WiFi/BLE/Serial OFF, which breaks a WiFi MQTT gateway. ROUTER is only for well-sited dedicated infrastructure radios, not for a node whose job is bridging to MQTT. - Mount at a good location with LoRa antenna and reliable WiFi
Privacy warning: A gateway that uplinks the default LongFast channel publishes all traffic it hears - including node positions - to the public internet, and (despite the channel PSK) Meshtastic uploads to MQTT unencrypted by default unless you set mqtt.encryption_enabled true. Use a private channel with encryption enabled, or accept that default-channel traffic is fully public. Set lora.ignore_mqtt true to prevent rebroadcast loops.
Option 2: Raspberry Pi + USB LoRa node
Connect an nRF52840-based device (RAK4631, T-Echo) via USB to a Raspberry Pi. The Pi handles internet connectivity while the LoRa node handles radio.
Setup the LoRa node
# Flash with Meshtastic firmware
# Leave the role at the default CLIENT for a USB-attached gateway radio.
# Do NOT set ROUTER, which disables serial and forces sleep on ESP32.
Install meshtasticd (Meshtastic daemon)
The Python CLI installs from PyPI, but meshtasticd is not on PyPI - install it from the official Meshtastic apt repository:
# Python CLI (PyPI)
pip3 install meshtastic
# meshtasticd daemon (apt repo, not pip)
sudo add-apt-repository ppa:meshtastic/beta
sudo apt update
sudo apt install meshtasticd
Configure MQTT bridging via Python
import meshtastic
import meshtastic.serial_interface
from pubsub import pub
import paho.mqtt.client as mqtt
import json
# Connect to the LoRa node
iface = meshtastic.serial_interface.SerialInterface("/dev/ttyUSB0")
# Connect to MQTT broker
mq = mqtt.Client()
mq.username_pw_set("meshdev", "large4cats")
mq.tls_set()
mq.connect("mqtt.meshtastic.org", 8883)
# Forward all received packets to MQTT.
# Topic format: msh/REGION/2/json/CHANNELNAME/USERID
# (version "2" comes before the channel name; packet type is a field inside the JSON, not a topic segment)
def on_receive(packet, interface):
userid = packet.get('fromId', '?')
topic = f"msh/US/2/json/LongFast/{userid}"
mq.publish(topic, json.dumps(packet))
# Packets are delivered via PyPubSub, not an iface.on_receive attribute
pub.subscribe(on_receive, "meshtastic.receive")
mq.loop_forever()
Note: meshdev / large4cats is the shared public broker, which is rate-limited, filtered, and not an open firehose. The firmware's built-in MQTT uplink is the supported, recommended path; this raw-publish script is illustrative only.
Monitoring your gateway
A healthy gateway should be publishing to MQTT continuously. Monitor with:
# Subscribe to your node's traffic (replace !abcd1234 with your node ID)
mosquitto_sub -h mqtt.meshtastic.org -p 8883 -t "msh/US/#" -u meshdev -P large4cats --tls-use-os-certs | grep abcd1234
You should see JSON packets appearing whenever your node hears a packet on the mesh. If the stream is silent for more than a few minutes in an active network, your WiFi or MQTT connection may have dropped.
Adding your gateway to the community map
To appear on third-party maps, your gateway needs MQTT uplink working plus map reporting enabled in the MQTT module config (mapReportingEnabled, firmware ≥2.3.2) and a fixed or GPS position set. Map reports publish at most about hourly, so allow up to an hour or more before your node shows up. Appearance on meshmap.net depends on that third-party site and is not instantaneous or guaranteed; verify by searching for your node name.
MQTT Topic Structure and Packet Format
Understanding Meshtastic's MQTT topic structure and packet encoding lets you build integrations, parse data, and troubleshoot gateway connectivity issues.
Topic Structure
Meshtastic publishes to MQTT topics using this hierarchy:
msh/{region}/2/{e|json}/{channel_name}/{user_id}
Examples:
msh/US/2/e/LongFast/!ab12cd34 # Encrypted packet, published by gateway !ab12cd34
msh/US/2/json/LongFast/!ab12cd34 # JSON-decoded packet (only if JSON enabled on the gateway)
Where:
msh/ - Meshtastic prefix
US/ - Region code (US, EU, etc.)
2/ - Protocol version
e/ - Encrypted protobuf (default)
json/ - JSON decoded (optional, only if JSON output is enabled on the gateway)
LongFast/ - Channel name (this segment is always present)
!ab12cd34 - User/gateway node ID (the node that published this packet to MQTT, not necessarily the originator)
The 4th segment is e (encrypted protobuf) or json; the 5th segment is the channel name; the last segment is the publishing (gateway) node's hex ID. The packet's type (text, telemetry, position, etc.) is a field inside the payload, not a topic segment - there are no .../json/text/ or .../json/telemetry/ topics.
Subscribing to All Traffic
# Subscribe to all Meshtastic packets from all US nodes:
mosquitto_sub -h mqtt.meshtastic.org -u meshdev -P large4cats -t "msh/US/2/e/#" -v
# Subscribe to a specific channel only:
mosquitto_sub -h mqtt.meshtastic.org -t "msh/US/2/e/CommunityMesh/#" -v
# Subscribe to JSON-decoded packets (if gateway has JSON enabled):
mosquitto_sub -h localhost -t "msh/US/2/json/#" -v
Packet Format
Each MQTT message payload is a protobuf-encoded ServiceEnvelope (defined in meshtastic.protobuf.mqtt_pb2) - the wrapper a gateway publishes - containing:
- packet - The MeshPacket (encrypted payload + routing info)
- channel_id - Channel name (e.g., "LongFast")
- gateway_id - Node ID of the gateway that published to MQTT
Enabling JSON Output on a Gateway Node
Security warning: JSON output publishes fully decoded (plaintext) packet content to MQTT, bypassing channel encryption entirely. The gateway can only emit JSON for packets it can decrypt (channels whose key it holds). Never enable json_enabled on a gateway that uplinks to a public or shared broker if any channel content is sensitive - anyone subscribed to that broker will read the cleartext.
# Enable JSON output (in addition to protobuf) on the GATEWAY node that publishes to MQTT.
# Only packets the gateway can decode (known channel key) are emitted as JSON:
meshtastic --set mqtt.json_enabled true
# JSON packets are published to: msh/REGION/2/json/CHANNELNAME/USERID
# CHANNELNAME = the channel's name (e.g. LongFast)
# USERID = the gateway node's hex ID (e.g. !7efeee00)
# JSON payload example:
{
"from": 2881537332,
"to": 4294967295,
"channel": 0,
"type": "text",
"id": 123456789,
"rx_time": 1712000000,
"hop_limit": 3,
"payload": {
"text": "Hello mesh!"
}
}
Decoding Protobuf Packets in Python
import paho.mqtt.client as mqtt
from meshtastic.protobuf.mqtt_pb2 import ServiceEnvelope
from meshtastic.protobuf.portnums_pb2 import PortNum
import base64
def on_message(client, userdata, msg):
try:
se = ServiceEnvelope()
se.ParseFromString(msg.payload)
packet = se.packet
print(f"From: !{packet.from_:08x}")
print(f"To: !{packet.to:08x}")
print(f"Channel: {se.channel_id}")
# Decrypt and decode based on portnum for full payload
except Exception as e:
print(f"Parse error: {e}")
client = mqtt.Client()
client.username_pw_set("meshdev", "large4cats")
client.on_message = on_message
client.connect("mqtt.meshtastic.org", 1883)
client.subscribe("msh/US/2/e/#")
client.loop_forever()
Preventing MQTT Message Loops
A common misconfiguration in networks with MQTT gateways is the "MQTT loop" - packets sent over LoRa get forwarded to MQTT, which then re-injects them back into the LoRa network, causing each message to be transmitted multiple times and rapidly increasing channel utilization.
How MQTT Loops Happen
- Node A sends a message over LoRa
- Gateway node G receives the LoRa packet and publishes it to MQTT
- MQTT broker delivers the packet to Gateway G's downlink subscription
- Gateway G injects the packet back into the LoRa network
- Node A receives its own message again as if from the MQTT cloud
- This can loop indefinitely if not properly configured
Prevention: The ignore_mqtt Setting
A key anti-loop control is the lora.ignore_mqtt flag. When set to true, the device will ignore (and not re-broadcast) any messages it receives over LoRa that came via MQTT somewhere along the path toward the device. Note this only works when both your device and the MQTT node are running at least firmware version 2.2.19:
# Enable on ALL infrastructure nodes (routers, repeaters):
meshtastic --set lora.ignore_mqtt true
# Verify:
meshtastic --get lora.ignore_mqtt
Critical: Set this on every infrastructure/relaying node (ROUTER, ROUTER_LATE, and REPEATER roles). Leave it as false only on end-client nodes that may need to receive messages downlinked from MQTT.
Gateway Configuration Best Practices
# Configure the gateway node correctly:
# 1. Enable MQTT publishing (uplink):
meshtastic --ch-index 0 --ch-set uplink_enabled true
# 2. Enable MQTT subscribing (downlink) ONLY if needed:
# Only enable downlink if you want to receive messages sent to MQTT
# from the global mesh or other external systems.
# If you only want to publish (monitoring), keep downlink disabled:
meshtastic --ch-index 0 --ch-set downlink_enabled false
# 3. Enable ignore_mqtt on the gateway node itself:
meshtastic --set lora.ignore_mqtt true
Detecting a Loop in Progress
Signs that a loop is occurring:
- Channel utilization (CU) rapidly increasing after enabling MQTT
- Messages appearing in the chat multiple times
- The same packet ID appearing in MQTT subscription twice or more within a few seconds
- Nodes reporting high packet counts in stats
# Check channel utilization. There is no --get for this; it is a live
# telemetry metric, read it from --info or the app's node metrics.
# Aim to keep it under ~25% (the firmware begins delaying transmissions above that):
meshtastic --info
# Monitor for duplicate packet IDs via MQTT:
mosquitto_sub -t "msh/US/2/e/#" -v | grep -E "packet.id"
# Watch for repeating IDs within 30 seconds
Advanced Features
Meshtastic Web Interface and Remote Access
Many Meshtastic users don't realize a full web-based interface is available in addition to the mobile app. The web interface provides additional tools for configuration and administration that aren't available in the app.
The Meshtastic web app (client.meshtastic.org)
The official Meshtastic web application is hosted at client.meshtastic.org and runs in any modern browser (Chrome or Edge recommended for WebSerial support), connecting to your device via USB serial or Bluetooth.
Connecting via USB serial
- Connect your Meshtastic device via USB cable
- Open client.meshtastic.org in Chrome or Edge
- Click "Connect" and select "Serial"
- Choose your device from the port list
- The device configuration loads automatically
Note: WebSerial requires Chrome or Edge. Firefox and Safari do not support WebSerial. If the device doesn't appear in the port list, install the appropriate driver (CH340 for Heltec, standard CDC for nRF52-based devices).
Connecting via Bluetooth
- Open client.meshtastic.org in a Chromium-based browser
- Click "Connect" and select "Bluetooth"
- Pair with your device - the device should be in BLE advertising mode (typically shown by LED blink pattern)
What the web app offers beyond the mobile app
| Feature | Mobile app | Web app |
|---|---|---|
| Node configuration | Yes | Yes (more detailed) |
| Channel management | Yes | Yes |
| Message view | Yes | Yes |
| Node map | Yes | Yes |
| Configuration export/import (YAML) | Channel/config sharing via URL/QR; recent apps also support config backup | Yes |
| Raw packet log | Limited | Full packet log |
| Serial debug console | No | Yes |
| Firmware update (OTA) | Yes (BLE OTA on supported devices) | Browser flashing via USB (Web Flasher); verify current per-platform support |
| Full module config access | Partial | Yes |
Configuration backup and restore
The web app's configuration export feature is an easy way to back up and restore a Meshtastic node's settings (the exported file is YAML; the CLI uses the same format):
- Connect to device via web app
- Navigate to Config → Export Config
- Save the YAML file to your computer
To restore: connect the device (after a reflash or factory reset), navigate to Config → Import Config, and upload the saved YAML file. This restores all radio settings, channel configurations, and module settings in one step. The equivalent CLI workflow is meshtastic --export-config > config.yaml to back up and meshtastic --configure config.yaml to restore (there is no --import-config flag).
Remote node management via WiFi
ESP32-based Meshtastic nodes (Heltec V3/V4, T-Beam, etc.) can be connected to a local WiFi network so they can be reached over the network:
meshtastic --set network.wifi_ssid "YourNetworkSSID"
meshtastic --set network.wifi_psk "YourPassword"
meshtastic --set network.wifi_enabled true
Once connected to WiFi, the device's local IP address is shown in the app or via meshtastic --info. That IP is used by the Meshtastic web client and API (TCP port 4403) rather than a self-hosted admin webpage; connect to it from client.meshtastic.org (choose the network/TCP connection option) to configure and monitor the node without USB or Bluetooth.
Use case: A deployed rooftop repeater with WiFi access can be configured and monitored remotely from any device on the same network, eliminating the need for physical access for routine configuration changes.
Security warning: The node's network interface has no built-in authentication. Anyone who can reach the node's IP on the network can connect to it, reconfigure it, change channels, or read its configuration (including channel PSKs via export). Only expose a node on a trusted, isolated network - never on open or shared WiFi. For untrusted environments, enable managed mode (security.is_managed) and use PKC admin keys for remote administration.
Serial debug console
The web app's serial console shows raw debug output from the device firmware. This is invaluable for diagnosing unusual behavior:
- View all received packets with raw headers and signal data
- See routing decisions in real time
- Monitor power-related messages (battery voltage, charge state)
- Debug GPS acquisition
The serial console is accessed via Tools → Serial Console in the web app. Output can be copied and shared when reporting bugs.
Meshtastic Module Configuration Reference
Meshtastic's module configuration system enables advanced features beyond basic mesh communication. This page covers the most commonly needed modules and their key settings.
Range Test Module
Automates systematic coverage testing by sending timed packets that include position data.
# Enable and configure via CLI
meshtastic --set range_test.enabled true
meshtastic --set range_test.sender 60 # Send every 60 seconds
meshtastic --set range_test.save true # Save results to rangetest.csv on the device's internal flash (ESP32 only)
The receiving node logs packets with GPS coordinates and signal data to its internal flash as rangetest.csv (ESP32 boards only; most boards have no SD card, so range_test.save has no effect on them). Download the file from the node's web interface at meshtastic.local/rangetest.csv, then import it into Google My Maps, uMap (OpenStreetMap), QGIS, or Google Earth to visualize coverage as a heatmap. Note: a range-test sender continuously floods airtime, so run it only briefly and on an isolated test channel to avoid disrupting your live mesh.
Telemetry Module
Controls how often nodes broadcast battery, environment, and air quality data.
meshtastic --set telemetry.device_update_interval 1800 # Battery/voltage every 30 min
meshtastic --set telemetry.environment_update_interval 900 # Temperature/humidity every 15 min
meshtastic --set telemetry.air_quality_enabled true # Enable if a particulate sensor (e.g. PMSA003I) is attached
Telemetry data is visible in the app's node detail view. Excessive telemetry frequency contributes to network congestion in dense deployments - don't set intervals below 5 minutes for infrastructure nodes. Note: the Air Quality module requires a supported particulate sensor (e.g. PMSA003I). A BME680/BME68x reports gas resistance under Environment Telemetry, not the Air Quality module.
Position Module
Controls how and when GPS position is broadcast.
meshtastic --set position.position_broadcast_secs 1800 # Broadcast every 30 min
meshtastic --set position.broadcast_smart_minimum_distance 100 # Smart broadcast if moved 100m
meshtastic --set position.gps_update_interval 120 # Update GPS position every 2 min
meshtastic --set position.gps_mode ENABLED # DISABLED to save power on static nodes
For static repeaters: Disable GPS or set a fixed position instead of active GPS polling to save power:
meshtastic --set position.gps_mode DISABLED
meshtastic --setlat 47.6062 --setlon -122.3321 --setalt 150
External Notification Module
Triggers a buzzer, LED, or vibration motor on message receipt. Useful for personal portable nodes where you can't always watch the screen.
meshtastic --set external_notification.enabled true
meshtastic --set external_notification.alert_message true
meshtastic --set external_notification.alert_bell true # Triggers buzzer
meshtastic --set external_notification.output_ms 1000 # Buzzer on for 1 second
Store and Forward Module
Enables a node to store messages and deliver them to nodes that were offline when the message was sent. Requires an ESP32 device with onboard PSRAM (e.g. T-Beam v1.0+, T3S3). Records are held in volatile PSRAM (lost on reboot or power loss), not flash. nRF52 boards (RAK4631, T-Echo) cannot act as a Store and Forward server at all.
meshtastic --set store_forward.enabled true
meshtastic --set store_forward.records 0 # Max records to store; default 0 auto-sizes to ~2/3 of PSRAM (~11,000 records). 100 here is just an example
meshtastic --set store_forward.history_return_max 25 # Return up to 25 historical messages on request
Note: Store and Forward is resource-intensive and delivery is best-effort and request-driven, not guaranteed. Enable it only on an ESP32 device with onboard PSRAM; nRF52 devices cannot run a Store and Forward server. This feature provides some of the functionality that MeshCore's room servers offer natively.
Serial Module
Bridges mesh messages to/from a connected serial device (microcontroller, computer, sensors). Enables custom IoT integrations without modifying firmware.
meshtastic --set serial.enabled true
meshtastic --set serial.baud BAUD_115200
meshtastic --set serial.timeout 0 # No timeout
meshtastic --set serial.mode TEXTMSG # Treat serial data as text messages
Use case: Connect an Arduino to a Meshtastic node via serial. The Arduino reads sensor data and sends it as a serial string, which the Meshtastic node broadcasts as a mesh message.
Canned Messages Module
Pre-loads a set of frequently used messages that can be selected and sent without typing. Especially useful on devices without keyboards (T-Echo, T1000-E).
meshtastic --set canned_message.enabled true
meshtastic --set-canned-message "OK, copy|Need assistance|ETA 15 minutes|At base|En route"
Messages are separated by pipe characters. On devices with buttons, scroll through the list and press to send. On the T-Echo, use the rotary encoder to navigate and click to send.
Module configuration best practices
- Don't enable everything: Each active module adds to channel traffic. Enable only what you actually use.
- Set conservative intervals: Telemetry and position intervals of 15 - 30 minutes are appropriate for most deployments. Shorter intervals increase traffic without proportional benefit.
- Test after configuration: After enabling any module, verify it behaves as expected before relying on it operationally.
- Back up your config: After a working configuration, export it via the web app so you can restore it after a firmware update.
Traceroute and Path Diagnostics
What Is Meshtastic Traceroute?
Traceroute is a diagnostic feature built into Meshtastic firmware that lets you discover the path a packet takes through the mesh to reach a destination node. Unlike a simple ping, traceroute collects the ID of each intermediate node that relayed the packet, along with signal quality data for each hop. Treat the result as one probe's path, not a definitive map: like normal traffic, a traceroute probe's route can differ from where your real messages flow, so it shows how this particular packet was routed rather than a guaranteed picture of all traffic.
How It Works
When you initiate a traceroute, your node sends a special packet addressed to the destination node ID. Each relaying node that knows the channel's encryption key appends its own node ID and the SNR at which it received the packet to the route record (this is not limited to nodes configured with a ROUTER role - any rebroadcasting node that shares the channel key is recorded). From firmware version ≥ 2.5, the route back to the origin is also recorded, with the SNR for each link. When the destination receives the packet, it sends the complete hop list back to you. The result is an ordered list: source → relay 1 → relay 2 → ... → destination.
Traceroute uses Meshtastic's managed flood-routing mechanism (see the mesh-algorithm docs), the same mechanism as normal traffic, but the route-record payload (the RouteDiscovery protobuf) grows with each hop. This means traceroute is more bandwidth-intensive than a regular message and should be used sparingly on busy networks.
Running Traceroute from the App
In the Meshtastic Android or iOS app:
- Open the Node List and tap the target node.
- Tap the three-dot menu or the node detail view.
- Select Traceroute.
- Wait for the result - it may take 10 - 30 seconds depending on hop count and channel utilization.
The result displays each hop's node ID and the SNR value observed at that hop.
Running Traceroute from the CLI
meshtastic --traceroute '!a1b2c3d4'
Replace !a1b2c3d4 with the destination's node ID (the exclamation-mark prefix is required). Single-quote the node ID as shown - in bash the bare ! triggers history expansion and you will get an "event not found" error otherwise. The CLI prints each hop as it is received:
Traceroute to !a1b2c3d4:
You (!ff00ee11) --> !11223344 (SNR: 8.25) --> !55667788 (SNR: 4.50) --> !a1b2c3d4 (destination)
Interpreting the Output
- Hop count: fewer hops generally means lower latency and higher reliability.
- SNR values: higher is better, but there is no single fixed "weak link" cutoff - the usable SNR floor is spreading-factor-dependent. LoRa can demodulate well below 0 dB: roughly -7.5 dB at SF7 down to about -20 dB at SF12. On the default LongFast preset (SF11) the decode floor is around -17 dB, so a hop at -10 dB on LongFast is still usable with margin. A link is weak only when its SNR is within a few dB of the preset's floor (on LongFast, worse than roughly -14 dB is marginal). See a Semtech LoRa SNR demodulation table for the per-SF limits.
- Missing hops: if the traceroute times out without a complete result, the destination may be unreachable or the return path failed.
Using Traceroute to Diagnose Coverage Gaps
Run traceroute from multiple points in your network to map which nodes are serving as critical relays. If a single node appears in most traceroute paths, it is a single point of failure - consider adding a redundant node nearby. An unexpectedly long hop count (e.g. 5 hops when you expect 2) suggests a shorter path exists but is not being used, possibly due to SNR thresholds or a misconfigured router role.
Comparing Two Runs to Detect Path Changes
Run traceroute to the same destination before and after a node failure or configuration change. A change in the hop list confirms that traffic is now routing differently. This is especially useful after repositioning a relay node or changing antenna - it lets you verify the improvement objectively rather than relying on signal reports alone. Bear in mind each run reflects one probe's path, so compare several runs rather than reading a single result as definitive.
Neighbor Info and Signal Mapping
What Is the Neighbor Info Module?
The Neighbor Info module is a built-in Meshtastic feature that periodically broadcasts a summary of every node your device can hear directly, along with the signal-to-noise ratio (SNR) of each link. This data lets you build an objective picture of your mesh's link quality without relying on anecdotal reports.
What Neighbor Info Reports
Each Neighbor Info packet contains:
- Node ID of each directly-heard neighbor
- SNR (signal-to-noise ratio in dB) - how cleanly the signal was received
- The timestamp of the last reception from that neighbor
Note: Neighbor Info reports SNR only per neighbor - the NeighborInfo payload does not carry a per-neighbor RSSI value. RSSI is available locally for each packet your own node receives, but it is not part of the broadcast neighbor report.
Reports are sent at a configurable interval (default 21600 seconds / 6 hours). The firmware enforces a minimum of 14400 seconds / 4 hours - values below this are rejected. The data is broadcast on the mesh and visible to any node that receives it.
Privacy note: enabling Neighbor Info publicly discloses which nodes your node can hear - an RF "social graph" of who-can-hear-whom. On any channel that is uplinked to MQTT, that graph is published to the internet (and can appear on third-party maps). For sensitive or EmComm deployments, consider leaving Neighbor Info disabled on nodes whose neighbor relationships are operationally sensitive.
Enabling Neighbor Info
- Open the Meshtastic app and connect to your node.
- Go to Radio Configuration → Modules → Neighbor Info.
- Toggle Enabled to on.
- Optionally adjust the Update Interval (in seconds; minimum 14400 / 4 hours). Shorter intervals give fresher data but increase channel utilization.
- Save and reboot the node.
Via CLI: meshtastic --set neighbor_info.enabled true --set neighbor_info.update_interval 21600 (any value >= 14400; lower values are rejected by firmware).
Reading the Data
In the Meshtastic app, go to Node List → [select a node] → Neighbor Info to see that node's reported neighbors. In the Python CLI, listen for neighbor info packets:
meshtastic --listen
# Look for portnum: NEIGHBORINFO_APP packets in the JSON output
The JSON payload includes a neighbors array, with each neighbor carrying its node ID and snr. (Confirm exact serialized field names against a live NEIGHBORINFO_APP JSON sample or the neighbor_info.proto before relying on them in a parser.)
Building a Link Quality Map
Collect neighbor info data from all nodes over a period of time to build a directional link quality graph. Each directed edge in the graph represents one node hearing another, with the SNR as the edge weight. Nodes with low average SNR to all their neighbors are candidates for antenna upgrades or repositioning.
Tools like Meshview or a custom Python script consuming the MQTT JSON feed can automatically visualize this as a network graph, coloring links by quality. Useful SNR thresholds are preset-dependent, because LoRa's decode floor varies with spreading factor (roughly -7.5 dB at SF7 up to about -20 dB at SF12; ~-17 dB on the default LongFast/SF11 preset). As a suggested visualization convention for the default LongFast preset (not an official spec): green > 0 dB, yellow -10 to 0 dB, and red only when within a few dB of the ~-17 dB decode floor.
Exporting to CSV for Analysis
Using a simple Python MQTT subscriber. Note that JSON packets are published to msh/REGION/2/json/CHANNELNAME/USERID - segmented by channel name and gateway user ID, not by packet type. Subscribe to the JSON tree (or a specific channel) and filter on the JSON type field instead of using a type-named topic:
import paho.mqtt.client as mqtt, json, csv, time
rows = []
def on_message(client, userdata, msg):
d = json.loads(msg.payload)
if d.get("type") == "neighborinfo":
for n in d["payload"].get("neighbors", []):
rows.append({
"reporter": hex(d["from"]),
"neighbor": hex(n["node_id"]),
"snr": n["snr"],
"time": time.strftime("%Y-%m-%d %H:%M:%S")
})
client = mqtt.Client()
client.on_message = on_message
client.connect("localhost", 1883)
client.subscribe("msh/US/2/json/#") # all channels; or msh/US/2/json/LongFast/# for one
client.loop_forever()
Write rows to a CSV and import into a spreadsheet to sort and filter weak links.
Identifying Weak Backbone Links
Sort the exported CSV by SNR ascending. Treat a link as fragile when its SNR is within a few dB of the decode floor for your active modem preset - not at a fixed cutoff. On the default LongFast preset (SF11, floor ~-17 dB) a -5 dB link still has roughly 12 dB of margin and is healthy; the fragile links there are the ones worse than about -14 dB. A -5 dB cutoff is only appropriate near SF7 (floor ~-7.5 dB). For genuinely weak links, consider: raising antenna height, switching to a high-gain directional antenna, or adding an intermediate relay node to split the hop into two shorter, stronger links.
Integrations & Automation
Bridging Meshtastic with APRS, external services, and scripting automation via the Python library.
Meshtastic APRS Gateway
What is APRS?
APRS - Automatic Packet Reporting System - is an amateur radio protocol designed for real-time tactical digital communications. Originally developed by Bob Bruninga (WB4APR), it carries position data, weather reports, text messages, and telemetry over RF. Reports are aggregated on publicly accessible maps such as aprs.fi, making it a cornerstone of ham radio situational awareness worldwide.
Why Integrate Meshtastic with APRS?
Meshtastic nodes equipped with GPS can be bridged into APRS-IS (the internet backbone of APRS), causing them to appear on aprs.fi maps. This provides several advantages:
- Mesh position data becomes visible to the broader amateur radio community without requiring a dedicated APRS radio.
- Enables interoperability with Winlink gateway operators who already monitor APRS.
- During emergency activations, mesh nodes show up alongside APRS-equipped vehicles and portable stations on the same common operating picture.
License Requirement
Transmitting on APRS RF (144.390 MHz) legally requires an amateur radio license - Technician class or higher in the United States, under FCC Part 97. An internet-only APRS-IS feed (the software bridge described here) does not transmit on RF, so it is not license-gated by FCC law. However, APRS-IS network policy still requires you to connect with a valid amateur callsign and a callsign-derived passcode; the passcode system exists to keep APRS-IS restricted to amateur radio use. Receive-only IS access does not require a license, but to inject your nodes' positions you connect with your callsign. Check your national regulations.
aprstastic
The open-source aprstastic project (GitHub: afourney/aprstastic) is a bidirectional Meshtastic-to-APRS gateway that bridges Meshtastic output to APRS-IS. It runs on any Linux system with internet access - a Raspberry Pi is the most common deployment platform. Verify the current repository and its setup instructions before deploying.
Core requirements:
- Python 3.8+
- An MQTT broker already receiving packets from your Meshtastic node(s)
- An APRS-IS account (callsign + APRS-IS passcode - generated from your callsign)
Setup Overview
- Install the gateway and its Python dependencies (follow the project's own README; for a manual stack:
pip install meshtastic paho-mqtt aprslib). - Clone the gateway repository and copy the example config file.
- Edit the config: enter your callsign, APRS-IS passcode, APRS-IS server (e.g.
rotate.aprs2.net:14580), and MQTT broker address. - Test manually using the command documented by the gateway project.
- Install as a systemd service for automatic start on boot:
/etc/systemd/system/aprstastic.service
SetRestart=on-failureandRestartSec=10to recover from transient network drops.
Packet Flow
Meshtastic node ↓ (LoRa RF) Mesh network ↓ (LoRa RF) MQTT gateway node (USB-connected Pi) ↓ (TCP, localhost) Mosquitto MQTT broker ↓ (TCP, internet) aprstastic gateway ↓ (TCP, APRS-IS protocol) APRS-IS network ↓ aprs.fi (and other APRS clients)
Position Filtering and Consent
The gateway config allows you to whitelist specific node IDs or callsigns for forwarding. Only forward nodes whose operators have consented. Do not expose the positions of private nodes, shelters, or sensitive installations to the public APRS map without explicit permission from the node owner.
Best practice: maintain a config-controlled allowlist and review it whenever new nodes join your mesh. Note that allowlisting at the APRS gateway only limits APRS exposure - if the source mesh already uplinks to the public MQTT broker, those positions are already public regardless of the APRS allowlist.
Bidirectional Messaging
Some implementations support two-way message bridging - APRS messages addressed to your gateway callsign arrive as Meshtastic messages, and mesh messages can be relayed outbound as APRS messages. This is useful during ARES/RACES exercises where some operators have APRS radios and others have Meshtastic nodes.
Part 97 compliance warning: if you relay Meshtastic content outbound onto APRS/amateur RF, FCC Part 97 applies. Encrypted or meaning-obscured traffic is prohibited (§97.113(a)(4)) - so default-encrypted Meshtastic channels cannot lawfully be bridged onto amateur frequencies - and the station must transmit its callsign identification at least every 10 minutes (§97.119). An anonymous or encrypted mesh payload cannot lawfully be retransmitted as amateur traffic.
Implementation complexity is higher than position-only bridging; test thoroughly before deploying in an operational scenario.
Frequency Note
APRS in North America operates on 144.390 MHz (a 2-meter FM frequency). This is entirely separate from LoRa mesh frequencies (915 MHz in North America). The APRS gateway described here is a software bridge over IP - it does not require your Pi to transmit on 144.390 MHz. The RF-to-internet connection is handled by existing APRS-IS infrastructure.
If you want true RF APRS transmission (e.g. for locations without internet), you need a licensed APRS radio and a TNC - that is a separate project beyond this software gateway.
Meshtastic Python Scripting
The meshtastic Python Library
The official meshtastic Python library provides programmatic access to any Meshtastic device. It exposes the full device API: sending and receiving messages, reading/writing configuration, querying node lists, requesting telemetry, and subscribing to real-time events.
Install:
pip install meshtastic
Official documentation: python.meshtastic.org
Source and examples: github.com/meshtastic/python
Connection Types
| Interface class | Transport | Typical use |
|---|---|---|
meshtastic.SerialInterface |
USB serial (CDC ACM) | Direct connection, most reliable; default port auto-detected or specify /dev/ttyUSB0 |
meshtastic.TCPInterface |
TCP/IP over WiFi | Wireless connection to nodes with WiFi enabled (ESP32 devices); specify host IP (default port 4403) |
meshtastic.BLEInterface |
Bluetooth Low Energy | Short-range wireless; requires BlueZ on Linux or appropriate BLE stack |
Basic Usage Examples
The snippets below assume you have already created an iface object as shown in the first example. When copying a single snippet, include that connection setup line first.
Connect and print node info
import meshtastic import meshtastic.serial_interface iface = meshtastic.serial_interface.SerialInterface() print(iface.nodes) # dict of all known nodes iface.close()
Send a message to a channel
iface.sendText("Hello mesh", channelIndex=0)
# channelIndex=0 is the primary channel; 1 - 7 are secondary channels
Subscribe to received messages
import meshtastic
import meshtastic.serial_interface
from pubsub import pub
def on_receive(packet, interface):
print(f"Received: {packet}")
iface = meshtastic.serial_interface.SerialInterface()
pub.subscribe(on_receive, "meshtastic.receive")
# Keep the script alive while the interface thread processes events
import time
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
iface.close()
Get node list with last-heard timestamps
for node_id, node in iface.nodes.items():
last_heard = node.get("lastHeard", "unknown")
name = node.get("user", {}).get("longName", node_id)
print(f"{name}: last heard {last_heard}")
Read and write device config
local = iface.getNode('^local')
# Read
print(local.localConfig.lora.hop_limit)
# Write
local.localConfig.lora.hop_limit = 5
local.writeConfig("lora")
Note: getNode('^local') returns the local Node object. localConfig, writeConfig(), writeChannel(), getMyNodeNum() and removeNode() are methods/attributes of the Node object (reached via getNode(...)), not of the base interface.
Request position from local node
iface.getNode('^local').requestPosition()
To set a fixed position programmatically (including altitude), use iface.localNode.setFixedPosition(lat, lon, alt).
Automation Use Cases
- Automated mesh announcements - scheduled weather, news, or system status broadcasts on a channel.
- Message bridges - relay messages between the mesh and Telegram, Discord, Matrix, or SMS (via Twilio). Bidirectional bridging is straightforward with the pub/sub event model.
- Telemetry logging - write incoming telemetry packets to CSV, SQLite, or InfluxDB for Grafana dashboards.
- Position mapping - forward GPS positions to a self-hosted map (e.g. Traccar, OwnTracks, or a custom Leaflet map).
- Alert systems - trigger SMS or email alerts when specific nodes go offline (last-heard threshold exceeded).
- Config management - script bulk configuration changes across many nodes connected via TCP.
Privacy note: the bridging and position-mapping patterns export mesh content and node positions to external services. Make sure that exposure is acceptable for your deployment before forwarding messages or locations off the RF mesh.
Common Patterns
# Connect (serial, auto-detect port)
iface = meshtastic.serial_interface.SerialInterface()
# Connect (TCP)
iface = meshtastic.tcp_interface.TCPInterface("192.168.1.50")
# Send text
iface.sendText("Hello mesh", channelIndex=0)
# Send to specific node (DM)
iface.sendText("Private message", destinationId="!a1b2c3d4", channelIndex=0)
# Request position
iface.getNode('^local').requestPosition()
# Write channel config
ch = iface.getNode('^local').channels[0]
ch.settings.name = "MyNet"
iface.getNode('^local').writeChannel(0)
Event Loop and Threading
The library spawns a background thread that reads from the serial/TCP connection and dispatches events via the PyPubSub pub/sub system. Your on_receive callback is called in that background thread - use thread-safe data structures (queues, locks) if you share state with your main thread.
Available topics:
meshtastic.receive- any incoming packetmeshtastic.receive.text- text messages onlymeshtastic.receive.position- position updatesmeshtastic.receive.user- node user/info packetsmeshtastic.receive.data.portnum- data packets by port number (telemetry arrives here under theTELEMETRY_APPportnum; there is no separatemeshtastic.receive.telemetrytopic)meshtastic.connection.established- fired after successful connectmeshtastic.connection.lost- fired on disconnect
Error Handling
- Serial port busy: only one process can hold the serial port. Close the Meshtastic app or CLI before running your script. Use
try/finally: iface.close()to release cleanly. - Reconnection on disconnect: subscribe to
meshtastic.connection.lostand re-instantiate the interface with exponential backoff. - Timeout handling:
SerialInterfacehas anoProtoparameter - set it toTruefor raw serial access without waiting for a Meshtastic handshake, useful for debugging hardware. - Packet validation: always guard against missing keys in the packet dict; not all fields are present in every packet type.
Home Assistant Integration via MQTT
Overview
Integrating Meshtastic into Home Assistant unlocks powerful home automation possibilities: track family members on a mesh map, get alerts when a node goes offline, and trigger smart-home actions based on mesh events. The integration uses MQTT as the transport layer, with Mosquitto as the local broker.
Step 1: Install and Configure Mosquitto
In Home Assistant, navigate to Settings → Add-ons → Add-on Store and install the Mosquitto broker add-on. After installation, open its configuration tab and add a user:
logins: - username: meshuser
password: yourpassword
Start the add-on and enable Start on boot. Note your Home Assistant host IP - your Meshtastic node will connect to this address.
Step 2: Configure Your Meshtastic Node for MQTT
Open the Meshtastic app, go to Radio Configuration → MQTT and set:
- MQTT Server Address: your HA host IP (e.g.
192.168.1.100) - Username / Password: the credentials you set above
- Root Topic:
msh/US(or your region) - JSON output: enable JSON enabled so the node publishes the human-readable JSON used by the sensors below.
- Uplink Enabled: on. Leave Downlink DISABLED for Home Assistant monitoring - you only need uplink to read data. Downlink lets MQTT inject messages back into your RF mesh, which is an injection risk if the broker is ever reachable by anyone else; only enable it if you specifically intend to send messages from HA into the mesh.
Also ensure your node has WiFi configured under Radio Configuration → Network.
Step 3: MQTT Sensor YAML in Home Assistant
Add the following to your configuration.yaml (adjust the region, channel name, and node IDs as needed). The Meshtastic JSON topic format is msh/REGION/2/json/CHANNELNAME/!gatewayNodeId - the segment after json is the channel name (e.g. LongFast), not the packet type. The packet type (telemetry, position, etc.) is a field inside the JSON payload (value_json.type), so we subscribe with a + wildcard for the channel and filter on the type in each template:
mqtt:
sensor: - name: "Node !a1b2c3d4 Battery"
# Wildcard channel segment; replace !a1b2c3d4 with the publishing gateway node's id
state_topic: "msh/US/2/json/+/!a1b2c3d4"
value_template: >-
{% if value_json.type == 'telemetry' %}
{{ value_json.payload.battery_level }}
{% else %}{{ states('sensor.node_a1b2c3d4_battery') }}{% endif %}
unit_of_measurement: "%"
device_class: battery - name: "Node !a1b2c3d4 Latitude"
state_topic: "msh/US/2/json/+/!a1b2c3d4"
value_template: >-
{% if value_json.type == 'position' %}
{{ value_json.payload.latitude_i | float / 1e7 }}
{% else %}{{ states('sensor.node_a1b2c3d4_latitude') }}{% endif %} - name: "Node !a1b2c3d4 Longitude"
state_topic: "msh/US/2/json/+/!a1b2c3d4"
value_template: >-
{% if value_json.type == 'position' %}
{{ value_json.payload.longitude_i | float / 1e7 }}
{% else %}{{ states('sensor.node_a1b2c3d4_longitude') }}{% endif %}
Note: the last topic segment is the gateway node that published to MQTT, which is not necessarily the originating node. Verify the exact JSON payload field names (e.g. battery_level, latitude_i) against the JSON your firmware actually emits, and see this wiki's MQTT Topic Structure and Packet Format page for the canonical topic layout.
Reload your YAML configuration after saving.
Step 4: Automations
Alert when a node hasn't been heard in 30 minutes:
automation: - alias: "Mesh node offline alert"
trigger:
platform: state
entity_id: sensor.node_a1b2c3d4_battery
to: unavailable
for: "00:30:00"
action:
service: notify.mobile_app_your_phone
data:
message: "Node !a1b2c3d4 has not reported in 30 minutes"
Notify on critical battery:
- alias: "Mesh node low battery"
trigger:
platform: numeric_state
entity_id: sensor.node_a1b2c3d4_battery
below: 15
action:
service: notify.mobile_app_your_phone
data:
message: "Node !a1b2c3d4 battery critical: {{ states('sensor.node_a1b2c3d4_battery') }}%"
Trigger lights when a family member arrives home via mesh position:
- alias: "Welcome home via mesh"
trigger:
platform: zone
entity_id: device_tracker.mesh_family_member
zone: zone.home
event: enter
action:
service: light.turn_on
target:
entity_id: light.porch
To use zone-based presence, create a device_tracker that updates from the latitude/longitude sensors using a template or the MQTT device tracker integration.
Step 5: Lovelace Dashboard
For a mesh map view, install the Map card in Lovelace. Add a card of type map and reference your device tracker entities. You can also use the auto-entities HACS card to dynamically list all mesh node sensors. For richer visualization, some community members use Grafana with the InfluxDB integration to feed in MQTT telemetry.
Node-RED Flows for Mesh Automation
Overview
Node-RED is a visual flow-based programming tool that acts as powerful middleware between your Meshtastic MQTT feed and virtually any other service. It runs on Linux (including Raspberry Pi), inside Home Assistant, or on any Node.js-capable machine.
About the JSON MQTT topics used below: Meshtastic publishes decoded JSON packets to msh/REGION/2/json/CHANNELNAME/USERID - the segment after json is the channel name, not the packet type. There is no .../json/text/ sub-topic. To catch all channels, subscribe to msh/US/2/json/# and filter on the packet type (a type field inside the JSON, e.g. "text", "position") in a switch/function node. Also note these JSON flows require the gateway to publish decoded (plaintext) packets to the broker - which bypasses channel encryption at the broker. If that broker is the public one, the decoded content (including any "emergency" channel) is public. Use a private, access-controlled broker for any sensitive channel.
Installing Node-RED
Standalone on Raspberry Pi / Linux:
bash <(curl -sL https://raw.githubusercontent.com/node-red/linux-installers/master/deb/update-nodejs-and-nodered)
sudo systemctl enable nodered
sudo systemctl start nodered
Access the editor at http://your-pi-ip:1880.
In Home Assistant: Install the Node-RED add-on from the add-on store. It integrates directly with your HA entities.
Core Flow Pattern 1: Message Logger
This flow captures all text messages from Meshtastic and writes them to a log file.
[MQTT In] → [JSON Parse] → [Switch: msg.payload.type == "text"] → [Function: format line] → [File Write]
MQTT In topic: msh/US/2/json/# (subscribe to all channels; filter type in the switch)
Function node:
msg.payload = new Date().toISOString() + " [!" +
msg.payload.from.toString(16).toUpperCase() + "] " +
msg.payload.payload + "
";
return msg;
File node: /home/pi/mesh_log.txt (append mode)
In the decoded text-message JSON, from is a top-level decimal node ID (prefix it with ! when displaying the hex), and for a text packet the message string is at payload.payload (not payload.payload.text). Verify the exact field nesting against a live JSON sample, since it varies by packet type.
Core Flow Pattern 2: Position Tracker to Google Sheets
Filter position packets by a specific node ID and push lat/lon to a Google Sheet via the Sheets API node (node-red-contrib-google-sheets).
[MQTT In: msh/US/2/json/#] → [JSON Parse] → [Switch: msg.payload.type == "position" AND msg.payload.from == targetNodeId]
→ [Function: build row] → [Google Sheets: append row]
Function node:
var pos = msg.payload.payload;
msg.payload = [
new Date().toISOString(),
pos.latitude_i / 1e7,
pos.longitude_i / 1e7,
pos.altitude
];
return msg;
Core Flow Pattern 3: Two-Way Bridge - Meshtastic ↔ Telegram
Install node-red-contrib-telegrambot. This flow forwards incoming mesh messages to a Telegram chat and relays Telegram replies back to the mesh channel.
Meshtastic → Telegram:
[MQTT In: msh/US/2/json/#] → [JSON] → [Switch: type == "text"] → [Function: build TG msg]
→ [Telegram Sender]
Telegram → Meshtastic:
[Telegram Receiver] → [Function: build MQTT payload]
→ [MQTT Out: msh/US/2/json/mqtt/] (downlink topic)
Downlink is more involved than it looks. Publishing arbitrary JSON to an output topic does not reliably inject a message into the mesh. For the firmware to transmit a downlinked message the gateway must have downlink enabled for that channel (mqtt module / channel downlink_enabled), and the payload must use the correct topic and envelope that the firmware accepts. If those conditions are not met, your Telegram replies will silently never reach the mesh. Confirm the current downlink topic/envelope against the Meshtastic MQTT integration docs before relying on this direction.
Emergency Channel SMS Forwarding via Twilio
Install node-red-contrib-twilio. Watch a dedicated emergency channel (channel index 1, for example) and forward any message to a phone number via SMS:
[MQTT In: msh/US/2/json/#] → [JSON] → [Switch: type == "text" AND channel == 1]
→ [Function: format SMS] → [Twilio: send SMS]
Function node:
var from = msg.payload.from.toString(16).toUpperCase();
msg.payload = "MESH EMERGENCY from !" + from + ": " +
msg.payload.payload;
return msg;
Set the Twilio node with your Account SID, Auth Token, and destination phone number.
Caution - this is a best-effort convenience path, not a primary emergency channel. This SMS-forwarding chain depends on the gateway hearing the packet over RF and on the gateway's internet/cellular connection, the MQTT broker, Node-RED, and Twilio all being up - any of which can fail silently during the exact grid-down / internet-outage scenario an emergency net plans for. Treat it as a convenience overlay on top of direct RF monitoring, never as your primary emergency communications method.
Exporting Flows
Security & Privacy
Meshtastic Channel Encryption
How Meshtastic Encryption Works
Each Meshtastic channel is encrypted with AES-CTR (counter mode), keyed by the channel pre-shared key (PSK). The key length depends on the PSK: the default public key (AQ==) yields AES-128, while a generated 32-byte PSK yields AES-256. Any node that has both the correct channel name and the correct key can decrypt messages on that channel. This is shared-key encryption, not end-to-end: every member holding the PSK can read all traffic on the channel, while relay nodes that do not have the key forward ciphertext without decrypting it. There is no per-recipient secrecy and no forward secrecy, and an MQTT gateway can decrypt the traffic before uplink. They do not need the channel key to relay packets.
The Default Public Key
Meshtastic's built-in "Default" channel uses a well-known, publicly documented key. The PSK is represented in base64 as AQ==, which decodes to a single byte 0x01 - a shorthand "simple" key that selects the firmware's built-in default key (effectively AES-128). This is intentionally not secret - the Default channel is the public mesh. Anyone running the Meshtastic app can read messages on the Default channel. Do not send sensitive information on the Default channel.
Creating a Private Channel
To communicate privately:
- Set a custom channel name (different from "LongFast" or "Default").
- Use the app's Generate button to create a cryptographically random 256-bit PSK. Do not manually type a key - human-chosen keys have low entropy.
- Share the channel configuration with intended participants using the channel QR code. Share this QR code only through a secure side channel (e.g., in person or via an encrypted messenger).
Messages on your private channel are unreadable to any node that does not possess the key, even if those nodes relay the encrypted packets.
PKI and Direct Messages (Meshtastic 2.5+)
Starting with Meshtastic 2.5, the platform introduced PKI-based direct messaging (X25519 ECDH key exchange with AES-CCM). Each node generates a public/private key pair. When you send a direct message (DM) to a specific node, it is encrypted to that node's public key - only the intended recipient's private key can decrypt it. This is separate from channel encryption and provides stronger guarantees for one-to-one communication. Note that DMs to nodes running firmware 2.4.3 or older are unprotected, there is no forward secrecy ("harvest now, decrypt later" applies), and there is no key revocation.
What Encryption Does NOT Protect
Meshtastic encryption protects message content, but several pieces of information remain visible to anyone monitoring the RF spectrum:
- Packet metadata: Source node ID, destination node ID, hop count, packet ID, SNR, and timing are in the unencrypted packet header and visible to any LoRa receiver tuned to the frequency.
- Node existence: Even on a private channel, NodeInfo packets (which advertise node IDs and positions) are broadcast on the public mesh. A passive observer can know your node exists and track its position even if they cannot read your messages.
- Traffic analysis: An attacker can observe transmission patterns - when and how often you transmit - without ever decrypting content. This can reveal usage patterns and correlate activity.
Admin Channel Security
Meshtastic's admin channel allows remote configuration of nodes. The admin channel PSK is a high-value secret: anyone who possesses it can reconfigure your nodes remotely, change channels, adjust power settings, or disable the device. Treat it accordingly:
- Use a unique PSK for the admin channel, separate from any user channels.
- Do not share the admin PSK with regular channel users.
- Store it securely (e.g., in a password manager).
Key Distribution and Revocation
Meshtastic has no built-in key revocation mechanism. If an admin channel or private channel PSK is compromised, you must manually change it on every node that uses it. For networks with many nodes, this can be operationally complex - plan key distribution carefully and limit who has access to keys from the outset. Because Meshtastic channel encryption has no forward secrecy, rotating a compromised PSK protects only future traffic. Any ciphertext an attacker captured over RF before the rotation can be decrypted with the old key. Assume all traffic sent under a leaked PSK is exposed.
Privacy Best Practices
Position Privacy
By default, Meshtastic nodes broadcast GPS coordinates to the entire channel at regular intervals. Your approximate location is visible to all channel participants. If your node is on the Default public channel and your node (or a node that hears it) uplinks to the public Meshtastic MQTT server with map reporting / "OK to MQTT" enabled, your position also appears on aggregation sites such as meshmap.net. Being on the Default channel alone, with no MQTT path, does not publish you to those maps.
Mitigations
- Use a private channel: Position broadcasts on a private channel are only visible to nodes with the key - but only if no gateway on that channel uplinks to MQTT. By default the MQTT uplink sends packets to the broker unencrypted (unless
mqtt.encryption_enabledis set true), which publishes positions to the internet. The protection also fails if the node is simultaneously broadcasting position on a public channel. A private PSK alone does not stop MQTT exposure. - Disable the position module: Navigate to Config → Position → GPS Mode → DISABLED. The node will not broadcast any location data.
- Set a fixed imprecise location: Instead of live GPS, configure a fixed position - use the center of your neighborhood or a nearby landmark rather than your exact address. This allows you to appear on mesh maps without revealing your precise location.
- Reduce position precision: Meshtastic lets you broadcast a coarsened position. Position precision is configured per channel (in the app: Channel settings → Position precision), so it must be set on the channel you broadcast on, then verified. This reduces - but does not eliminate - location disclosure; an approximate fix (e.g. nearest kilometer) can still identify a small town or a single rural site. Note: position is not used for mesh routing (Meshtastic uses managed-flood routing, not GPS-based routing), so coarsening or disabling it does not affect message delivery.
Node Naming
Your node's long name is broadcast to everyone on the channel and appears on public mesh maps. Use a callsign, handle, or alias rather than your full real name if privacy is a concern. Your node's short name (4 characters) is used in the mesh and is also public.
Telemetry Data
If you enable device telemetry or environmental sensors, data such as battery voltage, temperature, humidity, and pressure are broadcast on the channel. This data can reveal:
- Whether your device is plugged in or on battery (usage patterns)
- Environmental conditions at your location (indirectly revealing location details)
- When the device is active or idle
Disable telemetry modules you do not need, especially on nodes deployed in sensitive locations.
Public Mesh Visibility
If your node is on the Default channel and is bridged to the public Meshtastic MQTT server (map reporting / "OK to MQTT" enabled), sites like meshmap.net aggregate and display:
- Node long name and short name
- GPS position
- Last-heard timestamp (this is derived by the map, not part of the report payload)
- Hardware model
- Firmware version
This information is public and indexed. Consider the public mesh as a zero-privacy environment.
Sensitive Use Cases
For deployments where participant safety depends on privacy (e.g., domestic violence shelters, witness protection situations, activist networks operating in hostile environments), apply all of the following:
- Use a private channel with a strong random PSK generated by the app.
- Disable GPS entirely (Config → Position → GPS Mode → DISABLED).
- Use an alias or callsign as the node name - never a real name.
- Consider MeshCore instead of Meshtastic (see below): its routing may expose less network-wide identity data, because it follows discovered paths rather than flooding the whole network. Evaluate this carefully against current MeshCore documentation - neither platform provides anonymity.
- Disable all telemetry modules.
Meshtastic vs. MeshCore Privacy Profile
The two platforms have meaningfully different privacy characteristics as a consequence of their routing architectures:
Meshtastic (Flood Routing)
Meshtastic uses a managed-flood routing model. NodeInfo packets - advertising each node's ID, name, and position - are flooded outward up to the hop limit (default 3 hops), reaching nodes within that hop-limited radius rather than literally the entire mesh. Within the reachable area, nodes typically learn about all other nodes they can reach. This is high-transparency by design and enables features like mesh maps, but it means even passive observers within range on the channel receive location and identity data for nearby nodes.
MeshCore (Path-Based Routing)
MeshCore uses a path-based (source-routed) architecture in which messages follow discovered paths rather than flooding the entire network. According to MeshCore's own description, nodes primarily exchange routing information along discovered paths rather than broadcasting advertisements to the whole mesh; confirm the exact default advertisement behavior against current MeshCore documentation before relying on it for a sensitive deployment. In dense networks this can mean a node's existence and location may not be known to distant nodes that have no route to it, which can be more privacy-preserving where blanket network-wide NodeInfo broadcasting is undesirable.
Neither platform should be considered a complete anonymity solution - LoRa transmissions are detectable and direction-findable by anyone with appropriate radio hardware, regardless of the software layer's privacy features. For genuine life-safety deployments, seek professional operational-security guidance rather than relying on radio-software settings alone.
Channel Management
How Meshtastic channels and PSKs work, how to share them via QR code, and strategies for deploying channels in community networks.
Understanding Channels and PSKs
Overview
Meshtastic supports up to 8 simultaneous channels (index 0 - 7). Channel 0 is the primary channel; channels 1 - 7 are secondary channels used for specific groups or purposes.
Channel Anatomy
Every channel has three defining properties:
- Name - a human-readable label used to match channels between nodes.
- PSK (Pre-Shared Key) - a pre-shared key that is 0 bytes (no encryption), 16 bytes (AES-128), or 32 bytes (AES-256), used to encrypt and decrypt messages on that channel.
- Role - one of
PRIMARY,SECONDARY, orDISABLED.
AES-CTR Encryption per Channel
Each channel is encrypted independently with AES-CTR using its PSK. The key length depends on the PSK: the default public channel uses the built-in single-byte default key and is effectively AES-128, while a channel configured with a generated 32-byte PSK uses AES-256. All nodes that possess the matching channel settings and PSK can decrypt traffic on that channel. Relay nodes forward ciphertext blindly - they do not need the PSK to relay packets, only the destination nodes need it to decrypt.
The Default "LongFast" Public Channel
LongFast is the default modem preset. The default primary channel uses the well-known, publicly documented default key (AQ==, a single 0x01 byte) and an empty/default name - it is intentionally not secret. Any Meshtastic device running stock firmware with default settings can read messages on this channel. It exists as a shared public mesh for interoperability.
Creating a Private Channel
To create a private channel, set a custom name and generate a random PSK. Only the PSK provides confidentiality - the channel name and its derived hash are effectively public, so the name does not protect your messages. This protection is also defeated if a gateway on the channel uplinks to MQTT without mqtt.encryption_enabled set true, which republishes the traffic in cleartext. Share access via QR code (see page 2).
Channel Roles
- PRIMARY - the node broadcasts NodeInfo, position packets, and telemetry on this channel. There is exactly one primary channel per node (index 0).
- SECONDARY - carries only messages explicitly addressed to or sent on that channel. No automatic position or telemetry broadcasts.
- DISABLED - the channel slot is configured but inactive.
Running Multiple Channels Simultaneously
A single node can participate in up to 8 channels at once. A node floods and relays packets it hears (subject to channel-hash matching and duplicate suppression) independent of whether it holds the PSK - being configured with a channel governs whether it can decrypt that traffic, not whether it relays it. This makes it possible to bridge a private club channel and the public mesh on the same hardware - the node relays both transparently.
Channel Index 0 vs. Named Secondary Channels
Index 0 (the primary channel) carries the majority of mesh traffic: NodeInfo, position, telemetry, and general messages. Indices 1 - 7 carry traffic only for the specific groups or functions those channels are assigned to. Most community deployments use channel 0 for public connectivity and one or more secondary channels for group communications.
Sharing Channels via QR Code
How Channel Sharing Works
Meshtastic encodes channel configuration as a URL containing a Base64-encoded protobuf payload (a ChannelSet). This URL can be displayed as a QR code or shared as a plain link. At minimum the payload encodes the channel definition(s) — channel name and PSK. The shared LoRa config, which includes the modem preset, is only included when you enable the “Add to URL” / share-full-config option; with a plain channel share it is not carried. If you share only the channel (no LoRa config), recipients must be told the modem preset separately, or they will be on the right channel but unable to communicate.
Generating a QR Code
- Open the Meshtastic app and navigate to Channels.
- Select the channel you want to share.
- Tap the Share (QR icon) button.
- The app displays a QR code and a shareable URL. (Enable the option to include LoRa config if you also want to share the modem preset.)
Importing a Channel
To import a channel on another device:
- Scan the QR code directly from the Meshtastic app's channel import screen, or
- Navigate to the channel URL on a device that has the Meshtastic app installed - the app intercepts the URL and offers to import the channel.
URL Format
Channel URLs follow this structure:
https://meshtastic.org/e/#<Base64-encoded channel data>
The fragment (#...) portion is the Base64-encoded protobuf. (Legacy links used a /c/ path without the # fragment; /e/# is the current form.) Because it is a URL fragment, it is not included in the HTTP request line sent to the server, which reduces incidental exposure during a direct request. This is not a strong privacy guarantee, however: link-preview bots, browser history and account sync, and messaging clients can still capture the full URL including the PSK. Treat the link as equivalent to the key regardless of how it is transported.
Security: What Sharing a QR Means
Sharing a channel QR code shares the encryption key. Anyone who receives the QR or link can decode all past and future messages on that channel if they have access to the ciphertext. Key management rules:
Admin Channel QR
An admin channel PSK grants remote configuration access to a node. Store any admin channel QR securely and share it only with authorized administrators. If you lose admin access to a remote, unattended node you may lose the ability to remotely reconfigure it - physical access becomes necessary. Note that modern firmware (2.5 and later) uses public-key (PKC) admin keys for remote administration rather than a shared admin-channel PSK; the shared-PSK admin channel is the older mechanism.
Multi-Channel QR Codes
The Meshtastic app can generate a single QR code that encodes multiple channel definitions simultaneously. This makes onboarding new nodes to a full community channel set (e.g., public + club + EmComm) a single scan rather than importing each channel individually.
Channel Strategy for Community Networks
Public Infrastructure Nodes
Nodes acting as public infrastructure (repeaters, routers) are commonly configured with the default public PSK on channel 0, which lets them participate in and originate traffic on the public channel. But relaying does not require sharing any channel's PSK. A node rebroadcasts any packet whose LoRa settings (modem preset and frequency) match its own, even packets it cannot decrypt - the packet header is unencrypted. So a node on a private-only channel still relays public-channel traffic. What actually extends range for every local Meshtastic user is matching the regional modem preset and frequency and keeping the default (open) rebroadcast mode - not the channel-0 PSK choice. Running the default public channel 0 governs whether the node can read and originate public traffic, not whether it relays.
Club and Group Channels
Add a secondary channel (index 1 or higher) with a private PSK for your group's internal communications. The node relays both the public channel 0 traffic and your private group traffic. Be aware there is a shared-airtime cost: a node relays all of its channels over one physical radio, so additional channel traffic adds to total airtime and raises channel utilization and contention. Members of your group get private messaging; the public mesh still benefits from your relay - but provision channels conservatively, since more traffic means more congestion on the same duty cycle.
EmComm Channel Strategy
A tiered channel layout works well for emergency communications deployments:
- Channel 0 (public PSK) - general mesh connectivity, open to all Meshtastic users. Use for public outreach and coordination with unknown operators.
- Channel 1 (group PSK) - EmComm team coordination. PSK known to all trained EmComm volunteers.
- Channel 2 (restricted PSK) - incident command. Tightly restricted to net control and command staff only.
This allows the same physical nodes to serve all three audiences without deploying separate hardware for each level. Note that this is access-control segregation only: all three channels share the same physical LoRa airtime, so PSK separation controls who can read each channel - it does not give each tier isolated bandwidth.
Security caution. Channel PSKs are shared symmetric keys with no per-user revocation and no forward secrecy - a leaked key, or a departed member's key, compromises all past captured traffic on that channel until you rotate it. Metadata (who is transmitting, and when) is plaintext on the RF regardless of which tier you use. And a single gateway that uplinks any of these channels to MQTT can leak that channel's content and metadata publicly. Do not treat any Meshtastic channel as secure for genuinely sensitive incident-command content, and make sure no gateway uplinks your command channels to MQTT.
Regional Preset Coordination
When establishing a community channel, document and distribute the following so newcomers can join successfully. Note that name and PSK are per-channel parameters, while modem preset and frequency slot are device-wide LoRa settings shared by all channels on a node:
- Channel name - must match exactly (case-sensitive). The PRIMARY channel name also auto-derives the default frequency slot unless you explicitly override the slot.
- PSK - share via QR code, not as a raw string.
- Modem preset - e.g. LONG_FAST, LONG_SLOW, MEDIUM_FAST. This is part of each device's LoRa radio config (set per device), not channel metadata carried in the PSK/QR. Every node must independently be set to the same preset. Nodes on different presets use different spreading factor / bandwidth / coding and are physical-layer incompatible, so they cannot decode each other's packets.
- Frequency slot - whether you use the regional default slot (derived from the primary channel name) or a specific slot. To guarantee nodes with different PRIMARY channel names transmit on the same frequency, set the LoRa frequency slot explicitly. Mismatched frequency = no communication.
Node Naming Conventions
Consistent naming makes map views and traceroutes readable. Recommended conventions:
- Long name:
CALLSIGN-Location- e.g.KD9XYZ-Home,KD9XYZ-Mobile,MeshAmerica-SiteA. Using a callsign in a node name is purely a naming convention; Meshtastic on 902-928 MHz ISM is Part 15 unlicensed and carries no amateur (Part 97) obligation. - Short name (4 characters, shown on maps): use callsign suffix or a meaningful location code - e.g.
9XYZ,STA,M001. - Infrastructure nodes: prefix with the network name (e.g.
MeshAmerica-) to distinguish them from personal nodes on map views.
Admin Channel Designation
A legacy approach is to designate one secondary channel as the admin-only channel with a tightly controlled PSK, then configure remote nodes with this channel during initial setup. Be clear about what this means: the admin PSK is itself a high-privilege credential - anyone holding it has full remote administrative control of every node that trusts that channel. It does not grant "admin without elevated access"; the PSK is the elevated access. Store the admin channel QR in a secure location (password manager or encrypted file) and restrict distribution strictly to network administrators. Modern Meshtastic prefers the current public-key remote-admin model (per-node admin public keys via admin_key) over a shared admin-channel PSK - prefer that where available.
Network Diagnostics and Health Monitoring
Tools and techniques for understanding the health of your Meshtastic mesh.
Reading Network Statistics in the Meshtastic App
Understanding what your Meshtastic network is actually doing requires knowing how to read the statistics the app surfaces. The mobile app (Android and iOS) and the web client all expose channel utilization, airtime, packet counters, and radio-level metrics. This page explains every number, what healthy ranges look like, and how to spot a congested network before it starts dropping messages.
Where to Find Network Statistics
Android App
Open the Meshtastic Android app and open a node's Device Metrics (telemetry) view — tap a node in the node list, then open its Device Metrics / telemetry panel. Channel utilization and air-utilization metrics are shown there (the live stats live in the telemetry view, not in Settings → Radio Configuration → Device, which is the configuration editor). Individual node signal metrics (SNR and RSSI) are shown in the node list by tapping any node row and selecting Node Info.
iOS App
Tap the Nodes tab, then tap any node to see its detail card. Signal strength, last heard time, hop count, and telemetry battery readings are all shown here. The channel utilization percentage is displayed on the Mesh tab's header area when connected to a local node.
Web Client (meshtastic.local or IP)
On the web client served directly from the device, the Dashboard section shows live channel utilization, air utilization, and packet statistics. This is the most detailed view and updates periodically as new telemetry arrives.
Channel Utilization %
Channel utilization measures what fraction of airtime on the LoRa channel has been consumed by transmitted packets, averaged over a rolling 1-minute window. The firmware computes it by summing on-air milliseconds across six 10-second sub-windows. (This is a separate metric from the per-hour duty cycle, which is the regulatory airtime percentage discussed below.)
| Utilization Range | Network Health | Interpretation |
|---|---|---|
| 0 - 25% | Healthy (green) | Plenty of spare capacity. All traffic should get through. This is the firmware's green/throttle boundary — above ~25% the firmware begins delaying transmissions. |
| 25 - 50% | High (orange) | Collisions increasing. The firmware delays TX above ~25%; routers cut back telemetry around 40%. Expect occasional packet loss. |
| 50 - 100% | Congested (red) | Severe contention. Most non-acked packets will be lost. |
The underlying metric is computed by the device firmware using the LoRa modem's receive statistics. Every received or transmitted packet's on-air time is summed and divided by the window period. The default LongFast preset uses SF11/BW250/CR4/5; a small (~50-byte) packet at this configuration takes roughly 400–700 ms on air (the exact figure depends on payload size and preamble length). As an illustrative estimate, that means only a handful of such packets per minute will push utilization toward 25% — a level easily exceeded on a busy community mesh. (Note: SF11/BW250 LongFast is much slower than shorter presets — for example MediumFast at SF9 transmits roughly 3× faster than LongFast at SF11.)
Air Utilization
Air utilization is closely related but specifically counts the fraction of time the radio was actively transmitting (TX duty cycle). Some regulators impose per-hour duty-cycle limits (for example the EU 868 MHz sub-bands have 1% or 10% duty-cycle limits under ETSI EN 300 220). These per-hour duty-cycle limits apply to EU 868 MHz sub-bands. The US/Canada 902–928 MHz band (47 CFR 15.247 / ISED RSS-247) imposes no duty-cycle limit on digital-modulation LoRa systems — the governing limits there are conducted power (1 W / 30 dBm), EIRP, and frequency-hopping/dwell rules, not an airtime percentage. So in the US/Canada, watching air utilization is about avoiding mesh congestion, not staying under a legal airtime cap. Where a regional duty-cycle limit does apply, the firmware will delay outgoing packets to help stay compliant; if you see air utilization climbing, reducing your transmit frequency reduces congestion either way.
Packet Counters
The firmware maintains several packet counters that help diagnose forwarding behavior:
- Packets Rx: Total valid LoRa packets received (passed CRC check).
- Packets Rx Bad: Packets received with a failed CRC. A high ratio of bad packets to good packets indicates RF interference or a node with a damaged antenna.
- Packets Tx: Packets this node has transmitted (original or forwarded).
- Packets Tx Relay: Subset of Tx that were relayed on behalf of another node. A router node should have a high relay ratio. A client node with a very high relay count may be flooded because its hop limit is too generous.
- Packets Rx Duplicate: Packets seen more than once. Some duplication is normal in mesh (same packet arrives via multiple paths); more than 10 - 20% of total Rx suggests a routing loop or misconfigured hop limits.
SNR - Signal-to-Noise Ratio
SNR is the most important single-number indicator of link quality. It is reported in decibels (dB) and expresses how far above the noise floor the received signal sits. LoRa can decode packets at negative SNR values because spread-spectrum processing gain allows it to recover signal from noise. The decode floor is spreading-factor-dependent, so the bands below are rules of thumb that shift with your preset (see the note after the table).
| SNR (dB) | Link Quality | Notes |
|---|---|---|
| ≥ 5 | Excellent | Strong link (rule of thumb). Consider a lower spreading factor for shorter air time. |
| 0 to 5 | Good | Reliable communications. Typical for in-range nodes. |
| −5 to 0 | Marginal | Some packet loss possible at low SF; still well within the decode floor on long presets. |
| −10 to −5 | Weak (low SF) / usable (long presets) | On short presets (low SF) packet loss begins here; on the default LongFast (SF11) preset this range still decodes reliably. |
| < −10 | SF-dependent | Below the floor at low SF, but on LongFast (SF11/BW250) links down to about −15 dB are still usable and the floor is around −17.5 dB. Only near each preset's own floor will most packets be lost. |
The minimum decodable SNR depends on the spreading factor: the floor ranges from approximately −7.5 dB at SF7 to about −20 dB at SF12 (LongSlow), stepping roughly −2.5 dB per SF (per the SX1276 datasheet / Semtech AN1200.22). The default LongFast preset uses SF11/BW250, giving a floor around −17.5 dB — so a −12 dB link on LongFast is healthy, not weak. Interpret the table above relative to your configured preset.
RSSI - Received Signal Strength Indicator
RSSI measures the absolute power level of the received signal in dBm (decibels relative to 1 milliwatt). A more negative number means a weaker signal. Unlike SNR, RSSI alone does not tell you whether a packet can be decoded - a −120 dBm signal in a quiet environment may decode fine while a −100 dBm signal swamped by noise will not. The bands below are rules of thumb, not published Meshtastic specifications.
| RSSI (dBm) | Interpretation |
|---|---|
| −60 to −80 | Very strong (rule of thumb). Nodes are physically close or have excellent antennas. |
| −80 to −100 | Normal operational range for most deployments. |
| −100 to −115 | Weak but decodable at high spreading factors. |
| < −120 | Conservative low-signal threshold. Note the datasheet sensitivity floor is preset-dependent and can reach below −140 dBm at high SF/narrow BW, so −120 dBm is a cautious cutoff rather than an absolute floor. |
What Healthy Numbers Look Like
A well-configured community mesh with 10 - 30 nodes should exhibit:
- Channel utilization consistently below ~25% (the firmware's green/throttle boundary).
- SNR between −5 and +10 dB on all primary relay links (and remember the usable floor is lower on long presets).
- RSSI above −115 dBm on relay links.
- Duplicate packet ratio below 10%.
- Zero or near-zero "Rx Bad" packets (CRC failures) unless there is known interference.
Spotting a Congested Network
The most common congestion warning signs:
- Rising channel utilization: If utilization creeps above 25% during normal operation (not an emergency event), the first remediation step is to increase the position broadcast interval for all nodes. The default 15-minute interval is reasonable for a small network but too aggressive for a mesh with 50+ nodes.
- High duplicate count: More than 20% duplicates often indicates that too many nodes are configured as ROUTER or that hop limits are set too high, causing the same packet to traverse the network redundantly.
- Increasing Rx Bad rate: If the ratio of CRC-failed packets is rising, a hidden-node problem or external interference source (other LoRa devices on the same frequency, industrial equipment) is likely the cause.
- Messages not delivered even to nearby nodes: When a close-range node shows good SNR and RSSI but messages are still failing, the channel is likely saturated and ACKs are being lost.
Node Telemetry Metrics
Each node periodically transmits a DeviceTelemetry packet containing battery voltage, battery level percentage, and (if available) temperature. In the node list you can see these values next to each node. A battery level below 20% combined with low SNR from that node often means the node is running on backup power after a mains failure - an important operational signal in an emergency mesh.
Using the Meshtastic Python CLI for Diagnostics
The Meshtastic Python package ships both a library and a command-line interface (CLI). The CLI is the fastest way to interrogate a connected node, export its configuration, watch live packet traffic, and run one-off diagnostic commands from a laptop. This page covers installation, every useful diagnostic command, how to read the output, and the difference between serial and TCP connections.
Installation
# Requires Python 3
pip3 install --upgrade "meshtastic[cli]"
# Verify installation
meshtastic --version
# Output: meshtastic v2.x.x
On Linux you may need to add your user to the dialout group to access serial
ports without sudo:
sudo usermod -aG dialout $USER
# Log out and back in for the change to take effect
Connecting to a Node
Serial (USB)
The most common connection method. Plug the device in via USB and run any command without extra flags - the CLI auto-detects the first available serial port:
meshtastic --info
To specify a port explicitly:
meshtastic --port /dev/ttyUSB0 --info # Linux
meshtastic --port /dev/cu.usbserial-* --info # macOS
meshtastic --port COM4 --info # Windows
TCP (Wi-Fi)
Nodes running ESP32 with Wi-Fi enabled expose a TCP port (default 4403). Use
--host instead of --port:
meshtastic --host 192.168.1.42 --info
meshtastic --host meshtastic.local --info # mDNS name on the local LAN
BLE
BLE support is included by default — bleak is now a core dependency of the
meshtastic package, so the old [ble] extra is no longer required:
pip3 install --upgrade "meshtastic[cli]"
meshtastic --ble-scan # lists visible Meshtastic BLE devices
meshtastic --ble <MAC-or-name> --info
--info: Device Summary
meshtastic --info
This is the single most useful diagnostic command. It prints a comprehensive summary of the connected node's radio configuration and node database (largely human-readable text with some structured sections), including:
Connected to radio
Owner: WB5ABC (4 char id: !aabbccdd)
My info:
{
"myNodeNum": 2864434397,
"hasGps": true,
...
}
Metadata: {
"firmwareVersion": "2.5.3.abcdef",
"deviceStateVersion": 23,
"hasWifi": true,
"hasBluetooth": true,
"hasEthernet": false,
"hwModel": "TLORA_V2_1_1P6",
"hasRemoteHardware": false,
"canShutdown": false
}
Nodes in mesh: {
"!aabbccdd": {
"num": 2864434397,
"user": { "id": "!aabbccdd", "longName": "WB5ABC", "shortName": "W5", "hwModel": "TLORA_V2_1_1P6", "role": "CLIENT" },
"position": { "latitudeI": 323456789, "longitudeI": -970123456, "altitude": 312, "time": 1714000000 },
"snr": 6.5,
"lastHeard": 1714010000,
"deviceMetrics": { "batteryLevel": 87, "voltage": 3.98, "channelUtilization": 4.25, "airUtilTx": 0.81 }
},
...
}
Preferences: { ... full radio config (role, positionFlags, etc. live here) ... }
Channels: [ { "index": 0, "role": "PRIMARY", "settings": { ... } }, ... ]
Note that the fields live in different objects: device capabilities and firmware version are under Metadata, while the node's role and positionFlags live in the radio config / Preferences (localConfig), not in Metadata. Script your lookups against the correct object.
Key fields to check during diagnostics:
channelUtilization: percentage of channel airtime used over the last ~1 minute (the firmware sums on-air time across six 10-second sub-windows).airUtilTx: TX duty cycle percentage.snr: last-heard SNR from each remote node (dB).lastHeard: Unix timestamp. Subtract from current time to see how stale a node entry is.firmwareVersion: compare against the latest release to check if updates are needed.
--nodes: Node Table
meshtastic --nodes
Prints a compact tabular summary of all nodes in the mesh database:
N Num User AKA Hardware Latitude Longitude Altitude Battery SNR LastHeard
1 2864434397 WB5ABC W5 TLORA_V2 32.3456 -97.0123 312m 87% 6.5 2024-04-25 10:31:22
2 3109276812 K5ZZZ K5 RAK4631 32.3512 -97.0099 289m 72% 2.1 2024-04-25 10:29:55
3 4012345678 N5XYZ N5 HELTEC_V3 32.3390 -97.0210 278m -- -4.7 2024-04-25 09:15:10
The LastHeard column quickly shows stale nodes (entries in the node DB that
haven't been heard in hours or days). These are candidates for manual removal if you are
troubleshooting ghost-node problems. The SNR column shows the signal quality of the last packet
heard from that node (not necessarily a direct link - the packet may have been
relayed).
--export-config: Full Configuration Export
meshtastic --export-config > node_backup_$(date +%Y%m%d).yaml
Exports the complete device configuration as YAML. This includes radio settings, channel definitions, module configs (telemetry intervals, MQTT settings, serial module, etc.), and device metadata. Use this to:
- Back up a node before a firmware update.
- Clone configuration from one device to another with
meshtastic --configure config.yaml. - Audit configuration differences between nodes in a community mesh.
Treat the exported file as secret. The YAML contains channel PSKs and any MQTT username/password in recoverable form. Store the backup encrypted and do not commit it to a public repository.
--listen: Live Packet Watch
meshtastic --listen
Subscribes to all decoded packets from the connected node and prints them as they arrive. This is the equivalent of a packet sniffer for the mesh. Press Ctrl-C to stop. The CLI prints library log lines and packet dictionaries; the simplified example below illustrates the key fields you will see rather than the literal output format:
TEXT_MESSAGE_APP: from=!aabbccdd, to=^all, id=0x12345678, hop_limit=3, want_ack=False
payload: b'Hello from WB5ABC'
POSITION_APP: from=!aabbccdd, to=^all, id=0xdeadbeef, hop_limit=3, want_ack=False
payload: Position(latitudeI=323456789, longitudeI=-970123456, altitude=312, time=1714010500)
TELEMETRY_APP: from=!ccccdddd, to=^all, id=0x87654321, hop_limit=1, want_ack=False
payload: Telemetry(deviceMetrics=DeviceMetrics(batteryLevel=72, voltage=3.91,
channelUtilization=11.3, airUtilTx=2.1))
During a --listen session, watch for:
- Packets arriving with
hop_limit=0.hop_limitis the number of remaining hops: it is set to the configured hop count when the packet is sent and is decremented by one at each relay. Ahop_limitof 0 on arrival means the packet can no longer be rebroadcast — it used all of its allowed hops, so the hop budget may be too low. - Repeated identical packet IDs arriving within seconds (duplicate storm).
- High-frequency position packets from a single node ID indicating that node's smart position interval is too aggressive.
Reading Telemetry from --listen
Every 30 minutes (1800 s) by default each node broadcasts device telemetry. While listening you can redirect output and grep for telemetry to build a quick health log:
meshtastic --listen 2>&1 | grep TELEMETRY_APP | tee mesh_telemetry.log
Practical Diagnostic Workflow
- Start with
--infoto get baseline node count and channelUtilization. If utilization > 25%, immediately check individual node position intervals. - Run
--nodesand flag any node whereLastHeardis more than 2 hours old during an active session. These are likely ghost nodes. - Run
--listenfor 5 - 10 minutes during peak mesh activity to count duplicate packet IDs and identify high-frequency transmitters. - Export config with
--export-configand comparehop_limitacross nodes. A community mesh should generally use a maximum hop limit of 3 (the default).
Serial vs TCP: When to Use Each
| Method | Best For | Limitations |
|---|---|---|
| Serial (USB) | Local bench testing, initial setup, firmware examination | Requires physical access; occupies USB port preventing simultaneous app connection |
| TCP (Wi-Fi) | Remote diagnostics on deployed nodes, scripted automation, Raspberry Pi gateways | Requires node to have Wi-Fi enabled and be on local network or accessible via port forward |
| BLE | Temporary field diagnostics without Wi-Fi | Short range, platform-specific BLE stack issues, slower data rate |
Mesh Topology and Path Analysis
Knowing how your messages travel through the mesh is essential for identifying bottleneck nodes, optimizing relay placement, and diagnosing delivery failures. Meshtastic exposes hop-count information in every received packet, and community tools like meshmap.net provide geographic visualization of the whole network. This page explains how to read that data and use it to understand your mesh's topology.
How Meshtastic Routes Messages
Meshtastic uses a managed flood routing algorithm (not a distance-vector or link-state protocol). When a node transmits a packet:
- The originating node sets the
hop_limitfield (default 3, maximum 7). - Every node that receives the packet and has not seen this packet ID before will relay it
after a random back-off delay, decrementing
hop_limitby 1. - A node will not relay a packet whose
hop_limithas reached 0. - Each node keeps a recent-packet cache (typically 256 entries) to drop duplicates it has already forwarded.
This means there is no explicit source-routed path - messages can arrive via multiple routes simultaneously. The first copy to arrive is delivered; subsequent copies are dropped by the destination's duplicate cache.
Reading Hop Count in Received Packets
When a node receives a packet the hop_start and current hop_limit
fields tell you how many hops the packet took:
hops_taken = hop_start - hop_limit
For example, if hop_start = 3 and the received hop_limit = 1, the
packet took 2 hops to reach you. In the Meshtastic mobile app, the node detail view shows
"Hops Away" which is this pre-computed value. A value of 0 means the node is a
direct neighbor; 1 means one relay was used; and so on.
In the Python CLI --listen output, the raw hop_limit of each
arriving packet is printed. Since the original hop_start is stored in the packet,
you can compute hops taken from any arriving packet.
Traceroute Equivalent: meshtastic --traceroute
Meshtastic has included a built-in Traceroute Module since firmware 2.0.8 that discovers the actual relay path to a destination node. (Recording the full return route and the per-link SNR shown below requires firmware 2.5 or newer; on older firmware only the outbound path is reported.)
# Syntax: meshtastic --traceroute '!<destination_node_id>'
meshtastic --traceroute '!aabbccdd'
Example output:
Sending traceroute request to !aabbccdd (this could take a while)
Route: !11223344 → !55667788 → !99aabbcc → !aabbccdd
!11223344 WB5ABC (this node)
!55667788 K5ZZZ SNR: 3.25 dB
!99aabbcc W5RELAY SNR: -2.50 dB
!aabbccdd N5XYZ SNR: 1.75 dB
Each arrow represents one relay hop. The SNR shown next to each node is the signal quality measured at that receiving node for the link it just traversed; on firmware 2.5+ the return path is recorded the same way, so you get per-link SNR for both the outbound and return legs. Lower (more negative) SNR on a leg indicates a weaker, less reliable link. How negative is "too weak" depends on the spreading factor: the decode floor ranges from about −7.5 dB at SF7 to about −20 dB at SF12. On the default LongFast preset (SF11, floor around −17.5 dB) only links worse than roughly −14 dB are marginal, so a leg at, say, −7 dB on LongFast still has comfortable margin.
Important caveats for traceroute:
- The traceroute packet itself consumes channel airtime, so avoid running it repeatedly on a congested mesh.
- The path shown is the path taken by the traceroute probe - actual message traffic may take a different path because nodes relay whichever copy arrives first.
- Traceroute requires the destination node to be online and reachable.
Visualizing the Network with meshmap.net
meshmap.net is a community-maintained map that ingests position and neighbor-info telemetry from Meshtastic nodes that have MQTT upload enabled. It shows:
- Node locations on a geographic map.
- Neighbor links drawn between nodes that have directly heard each other (neighbor-info packets).
- Signal quality (SNR) color-coded on each link.
- Node metadata: firmware version, hardware model, last seen time.
To appear on meshmap.net your node must:
- Have GPS or a fixed position configured.
- Have MQTT enabled pointing to
mqtt.meshtastic.orgor a community server that feeds into the map. - Have the neighbor-info module enabled so it reports detected neighbors.
Privacy note: enabling MQTT to the public broker so your node appears on meshmap.net also publishes your node's position and its neighbor list (which nodes you can hear) to the public internet. Consider whether that exposure is acceptable for your deployment before opting in.
Neighbor Info Module
The neighbor-info module (enabled in Settings → Modules → Neighbor Info) causes each node to periodically broadcast a list of all nodes it has directly heard, along with the SNR of the last packet from each. This data is the foundation of topology visualization:
meshtastic --get neighbor_info
# Shows:
# neighbor_info {
# enabled: true
# update_interval: 21600 # seconds between neighbor broadcasts (default 6 hours)
# }
The neighbor-info update interval cannot be set lower than 4 hours (14400 seconds) — the firmware rejects shorter values to protect channel utilization. The default is 6 hours (21600 seconds). Neighbor Info reports SNR only (no per-neighbor RSSI). Do not set it to short intervals such as 900 seconds; that value is invalid and will not take effect.
Identifying Bottleneck Nodes
A bottleneck node is one that a large fraction of the mesh depends on for connectivity. To identify them:
- On meshmap.net: look for nodes with many link lines converging on them. A hub node with 6+ direct neighbors that are otherwise isolated from each other is a critical single point of failure.
- Via traceroute: run traceroute to several different destination nodes. If the same relay node ID appears in every path, that node is a bottleneck.
- Via --nodes SNR analysis: if several nodes only have high SNR to one other node (their only relay), that relay is a bottleneck.
Mitigation strategies for bottleneck nodes:
- Deploy a second relay node with overlapping coverage to provide redundancy.
- Move the bottleneck node to a higher elevation to increase its coverage radius.
- If — and only if — the bottleneck node is genuinely well-sited (excellent RF location, high elevation), consider configuring it as ROUTER role so the firmware prioritizes its rebroadcast duty. Note that on ESP32 boards the ROUTER role automatically enables power-saving sleep (it cannot be turned off) and disables BLE/WiFi/Serial by default — it does not stay awake more. Do not promote a poorly-sited node to ROUTER; add a second relay instead.
Understanding Hop Count in Received Packets - Practical Example
import meshtastic
import meshtastic.serial_interface
from pubsub import pub
def on_receive(packet, interface):
hop_start = packet.get("hopStart", 0)
hop_limit = packet.get("hopLimit", 0)
hops_taken = hop_start - hop_limit
sender = packet.get("fromId", "unknown")
print(f"Packet from {sender}: {hops_taken} hop(s) away, hop_limit_remaining={hop_limit}")
pub.subscribe(on_receive, "meshtastic.receive")
iface = meshtastic.serial_interface.SerialInterface()
input("Listening... press Enter to exit
")
iface.close()
Running this script while observing mesh traffic gives a live view of hop distances for every packet, which is invaluable for understanding how your network topology performs in practice.
Common Network Problems and Solutions
Meshtastic networks are remarkably self-organizing, but they are not self-diagnosing. When the mesh stops working well - messages drop, nodes disappear, delivery becomes unreliable - there are a small number of root causes that account for the vast majority of problems. This page covers the four most common issues: high channel utilization, ghost nodes, routing loops, and clock skew, along with specific remediation steps for each.
Problem 1: High Channel Utilization
Symptoms
- Channel utilization percentage consistently above 25%.
- Messages sometimes fail to deliver even between nearby nodes.
- The node list shows many nodes as "last heard" minutes ago even though they are known to be online.
- ACKs are not received for sent messages despite short distances.
Root Cause
The main drivers of channel utilization are position, nodeinfo, and telemetry broadcasts. Every node broadcasts its GPS position on a smart interval (minimum interval) and an update interval, alongside periodic NodeInfo and telemetry. On a large mesh with 30+ nodes all broadcasting position every 15 minutes, the channel quickly fills. Each position packet is ~50 bytes; at LongFast (SF11/BW250/CR4-5) this is roughly 400 - 700 ms of airtime (use a LoRa time-on-air calculator for the exact value at your payload size). With 30 nodes broadcasting every 900 seconds, position alone consumes on the order of 30 * 0.5 / 900 ≈ 1.7% airtime continuously for a single transmission each - and that understates the real load, because each broadcast is rebroadcast by multiple nodes across the mesh, and additional overhead (ACKs, route discovery, telemetry) multiplies the effective utilization.
Solutions
-
Increase minimum and maximum position broadcast intervals.
In the Meshtastic app: Settings → Radio Configuration → Position → Broadcast Interval. For a community mesh where nodes are mostly stationary, intervals of 30 - 60 minutes are appropriate. Mobile nodes can use smart position which triggers a broadcast only when the node has moved more than a threshold distance.
meshtastic --set position.position_broadcast_secs 3600 meshtastic --set position.position_broadcast_smart_enabled true meshtastic --set position.gps_update_interval 120 -
Disable unnecessary telemetry modules.
Environment telemetry (temperature, humidity) and power telemetry broadcast at their own intervals. If sensors are not connected, disable the modules to stop unnecessary transmissions. Note: setting an update interval to 0 does not disable telemetry - a value of 0 reverts to the 30-minute (1800s) default. To actually stop the broadcasts, disable the measurement:
meshtastic --set telemetry.environment_measurement_enabled false meshtastic --set telemetry.power_measurement_enabled false -
Reduce hop limit on client nodes.
Most messages on a well-designed mesh reach their destination in 1 - 2 hops. Reducing the hop limit lowers the number of times a packet is relayed across the mesh, reducing airtime. Tune carefully: setting it too low can cut off nodes that genuinely need 3 hops to reach the rest of the network.
meshtastic --set lora.hop_limit 2 -
Switch to a lower-duty-cycle modem preset for dense networks.
The MediumFast preset (SF9/BW250) offers significantly shorter air time per packet at the cost of reduced range. For a dense urban mesh where all nodes are within 2 - 3 km of a relay, this trade-off is often worthwhile.
Problem 2: Ghost Nodes
Symptoms
- The node list contains nodes with "Last Heard" timestamps hours or days in the past that never update.
- The node count shown in the app is higher than the number of known active devices.
- Sending a message to a ghost node never gets ACKed.
Root Cause
Meshtastic stores a local database of every node it has ever heard. Nodes that leave the area, run out of battery, or are turned off permanently remain in the database indefinitely until manually cleared or the database fills and the oldest entry is evicted. These stale entries are "ghost nodes."
Ghost nodes are not just a cosmetic issue. On a network with strict channel utilization budgets, any mechanism that sends directed packets to ghost nodes (e.g., automated pings or position requests) wastes airtime. Additionally, some firmware versions attempt to route through recently-known paths, which can cause messages to fail if those paths included a now-offline ghost.
Solutions
-
Remove ghost nodes via the CLI:
# Remove a single node by its node ID. Replace !deadbeef with the # target node's actual ID (the !-prefixed hex shown in the node list): meshtastic --remove-node !deadbeef # Clear the entire node database (nuclear option) meshtastic --reset-nodedbCaution:
--reset-nodedbremoves all known nodes including active ones. The mesh will rebuild the database over the next few hours as nodes broadcast again. Only use this when the database is severely polluted. -
Remove ghost nodes via the app:
In the Android app, long-press a node in the node list and select Remove from node list. iOS uses a swipe-left gesture on the node row.
-
Note on node-info broadcast cadence:
The
device.node_info_broadcast_secssetting controls how often this node broadcasts its own NodeInfo (default 10800s / 3 hours, minimum 3600s). It does not configure automatic eviction of other nodes - there is no documented "not heard within this window, auto-evict" setting. Remove stale entries manually via the app or--remove-nodeas above.# Adjust how often THIS node advertises its NodeInfo (does not evict others): meshtastic --set device.node_info_broadcast_secs 10800
Problem 3: Routing Loops
Symptoms
- Channel utilization spikes suddenly and stays high.
- The
--listenoutput shows the same packet ID appearing many times from many different node IDs in rapid succession. - A high proportion of received packets are duplicates of ones already seen.
- Battery drain on relay nodes accelerates noticeably.
Root Cause
Meshtastic uses managed flooding: before rebroadcasting, a node listens briefly to see whether another node has already rebroadcast the packet, and it skips packets it has recently seen (tracked in a packet history). Each hop also decrements the hop limit, which bounds how long a packet can live. Apparent "loops" - the same packet circulating repeatedly - usually trace back to:
- Congestion / collisions: when the channel is busy, a node may not hear the earlier rebroadcast it would otherwise have suppressed against, so it rebroadcasts anyway.
- Misconfigured infrastructure roles: too many always-rebroadcasting nodes in one area increases redundant transmissions.
- Firmware bug: certain firmware versions had rebroadcast-suppression bugs that caused excess relaying. Updating firmware is the primary fix.
Solutions
-
Update to the latest stable firmware.
Many loop-related bugs have been fixed in 2.3+ firmware. Check github.com/meshtastic/firmware/releases for the current stable release and update all nodes.
-
Reduce hop limit to 2 or 1 for the duration of the incident.
A lower hop limit reduces the number of relays that participate, helping the rebroadcast-suppression mechanism contain the excess traffic:
meshtastic --set lora.hop_limit 2 -
Identify and temporarily disable the node that triggered the loop.
During a
--listensession, the node ID that appears most frequently as the source of duplicate bursts is often the loop originator or an amplifying relay. Temporarily taking that node off the air allows the loop to die out. -
Do not cluster multiple infrastructure relay nodes in the same RF coverage area.
Infrastructure roles (ROUTER, and the deprecated REPEATER) rebroadcast with higher priority - they rebroadcast even when they hear another rebroadcast. Clustering several of them in the same area increases collision probability and redundant relaying. Note: the older ROUTER_CLIENT role was removed in firmware 2.3.15; for infrastructure use ROUTER (or ROUTER_LATE), and for ordinary nodes use CLIENT. ROUTER nodes appear in the node list and run a full client; REPEATER nodes do not appear in the node list (and REPEATER itself was deprecated in firmware 2.7.11).
Problem 4: Clock Skew
Symptoms
- Message timestamps in the app appear wrong (hours off or showing Unix epoch 0).
- Nodes show "Last Heard" timestamps in the future.
- Position packets are ignored or treated as stale even for active nodes.
Root Cause
Meshtastic nodes use GPS time when GPS is available. Without GPS or NTP, most ESP32 and nRF52 boards have no battery-backed real-time clock, so the clock resets entirely on every power loss (it does not merely drift) and timestamps cannot be trusted until re-synced from GPS, NTP, or a connected app/PC. A node with a wrong clock produces incorrect "last heard" calculations and misleading message timestamps. (Clock skew does not cause packet-ID collisions - packet IDs are 32-bit identifiers, not timestamp-derived.)
Solutions
-
Enable GPS time synchronization.
Nodes with GPS hardware will sync their RTC from GPS time lock. Ensure GPS is enabled and the device has a clear sky view for at least 5 minutes after power-on to acquire a time fix.
-
Use NTP time synchronization via Wi-Fi.
ESP32 nodes connected to Wi-Fi can sync time via NTP. This happens automatically when Wi-Fi is connected. Ensuring your gateway node has working Wi-Fi keeps its clock accurate and propagates timestamps to the mesh through its packets.
-
Check and correct time via the Python CLI:
import meshtastic import meshtastic.serial_interface import time iface = meshtastic.serial_interface.SerialInterface() # The library sets the RTC on connect if the local system clock is accurate # You can also check the current node time: info = iface.getMyNodeInfo() print("Node time:", info.get("localConfig", {}).get("device", {}).get("nodeInfoBroadcastSecs")) iface.close()Simply connecting with the Python library sets the node's time to the host computer's system clock, which is a quick fix for a clock-skewed node when you have USB access.
-
For nodes without GPS or Wi-Fi: use the Meshtastic app to sync time on connect.
The mobile app automatically sends the phone's current time to the node when it connects over BLE. Briefly connecting the app to a clock-skewed node is often the easiest fix in the field.
Quick Reference: Problem-to-Solution Matrix
| Symptom | Most Likely Cause | First Action |
|---|---|---|
| Channel utilization > 25% | Frequent position, nodeinfo, and telemetry broadcasts (plus message traffic and hop count) | Increase position broadcast interval to 1800 - 3600s; trim telemetry |
| Nodes in list never heard | Ghost nodes in DB | Remove stale nodes via app or --remove-node |
| Same packet seen 10+ times | Excess rebroadcasting (congestion or misconfigured roles) | Update firmware; reduce hop_limit to 2 |
| Timestamps wrong / future dates | Clock skew (no GPS/NTP) | Connect app or CLI to sync time from phone/PC |
| ACKs missing, good SNR | Congested channel saturating ACK window | Reduce channel utilization; check for loops |
Building a Meshtastic Network Map
A network map shows you which nodes can hear each other, the quality of each link, and how messages actually route through your network. Building and maintaining an accurate map is essential for optimizing coverage and identifying problem areas.
What to Map
A useful network map shows:
- Geographic position of all nodes (lat/lon from GPS or fixed position setting)
- Link quality between neighboring nodes (SNR)
- Hop counts between all node pairs
- Node roles — common roles include CLIENT, ROUTER, and ROUTER_LATE; the full set also includes CLIENT_MUTE, CLIENT_HIDDEN, TRACKER, SENSOR, TAK, TAK_TRACKER, LOST_AND_FOUND, and REPEATER (REPEATER is deprecated). Do not assume only three exist.
- Last-heard timestamps (to distinguish active vs. stale nodes)
Using meshmap.net
meshmap.net aggregates position data from Meshtastic nodes that have position reporting and the MQTT gateway enabled. It provides a global view of the Meshtastic community:
- Zoom to your region to see local node density
- Click a node for details: ID, last heard, firmware version, battery level (if reporting telemetry)
- Use the time filter to see nodes active in the last 24 hours vs. all time
Limitation: Only shows nodes that uplink to the public MQTT broker (GPS/position enabled and a reachable MQTT gateway). meshmap.net is a third-party, opt-in service. Many private community networks don't use the public MQTT server.
Privacy warning: enabling position reporting plus MQTT to the public broker publishes your node's location to the public internet (meshmap.net and any other broker subscriber), potentially indefinitely. Only do this for nodes whose location you intend to be public. For fixed sites you don't want pinpointed, use a coarsened or deliberately offset fixed position.
Building a Private Community Map
For a private community network not connected to the public MQTT, build your own map:
# Option 1: Self-hosted Meshtastic map
# Run a local MQTT broker + a self-hosted map application.
# See github.com/brianshea2/meshmap.net or github.com/liamcottle/meshtastic-map
# for self-hostable map projects (verify the repo before deploying).
# Option 2: Grafana Geomap panel with InfluxDB
# Telemetry data (positions) -> InfluxDB -> Grafana Geomap panel
# Shows all nodes with position and telemetry data
# Option 3: Simple Python script to generate KML
import json, paho.mqtt.client as mqtt
nodes = {}
def on_message(client, userdata, msg):
# Parse position packets and build nodes dict
# Export to KML for Google Earth or KMZ for Google Maps
pass
Link Quality Analysis
Meshtastic's Neighbor Info module reports each node's heard neighbors and the SNR of those links (it carries SNR, not per-neighbor RSSI). Enable it on key backbone nodes — but note it is airtime-intensive and is discouraged on the default public channel:
# Enable Neighbor Info module:
meshtastic --set neighbor_info.enabled true
# Interval cannot be set below 14400 s (4 hours); default is 21600 s (6 hours).
meshtastic --set neighbor_info.update_interval 21600 # 6 hours (default)
With Neighbor Info data flowing through MQTT, you can build a heat map of link quality across your network. The usable SNR floor depends on the modem preset (roughly -7.5 dB at ShortFast/SF7 down to about -20 dB at SF12; around -17 dB on the default LongFast/SF11). A fixed "-10 dB" rule of thumb only fits mid presets — on LongFast a -10 dB link still has healthy margin. Treat links within a few dB of the preset's decode floor (on LongFast, roughly worse than -14 dB) as weak candidates for repeater insertion or antenna improvement.
Topology Visualization Tools
- Gephi - Open-source network visualization. Export node list and edge list from your MQTT data; import into Gephi for force-directed layout visualization of your mesh topology.
- NetworkX (Python) - Build a graph of your mesh programmatically; use matplotlib for visualization; calculate graph properties (diameter, clustering coefficient, cut vertices).
- MeshView - Community tool that parses Meshtastic position and neighbor data to produce a topology map. Check GitHub for current maintained versions.
Meshtastic Python API
Complete guide to the Meshtastic Python library: connection, messaging, automation, and API reference.
Getting Started with the Meshtastic Python Library
The Meshtastic Python library (meshtastic on PyPI) provides a clean API for
connecting to Meshtastic devices, reading their state, sending messages, and reacting to
received packets via callbacks. This page covers installation, all three connection methods
(serial, TCP, BLE), and complete working code examples for the most common operations.
Installation
# Core library (serial + TCP + BLE all included)
pip install meshtastic
# Development / latest from GitHub
pip install git+https://github.com/meshtastic/python.git
The library requires Python 3.9+. As of recent releases, bleak (which provides BLE
support) is a mandatory core dependency and is always installed - the old [ble] extra
no longer exists, so pip install meshtastic gives you serial, TCP, and BLE. Its core
runtime dependencies include pyserial, protobuf, pypubsub,
bleak, tabulate, requests, pyyaml, and
packaging.
Connecting via Serial
import meshtastic
import meshtastic.serial_interface
# Auto-detect first available Meshtastic device
iface = meshtastic.serial_interface.SerialInterface()
# Specify a port explicitly
iface = meshtastic.serial_interface.SerialInterface(devPath="/dev/ttyUSB0")
iface = meshtastic.serial_interface.SerialInterface(devPath="COM4") # Windows
When a SerialInterface is created, the library:
- Opens the serial port at 115200 baud.
- Performs a handshake to confirm the device is running Meshtastic firmware.
- Downloads the full node database and device configuration from the node.
The constructor blocks until the download completes (typically 1 - 3 seconds). After that
the iface object is fully populated.
Connecting via TCP
import meshtastic.tcp_interface
# Connect to a node by IP address
iface = meshtastic.tcp_interface.TCPInterface(hostname="192.168.1.42")
# Connect by mDNS hostname
iface = meshtastic.tcp_interface.TCPInterface(hostname="meshtastic.local")
TCP connection is useful for nodes deployed without USB access. The default port is 4403;
you can override it with the portNumber parameter. The TCP interface requires the
node to have Wi-Fi enabled and the TCP API enabled in Radio Configuration.
Connecting via BLE
import meshtastic.ble_interface
# Scan for available BLE devices.
# BLEInterface.scan() is a synchronous @staticmethod - do NOT await it.
devices = meshtastic.ble_interface.BLEInterface.scan()
for d in devices:
print(d.name, d.address)
# Connect by device name or MAC address
iface = meshtastic.ble_interface.BLEInterface("Meshtastic_abcd")
Reading Device Info
import meshtastic
import meshtastic.serial_interface
import json
iface = meshtastic.serial_interface.SerialInterface()
# My node info
my_info = iface.getMyNodeInfo()
print("My node number:", my_info["num"])
print("My user:", my_info.get("user", {}).get("longName"))
# Full local config as a protobuf object.
# localConfig lives on the Node object, not on the interface.
config = iface.localNode.localConfig
print("Hop limit:", config.lora.hop_limit)
print("Region:", config.lora.region)
# Channel config (channels are held on the local Node)
for ch in iface.localNode.channels:
print(f"Channel {ch.index}: role={ch.role}, name={ch.settings.name or 'default'}")
iface.close()
Listing Nodes
import meshtastic
import meshtastic.serial_interface
import time
iface = meshtastic.serial_interface.SerialInterface()
nodes = iface.nodes # dict keyed by "!<hex_node_id>"
for node_id, node in nodes.items():
user = node.get("user", {})
pos = node.get("position", {})
metrics = node.get("deviceMetrics", {})
last_heard = node.get("lastHeard", 0)
age_minutes = (time.time() - last_heard) / 60 if last_heard else None
print(f"Node: {node_id}")
print(f" Name: {user.get('longName', 'Unknown')}")
print(f" Short name: {user.get('shortName', '???')}")
print(f" Hardware: {user.get('hwModel', 'Unknown')}")
if pos.get("latitudeI"):
lat = pos["latitudeI"] / 1e7
lon = pos["longitudeI"] / 1e7
print(f" Position: {lat:.5f}, {lon:.5f} alt={pos.get('altitude', 0)}m")
if metrics:
print(f" Battery: {metrics.get('batteryLevel', '?')}%")
print(f" Chan util: {metrics.get('channelUtilization', '?')}%")
print(f" SNR: {node.get('snr', 'N/A')} dB")
if age_minutes is not None:
print(f" Last heard: {age_minutes:.1f} min ago")
print()
iface.close()
Sending a Text Message
import meshtastic
import meshtastic.serial_interface
iface = meshtastic.serial_interface.SerialInterface()
# Broadcast to all nodes on the primary channel
iface.sendText("Hello mesh!")
# Send to a specific node (direct message)
iface.sendText("Hello from Python", destinationId="!aabbccdd")
# Send on a secondary channel (index 1)
iface.sendText("Private message", channelIndex=1)
# Fire-and-forget with wantAck set (ACK is NOT checked here - see callbacks below)
import time
iface.sendText("Test with ACK", wantAck=True)
time.sleep(5) # gives the packet time to send; does not confirm delivery
iface.close()
The sendText method returns immediately after queuing the packet. The example above
is fire-and-forget: setting wantAck=True and sleeping does not confirm delivery.
For confirmed delivery you must listen for the ACK packet via a callback (see below).
A "direct message" is only end-to-end encrypted (PKC) when both nodes run firmware v2.5 or newer; otherwise the DM is encrypted only with the channel PSK and is readable by anyone sharing that channel.
Receiving Messages with Callbacks
The library uses pypubsub for its event system. Subscribe to topics before
creating the interface, or add subscriptions after connection. The main topics are:
meshtastic.receive- all decoded packets from the mesh.meshtastic.receive.text- text message packets only.meshtastic.receive.position- position packets.meshtastic.receive.telemetry- telemetry packets.meshtastic.receive.user- node-info (user) packets.meshtastic.connection.established- fired when connection and download complete.meshtastic.connection.lost- fired on disconnect.
import meshtastic
import meshtastic.serial_interface
from pubsub import pub
import time
def on_connect(interface, topic=pub.AUTO_TOPIC):
# Called when the library finishes downloading node data.
print(f"Connected! Nodes in mesh: {len(interface.nodes)}")
def on_receive(packet, interface):
# Called for every decoded packet from the mesh.
port_num = packet.get("decoded", {}).get("portnum", "UNKNOWN")
from_id = packet.get("fromId", "?")
to_id = packet.get("toId", "?")
hops = packet.get("hopStart", 0) - packet.get("hopLimit", 0)
if port_num == "TEXT_MESSAGE_APP":
text = packet["decoded"].get("text", "")
print(f"[TEXT] {from_id} -> {to_id} ({hops} hop(s)): {text}")
elif port_num == "POSITION_APP":
pos = packet["decoded"].get("position", {})
lat = pos.get("latitudeI", 0) / 1e7
lon = pos.get("longitudeI", 0) / 1e7
print(f"[POSITION] {from_id}: {lat:.5f}, {lon:.5f}")
elif port_num == "TELEMETRY_APP":
tel = packet["decoded"].get("telemetry", {})
dm = tel.get("deviceMetrics", {})
# channelUtilization is often absent on a given packet - guard before formatting
cu = dm.get("channelUtilization")
cu_str = f"{cu:.1f}%" if cu is not None else "N/A"
print(f"[TELEMETRY] {from_id}: battery={dm.get('batteryLevel')}%, "
f"chan_util={cu_str}")
def on_text_receive(packet, interface):
# Called only for TEXT_MESSAGE_APP packets.
text = packet.get("decoded", {}).get("text", "")
print(f"Text message received: {text!r}")
pub.subscribe(on_connect, "meshtastic.connection.established")
pub.subscribe(on_receive, "meshtastic.receive")
pub.subscribe(on_text_receive, "meshtastic.receive.text")
iface = meshtastic.serial_interface.SerialInterface()
try:
print("Listening for packets. Press Ctrl-C to exit.")
while True:
time.sleep(1)
except KeyboardInterrupt:
pass
finally:
iface.close()
Complete Minimal Example: Echo Bot
# echo_bot.py -- Meshtastic echo bot
# Responds to any text message with "Echo: <original message>"
import meshtastic
import meshtastic.serial_interface
from pubsub import pub
import time
iface = None
def on_receive(packet, interface):
decoded = packet.get("decoded", {})
if decoded.get("portnum") == "TEXT_MESSAGE_APP":
text = decoded.get("text", "")
from_id = packet.get("fromId")
my_id = interface.getMyNodeInfo()["user"]["id"]
# Don't echo our own messages
if from_id != my_id:
interface.sendText(f"Echo: {text}", destinationId=from_id)
print(f"Echoed to {from_id}: {text!r}")
pub.subscribe(on_receive, "meshtastic.receive")
iface = meshtastic.serial_interface.SerialInterface()
print("Echo bot running. Press Ctrl-C to stop.")
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
pass
finally:
if iface:
iface.close()
Closing the Interface
Always call iface.close() when done. This cleanly shuts down the background
receiver thread and closes the serial/TCP/BLE connection. Failing to close the interface can
leave the serial port locked, preventing other tools (the app, another script) from connecting.
# Best practice: use a try/finally block
iface = meshtastic.serial_interface.SerialInterface()
try:
# ... your code ...
pass
finally:
iface.close()
Automating Meshtastic: Practical Scripts
The Meshtastic Python library enables powerful automation workflows. This page provides four complete, ready-to-use scripts: a position logger, a message forwarder to Telegram, a battery monitor with alerts, and an automated network health reporter. Each script is self-contained and includes setup instructions.
Script 1: Position Logger to CSV
Logs GPS position updates from all mesh nodes to a CSV file in real time. Useful for tracking mobile assets, recording deployment surveys, or building a trace of network coverage over time. Privacy note: this logger writes every observed node's GPS coordinates to a plaintext file. Treat that file as sensitive location data and protect it accordingly.
# position_logger.py
# Logs all position packets from the Meshtastic mesh to a CSV file.
#
# Usage:
# pip install meshtastic
# python position_logger.py [--port /dev/ttyUSB0] [--output positions.csv]
import argparse
import csv
import datetime
import os
import sys
import time
import meshtastic
import meshtastic.serial_interface
import meshtastic.tcp_interface
from pubsub import pub
OUTPUT_FILE = "positions.csv"
CSV_FIELDS = ["timestamp", "node_id", "long_name", "short_name",
"latitude", "longitude", "altitude_m", "speed_kmh",
"heading_deg", "snr_db", "hop_count"]
def get_interface(args):
if args.host:
return meshtastic.tcp_interface.TCPInterface(hostname=args.host)
port = args.port or None
return meshtastic.serial_interface.SerialInterface(devPath=port)
def main():
parser = argparse.ArgumentParser(description="Log Meshtastic positions to CSV")
parser.add_argument("--port", help="Serial port (e.g. /dev/ttyUSB0 or COM4)")
parser.add_argument("--host", help="TCP hostname or IP address")
parser.add_argument("--output", default=OUTPUT_FILE, help="Output CSV file path")
args = parser.parse_args()
file_exists = os.path.isfile(args.output)
outfile = open(args.output, "a", newline="", encoding="utf-8")
writer = csv.DictWriter(outfile, fieldnames=CSV_FIELDS)
if not file_exists:
writer.writeheader()
iface_ref = [None] # mutable container for the interface
def on_position(packet, interface):
decoded = packet.get("decoded", {})
pos = decoded.get("position", {})
if not pos.get("latitudeI"):
return # no valid fix
node_id = packet.get("fromId", "unknown")
nodes = interface.nodes or {}
node_info = nodes.get(node_id, {})
user = node_info.get("user", {})
lat = pos["latitudeI"] / 1e7
lon = pos["longitudeI"] / 1e7
alt = pos.get("altitude", 0)
speed = pos.get("groundSpeed", 0)
# NOTE: verify the groundTrack/heading scaling in the Position protobuf for your
# firmware version before dividing by 100; the field's units have varied.
heading = pos.get("groundTrack", 0)
snr = node_info.get("snr", "")
# Guard against missing hopStart/hopLimit; record None rather than a bogus 0.
if "hopStart" in packet and "hopLimit" in packet:
hops = packet["hopStart"] - packet["hopLimit"]
else:
hops = None
row = {
"timestamp": datetime.datetime.now(datetime.timezone.utc).isoformat(),
"node_id": node_id,
"long_name": user.get("longName", ""),
"short_name": user.get("shortName", ""),
"latitude": f"{lat:.7f}",
"longitude": f"{lon:.7f}",
"altitude_m": alt,
"speed_kmh": speed * 3.6 if speed else 0,
"heading_deg": heading / 100 if heading else 0,
"snr_db": snr,
"hop_count": hops,
}
writer.writerow(row)
outfile.flush()
print(f"[{row['timestamp']}] {node_id} ({row['long_name']}) "
f"@ {lat:.5f},{lon:.5f} alt={alt}m")
pub.subscribe(on_position, "meshtastic.receive.position")
iface = get_interface(args)
iface_ref[0] = iface
print(f"Logging positions to {args.output}. Press Ctrl-C to stop.")
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
pass
finally:
iface.close()
outfile.close()
print("Logger stopped.")
if __name__ == "__main__":
main()
Script 2: Message Forwarder to Telegram
Forwards all text messages received on the mesh to a Telegram chat. Requires a Telegram bot
token (create one via @BotFather) and a chat ID. This is a popular pattern for
monitoring a community mesh from a smartphone without needing Bluetooth or Wi-Fi proximity to
a node.
# mesh_to_telegram.py
# Forwards Meshtastic text messages to a Telegram chat.
#
# Setup:
# pip install meshtastic requests
# Set environment variables:
# TELEGRAM_TOKEN=<your bot token>
# TELEGRAM_CHAT_ID=<chat id, e.g. -1001234567890>
# python mesh_to_telegram.py [--host 192.168.1.42]
import os
import sys
import time
import datetime
import requests
import meshtastic
import meshtastic.serial_interface
import meshtastic.tcp_interface
from pubsub import pub
TELEGRAM_TOKEN = os.environ.get("TELEGRAM_TOKEN", "")
TELEGRAM_CHAT_ID = os.environ.get("TELEGRAM_CHAT_ID", "")
if not TELEGRAM_TOKEN or not TELEGRAM_CHAT_ID:
print("ERROR: Set TELEGRAM_TOKEN and TELEGRAM_CHAT_ID environment variables.")
sys.exit(1)
def send_telegram(text: str):
# Send a message to the configured Telegram chat.
url = f"https://api.telegram.org/bot{TELEGRAM_TOKEN}/sendMessage"
data = {"chat_id": TELEGRAM_CHAT_ID, "text": text, "parse_mode": "HTML"}
try:
resp = requests.post(url, json=data, timeout=10)
resp.raise_for_status()
except requests.RequestException as exc:
print(f"Telegram send failed: {exc}")
def on_receive(packet, interface):
decoded = packet.get("decoded", {})
if decoded.get("portnum") != "TEXT_MESSAGE_APP":
return
text = decoded.get("text", "")
from_id = packet.get("fromId", "unknown")
to_id = packet.get("toId", "^all")
hops = (packet["hopStart"] - packet["hopLimit"]
if "hopStart" in packet and "hopLimit" in packet else None)
timestamp = datetime.datetime.now(datetime.timezone.utc).strftime("%H:%M:%S UTC")
nodes = interface.nodes or {}
node_info = nodes.get(from_id, {})
long_name = node_info.get("user", {}).get("longName", from_id)
dest_str = "broadcast" if to_id in ("^all", "4294967295") else to_id
tg_msg = (
f"Mesh Message [{timestamp}]
"
f"From: {long_name} ({from_id})
"
f"To: {dest_str} | {hops} hop(s)
"
f"
{text}"
)
print(f"Forwarding to Telegram: {text!r} from {long_name}")
send_telegram(tg_msg)
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--host", help="TCP hostname or IP")
parser.add_argument("--port", help="Serial port")
args = parser.parse_args()
pub.subscribe(on_receive, "meshtastic.receive.text")
if args.host:
iface = meshtastic.tcp_interface.TCPInterface(hostname=args.host)
else:
iface = meshtastic.serial_interface.SerialInterface(devPath=args.port)
print(f"Forwarding mesh messages to Telegram chat {TELEGRAM_CHAT_ID}. Press Ctrl-C to stop.")
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
pass
finally:
iface.close()
Script 3: Battery Monitor with Alerts
Monitors battery levels of all nodes in the mesh and sends a text-message alert over the mesh when any node's battery falls below a configurable threshold. Useful for solar-powered relay nodes where low battery means imminent network degradation.
# battery_monitor.py
# Sends a mesh alert when any node battery drops below a threshold.
#
# Usage:
# python battery_monitor.py [--threshold 20] [--interval 300]
import argparse
import time
import meshtastic
import meshtastic.serial_interface
from pubsub import pub
ALERT_COOLDOWN = {} # node_id -> last_alert_time to avoid spamming
def check_battery(iface, threshold, interval):
# Periodically check all nodes and alert on low battery.
while True:
time.sleep(interval)
nodes = iface.nodes or {}
now = time.time()
for node_id, node_data in nodes.items():
metrics = node_data.get("deviceMetrics", {})
battery = metrics.get("batteryLevel")
if battery is None:
continue # no telemetry available
# Skip nodes that are charging (level > 100 indicates USB power on some firmware)
if battery > 100:
continue
if battery < threshold:
last_alert = ALERT_COOLDOWN.get(node_id, 0)
# Only alert once per hour per node
if now - last_alert > 3600:
long_name = node_data.get("user", {}).get("longName", node_id)
alert_msg = (
f"LOW BATTERY ALERT: {long_name} ({node_id}) "
f"is at {battery}%"
)
print(alert_msg)
# Broadcast alert on the primary channel
iface.sendText(alert_msg)
ALERT_COOLDOWN[node_id] = now
def main():
parser = argparse.ArgumentParser(description="Meshtastic battery monitor")
parser.add_argument("--threshold", type=int, default=20,
help="Battery percentage threshold for alerts (default: 20)")
parser.add_argument("--interval", type=int, default=300,
help="Check interval in seconds (default: 300)")
parser.add_argument("--port", help="Serial port")
parser.add_argument("--host", help="TCP hostname")
args = parser.parse_args()
if args.host:
import meshtastic.tcp_interface
iface = meshtastic.tcp_interface.TCPInterface(hostname=args.host)
else:
iface = meshtastic.serial_interface.SerialInterface(devPath=args.port)
print(f"Battery monitor started. Alert threshold: {args.threshold}%. "
f"Check interval: {args.interval}s.")
import threading
t = threading.Thread(target=check_battery,
args=(iface, args.threshold, args.interval),
daemon=True)
t.start()
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
pass
finally:
iface.close()
if __name__ == "__main__":
main()
Script 4: Automated Network Health Reporter
Generates a periodic network health report and either prints it to the console or sends it as a mesh broadcast. Summarizes node count, online/offline status, average SNR, channel utilization, and identifies any nodes not heard in the last configured window.
# health_reporter.py
# Generates periodic Meshtastic network health reports.
#
# Usage:
# python health_reporter.py [--interval 3600] [--broadcast]
import argparse
import time
import datetime
import meshtastic
import meshtastic.serial_interface
import meshtastic.tcp_interface
def generate_report(iface, offline_threshold_minutes=60) -> str:
# Build a health report string from current node data.
nodes = iface.nodes or {}
now = time.time()
total = len(nodes)
online = []
offline = []
snr_values = []
util_values = []
for node_id, node_data in nodes.items():
last_heard = node_data.get("lastHeard", 0)
age_min = (now - last_heard) / 60 if last_heard else None
long_name = node_data.get("user", {}).get("longName", node_id)
metrics = node_data.get("deviceMetrics", {})
snr = node_data.get("snr")
util = metrics.get("channelUtilization")
if snr is not None:
snr_values.append(snr)
if util is not None:
util_values.append(util)
if age_min is not None and age_min < offline_threshold_minutes:
online.append((long_name, age_min, snr))
else:
offline.append((long_name, age_min))
avg_snr = sum(snr_values) / len(snr_values) if snr_values else None
avg_util = sum(util_values) / len(util_values) if util_values else None
lines = [
f"=== Mesh Health Report {datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%d %H:%M UTC')} ===",
f"Total nodes: {total} | Online (<{offline_threshold_minutes}m): {len(online)}"
f" | Offline: {len(offline)}",
]
if avg_util is not None:
# These bands are custom to this script. The app's conventional channel-utilization
# bands are roughly green < 25% / orange 25-50% / red > 50%.
health = "OK" if avg_util < 15 else "WARN" if avg_util < 25 else "HIGH"
lines.append(f"Avg channel utilization: {avg_util:.1f}% [{health}]")
if avg_snr is not None:
lines.append(f"Avg SNR (last heard): {avg_snr:.1f} dB")
if offline:
lines.append(f"Offline nodes ({len(offline)}):")
for name, age in offline:
age_str = f"{age:.0f}m ago" if age is not None else "never"
lines.append(f" - {name}: last heard {age_str}")
return "
".join(lines)
def main():
parser = argparse.ArgumentParser(description="Meshtastic network health reporter")
parser.add_argument("--interval", type=int, default=3600,
help="Report interval in seconds (default: 3600)")
parser.add_argument("--broadcast", action="store_true",
help="Broadcast report as mesh text message")
parser.add_argument("--offline", type=int, default=60,
help="Minutes without a packet to consider a node offline (default: 60)")
parser.add_argument("--port", help="Serial port")
parser.add_argument("--host", help="TCP hostname")
args = parser.parse_args()
if args.host:
iface = meshtastic.tcp_interface.TCPInterface(hostname=args.host)
else:
iface = meshtastic.serial_interface.SerialInterface(devPath=args.port)
print("Health reporter running. First report in", args.interval, "seconds.")
try:
while True:
time.sleep(args.interval)
report = generate_report(iface, offline_threshold_minutes=args.offline)
print(report)
print()
if args.broadcast:
# Mesh text messages cap at ~200 bytes of application payload; send a summary
nodes = iface.nodes or {}
now = time.time()
online = sum(
1 for n in nodes.values()
if (now - n.get("lastHeard", 0)) / 60 < args.offline
)
metrics = [n.get("deviceMetrics", {}).get("channelUtilization")
for n in nodes.values()
if n.get("deviceMetrics", {}).get("channelUtilization") is not None]
avg_util = sum(metrics) / len(metrics) if metrics else 0
short_report = (
f"Mesh status: {online}/{len(nodes)} online, "
f"chan util {avg_util:.1f}%"
)
iface.sendText(short_report)
print(f"Broadcast: {short_report!r}")
except KeyboardInterrupt:
pass
finally:
iface.close()
if __name__ == "__main__":
main()
Meshtastic Python API Reference
This page documents the key classes, methods, and patterns of the Meshtastic Python
library. It covers the interface classes (the MeshInterface base class plus
three concrete transports: Serial, TCP, and BLE), the event system, protobuf message types,
and error handling. Refer to the library's source on GitHub and the API docs at
python.meshtastic.org for the full, authoritative method signatures as the API evolves with
each firmware release.
Interface Class Hierarchy
MeshInterface (base class, meshtastic.mesh_interface)
├── SerialInterface (meshtastic.serial_interface)
├── TCPInterface (meshtastic.tcp_interface)
└── BLEInterface (meshtastic.ble_interface)
The three concrete transports inherit the common message-sending, node-state, and
configuration API from MeshInterface, but they are not identical: each subclass
adds transport-specific constructor parameters (e.g. SerialInterface
devPath, TCPInterface hostname, BLEInterface
address) and some transport-specific helper methods. The shared send/read/config
surface is inherited from the base class.
MeshInterface (Base Class)
Constructor Parameters
The MeshInterface base constructor takes exactly three parameters:
__init__(self, debugOut=None, noProto=False, noNodes=False). There is no
configTimeout parameter.
| Parameter | Type | Description |
|---|---|---|
debugOut | file-like | Stream for debug output. Default None. |
noProto | bool | Skip protocol handshake (testing only). Default False. |
noNodes | bool | Skip downloading node database on connect. Default False. |
Key Properties
Note: configuration and channel data physically live on the local Node object,
reachable via iface.localNode (e.g. iface.localNode.localConfig,
iface.localNode.moduleConfig, iface.localNode.channels). The rows
below describe those objects' shapes.
| Property / Path | Type | Description |
|---|---|---|
nodes |
Optional[dict[str, dict]] |
All known nodes keyed by "!<hex_id>" (None before connect). Each
value is typically a dict with num, user, position,
snr, lastHeard, deviceMetrics — though
position/deviceMetrics are not guaranteed present on every node. |
myInfo |
protobuf MyNodeInfo (Optional) |
Basic info about the local node. It is a protobuf object, not a plain dict; the
node-number field is my_node_num. |
metadata |
protobuf DeviceMetadata |
Firmware version, hardware model, capability flags (verify it is populated post-connect in your installed library version before relying on it). |
localNode.localConfig |
protobuf LocalConfig |
Full device configuration: .lora, .device,
.position, .power, .network,
.display, .bluetooth, .security. |
localNode.moduleConfig |
protobuf LocalModuleConfig |
Module configs: .mqtt, .serial, .telemetry,
.neighbor_info, .ambient_lighting, etc. |
localNode.channels |
list[protobuf Channel] |
List of channel objects (accessed via the local Node, not a public
localChannels property on the interface). Each has .index,
.role, .settings (name, PSK, etc.). |
Key Methods
# Send a text message
iface.sendText(
text: str,
destinationId: Union[int, str] = BROADCAST_ADDR, # "^all" or "!aabbccdd"
wantAck: bool = False,
wantResponse: bool = False,
onResponse: Optional[Callable] = None,
channelIndex: int = 0,
portNum: PortNum = portnums_pb2.PortNum.TEXT_MESSAGE_APP,
)
# Send raw data (any port number)
iface.sendData(
data: bytes,
destinationId: Union[int, str] = BROADCAST_ADDR,
portNum: int = portnums_pb2.PortNum.PRIVATE_APP,
wantAck: bool = False,
wantResponse: bool = False,
onResponse: Optional[Callable] = None,
onResponseAckPermitted: bool = False,
channelIndex: int = 0,
hopLimit: Optional[int] = None,
pkiEncrypted: bool = False,
publicKey: Optional[bytes] = None,
priority: MeshPacket.Priority = MeshPacket.Priority.RELIABLE,
)
# Get my node info (None if not connected)
iface.getMyNodeInfo() -> Optional[Dict]
# The node number is read from getMyNodeInfo()["num"] or iface.myInfo.my_node_num
# (there is no getMyNodeNum() method on MeshInterface).
# Send a traceroute request (hopLimit is required, no default)
iface.sendTraceRoute(
dest: Union[int, str],
hopLimit: int,
channelIndex: int = 0,
)
# Node-database and config writes are methods on the Node object, NOT on
# the base MeshInterface. Obtain the local Node via iface.localNode (or
# iface.getNode("^local")):
# Remove a node from the local database
iface.localNode.removeNode(nodeId: str) # nodeId: "!aabbccdd"
# (or use the CLI: meshtastic --remove-node ...)
# Write a config change to the device
# (after modifying iface.localNode.localConfig protobuf object)
iface.localNode.writeConfig(config_name: str)
# config_name is one of: "device", "position", "power", "network",
# "display", "lora", "bluetooth", "security"
# Write module config
iface.localNode.writeModuleConfig(config_name: str)
# config_name is one of: "mqtt", "serial", "external_notification",
# "store_forward", "range_test", "telemetry",
# "canned_message", "audio", "remote_hardware",
# "neighbor_info", "ambient_lighting", "detection_sensor"
# Cleanly close the connection
iface.close() -> None
SerialInterface
class meshtastic.serial_interface.SerialInterface(
devPath: Optional[str] = None, # e.g. "/dev/ttyUSB0", "COM4"; None = auto-detect
debugOut=None,
noProto: bool = False,
connectNow: bool = True,
noNodes: bool = False,
)
Auto-detection scans /dev/ttyUSB*, /dev/ttyACM*,
/dev/cu.usbserial-*, and Windows COM ports for a device that responds to the
Meshtastic handshake.
TCPInterface
class meshtastic.tcp_interface.TCPInterface(
hostname: str, # IP address or mDNS hostname
debugOut=None,
noProto: bool = False,
connectNow: bool = True,
portNumber: int = 4403, # TCP port; default is 4403
noNodes: bool = False,
)
BLEInterface
class meshtastic.ble_interface.BLEInterface(
address: str, # Device name or MAC address
noProto: bool = False,
debugOut=None,
noNodes: bool = False,
)
# Static method: scan for devices (synchronous, do NOT await it)
BLEInterface.scan() -> list[BLEDevice]
Event System (pypubsub Topics)
The library uses pypubsub for all asynchronous notifications. Subscribe before
or after creating the interface; the subscription takes effect for all future events.
from pubsub import pub
# All received packets
pub.subscribe(callback, "meshtastic.receive")
# Filtered by port (the trailing segment is the protocol name)
pub.subscribe(callback, "meshtastic.receive.text") # TEXT_MESSAGE_APP
pub.subscribe(callback, "meshtastic.receive.position") # POSITION_APP
pub.subscribe(callback, "meshtastic.receive.user") # NODEINFO_APP
pub.subscribe(callback, "meshtastic.receive.telemetry") # TELEMETRY_APP (derived from the
# protocol registry name; verify against your library's dispatch path)
pub.subscribe(callback, "meshtastic.receive.routing") # ROUTING_APP; note ACK/NAK delivery
# to callbacks depends on the response (ackPermitted) handlers, not solely this topic
pub.subscribe(callback, "meshtastic.receive.data.<portnum>") # <portnum> is the integer
# port or PortNum enum name (e.g. meshtastic.receive.data.1); no "portnum_" prefix
# Connection lifecycle
pub.subscribe(callback, "meshtastic.connection.established") # on connect + data download
pub.subscribe(callback, "meshtastic.connection.lost") # on disconnect
pub.subscribe(callback, "meshtastic.node.updated") # when node DB entry changes
Callback Signatures
# For meshtastic.receive.*
def on_receive(packet: dict, interface: MeshInterface) -> None:
...
# For meshtastic.connection.established
def on_connect(interface: MeshInterface, topic=pub.AUTO_TOPIC) -> None:
...
# For meshtastic.connection.lost
def on_lost(interface: MeshInterface, topic=pub.AUTO_TOPIC) -> None:
...
Packet Dictionary Structure
{
"id": 4012345678, # uint32 packet ID
"from": 2864434397, # sender node number (integer)
"fromId": "!aabbccdd", # sender node ID (hex string)
"to": 4294967295, # destination (4294967295 = broadcast)
"toId": "^all", # destination as string
"hopLimit": 3, # remaining hops
"hopStart": 3, # original hop limit
"rxSnr": 4.25, # SNR at receiver (dB)
"rxRssi": -98, # RSSI at receiver (dBm)
"rxTime": 1714010000, # Unix time packet was received
"viaMqtt": False, # True if packet came via MQTT gateway
"channel": 0, # channel index
"decoded": {
"portnum": "TEXT_MESSAGE_APP",
"text": "Hello mesh!", # present for TEXT_MESSAGE_APP
"position": { ... }, # present for POSITION_APP
"telemetry": { ... }, # present for TELEMETRY_APP
"user": { ... }, # present for NODEINFO_APP
"routing": { ... }, # present for ROUTING_APP
},
"raw": <protobuf MeshPacket object>
}
Protobuf Message Types
The library exposes raw protobuf objects for advanced use. The most important generated modules in the package are:
| Module | Key Message Types |
|---|---|
meshtastic.mesh_pb2 |
MeshPacket, NodeInfo, User, Position,
Data, Routing |
meshtastic.config_pb2 |
Config, Config.DeviceConfig, Config.LoRaConfig,
Config.PositionConfig, Config.NetworkConfig |
meshtastic.module_config_pb2 |
ModuleConfig, ModuleConfig.MQTTConfig,
ModuleConfig.TelemetryConfig |
meshtastic.telemetry_pb2 |
Telemetry, DeviceMetrics, EnvironmentMetrics,
PowerMetrics |
meshtastic.portnums_pb2 |
PortNum enum (TEXT_MESSAGE_APP, POSITION_APP, TELEMETRY_APP, etc.) |
meshtastic.channel_pb2 |
Channel, ChannelSettings |
Example: Modifying a Config Setting
import meshtastic
import meshtastic.serial_interface
iface = meshtastic.serial_interface.SerialInterface()
# Get the local Node object (config reads/writes happen on the Node)
node = iface.getNode("^local")
# Read current value
print("Current hop limit:", node.localConfig.lora.hop_limit)
# Modify the protobuf object directly
node.localConfig.lora.hop_limit = 2
node.localConfig.lora.tx_power = 20 # dBm
# Write back to the device (triggers a reboot if radio settings changed)
node.writeConfig("lora")
iface.close()
Error Handling Patterns
import meshtastic
import meshtastic.serial_interface
from meshtastic.mesh_interface import MeshInterface
# Handle connection failures
try:
iface = meshtastic.serial_interface.SerialInterface()
except Exception as exc:
print(f"Failed to connect: {exc}")
# Common causes:
# - No Meshtastic device found on any serial port
# - Permission denied (Linux: add user to dialout group)
# - Device busy (app connected via BLE using same serial port implicitly)
raise
# Handle send timeout (device not responding)
import meshtastic.mesh_interface as mi
try:
iface.sendText("test", destinationId="!aabbccdd", wantAck=True)
except Exception as exc:
print(f"Send failed: {exc}")
# Handle TCP connection refused
try:
import meshtastic.tcp_interface
iface = meshtastic.tcp_interface.TCPInterface("192.168.1.99")
except ConnectionRefusedError:
print("TCP connection refused. Is the node on Wi-Fi with TCP API enabled?")
# Handle device disconnect mid-session
from pubsub import pub
def on_lost(interface, topic=pub.AUTO_TOPIC):
print("Connection to device lost. Attempting reconnect in 5 seconds...")
import time, threading
def reconnect():
time.sleep(5)
try:
new_iface = meshtastic.serial_interface.SerialInterface()
print("Reconnected successfully.")
except Exception as e:
print(f"Reconnect failed: {e}")
threading.Thread(target=reconnect, daemon=True).start()
pub.subscribe(on_lost, "meshtastic.connection.lost")
Thread Safety
The Meshtastic Python library spawns a background reader thread on connection. All pubsub
callbacks are invoked from this thread. If your callback modifies shared state, use a
threading.Lock to prevent race conditions. Outbound publishing is handled by an
internal deferred-execution thread; if you call send methods from multiple threads, guard
your own shared state and consult the current mesh_interface.py source for the
library's locking behavior rather than assuming a particular guarantee.
import threading
lock = threading.Lock()
message_log = []
def on_receive(packet, interface):
decoded = packet.get("decoded", {})
if decoded.get("portnum") == "TEXT_MESSAGE_APP":
with lock:
message_log.append({
"time": packet.get("rxTime"),
"from": packet.get("fromId"),
"text": decoded.get("text"),
})
Version Compatibility
| Library Version | Firmware Compatibility | Notes |
|---|---|---|
| 2.3.x | Firmware 2.3.x | Refined traceroute and neighbor-info handling (neighbor-info was introduced in firmware 2.2.0; traceroute predates 2.3). |
| 2.4.x | Firmware 2.4.x | BLE improvements and config refinements. |
| 2.5.x | Firmware 2.5.x | Public Key Cryptography (X25519) for direct messages and admin session keys; detection sensor module; power telemetry; new config fields. |
Always match the library version to your firmware major/minor version. Mismatches can cause
silent config field drops or protobuf decode errors. Run
pip install --upgrade meshtastic after each firmware update.
Meshtastic Modules and Plugins
Telemetry Module: Device, Environment, and Power
The Telemetry module broadcasts sensor data from your node across the mesh - battery voltage, temperature, humidity, barometric pressure, and more. Other nodes receive this data and display it in the app, and it can be forwarded to external systems via MQTT. Note that telemetry (battery state, temperature, and occupancy-correlated patterns) is broadcast on the channel and, on any channel with an MQTT uplink, is republished to the broker externally. Disable telemetry types you do not need on sensitive or privacy-critical nodes.
Enabling Telemetry
In the Meshtastic app: Radio Config → Modules → Telemetry
Via CLI:
meshtastic --set telemetry.device_update_interval 1800
meshtastic --set telemetry.environment_update_interval 1800
meshtastic --set telemetry.power_update_interval 1800
Intervals are in seconds. The default device telemetry update interval is 1800 seconds (30 minutes), which is appropriate for most deployments. Setting an interval to 0 selects the default (1800 s) - it does not disable that telemetry type. To disable a telemetry type, set its *_measurement_enabled flag to false instead.
Device Telemetry
Device telemetry is always available - it reports internal state from the node itself, no external sensors required:
| Field | Source | Notes |
|---|---|---|
| Battery level (%) | Hardware ADC | Accuracy varies by board; some boards always report 100% if battery not detected |
| Voltage (V) | Hardware ADC | Useful for deriving SoC for LiFePO4 packs |
| Channel utilization (%) | Radio stats | Percentage of airtime used; over 25% indicates congestion |
| Air utilization TX (%) | Radio stats | Percentage of time this node was transmitting |
| Uptime (seconds) | System clock | Resets on power cycle |
Environment Telemetry
Requires an external I2C sensor. Supported sensors include (accuracy figures are from each sensor's manufacturer datasheet):
| Sensor | Measures | I2C Address | Notes |
|---|---|---|---|
| BME280 | Temp, Humidity, Pressure | 0x76 or 0x77 | Most popular; inexpensive; not suitable for air quality |
| BME680 / BME688 (BME68x) | Temp, Humidity, Pressure, gas resistance | 0x76 or 0x77 | Reports gas resistance (a VOC/air-quality proxy), not a fully-calibrated IAQ index, in Meshtastic; the gas sensor element needs a warm-up period before readings stabilize |
| SHT31 | Temp, Humidity | 0x44 or 0x45 | High accuracy; ±0.3°C, ±2% RH (per Sensirion SHT3x datasheet) |
| MCP9808 | Temperature only | 0x18 | ±0.25°C accuracy (per Microchip MCP9808 datasheet, which is address-selectable across 0x18-0x1F); Meshtastic auto-detects it at 0x18 |
| SHTC3 | Temp, Humidity | 0x70 | Low power; used on some integrated boards |
meshtastic --set telemetry.environment_measurement_enabled true
meshtastic --set telemetry.environment_screen_enabled true
Power Telemetry
Requires a supported I2C current/voltage sensor on the bus: INA219, INA226, INA260, or INA3221. Reports voltage, current draw, and calculated power consumption - useful for monitoring solar systems and diagnosing battery drain.
meshtastic --set telemetry.power_measurement_enabled true
Telemetry Intervals and Airtime Impact
Every telemetry broadcast consumes airtime. At Long Fast preset, a small telemetry packet takes roughly 0.3-0.5 seconds of airtime. At 30-minute intervals with one sensor type, this is negligible. But if you enable all three telemetry types at 5-minute intervals on a busy network, the combined airtime impact becomes meaningful. As a guideline:
- Fixed infrastructure nodes: 15-30 minute intervals are appropriate
- Portable/personal nodes: 30-60 minute intervals to reduce network load
- Emergency operations (where battery monitoring is critical): 5-minute intervals acceptable
Store and Forward Module
The Store and Forward module lets a designated node buffer messages for clients that were offline when a message was sent. When the offline client reconnects to the mesh, it can request the message history and receive messages it missed.
Important caveat: Store and Forward is best-effort, not a delivery guarantee. The server only buffers traffic it personally received over RF; clients only get the history they explicitly request while in range of the server; and the buffer is volatile (held in RAM and lost on reboot or power loss). Do not rely on it to guarantee delivery of critical messages.
How Store and Forward Works
- A node configured as a Store and Forward server (typically a fixed repeater with reliable power) listens to all channel traffic and stores messages in the device's PSRAM (volatile RAM, not flash). The buffer is lost on reboot or power loss.
- The server periodically sends a heartbeat advertising itself to the mesh.
- A client that was offline requests history from the Store and Forward server (on Android, by sending the text command
SFto the server; on the Apple/connected app this happens automatically). - The server replays stored messages to the requesting client.
Enabling on a Server Node (Repeater)
Configure the node that will store messages. Note that only PSRAM-equipped ESP32 boards (for example T-Beam v1.0+, T3S3) can act as a server — nRF52840 devices cannot:
meshtastic --set store_forward.enabled true
meshtastic --set store_forward.is_server true
meshtastic --set store_forward.records 0
meshtastic --set store_forward.history_return_max 25
records sets the maximum number of messages to store (circular buffer; oldest messages are overwritten). The default is 0, which auto-allocates roughly two-thirds of the device's PSRAM (about 11,000 records); set a specific number to cap it. Capacity scales with the device's PSRAM, not its flash — only ESP32 boards with onboard PSRAM (e.g. T-Beam, T3S3) can run an S&F server, and nRF52840 boards cannot.
Enabling on a Client Node
A client generally does not need any special Store and Forward configuration beyond being on the same channel as the server. The is_server flag already defaults to false:
meshtastic --set store_forward.enabled true
meshtastic --set store_forward.is_server false
When an app connects to the server, history can be retrieved automatically. Over LoRa, an Android client requests history by sending the text command SF to the server node.
Limitations and Considerations
- Only one Store and Forward server per channel is recommended. Multiple servers on the same channel will each replay messages independently, causing duplicate deliveries.
- Server node must be always-on. A server that goes offline defeats the purpose. Use a mains or solar-powered repeater with reliable uptime.
- Only text messages are stored and replayed. Position reports and telemetry are not stored.
- Buffer is volatile. Messages are held in PSRAM, not flash, so the entire history is lost on reboot or power loss. There is no flash-wear concern because nothing is written to flash for this purpose.
- Message delivery is best-effort. If the client is out of radio range of the server, history request packets cannot reach the server. History retrieval over LoRa is also unavailable on the default public channel.
Best Deployment Practice
Deploy Store and Forward on your highest-uptime fixed node - typically the primary community repeater with mains or solar power, and one that has onboard PSRAM. This is the node most likely to have been online and collecting messages during the period a user was offline.
Range Test Module
The Range Test module automates signal strength measurement for deployment validation - letting you map exactly where in your coverage area packets arrive successfully, and at what SNR and RSSI values.
What the Range Test Module Does
Range Test operates as a sender/receiver pair:
- Sender node - Broadcasts a test packet at a configurable interval, incrementing a sequence number each time. The test packet carries the sender's position.
- Receiver node - Any node with the module enabled can receive and log each packet's SNR, RSSI, and sequence number. The canonical GPS-tagged
rangetest.csvis written to the flash of an ESP32-based receiver (saving is only available on ESP32 boards). - Output - A CSV file (
rangetest.csv) saved to the ESP32 receiver's internal flash, containing all received packets with position data, signal quality, and sequence numbers. Missing sequence numbers identify packet loss. The Apple/Android apps also offer their own separate position-log exports.
Setting Up a Range Test
Configure the Sender Node
meshtastic --set range_test.enabled true
meshtastic --set range_test.sender 60
sender is the interval in seconds between test packets. 60 seconds works well for driving tests; 30 seconds for walking tests where you move slower.
Configure the Receiver
meshtastic --set range_test.enabled true
meshtastic --set range_test.save true
With save true on an ESP32-based device, received packets are logged to a file called rangetest.csv in the device's internal flash memory (no SD card or smartphone required). Retrieve it over WiFi by browsing to meshtastic.local/rangetest.csv, which downloads the file automatically. Saving is only available on ESP32 boards; nRF52 boards cannot write the CSV.
Disable Range Test after testing. Leaving range_test.enabled on keeps the node broadcasting extra position-bearing test packets - that adds needless airtime and location exposure. Set range_test.enabled false when the test is complete.
Conducting a Range Test Drive
- Place the sender node at your repeater location or test deployment point. Ensure it has GPS lock and is transmitting.
- Configure an ESP32-based portable node as the receiver (so it can save the CSV to flash).
- Drive or walk through your intended coverage area.
- After the test, retrieve the CSV file from the receiver at
meshtastic.local/rangetest.csv. Each row contains: timestamp, GPS lat/lon, SNR, RSSI, sequence number. - Import the CSV into Google Maps (My Maps), QGIS, or any mapping tool to visualize coverage.
Interpreting Results
The bands below are rough rules of thumb, not a Meshtastic-published spec. The actual SNR floor for decoding shifts with the modem preset / spreading factor, so treat the RSSI and SNR columns as approximate and read them alongside the SF note that follows.
| RSSI | SNR | Connection Quality |
|---|---|---|
| -80 to -100 dBm | >5 dB | Excellent - reliable delivery |
| -100 to -115 dBm | 0 to 5 dB | Good - occasional packet loss |
| -115 to -125 dBm | -5 to 0 dB | Marginal (preset-dependent) |
| Below -125 dBm | near the SF floor* | Edge of range - unreliable |
* Note: The "edge of range" SNR is spreading-factor-dependent, not a flat -10 dB. LoRa can decode packets at negative SNR values - roughly -7.5 dB at SF7 down to approximately -20 dB at SF12. On the default LongFast preset (SF11) the decode floor is about -17 dB, so a -10 dB SNR link on LongFast is still well within usable range, not the edge. This is one of LoRa's most remarkable properties. RSSI alone is not the full picture; low RSSI with high SNR can still be a reliable link. (For consistency, reconcile these thresholds with the reading-network-statistics and neighbor-info pages, which should use the same canonical bands.)
Using Range Test for Repeater Placement Decisions
Deploy a temporary repeater at a candidate site, run a range test drive across the intended coverage area, then compare the CSV output against a test from your next-best candidate site. This gives objective, data-driven evidence for repeater placement decisions rather than guessing based on map topology alone.
External Notification and Canned Messages
External Notification Module
The External Notification module triggers a visual or audio alert on the node hardware when a message is received - useful for heads-up awareness without constantly watching a screen.
Configuration
meshtastic --set external_notification.enabled true
meshtastic --set external_notification.output 25
meshtastic --set external_notification.output_ms 1000
meshtastic --set external_notification.active true
meshtastic --set external_notification.alert_message true
output is the GPIO pin number the buzzer or LED is wired to — it must be set to the pin your hardware is on, otherwise the module activates nothing (use a safe GPIO for your board; 25 is only an example). output_ms is how long to activate the output in milliseconds. active sets the logic level (true = active high output). alert_message triggers on any incoming text message; alert_bell triggers only on text messages that contain the ASCII bell character (0x07). The module monitors text messages only and never triggers on non-text packet types.
Hardware Outputs
The module drives a GPIO pin that can trigger:
- Buzzer - Active buzzer connected between the GPIO pin and GND (through a transistor for modules requiring more current)
- LED - External LED indicator, useful for daytime visibility or status display
- Relay - Via a relay module, can trigger any external device: alarm siren, strobe light, or notification device
Most T-Beam and Heltec boards have accessible GPIO pins; consult your board's pinout documentation for safe GPIO numbers (avoid pins used by LoRa, I2C, and UART).
Canned Messages Module
The Canned Messages module allows quick message sending from the device hardware without using the phone app - ideal for when you have a dedicated node with a small rotary encoder or button input.
Use Case
Deploy a node in a fixed location (workshop, vehicle, equipment room) with a rotary encoder. The user can navigate a pre-programmed list of messages and send them with a button press, without needing to open a phone app. Useful for sending routine status messages in situations where opening a phone is inconvenient.
Configuration
meshtastic --set canned_message.enabled true
meshtastic --set-canned-message "OK|On my way|Need help|ETA 10 min|Stand by"
Messages are separated by pipe characters and are set with the dedicated --set-canned-message flag (not --set canned_message.messages). The total message list is limited to 200 bytes, so keep individual messages short.
Input Hardware
Requires a rotary encoder connected to GPIO pins, or can use UP/DOWN/SELECT button inputs. Configure the GPIO pin assignments:
meshtastic --set canned_message.inputbroker_pin_a 39
meshtastic --set canned_message.inputbroker_pin_b 36
meshtastic --set canned_message.inputbroker_event_cw UP
meshtastic --set canned_message.inputbroker_event_ccw DOWN
Pin numbers are board-specific and must be a valid GPIO in the 1-39 range. The event values are InputEventChar names such as UP, DOWN, and SELECT (not internal firmware enum names). The RAK WisBlock Input module provides a pre-built button/encoder input board compatible with the WisBlock system.
Serial, MQTT, and Ambient Light Modules
Serial Module
The Serial module lets external hardware send and receive Meshtastic messages over a UART serial connection - enabling integration with microcontrollers, GPS units, custom sensors, and computer software.
Modes of Operation
| Mode | Description | Use Case |
|---|---|---|
| DEFAULT | Same as SIMPLE - a raw (dumb) UART byte tunnel | Generic byte bridging |
| SIMPLE | Raw byte UART tunnel (no framing); requires a channel named serial | Bridging arbitrary bytes between two nodes |
| TEXTMSG | Sends/receives strings as text messages on the default text channel | Simple text message bridging |
| PROTO | Protobuf framing | Programmatic Arduino/ESP32 integration / full access |
| NMEA | Outputs NMEA sentences from node GPS | Feeding position to chartplotters |
| CALTOPO | CalTopo-compatible position format | SAR map integration |
Configuration
meshtastic --set serial.enabled true
meshtastic --set serial.mode SIMPLE
meshtastic --set serial.rxd 16
meshtastic --set serial.txd 17
meshtastic --set serial.baud BAUD_9600
RXD/TXD pin numbers are board-specific, and serial.baud takes an enum value (e.g. BAUD_9600, BAUD_115200), not a raw integer. SIMPLE (and DEFAULT) is a raw UART byte tunnel that requires a channel named serial - it does not broadcast to the default text channel. If you want serial input broadcast as ordinary text messages on the default channel (and received text messages printed back out as <Short Name>: <text>), use the TEXTMSG mode instead.
MQTT Module
The MQTT module directly connects a Meshtastic node to an MQTT broker (internet required), enabling cloud integration without a separate gateway computer.
Warning: uplinking to the public broker mqtt.meshtastic.org (with the shared meshdev credentials) publishes your default-channel traffic and node positions to the public internet - readable by anyone and shown on third-party maps such as meshmap.net. Meshtastic also publishes to MQTT unencrypted by default, even on a channel with a PSK, unless you set mqtt.encryption_enabled true. If you do not intend that public exposure, use a private broker and enable encryption.
meshtastic --set mqtt.enabled true
meshtastic --set mqtt.address "mqtt.meshtastic.org"
meshtastic --set mqtt.username "meshdev"
meshtastic --set mqtt.password "large4cats"
meshtastic --set mqtt.root "msh"
meshtastic --set mqtt.uplink_enabled true
meshtastic --set mqtt.downlink_enabled false
uplink_enabled sends local mesh packets to the broker. downlink_enabled receives packets from the broker and re-broadcasts locally - useful for extending the mesh over the internet but can cause feedback loops if misconfigured. Start with downlink disabled until you understand the topology.
Topic Structure
Meshtastic publishes to: msh/REGION/2/e/CHANNELNAME/USERID for raw protobuf packets, or msh/REGION/2/json/CHANNELNAME/USERID when JSON output is enabled. The 2/e (or 2/json) segment is the protocol-version/encoding marker, and the final segment is the gateway node's USERID. There is no per-packet-type topic level - the packet type is a field inside the JSON payload, not a topic segment. (Firmware before 2.3.0 used /c/ in place of /e/.)
Example: msh/US/2/e/LongFast/!abcd1234 for protobuf packets on the LongFast channel in the US region (or msh/US/2/json/LongFast/!abcd1234 when JSON is enabled). Subscribe with wildcards: msh/US/# to receive all traffic from US nodes.
Ambient Lighting Module
The Ambient Lighting module controls an onboard I2C RGB LED controller (the NCP5623, as used on the RAK14001), letting you set the LED on/off state, its drive current, and the red/green/blue levels. It is not a light sensor and does not auto-adjust screen-backlight brightness. (Light-intensity sensors such as the BH1750, VEML7700, or TSL2591 are read by the Telemetry module, not by this module.)
meshtastic --set ambient_lighting.led_state 1
meshtastic --set ambient_lighting.current 10
Use ambient_lighting.led_state (1 to enable, 0 to disable), ambient_lighting.current for the LED output drive, and the red/green/blue fields for color. This module targets an I2C RGB LED driver (e.g. the NCP5623 on the RAK14001); it is not documented to drive addressable NeoPixel/WS2812 strips.
Encryption and Privacy
PKC Direct Messaging (v2.5+)
Meshtastic v2.5 introduced Public Key Cryptography (PKC) encrypted direct messages - a significant security upgrade that encrypts DM content per-recipient rather than relying only on the shared channel key. Note that PKC protects the message body: the packet header (source/destination node IDs, hop count, timing) remains plaintext and can be uplinked to MQTT, so PKC protects what you say, not the fact that two nodes are communicating.
Note on terminology: This feature is officially called "PKC Direct Messages" or "encrypted direct messages" in Meshtastic documentation. PKC encrypted direct messages were introduced in firmware v2.5.0 (not v2.3 or v2.4 as some sources incorrectly state).
Before v2.5: How DMs Worked
Prior to v2.5, "direct messages" in Meshtastic were standard channel messages with a to field set to the recipient's node ID. Anyone on the same channel with the channel key could decrypt and read all DMs. There was no per-recipient encryption.
v2.5+: PKC Encrypted Direct Messages
From v2.5 onward, direct messages use per-node asymmetric encryption:
- Key exchange: X25519 ECDH - each node has an X25519 public/private key pair
- Encryption: AES-CCM - using the derived shared secret as the key (per the Meshtastic encryption documentation)
- Only the intended recipient can decrypt the message - the channel key is not used
- Node public keys are distributed automatically via NodeInfo packets
Limitations: PKC has no forward secrecy — keys are long-lived, so the "harvest now, decrypt later" risk applies if a node's private key is ever compromised — and there is no key revocation mechanism. The plaintext packet header still leaks metadata even on PKC DMs.
Backward Compatibility
If you send a PKC-encrypted DM to a node running firmware 2.4.3 or older, Meshtastic automatically falls back to the legacy channel-based method (which is unprotected against anyone holding the channel key). Some app versions display a key/lock icon indicating that PKC was used for a given message.
Requirements
- Both sender and recipient must be running Meshtastic firmware v2.5 or later
- Both nodes must have exchanged NodeInfo packets (public keys are included automatically)
- Compatible with Android, iOS, and Python CLI clients that support v2.5+
Source: meshtastic.org/docs/overview/encryption/ and meshtastic.org/blog/introducing-new-public-key-cryptography-in-v2_5/. Verified 2026-05-03.
Meshtastic Managed Mode and Admin Channels
For deployed infrastructure nodes - community repeaters, fixed gateways - you want to prevent unauthorized configuration changes while still being able to administer the node remotely. Meshtastic provides two tools for this: Managed Mode and Admin Channels.
Managed Mode
When Managed Mode is enabled, the node blocks all client applications from writing configuration over any local interface - BLE, USB serial, and TCP alike (configuration can still be read). Once enabled, configuration can only be changed through PKC Remote Admin (firmware 2.5+) or the legacy Admin channel (firmware prior to 2.5). This prevents anyone who walks up to the repeater and pairs their phone from changing the configuration.
Managed Mode is set via the security.is_managed boolean, not a device role - there is no "MANAGED" device role:
meshtastic --set security.is_managed true
Configure and test your admin access (admin channel or PKC remote admin key) before enabling security.is_managed. If you enable Managed Mode without working admin access, you lose the ability to reconfigure the node over BLE, USB serial, and TCP - the only recovery is a physical firmware erase / factory reset, which also wipes your keys and configuration.
With Managed Mode active:
- Local config writes over BLE, USB serial, and TCP are all blocked (changes require Remote Admin or the admin channel)
- Configuration can still be read over local interfaces
- The node continues to route and relay normally
Admin Channel
The Admin Channel is an encrypted control channel that allows authorized administrators to configure any node in the mesh remotely - even nodes that are out of direct radio range (configuration packets are relayed through the mesh). On firmware 2.5 and newer, the preferred approach is PKC Remote Admin, which authorizes administrators by their public key rather than a shared channel PSK; the legacy shared-PSK admin channel remains for older firmware.
Setting Up an Admin Channel
- Create a channel with a random PSK and name it "admin" (or any name you choose)
- Designate that channel as the admin channel (the security admin-channel setting)
- Add this channel to all nodes you want to manage
- Only administrators should have the admin channel PSK
meshtastic --ch-set name "admin" --ch-index 1
meshtastic --ch-set psk random --ch-index 1
meshtastic --set security.admin_channel_enabled true
The legacy integer admin_channel_index field is deprecated; on current firmware the admin channel is enabled via the security.admin_channel_enabled boolean. On firmware 2.5+, prefer PKC Remote Admin (admin keys) over the shared-PSK admin channel where possible.
Remote Configuration via Admin Channel
Once an admin channel is configured, you can send configuration commands to remote nodes via the app's remote admin feature. The command is encrypted with the admin channel PSK (or, with PKC remote admin, the admin keypair), relayed through the mesh, and executed on the target node. The target node responds with its updated configuration.
Security Considerations
- The admin channel PSK (or PKC admin key) is the master key for your infrastructure - anyone holding it can administer every node that trusts it, so guard it carefully. Managed Mode does not protect against an attacker who already has this credential.
- Distribute admin credentials only to trusted operators
- Consider a separate admin channel/key per node, or per geographic cluster, to limit blast radius if a key is compromised
- Managed Mode blocks normal config writes over USB serial too. Physical access still allows a firmware erase / factory reset, which wipes Managed Mode, keys, and configuration - but that is a reset, not live reconfiguration of a running node.
Meshtastic Configuration Reference
Comprehensive reference for all settings under Config > Device, Config > Position, and Config > Network in the Meshtastic firmware.
Device Configuration Settings
The Device configuration section (Config > Device) controls fundamental node behavior: its role in the network, how it handles rebroadcasting, node info broadcasts, and administrative access. These are the most impactful settings for network performance and should be understood before deploying any node.
Access these settings in the Meshtastic app under Settings > Radio Configuration > Device, via the web interface at http://<node-ip>/config, or through the Python CLI with meshtastic --set device.*.
Role
Config key: device.role
Default: CLIENT
The Role setting is the single most important Device configuration choice. It tells the firmware how this node should behave in the mesh - specifically, how aggressively it should rebroadcast packets, whether it should send periodic node info, and how it prioritizes battery life versus network contribution.
Available Roles
CLIENT
The standard role for personal devices carried by users. CLIENT nodes:
- Participate normally in mesh routing (rebroadcast packets they receive)
- Send NodeInfo broadcasts on the configured interval
- Receive and send messages on all subscribed channels
- Display in other nodes' neighbor lists
Use when: You are deploying a personal handheld node, a node you carry daily, or any general-purpose device.
CLIENT_MUTE
Identical to CLIENT but with packet rebroadcasting disabled. A CLIENT_MUTE node does not relay other nodes' packets (no routing-layer rebroadcast), but it still sends and receives its own messages.
Use when: You have many devices in close proximity (e.g., all members of a team within radio range of each other). Adding non-muted CLIENT nodes in a dense cluster creates redundant rebroadcasts that waste airtime. Making most of them CLIENT_MUTE reduces channel utilization while maintaining full message delivery to those devices.
Also useful for: Devices behind NAT or VPN tunnels acting as monitoring receivers; test devices you don't want to affect mesh traffic.
ROUTER
Designed for fixed infrastructure nodes at high elevation or central locations. ROUTER nodes:
- Rebroadcast packets with the highest priority
- Use a longer NodeInfo broadcast interval to reduce overhead traffic
- Are favored in routing decisions - the mesh prefers to route through ROUTER nodes
- Disable the screen and reduce radio/BLE activity by default to save power (ROUTER enables Power Saving automatically on ESP32)
Use when: Deploying a node on a rooftop, tower, hilltop, or any other fixed elevated location whose primary job is to relay traffic for other nodes. ROUTERs form the backbone of a community mesh.
Important: Only set ROUTER on nodes that will genuinely be at good RF locations. A ROUTER with poor antenna placement will be preferred for routing but perform poorly, degrading the network. It's better to use CLIENT on a node that isn't actually a good relay point.
ROUTER_CLIENT (removed)
ROUTER_CLIENT was a hybrid role that combined ROUTER routing behavior with CLIENT-level NodeInfo and message participation. This role was deprecated and removed in firmware 2.3.15 and is no longer a selectable role in current firmware.
What to use instead: For a home node that both relays traffic and receives your own messages, use CLIENT (which performs smart rebroadcasting via managed flooding) or ROUTER_LATE. For a node that is purely infrastructure, use ROUTER.
REPEATER
The most minimal infrastructure role. Note: REPEATER is deprecated as of firmware 2.7.11; for new infrastructure deployments prefer ROUTER (or ROUTER_LATE). REPEATER nodes:
- Rebroadcast received packets once with prioritized timing similar to ROUTER, with minimal overhead
- Do NOT send NodeInfo broadcasts - they are intentionally invisible in the node list
- Do NOT participate in messaging - they cannot send or receive application-layer messages
- Consume minimal airtime overhead
Use when: Deploying a relay-only node in a gap in coverage where you only need packet forwarding and don't need the node to participate in messaging or appear in the network map. REPEATER nodes are ideal for embedded or remotely deployed relays with no user interaction.
Caution: Because REPEATER nodes don't send NodeInfo, they won't appear on your map or in your node list. This makes it harder to verify they are online. Plan for out-of-band monitoring (serial console, MQTT telemetry, physical LED) if using REPEATER in unattended deployments.
TRACKER
Optimized for asset and vehicle tracking use cases. TRACKER nodes:
- Send frequent position updates automatically
- Are optimized to conserve bandwidth by only broadcasting position data (they reduce other overhead)
- Prioritize position packets. Smart position broadcast (transmit when moving, reduce rate when stationary) is configured via the position settings (
position.position_broadcast_smart_enabledand related fields) and applies regardless of role
Use when: Attaching a node to a vehicle, pet, person, or asset for location tracking. The TRACKER role signals to the firmware and other nodes that this device's primary purpose is position reporting.
TAK
Designed for interoperability with TAK (Team Awareness Kit) - tactical situational awareness software used by military, law enforcement, SAR teams, and emergency management. TAK role nodes:
- Format outgoing position packets in a way compatible with TAK server ingestion
- Enable TAK-specific extensions in the Meshtastic packet format
- Work alongside ATAK (Android TAK) and WinTAK clients
Use when: Integrating Meshtastic into a TAK-based operational picture. Requires TAK server infrastructure or ATAK-capable devices on the network. Not useful for general community mesh deployments.
SENSOR
Optimized for sensor nodes that transmit telemetry data (temperature, humidity, air quality, power levels, etc.) rather than user messages. SENSOR nodes:
- Still participate in routing messages for other devices, but prioritize sending their own telemetry data; they can reduce routing/airtime and sleep between readings when
power.is_power_savingis enabled - May sleep between sensor readings
- Reduce rebroadcast activity to conserve battery (retransmit while awake only)
Use when: Deploying a battery-powered environmental sensor, weather station, or power monitor. The node's primary job is to report readings, not to relay traffic.
LOST_AND_FOUND
A specialized tracker role for lost-item finders (similar in concept to Apple AirTags or Tile trackers but on the Meshtastic mesh). Lost_And_Found nodes:
- Broadcast their location as a message to the default channel regularly to assist with device recovery
- Are designed for small form-factor nodes attached to items (bags, bikes, pets)
- The lost node broadcasts its own location to the default channel; anyone in range receives it and can use it to recover the device
Use when: Building a lost-item tracker on Meshtastic hardware. Not suitable for primary communication or infrastructure roles.
Note on role availability: The exact set of selectable roles (and their spelling) depends on your firmware version. Current roles include CLIENT, CLIENT_MUTE, CLIENT_HIDDEN, ROUTER, ROUTER_LATE, REPEATER (deprecated), TRACKER, SENSOR, TAK, TAK_TRACKER, and LOST_AND_FOUND. If a role above does not appear in your app's role dropdown, update to current firmware.
Serial Console Enabled
Config key: security.serial_enabled (this setting lives under the security.* namespace, not device.*)
Default: true
Controls whether the firmware exposes a serial console on the USB/UART port. When enabled, you can connect to the node via USB serial (commonly at 115200 baud) and interact with the device (view logs, run CLI commands). Disabling this prevents the Serial Console from initializing the Stream API.
Disable when: Deploying a node in an environment where unauthorized USB access is a concern, or to reduce power consumption marginally in deeply embedded deployments. Most users should leave this enabled - it is the primary recovery mechanism if you lose network access to the node.
Warning: If you disable Serial Console and also lose WiFi/Bluetooth access to the node, recovery may require a full firmware reflash.
Debug Log Enabled
Config key: security.debug_log_api_enabled (under the security.* namespace)
Default: false
When enabled, the firmware outputs verbose debug messages to the serial console. These messages include detailed information about packet processing, routing decisions, radio layer events, and subsystem state changes.
Enable when: Troubleshooting connectivity issues, investigating unexpected behavior, or developing integrations. Debug logging produces a high volume of output that can make normal serial console use harder to read.
Disable in production: Debug logging adds slight CPU overhead and can fill serial buffers. Leave disabled on deployed infrastructure nodes unless actively troubleshooting.
Rebroadcast Mode
Config key: device.rebroadcast_mode
Default: ALL
Controls which received packets this node will rebroadcast (relay) to other nodes. This setting interacts with the Role setting - Role determines the priority and frequency of rebroadcasting; Rebroadcast Mode determines what gets rebroadcast at all.
ALL
The default mode. This node rebroadcasts all received packets that pass the hop limit and deduplication filters, regardless of source or content. This is the correct mode for most nodes.
ALL_SKIP_DECODING
The node rebroadcasts all packets but skips attempting to decode their payload. This mode is primarily useful for REPEATER-role nodes where you want maximum rebroadcast speed and don't need the node to process message content. Slightly reduces CPU overhead per packet.
Use when: Operating a dedicated relay node where content processing is unnecessary and you want to minimize latency and CPU load.
LOCAL_ONLY
The node ignores observed messages from foreign meshes that are open or that it cannot decrypt, and only rebroadcasts traffic on its own local primary/secondary channels. LOCAL_ONLY filters by local channel membership / decryptability - it does not filter by hop count.
Use when: You share RF space with neighboring meshes on the same modem preset and want to avoid relaying traffic from foreign meshes you are not part of. LOCAL_ONLY restricts rebroadcasting to your own configured channels.
KNOWN_ONLY
The node only rebroadcasts packets from nodes that appear in its local node database (nodes it has seen NodeInfo from). Unknown nodes - those that have never sent a NodeInfo the local node has received - are not relayed.
Use when: Running a private or semi-private mesh where you want to restrict relay behavior to known network members. This can reduce relay of stray packets from neighboring networks that share the same channel.
Node Info Broadcast Interval
Config key: device.node_info_broadcast_secs
Default: 10800 seconds (3 hours). This default is the same for all roles - it is not role-dependent.
How often (in seconds) the node broadcasts a NodeInfo packet - the announcement that includes the node's name, hardware model, and public key. NodeInfo packets allow other nodes to know you exist and update their neighbor lists.
Lower values: More frequent announcements - nodes see each other more quickly after joining the network, and the map updates faster. Costs more airtime.
Higher values: Less frequent announcements - reduces channel utilization overhead, especially important on busy networks. As user-tunable guidance, fixed infrastructure nodes can keep the default or longer intervals (up to UINT MAX) since their presence is stable; the firmware default is 10800 s for all roles.
Minimum: 3600 seconds (1 hour). The firmware enforces 3600 s as the minimum accepted value; lower values such as 300 s are not permitted and will be rejected or clamped.
Double-Tap as Button
Config key: device.double_tap_as_button_press
Default: false
On devices with an accelerometer (such as the RAK WisBlock with RAK1904 accelerometer module), enabling this option allows a physical double-tap on the device to simulate a button press. Useful for waking a device from sleep or triggering notifications on nodes that don't have physical buttons.
Enable when: Using a device in a case without easy access to physical buttons, or when you want tap-to-wake functionality. Requires compatible hardware with an accelerometer.
Managed Mode
Config key: security.is_managed (boolean; set via meshtastic --set security.is_managed true). Managed Mode lives under the security.* namespace - it is not a device.role value and not device.is_managed.
Default: false
When Managed Mode is enabled, client applications are blocked from writing configuration to the radio locally (configurations may still be read). Once enabled, radio configurations can only be changed through PKC Remote Admin messages on firmware 2.5+ or the legacy Admin channel on firmware prior to 2.5. This is a security feature for remotely deployed infrastructure nodes.
Enable when: Deploying a ROUTER node (or other infrastructure node) that should be centrally managed and protected from unauthorized local modification. For example, a rooftop node that community members can physically access - Managed Mode prevents casual configuration changes.
Warning: Enabling Managed Mode without first verifying you can administer the node remotely (via PKC Remote Admin on firmware 2.5+, or a configured legacy admin channel on older firmware) will lock you out of local configuration. Managed Mode blocks config writes over all local interfaces, including USB/serial. Confirm remote admin works before enabling this.
Admin Channel (Legacy) and Remote Admin
Config key: security.admin_channel_enabled (boolean, default false / disabled). This enables the insecure legacy admin channel. The older integer admin_channel_index field is deprecated.
Modern remote admin: security.admin_key (PKC Remote Admin keys)
On current firmware (2.5+), administrative access is normally provided by PKC Remote Admin: you authorize one or more public keys via security.admin_key, and only messages signed by those keys are accepted for administrative control. The legacy admin channel - enabled by the boolean security.admin_channel_enabled - is the older mechanism, where admin messages are protected only by a channel PSK. Admin messages can change device configuration, reboot the node, and perform other privileged operations.
If you must use the legacy admin channel, configure a dedicated secondary channel with its own strong PSK so administrative messages are kept separate from user traffic. For example:
- Channel 0: public mesh channel (LongFast or community key)
- Channel 1: private admin channel (strong PSK known only to node operators)
- Enable
security.admin_channel_enabled
With this legacy setup, only operators with the channel 1 PSK can remotely reconfigure the node, while anyone on channel 0 can use the mesh normally. Note that the default public channel's PSK (AQ==) is publicly known, so never rely on channel 0 for admin.
Best practice for infrastructure nodes: Prefer PKC Remote Admin (security.admin_key) on firmware 2.5+ for separating and authenticating admin traffic. If you use the legacy admin channel, always give it a dedicated strong PSK. Never leave admin access on the default public channel in a production deployment.
Position Configuration Settings
The Position configuration section (Config > Position) controls how your node acquires, reports, and manages GPS and location data. Getting position configuration right affects mesh map accuracy, battery life, and channel airtime - particularly important for mobile nodes and large networks.
Access these settings in the Meshtastic app under Settings > Position (Android) or Settings > Device Configuration > Position (Apple), or via the Python CLI with meshtastic --set position.*.
GPS Mode
Config key: position.gps_mode
Default: ENABLED or NOT_PRESENT depending on the device/variant (ENABLED on devices with GPS hardware)
Controls the GPS receiver's operating state. This is a top-level switch that determines whether GPS hardware is used at all. Note: position.gps_mode (an enum) is the current control and replaced the legacy position.gps_enabled boolean.
ENABLED
The GPS receiver is active and continuously seeks satellite fixes. The node uses live GPS data for position reporting. This is the default for devices with GPS hardware (e.g., T-Beam, WisBlock with RAK1910/RAK12500 GPS module).
DISABLED
GPS is disabled. The node will use a fixed position if one has been set (see Fixed Position below), or will report no position if no fixed position is configured. Disabling GPS when you don't need real-time position significantly reduces power consumption.
Use when: The node is permanently fixed and its position is set manually via Fixed Position. A rooftop ROUTER node, for example, has no reason to run its GPS continuously - set a fixed position once and disable GPS to save power.
NOT_PRESENT
Informs the firmware that no GPS hardware is present on this device. This prevents the firmware from attempting to initialize GPS hardware that doesn't exist, which can cause startup delays and error log noise. Set this on devices without any GPS module (e.g., a bare ESP32 LoRa board without GPS, or a RAK4631 with no GPS WisBlock attached).
Fixed Position
Config key: Set via app "Set Fixed Position" action or CLI meshtastic --setlat <lat> --setlon <lon> (altitude is set via the Python API setFixedPosition(alt=...) or the app, not a CLI flag)
Default: Not set
A manually entered GPS coordinate that the node broadcasts as its position instead of (or in addition to, depending on GPS Mode) GPS-derived coordinates. Fixed position is stored in non-volatile memory and persists across reboots.
Use cases:
- Fixed infrastructure nodes (rooftop ROUTER, indoor gateway) - set their exact location once, disable GPS
- Devices without GPS hardware - allow them to appear on the map at a known location
- Privacy-conscious users who want to broadcast an approximate location rather than precise GPS coordinates
Privacy note: Any position the node broadcasts - fixed or live, precise or coarse - is published to the channel and, on any channel that is uplinked to MQTT, to the public internet (and third-party maps such as meshmap.net). An "approximate" fixed position is still exposed. For truly sensitive sites, disable position entirely (GPS Mode DISABLED with no fixed position set) rather than relying on a coarse fixed point.
Setting a fixed position:
- In the Meshtastic app: Settings > Position > "Set to current location" or enter coordinates manually (Android); Settings > Device Configuration > Position on Apple
- Via CLI:
meshtastic --setlat 37.7749 --setlon -122.4194(there is no--setaltflag; set altitude via the app or Python API) - Via Python API:
iface.localNode.setFixedPosition(lat=37.7749, lon=-122.4194, alt=15)
To clear a fixed position: Use meshtastic --remove-position in the CLI.
Position Broadcast SMART Enabled
Config key: position.position_broadcast_smart_enabled
Default: true
Smart position broadcasting adapts broadcast frequency based on movement. When enabled, the node transmits position updates more frequently when moving and less frequently when stationary. This significantly reduces channel airtime for mobile nodes compared to fixed-interval broadcasting.
How smart beaconing works:
- If the node has moved more than the configured Minimum Distance since the last broadcast (and the Smart Broadcast Minimum Interval has elapsed), a position broadcast is triggered
- If the node has not moved, it waits up to the configured Broadcast Interval before broadcasting
- Speed is factored in: faster movement triggers more frequent updates
Enable for: Any mobile node (vehicle tracker, handheld carried by a walking/driving user). Smart beaconing is almost always beneficial for mobile nodes.
Consider disabling for: Fixed infrastructure nodes with a set position - they should broadcast at a fixed, low-frequency interval (see Broadcast SECS below) rather than smart beaconing, which adds minor computational overhead.
Broadcast SECS (Position Broadcast Interval)
Config key: position.position_broadcast_secs
Default: 0 (interpreted as 900 seconds / 15 minutes)
The maximum interval in seconds between position broadcasts. When Smart Beaconing is enabled, this is the upper bound - the node will not go longer than this interval without broadcasting, even if stationary. When Smart Beaconing is disabled, this is the fixed broadcast interval.
Guidance by use case:
| Use Case | Recommended Interval | Rationale |
|---|---|---|
| Vehicle tracker (active event) | 60 - 120 seconds | Frequent updates needed for real-time tracking |
| Hiking/walking node | 120 - 300 seconds | Balance between track fidelity and airtime |
| Fixed CLIENT node | 900 - 1800 seconds | Position doesn't change; reduce overhead |
| Fixed ROUTER/infrastructure | 3600 - 10800 seconds | Minimal overhead for stable fixed nodes |
Channel utilization impact: Position packets are among the longer Meshtastic packets. On a busy network with many nodes, unnecessarily frequent position broadcasts are a significant source of channel congestion. Always use the longest interval consistent with your tracking needs.
Smart Minimum Distance
Config key: position.broadcast_smart_minimum_distance
Default: 0 (interpreted as 100 meters)
When Smart Position Broadcasting is enabled, this is the minimum distance (in meters) the node must travel from its last broadcast location before a new position broadcast is triggered by movement. If the node moves less than this distance, the movement does not by itself trigger a new broadcast.
Tuning guidance:
- Walking/hiking: 50 - 100 meters - captures meaningful position changes at walking speed
- Vehicle tracking: 100 - 500 meters - avoids rapid-fire updates at slow speeds (parking lots, traffic)
- Emergency/rescue: 25 - 50 meters - fine-grained position updates for close-range coordination
GPS Update Interval
Config key: position.gps_update_interval
Default: 0 (interpreted as 120 seconds / 2 minutes)
How often (in seconds) the firmware polls the GPS module for a new position fix. This is distinct from the broadcast interval - the GPS may update frequently internally while position broadcasts happen less often.
A shorter GPS update interval means the firmware always has a more current fix available when it decides to broadcast. A longer interval reduces GPS power consumption (the GPS receiver is one of the most power-hungry components on nodes like the T-Beam).
Recommended values:
- Active mobile use: 30 - 60 seconds
- Occasional position checks: 120 - 300 seconds
- Mostly stationary: 600 - 1800 seconds (or disable GPS and use fixed position)
Position Flags
Config key: position.flags
Default: UNSET
Position flags are a bitmask that controls which optional data fields are included in position packets. Each additional field adds bytes to the packet, increasing airtime. Choose only the flags relevant to your use case. On the CLI these are set with meshtastic --pos-fields ALTITUDE ALTITUDE_MSL ... (a space-separated list of flag names), not via --set position.flags.
| Flag | Data Added | Use When |
|---|---|---|
| ALTITUDE | Include an altitude value (if available) | Mountainous terrain, aviation, 3D positioning |
| ALTITUDE_MSL | Interpret the altitude value as height above mean sea level (vs ellipsoid); a modifier on ALTITUDE, not a separate field | When sea-level altitude is specifically needed |
| GEOIDAL_SEPARATION | Difference between WGS84 ellipsoid and geoid | Precision surveying; not needed for most uses |
| DOP | Dilution of Precision (accuracy estimate) | When position accuracy qualification is important |
| HVDOP | When DOP is enabled, send separate HDOP and VDOP instead of a single PDOP | Precision tracking applications |
| SATINVIEW | Number of GPS satellites in view | Diagnostics, signal quality assessment |
| SEQ_NO | Sequence number (incremented per packet) | When multiple position sources must be ordered |
| TIMESTAMP | Timestamp of the GPS fix | When time-of-fix (vs time-of-transmission) matters |
| HEADING | Course over ground in degrees | Vehicle and vessel tracking, direction of travel |
| SPEED | Speed over ground | Vehicle tracking, activity analysis |
Note: The exact byte cost, units, and field encoding of each flag are defined in the firmware mesh.proto Position definition; consult it (or the firmware protobuf) before relying on per-flag size estimates. Enabling more flags increases packet size and airtime.
Minimal position packet (lat/lon only): Omit all optional flags. Suitable for fixed nodes or simple presence-only tracking.
Full vehicle tracking: Enable Altitude, DOP, Heading, Speed. Gives a complete picture without the rarely useful fields.
Airtime consciousness: On the default LongFast channel (250 kHz bandwidth, ~1.07 kbps effective data rate), each position packet with all flags enabled may be 40+ bytes longer than a minimal packet, translating to measurably more on-air time per broadcast.
GPS Fix Acquisition Timeout
Config key: Handled internally by the firmware - there is no separate position.gps_attempt_time CLI config key.
Default: Determined by the firmware
In power-save GPS mode the firmware will attempt to acquire a GPS fix for a bounded period before giving up and putting the GPS to sleep until the next update cycle. This timeout is managed internally by the firmware; it is not exposed as a user-settable position.gps_attempt_time value. If the GPS acquires a fix before the internal timeout, it sleeps early; if it cannot get a fix, it stops trying until the next GPS update interval.
In open sky environments: A fix is typically acquired within 30 - 60 seconds, so the internal fix-attempt window is rarely a limiting factor.
In challenging environments (indoors, urban canyons, dense tree cover): GPS may struggle to get a fix. To save power in places where a fix is unlikely, increase the GPS Update Interval (so the GPS wakes less often) or disable GPS and use a fixed position.
GPS Power Management (Power Saving)
Config key: GPS power management is handled automatically based on GPS Mode and the GPS Update Interval - there is no separate position.gps_power_mode key.
Default: Automatic
On battery-powered nodes, GPS is one of the largest power consumers. A typical GPS receiver draws on the order of 20 - 50 mA when actively tracking, versus roughly 2 - 5 mA for the LoRa transceiver in sleep (figures vary by module; consult your GPS module datasheet and the SX126x/SX127x datasheet for exact values). Several strategies minimize GPS power draw:
Duty-Cycle GPS (Recommended for Mobile Nodes)
The GPS receiver powers on only when a new fix is needed (based on GPS Update Interval), acquires a fix, then powers down. Between updates, the GPS is completely off. This can dramatically reduce GPS-related power consumption compared to continuous operation - the longer the update interval, the larger the saving.
Meshtastic duty-cycles the GPS automatically based on the GPS Update Interval: larger intervals mean the GPS sleeps for longer between fixes. Set a longer GPS Update Interval to reduce GPS polling and power draw.
Fixed Position + GPS Disabled (Best for Fixed Nodes)
The most power-efficient approach for fixed nodes: enter the position manually once, set GPS Mode to DISABLED. The GPS receiver is never powered, eliminating all GPS power consumption entirely.
GPS NOT_PRESENT (For Devices Without GPS)
On boards without GPS hardware, setting GPS Mode to NOT_PRESENT prevents any attempt to initialize GPS, eliminating initialization delay and preventing power being applied to a non-existent module.
Practical Power Impact
The figures below are rough estimates derived from common GPS-module active currents and duty-cycle math (active current x on-time / period); actual draw depends on your specific module and time-to-first-fix. Treat them as ballpark, not measured Meshtastic values.
| GPS Mode | Approximate Current Draw (estimate) | Use When |
|---|---|---|
| Continuous (always on) | 30 - 50 mA continuous | Real-time tracking where every second matters |
| Duty-cycle (120s interval) | 2 - 8 mA average | Standard mobile node |
| Duty-cycle (600s interval) | 0.5 - 2 mA average | Low-power mobile node |
| DISABLED (fixed position) | 0 mA for GPS | Fixed infrastructure nodes |
Network Configuration Settings
The Network configuration section (Config > Network) controls how ESP32-based Meshtastic nodes connect to IP networks - WiFi and Ethernet - and associated services like NTP and remote logging. These settings are only relevant for ESP32 hardware; nRF52-based boards (like the RAK4631) do not support WiFi natively and these settings have no effect on them, with the exception of Ethernet on RAK builds fitted with a RAK13800 module.
Access these settings in the Meshtastic app under Settings > Radio Configuration > Network, or via the Python CLI with meshtastic --set network.*.
Note: Network connectivity unlocks important Meshtastic features: the web interface, MQTT gateway, APRS bridging, NTP time synchronization, and remote syslog. If you are using ESP32-based hardware (T-Beam, T-Lora, Heltec WiFi LoRa, Station G2, etc.), understanding these settings is important for gateway and infrastructure deployments.
WiFi SSID
Config key: network.wifi_ssid
Default: Empty (WiFi disabled)
The SSID (network name) of the WiFi network the node should connect to as a client. Case-sensitive. Maximum 32 characters.
To enable WiFi: Set both WiFi SSID and WiFi Password. The node will attempt to join the network on boot and reconnect if the connection drops.
Important considerations:
- Enabling WiFi disables Bluetooth - only one connection method works at a time on ESP32 devices. Enabling WiFi also increases power consumption noticeably compared to running LoRa with Bluetooth only.
- A node with WiFi enabled and a good internet connection can serve as an MQTT gateway, APRS-IS gateway, and NTP time source for the mesh
- On battery-powered mobile nodes, WiFi should generally be disabled to preserve battery life. Enable WiFi on mains-powered infrastructure nodes.
WiFi Password
Config key: network.wifi_psk
Default: Empty
The passphrase for the WiFi network specified by WiFi SSID (max 64 characters). Stored in the device's flash memory. Supports open networks (leave password empty if the network has no password, though this is strongly discouraged for security reasons).
Security note: The WiFi password is stored in plaintext in the device's NVS (Non-Volatile Storage) flash partition. Anyone with physical access to the device and a serial connection can potentially extract it. Do not configure a node with your primary home WiFi password on a device that could be physically compromised; consider using a dedicated IoT VLAN or guest network.
WiFi Mode
ESP32-based Meshtastic nodes connect to WiFi in client (station / STA) mode only. The firmware does not support SoftAP (Access Point) mode - there is no setting to make the node create its own WiFi access point, and there is no network.wifi_mode config key. WiFi is controlled simply by whether a WiFi SSID and password are set.
In client mode the node connects to an existing WiFi network as a client (station). This is the standard mode for gateway nodes that need internet access. In client mode, the node:
- Obtains an IP address via DHCP (or a configured static IP)
- Can reach the internet for MQTT, NTP, APRS-IS, and other services
- Is accessible on the local network at its assigned IP address
- Serves the Meshtastic web client at
http://<node-ip>/(or via the mDNS aliashttp://meshtastic.local/)
No phone-direct AP option: Because SoftAP mode is not supported, you cannot connect a phone or laptop directly to the node's own WiFi. For field configuration without an existing WiFi network, use Bluetooth or a USB serial connection instead.
Ethernet Enabled
Config key: network.eth_enabled
Default: false
Enables the Ethernet interface on hardware that supports it. The documented Ethernet reference hardware is the RAK4631 paired with the RAK13800 Ethernet module (a W5500-class SPI Ethernet controller). Note that JSON MQTT output is not supported on the nRF52 platform.
Advantages of Ethernet over WiFi for infrastructure nodes:
- More reliable and stable connection - no RF interference, no re-association delays
- Lower and more consistent latency
- Generally lower and steadier power consumption than active WiFi
- No PSK to manage or expose
Use when: Deploying a fixed infrastructure node (ROUTER or gateway) at a location with Ethernet infrastructure - server room, communications closet, network rack. Ethernet-connected gateway nodes are more reliable than WiFi-connected ones for long-term unattended operation.
NTP Server
Config key: network.ntp_server
Default: meshtastic.pool.ntp.org
The hostname or IP address of the NTP (Network Time Protocol) server the node uses to synchronize its real-time clock when IP networking is available. Accurate time is important for:
- Correct message timestamps displayed in the app
- Log entries with accurate timestamps for troubleshooting
- MQTT message timestamps
- Coordinating time-dependent operations across the mesh
Meshtastic nodes without internet connectivity rely on time received from other nodes on the mesh (nodes share time information in packets). An NTP-synced node improves its own clock accuracy; that time can then propagate to other nodes via the normal packet exchange, but the firmware docs do not promise that a single NTP node becomes an authoritative time source for the entire mesh.
Recommended NTP Servers
| Server | Description | Use When |
|---|---|---|
meshtastic.pool.ntp.org | Default NTP pool used by the firmware | General use, internet-connected nodes |
time.cloudflare.com | Cloudflare NTP (anycast, fast) | Reliable alternative with good global coverage |
time.google.com | Google Public NTP | Reliable alternative |
time.nist.gov | NIST time server | When US government standard time is needed |
192.168.x.x (local) | Your own local NTP server | Isolated networks, high-accuracy requirements, no internet |
Local NTP server: If your deployment has a local NTP server (common in enterprise and government networks, and in some emergency operations centers), set this to that server's address. This reduces internet dependency and may improve synchronization accuracy.
NTP without internet: If the node has no internet access but is on a local network with a router that provides NTP (most home routers do), using the router's IP address as the NTP server works well: 192.168.1.1 or similar.
rsyslog Server
Config key: network.rsyslog_server
Default: Empty (disabled)
Configures remote syslog logging. When set to a hostname or IP address (with optional port, e.g., 192.168.1.100:514), the node sends its log output to a remote syslog server over UDP using the standard syslog protocol (RFC 3164/5424).
Why use remote logging:
- Centralized log collection from multiple nodes - see all infrastructure logs in one place
- Persistent log storage - node logs that would otherwise be lost on reboot are captured on the server
- Real-time alerting - syslog servers can trigger alerts on specific log messages (errors, reconnections, etc.)
- Troubleshooting unattended nodes - diagnose issues without physically connecting a serial cable
Setup requirements:
- A syslog server running on the local network (rsyslog, syslog-ng, Graylog, or any standard syslog receiver)
- The node and syslog server must be on the same network (or routable to each other)
- UDP port 514 is the IANA standard and conventional default syslog port, but the port is configurable per the docs
Recommended syslog servers for small deployments:
- rsyslog on Linux (Raspberry Pi, server): Simple, lightweight, standard
- Graylog: Full log management with search and dashboards - good for larger deployments
- Loki + Grafana: Modern log aggregation with excellent visualization
Example rsyslog configuration to receive Meshtastic logs on a Linux server:
# /etc/rsyslog.d/meshtastic.conf
module(load="imudp")
input(type="imudp" port="514")
# Save Meshtastic logs to a dedicated file
if $fromhost-ip == '192.168.1.50' then /var/log/meshtastic/node1.log
Practical Configuration Guidance
Standard Home Gateway Node
For a mains-powered T-Beam or Station G2 acting as a gateway and router at home:
- WiFi SSID: your home network SSID (or use IoT VLAN if available)
- WiFi Password: your network password
- NTP Server:
meshtastic.pool.ntp.org(default is fine) - rsyslog Server: empty unless you have a log server
Field Deployment - No Internet
For a node deployed at an event or emergency operation without internet access:
- Leave WiFi SSID empty - there is no AP mode, so configure the node over Bluetooth or USB serial instead
- NTP Server: empty or your EOC's local network NTP if available
- Time will sync from other mesh nodes that have NTP access
Ethernet-Connected Infrastructure
For a fixed ROUTER node on a rack or network closet:
- Ethernet Enabled: true
- WiFi: disabled (leave SSID empty)
- NTP Server: your organization's NTP server or
time.cloudflare.com - rsyslog Server: your organization's syslog collector
- Managed Mode: true (set via
security.is_managed true, with admin keys configured)
Local Web Access
When the node is connected to your WiFi or Ethernet network, you can reach the Meshtastic web client in a browser:
- Open
http://<node-ip>/, or the mDNS aliashttp://meshtastic.local/ - The node serves the web client over its API (port 4403); this is the web client, not a separate self-hosted admin site
- For configuration when no network is available, use Bluetooth or a USB serial connection
Meshtastic Apps and Interfaces
Meshtastic Android App Overview
The Meshtastic Android app is the primary interface for configuring and using Meshtastic devices. It is the most feature-complete of the Meshtastic client apps and receives updates most frequently.
Installation and Requirements
- Google Play Store - Search "Meshtastic". Official app by Meshtastic LLC.
- Minimum Android version - Android 8.0 (Oreo). Devices on older Android versions cannot run the current app.
- Bluetooth - Required for local BLE connection to your node
- Location permissions - Required for mapping features; the app can use your phone's GPS to display your position on the mesh map
Connecting to a Node
- Enable Bluetooth on your phone
- Power on your Meshtastic device
- Open the app - it should automatically scan for nearby Meshtastic devices
- Tap your device in the "Connect a Radio" list
- For first connection: devices with a screen display a random 6-digit pairing PIN to enter; screenless devices use the fixed default PIN 123456
Alternative connections (for ESP32 boards):
- WiFi - If your node is in WiFi client mode, the app can connect via TCP at the node's IP address
- USB Serial - Some Android phones support OTG USB connection; the app supports serial connection if your phone has OTG capability
Key App Screens
Messages
Shows conversation threads. "Primary Channel" is the default public channel. Additional configured channels appear as separate tabs. Direct message threads appear separately. The app shows sent/received/acknowledged status for each message.
Nodes
Lists every node your device has heard of - directly or relayed through the mesh - as stored in the device's node database (NodeDB). It is not bounded by a "configured hop count" setting; hop distance is shown per node. Note that some roles are not advertised: REPEATER and CLIENT_HIDDEN nodes do not send NodeInfo and so do not appear in the list. For each node shown: node name, short name, last heard timestamp, signal quality (SNR/RSSI), battery level, and distance/bearing from your location. Tap a node to see detailed info or initiate a direct message.
Map
Displays node positions on a map. Your node appears as a solid icon; other nodes appear with their short names. Tap a node on the map to see its details. The map works offline if you've previously loaded the tiles for that area (tiles are cached on first use).
Radio Config
Device configuration organized into sections: Device, Position, Power, Network, Display, LoRa, Bluetooth, and Security. Changes are pushed to the connected node immediately when saved. The app's Security section maps to the firmware's security.* config namespace (for example security.serial_enabled, security.is_managed, and the admin-channel settings), not the device.* namespace.
Useful App Features
- Channel QR code sharing - Share your channel configuration as a QR code that other users can scan to join
- Export Config - Save your node's full configuration to a file for backup or migration
- Node DB - View and manage the list of known nodes; remove stale entries with "Remove Node"
- Waypoints - Share location pins with the mesh; useful for marking hazards, meeting points, or SAR targets
- Range Test - Activate the Range Test module directly from the app
Meshtastic iOS App Overview
The Meshtastic iOS app provides core functionality for iPhone and iPad users, including messaging, node management, and configuration. Feature parity with the Android app has improved substantially in recent releases.
Installation
- Apple App Store - Search "Meshtastic". Requires iOS 17 or later (as of June 2026 the App Store listing lists iOS 17.5 or later). The minimum tracks the most recent iOS versions, so verify the current requirement on the App Store listing when you install.
- TestFlight beta - Beta versions available through Apple TestFlight for users who want early access to new features
Connecting Your Node
iOS Bluetooth handling differs from Android:
- Enable Bluetooth on your iPhone/iPad
- Open the Meshtastic app - it will scan for nearby BLE devices
- On first launch the app presents a connection/Bluetooth screen listing nearby radios; select your node from that list to connect. (App menu labels change between releases, so the exact wording of this screen may differ from your version.)
- Tap to connect. iOS may require granting Bluetooth permission on first use.
- iOS typically prompts for the same 6-digit BLE pairing PIN as Android - a random PIN shown on the device screen for nodes with a display, or the default
123456on screenless nodes. Enter it when prompted.
Important iOS difference: iOS does not allow background BLE connections to stay active when the app is not in the foreground. Your phone must be awake and the Meshtastic app must be active to send and receive your own messages over BLE and to view the mesh. This is an iOS system limitation, not a Meshtastic issue. Note that the node itself continues to relay mesh traffic over LoRa independently of the phone - the phone is only needed for your own messaging and to view the mesh, not for the node to relay packets.
EmComm caveat: Do not rely on an iOS-tethered node for unattended, always-on relaying or alerting. When the phone sleeps or the app moves to the background, BLE drops and your phone will not receive messages. For emergency-communications relays or always-on coverage, use a dedicated always-on node (or an Android phone / USB connection) rather than an iPhone in your pocket.
Feature Comparison: Android vs iOS
| Feature | Android | iOS |
|---|---|---|
| Messaging | Yes | Yes |
| Node map | Yes | Yes |
| Full Radio Config | Yes | Yes (improving with each release) |
| Background BLE | Yes | No (iOS limitation) |
| USB Serial connection | Yes (with OTG) | Limited |
| WiFi TCP connection | Yes | Yes |
| Export/Import config | Yes | Yes |
| Range test module control | Yes | Yes |
| Apple Watch companion | N/A | Yes (basic) |
iOS-Specific Tips
- Keep the app active - To receive messages in real time, keep the app in the foreground or enable background app refresh (Settings → General → Background App Refresh → Meshtastic)
- Notifications - Enable notifications in iOS Settings to get alerts for new messages even when the app is in the background
- WiFi connection for always-on monitoring - If your node is on the same WiFi network, the iOS app can maintain a TCP connection more reliably than BLE for long sessions
Meshtastic Web Client and Python CLI
Meshtastic Web Client
The Meshtastic Web Client (client.meshtastic.org) provides a browser-based interface for configuring Meshtastic nodes without installing a mobile app. It supports WebSerial (Chrome/Edge) for USB connections and WebBluetooth for BLE connections on supported platforms.
Use Cases for the Web Client
- Configuring nodes on a desktop computer without a phone
- Full configuration access on laptops where the mobile app isn't practical
- Quick configuration of multiple nodes at a deployment event
- Advanced configuration options that may appear in the web client before the mobile apps
Connecting via Web Serial
- Open client.meshtastic.org in Chrome or Edge (not Firefox)
- Click "New Connection" → "Serial"
- Select your device's COM port or /dev/tty device
- The full configuration interface loads directly in the browser
Meshtastic Python CLI
The Python CLI is the most powerful configuration interface - it provides access to every configurable parameter and supports automation scripting.
Installation
pip install meshtastic
Requires a recent Python 3 (check the current minimum in the project's pyproject.toml on github.com/meshtastic/python). Works on Windows, macOS, and Linux.
Essential Commands
# Connect to a serial node and show device info
meshtastic --info
# List all nodes in the node database
meshtastic --nodes
# Set a configuration value
meshtastic --set lora.modem_preset MEDIUM_SLOW
# Set multiple values at once
meshtastic --set device.role ROUTER --set bluetooth.enabled false
# Export full configuration to a YAML file
meshtastic --export-config > config-backup.yaml
# Restore (apply) a configuration from that YAML file
meshtastic --configure config-backup.yaml
# Send a test message
meshtastic --sendtext "Test message from CLI"
# Listen to all incoming packets
meshtastic --listen
# Connect to a node via TCP (WiFi/network)
meshtastic --host 192.168.1.100 --info
# Factory reset
meshtastic --factory-reset
Scripting with Python API
import meshtastic
import meshtastic.serial_interface
# Connect
iface = meshtastic.serial_interface.SerialInterface()
# Get device info
info = iface.localNode.localConfig
print(info)
# Send a message
iface.sendText("Hello mesh!")
# Listen for messages
from pubsub import pub
def on_receive(packet, interface):
print(f"Received: {packet}")
pub.subscribe(on_receive, "meshtastic.receive")
iface.close()
Specifying a Connection
# Serial (auto-detect)
meshtastic --info
# Serial (specific port - Windows)
meshtastic --port COM3 --info
# Serial (specific port - Linux/Mac)
meshtastic --port /dev/ttyUSB0 --info
# TCP (WiFi connection)
meshtastic --host 192.168.1.100 --info
# BLE (by node name)
meshtastic --ble "Meshtastic_ABCD" --info
Power Configuration
Meshtastic Power Settings Reference
The Power configuration section in Meshtastic controls sleep modes, charge management, and power-related behavior. These settings are critical for battery and solar-powered deployments.
Accessing Power Settings
In the app: Radio Config → Power
Via CLI: meshtastic --get power
Is Power Saving Enabled
meshtastic --set power.is_power_saving true
When enabled, the node reduces power consumption by putting the CPU into light sleep between receive windows. In light sleep the CPU is suspended but the LoRa radio stays on, so the node can still hear and respond to traffic. This is appropriate for CLIENT role nodes that send and receive messages but want longer battery life.
For the ROUTER role, power.is_power_saving is force-enabled automatically (on ESP32) and cannot be turned off — you do not set it manually. Even with power saving on, the LoRa radio remains in standby and wakes on incoming packets, so the node continues to relay; light sleep (radio standby) does not prevent relaying. REPEATER is deprecated as of firmware 2.7.11; for infrastructure prefer ROUTER (or ROUTER_LATE).
On Battery Discharge Values
These settings affect how the node reports battery state-of-charge from measured voltage. The defaults work for LiPo batteries; the ADC multiplier may need adjustment for LiFePO4 or for boards whose voltage divider reads inaccurately:
| Setting | Default | Notes |
|---|---|---|
| adc_multiplier_override | 0 (auto) | May need tuning per board for accurate voltage reading, including LiFePO4 packs |
Meshtastic does not provide a generic “shut down after N seconds at a critical-voltage threshold” key. (The firmware option power.on_battery_shutdown_after_secs shuts the device down after external power is lost for N seconds — it is a power-loss timer, not a low-voltage cutoff — and defaults to 0 = disabled.) For low-battery protection rely on the firmware’s built-in low-battery handling rather than a fabricated voltage-cutoff timer.
Sleep Configuration
For ESP32-based nodes, Meshtastic uses light sleep between activity to reduce power consumption. Note that the older super-deep-sleep keys (power.sds_secs and power.mesh_sds_timeout_secs) have been removed/deprecated and are no longer part of the current, documented power config — do not rely on them.
# Enable power saving (CLIENT nodes; ROUTER force-enables it automatically)
meshtastic --set power.is_power_saving true
# Light sleep interval. A value of 0 means the firmware default (5 minutes).
meshtastic --set power.ls_secs 0
# Minimum wake time - stay awake at least this long after waking
meshtastic --set power.min_wake_secs 10
Screen and BT Power
# Screen timeout (0 = always off)
meshtastic --set display.screen_on_secs 30
# Bluetooth enable/disable
meshtastic --set bluetooth.enabled true
# BLE pairing mode (RANDOM_PIN, FIXED_PIN, or NO_PIN)
meshtastic --set bluetooth.mode RANDOM_PIN
Use RANDOM_PIN (the secure default) unless you have a specific reason not to. Setting NO_PIN disables BLE pairing security and lets any nearby device connect without a PIN — only use it on physically secured nodes. (These Bluetooth options live under bluetooth.*, not the Power config, but are included here because they affect power draw.)
Power Consumption by Role
Meshtastic does not publish per-board current-draw figures; actual milliamp draw is highly board-, display-, and firmware-dependent. The official device-role table describes power qualitatively (Regular / Low / High):
| Role | Power saving | Relative power level |
|---|---|---|
| CLIENT | Optional (user-set) | Regular |
| CLIENT_MUTE | Optional (user-set) | Low |
| ROUTER | Force-enabled on ESP32 (LoRa radio in standby, wakes on packets) | High |
| REPEATER (deprecated) | Yes | High |
Peripherals add to whatever the role draws: an OLED/TFT display and WiFi both materially increase ESP32 power (WiFi is ESP32-only and is not available on nRF52). For exact numbers, measure your specific board or consult its datasheet rather than relying on generic figures.
Practical Power Optimization Checklist
- Set role correctly: CLIENT for personal nodes (enable power saving for longer battery life); ROUTER for fixed infrastructure (power saving is automatic)
- Set screen timeout to 30 seconds or disable (
screen_on_secs 0) - On ESP32, note that enabling WiFi disables Bluetooth (the WiFi setting takes precedence)
- Set position broadcast to 30+ minutes for fixed nodes
- Set telemetry broadcast interval up (the device telemetry default is 1800 s / 30 min)
- Reduce TX power to the minimum needed for coverage. For the legal transmit-power ceiling (US/Canada: 1 W / 30 dBm conducted, with EIRP limits for high-gain antennas), see the LoRa Settings Reference.
- Use nRF52840 hardware over ESP32 for substantially better battery life on the same battery
Power Configuration Settings Reference
Meshtastic's power management settings control how your node balances battery life against responsiveness. Understanding these settings is essential for field deployments and battery-powered infrastructure.
Key Power Settings
| Setting | Default | Description |
|---|---|---|
| power.is_power_saving | false | Enable aggressive power saving (see below) |
| power.adc_multiplier_override | 0 | Override ADC calibration for voltage reading (float, typically ~2-6; 0 = use firmware default) |
| power.wait_bluetooth_secs | 60 | Keep BLE active for N seconds after last connection |
| power.ls_secs | 0 | Light sleep interval (ESP32 only). A default of 0 means the firmware uses its built-in 5-minute light-sleep interval; it does NOT mean "no light sleep." No effect on nRF52/RP2040. |
| power.min_wake_secs | 10 | Minimum time to stay awake after waking from sleep |
Note: the older power.sds_secs (super deep sleep) and power.mesh_sds_timeout_secs keys, and power.on_battery_shutdown_after_secs, are no longer part of the current power config and should not be set. Verify any power key against the current firmware config protobuf before using it.
is_power_saving Mode
When enabled, the node aggressively reduces power consumption:
- Turns off WiFi when not actively connecting. (Caution: on a WiFi/MQTT gateway this can disrupt the connection — leave power saving off for always-on gateways.)
- Turns off BLE after
wait_bluetooth_secswith no activity - Reduces CPU clock speed when idle
- Turns off the display backlight more aggressively
Recommended for: Battery-operated client nodes, portable nodes used intermittently
NOT recommended for: Infrastructure repeaters, always-on gateway nodes
Light Sleep vs Deep Sleep
Meshtastic supports power-saving sleep behavior on ESP32 hardware:
- Light sleep (ls_secs) - Node sleeps briefly between LoRa receive windows. LoRa radio stays partially active and can wake on a received packet. Moderate power reduction at the cost of slight receive latency. (ESP32 only.)
- Deep sleep - On ESP32 the node can drop into a much deeper low-power state during long idle periods, drawing very little power but missing incoming messages until it wakes. This is most useful for sensor-style nodes that transmit periodically and don't need to receive continuously.
Battery Voltage Monitoring
Meshtastic reports battery voltage and calculated charge percentage via the telemetry system. The voltage reading depends on the hardware's ADC (Analog-to-Digital Converter) and a calibration factor:
# If battery percentage reads incorrectly, adjust ADC multiplier:
# First, measure actual battery voltage with a multimeter
# Then compare to what meshtastic reports:
meshtastic --info # reported battery voltage appears under the node's deviceMetrics
# Adjust if needed (value is a float multiplier, typically ~2-6):
meshtastic --set power.adc_multiplier_override 1.05
ESP32 vs nRF52 Power Comparison
The figures below are approximate community measurements intended for rough comparison only. Actual current draw varies with board, firmware, peripherals (GPS, display), and LoRa preset. For authoritative numbers consult the relevant chip datasheets (Espressif ESP32/ESP32-S3, Nordic nRF52840) and the Semtech SX126x receive-current spec.
| Chip | Idle Current | LoRa RX | LoRa TX @22 dBm | WiFi Active |
|---|---|---|---|---|
| ESP32 (T-Beam) | ~80 mA | ~50 mA | ~120 mA | +100-200 mA |
| ESP32-S3 (T-Beam Supreme) | ~30 mA | ~25 mA | ~90 mA | +80-150 mA |
| nRF52840 (RAK4631) | ~0.5 mA | ~4 mA | ~80 mA | N/A (no WiFi) |
Note: 22 dBm here is a typical board hardware output level used for this benchmark, which is distinct from the 30 dBm (1 W) conducted FCC legal maximum for 902-928 MHz.
The nRF52840 platform has dramatically lower idle and receive current, which is why RAK4631-based nodes generally achieve significantly longer battery life (often several times) than ESP32 nodes in typical deployment scenarios.
Recommended Configurations by Use Case
# Portable client node (maximize battery life):
meshtastic --set power.is_power_saving true
meshtastic --set power.wait_bluetooth_secs 30
meshtastic --set position.position_broadcast_secs 300 # 5 min GPS broadcast
# Fixed indoor node (always accessible):
meshtastic --set power.is_power_saving false
meshtastic --set power.wait_bluetooth_secs 0 # always keep BLE on
# Solar outdoor node (balance receive and power):
meshtastic --set power.is_power_saving true
meshtastic --set power.ls_secs 30 # light sleep 30s intervals (ESP32)
meshtastic --set power.min_wake_secs 15
LoRa Radio Configuration
Meshtastic LoRa Settings Reference
The LoRa settings control every aspect of your radio's physical layer configuration. These are the settings that determine whether your node can communicate with the rest of the network - get them right and everything works; get them wrong and you're invisible.
The Golden Rule
All nodes on a channel must use identical LoRa settings: same modem preset (or same SF/BW/CR), same frequency (derived from channel name hash or explicitly set), and same region. One misconfigured node silently disappears from the network.
Accessing LoRa Settings
In app: Radio Config → LoRa
Via CLI: meshtastic --get lora
Region
meshtastic --set lora.region US
Region sets the legal frequency range and power limits. Always set this correctly for your country. Options: US (902-928 MHz), EU_868 (863-870 MHz), EU_433, AU, NZ, KR, TW, RU, IN, JP, ANZ, LORA_24 (2.4 GHz), UA, MY_919, MY_433, SG, UNSET.
Canada is not a separate option — Canadian operators select US (same 902-928 MHz band). Do not use TW for Canada.
If you see "please set region" on your node's display, this is the setting to configure first.
Modem Preset
meshtastic --set lora.modem_preset LONG_FAST
The most important LoRa setting. Match whatever preset your local community uses. Do not change this without coordinating with your network. Valid values: SHORT_TURBO, SHORT_FAST, SHORT_SLOW, MEDIUM_FAST, MEDIUM_SLOW, LONG_TURBO, LONG_FAST (default), LONG_MODERATE, LONG_SLOW.
Note: LONG_TURBO appears in the official Meshtastic radio-settings preset table, but whether it is exposed as a selectable ModemPreset may depend on your firmware/app build. Confirm it is available in your firmware version before relying on it.
Hop Limit
meshtastic --set lora.hop_limit 3
Maximum relay hops before a packet is discarded. Default 3, maximum 7. Increase to 4-5 for larger networks where edge nodes aren't reliably reached. Higher hop limits increase network load — raise it only when you understand the implications.
Transmit Power
meshtastic --set lora.tx_power 0 # 0 = use the region maximum (recommended default)
Power in dBm. Leave tx_power at 0, which tells the firmware to use the region maximum (the firmware applies the correct per-region and per-board cap). Only set an explicit dBm value if you must reduce power for EIRP compliance with a high-gain antenna. The firmware enforces the maximum for your region; setting higher than the max is silently clamped.
For US/Canada (47 CFR 15.247): the conducted maximum is 1 W (30 dBm), referenced to an antenna gain of up to 6 dBi — which yields 36 dBm EIRP only at exactly 6 dBi. For every dB of antenna gain above 6 dBi you must reduce conducted (transmitter) power by the same number of dB, so 36 dBm EIRP is a derived ceiling, not a flat allowance you can fill with any antenna. Note also that most Meshtastic LoRa radios (e.g. SX1262 modules) physically cap conducted output near ~22 dBm, so 30 dBm is the regulatory limit, not what the hardware actually emits.
Channel Number and Frequency Override
# Set to channel 7 (specific frequency slot)
meshtastic --set lora.channel_num 7
# Override frequency directly, in MHz (advanced users only)
meshtastic --set lora.override_frequency 906.875
By default, Meshtastic derives the frequency from a hash of your channel name, automatically aligning nodes on the same channel. Channel 0 (default hash-based slot) is correct for most deployments. override_frequency is specified in MHz. Warning: setting override_frequency disables the channel-name hash and takes the node off the community frequency silently — a single node set this way goes deaf to everyone else. Do not combine override_frequency with channel_num; use one or the other. Only change either setting if your community has standardized on a specific slot.
Bandwidth, Spreading Factor, Coding Rate (Advanced)
To set these individually, first disable the preset, then set the values. Not recommended unless you are an RF engineer or are matching a specific non-standard deployment:
meshtastic --set lora.use_preset false
meshtastic --set lora.bandwidth 250
meshtastic --set lora.spread_factor 11
meshtastic --set lora.coding_rate 5
Bandwidth values (kHz): 31.25, 62.5, 125, 250, 500. The CLI accepts the integers 31, 62, 125, 250, 500 (the device interprets 31 → 31.25 kHz and 62 → 62.5 kHz). Spreading Factor 7-12. Coding Rate 5-8 (represents 4/5 through 4/8).
Use Preset vs Ignore MQTT
# Ignore incoming packets from MQTT downlink
meshtastic --set lora.ignore_mqtt true
When downlink is enabled, nodes rebroadcast packets received via MQTT. In a large network with many MQTT-connected nodes, this can cause significant packet amplification. Setting ignore_mqtt true on repeaters that don't need to rebroadcast MQTT traffic reduces this load.
Choosing the Right Modem Preset
The modem preset is the single most impactful LoRa setting - it determines range, data rate, and network capacity. This page provides a decision framework for choosing the right preset for your deployment.
Step 1: Ask Your Community First
Before anything else: what does your local mesh network use? Ask on the community Discord, check meshmap.net, or contact local operators. Using a different preset isolates your node from the existing community network. This is the most important step.
Step 2: If Building a New Network - Choose by Size
Sparse / Rural Networks (under 30 nodes in range)
Use Long Fast (the Meshtastic default). At low node density, network capacity is not a concern. Maximizing range ensures the widest possible coverage from each node. Long Fast provides excellent range while keeping data rate high enough for reasonable message latency.
Medium-Density Networks (30-60 nodes)
Consider Medium Fast or Medium Slow. At this density, Long Fast's airtime per packet starts contributing to congestion, especially with position telemetry from many nodes. Medium presets carry data faster (Medium Fast is about 3x quicker than Long Fast, Medium Slow about 2x), which reduces airtime while maintaining similar range in most terrain.
Dense Urban Networks (60+ nodes)
Use Medium Fast or Medium Slow. Some large community networks have reported success migrating away from Long Fast to faster presets - for example, the Bay Area Group (150+ nodes) moved to Medium Slow, and the Wellington Region Mesh in New Zealand migrated its mesh to Short Fast - and report reliability improvements. At high node counts, a full-length Long Fast packet (SF11/BW250) occupies roughly 1-2 seconds of airtime, creating constant background congestion; faster Medium presets cut airtime substantially and free up channel capacity. Actual airtime depends on payload size - a small (~50 byte) Long Fast packet is roughly 400-700 ms.
Preset Selection Matrix
| Preset | Link Budget | Data Rate | Best For | Avoid When |
|---|---|---|---|---|
| Long Slow | 158.5 dB | 0.18 kbps | Extreme range testing | Network > 5 nodes; can saturate channel |
| Long Moderate | 156 dB | 0.34 kbps | Very sparse, maximum range needed | Any network with moderate traffic |
| Long Fast | 153 dB | 1.07 kbps | Sparse/rural networks; new deployments | Dense networks >50 nodes |
| Long Turbo | 150 dB | 1.34 kbps | Higher data rate at Medium-tier range | When you need Long-Fast range (its 150 dB link budget is below Long Fast's 153 dB) |
| Medium Slow | 150.5 dB | 1.95 kbps | Medium to dense networks; recommended | Absolute maximum range required |
| Medium Fast | 148 dB | 3.52 kbps | Dense networks; high message volume | Long range links needed |
| Short Slow | 145.5 dB | 6.25 kbps | Very dense, high-throughput local nets | Any nodes more than 5 km apart |
| Short Fast | 143 dB | 10.9 kbps | Highest throughput applications | Any nodes more than 3 km apart |
| Short Turbo | 140 dB | 21.9 kbps | Maximum throughput | Uses 500 kHz bandwidth, permitted in the US/Canada 902-928 MHz band but not legal in some other regions (e.g., parts of the EU) - check your region |
Note: Long Turbo's 150 dB link budget is essentially the same as Medium Slow (150.5 dB) and below Long Fast (153 dB). It trades Long-Fast range for a higher data rate by using 500 kHz bandwidth, so treat its coverage as Medium-tier, not Long-tier. (Long Turbo's status as a current firmware enum should be confirmed against the firmware protobuf.)
Migrating a Network to a Different Preset
Changing presets on an active community network requires careful coordination:
- Announce the planned change at least 1 week in advance
- Document the exact new preset value
- Schedule a cutover time (e.g., Sunday midnight when traffic is lowest)
- Have all operators change their nodes simultaneously
- Verify connectivity after the change
- Update all network documentation with the new preset
Nodes that don't update in time will be invisible to the network until their operator updates. Plan for 2-4 weeks of dual-operation where some users are still on the old preset.