Meshtastic

Everything about the Meshtastic protocol: how it works, how to set it up, and technical details.

📖 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

  1. Meshtastic Setup Guide - First-time setup walkthrough
  2. Initial Node Configuration Checklist - Everything to configure before going live
  3. Meshtastic Android App Overview or iOS App Overview
  4. Understanding What You're Seeing in the App

📚 What's In This Book

How Meshtastic Works

Device Roles

Configuration Reference

Channels and Encryption

MQTT and Internet Connectivity

Modules and Integrations

Network Diagnostics

How Meshtastic Works

The Meshtastic protocol, routing, and architecture explained.

How Meshtastic Works

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:

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

ParameterValue
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
ModulationLoRa (Long Range)
Channel encryptionAES-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
RoutingManaged flooding with hop limit
GPS supportOptional (device-dependent)
ProtocolOpen source, Protobuf-based
How Meshtastic Works

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:

  1. Your node broadcasts the packet over LoRa
  2. Every node that receives it checks whether it has seen this packet before (via a packet ID hash)
  3. If not seen before: the node rebroadcasts the packet (with hop count decremented by 1)
  4. 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:

Addressing: Broadcast vs Direct

Meshtastic packets can be addressed two ways:

Why Flooding Works Well for Small Networks

For networks up to ~50-100 nodes, flooding is remarkably effective:

Flooding's Limitations in Large Networks

As networks grow, flooding creates a "broadcast storm" problem:

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.

How Meshtastic Works

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:

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:

# 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:

  1. Nodes periodically broadcast "NodeInfo" packets containing their node ID, name, hardware model, and (optionally) GPS position
  2. Any node that receives a NodeInfo packet adds the sender to its local node database
  3. NodeInfo packets propagate through the mesh via the same flooding mechanism as messages
  4. 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.

Setting Up 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

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.

  1. Connect your device via USB
  2. Open flasher.meshtastic.org in Chrome or Edge
  3. Select your device type and click Flash
  4. 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:

  1. Go to Config → LoRa
  2. 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.
  3. 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.

Setting Up Meshtastic

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:

  1. Visit flasher.meshtastic.org in Chrome or Edge
  2. Connect node via USB
  3. Select your board model exactly (wrong board = failed flash)
  4. Select "Latest Release" and click Flash
  5. 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.

Setting Up Meshtastic

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

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

SituationRecommended Role
Default personal useClient
Dense urban area or event with congestionClient Mute
Reduce node-list visibilityClient Hidden
Device Roles — All 12

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

  1. Excellent placement - hilltop, tower, rooftop, or other elevated location with line-of-sight coverage over a wide area
  2. 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.
  3. 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?

AttributeRouterRouter LateRepeater
Visible in node listYesYesNo
Broadcasts positionYesYesNo
Relay timingStandard (prioritized routing)Late (backup)Standard (prioritized routing)
Network overheadModerateModerateMinimal
Best forPrimary hilltop relayBackup coverageHigh-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.

Device Roles — All 12

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.

Use for: Asset tags, pet trackers, equipment that is rarely moved but may need to be located. Lost and Found uses Regular power (it is not a low-power role).

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

RoleCategoryKey Behavior
ClientPersonalDefault. Rebroadcasts via managed flooding; visible in the node list.
Client MutePersonalNever relays. For dense/congested areas.
Client HiddenPersonalBroadcasts only as needed; not shown in the nodes list; for stealth/low-power deployments.
TrackerSpecializedHigh-priority location updates.
Lost and FoundSpecializedPeriodic location broadcast to the default channel (Regular power).
SensorSpecializedSensor readings + deep sleep between reports.
TAKTacticalATAK-optimized messaging.
TAK TrackerTacticalPriority tactical position for ATAK.
RouterInfrastructureAlways rebroadcasts once with prioritized routing; best on elevated/strategic sites; visible in the nodes list.
Router LateInfrastructureBackup relay; waits for others first.
RepeaterInfrastructureSilent relay; no node list presence. Deprecated as of firmware 2.7.11 - use Router for new infrastructure.

Channels & Encryption

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

The Default Public Channel

Out of the box, Meshtastic nodes are configured with:

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

Channels are shareable via URL or QR code. Example channel URL:

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.

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

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.

This is essential for maintaining remote or hard-to-reach infrastructure nodes.

Channels & Encryption

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

  1. Open the Meshtastic app and go to Radio Config → Channels
  2. Select an unused channel slot (index 1 - 7; leave index 0 as the public primary unless you have a specific reason to change it)
  3. Set a channel name (e.g., TeamAlpha)
  4. Tap Generate to create a random PSK, or enter a known PSK manually
  5. Save the channel
  6. 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

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:

  1. 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.
  2. 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

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:

Environment Telemetry (requires I2C sensor)

Available when a supported sensor board is connected via I2C:

Supported sensors include BME280, BME68x (BME680/BME688), SHT31, and others. Enable in app: Radio Config → Telemetry → Environment Telemetry.

Position

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:

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.

Telemetry & Monitoring

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 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:

Diagnosing High Channel Utilization

If channel utilization is above 25%, work through these checks:

  1. 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.
  2. Check telemetry intervals - Frequent device metrics, position, or environment telemetry from many nodes adds up quickly. Increase intervals across the network.
  3. 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.
  4. 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 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
Meshtastic CLI Reference

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

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

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.

How MQTT works in Meshtastic

When MQTT is enabled on a node:

  1. Every mesh packet received by the node is forwarded to an MQTT broker over WiFi or TCP
  2. The MQTT broker stores and redistributes the messages to other subscribers
  3. 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.
  4. 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

  1. Go to Settings → Module Config → MQTT
  2. Enable MQTT: toggle ON
  3. MQTT Server Address: mqtt.meshtastic.org
  4. Username: meshdev
  5. Password: large4cats
  6. TLS Enabled: toggle ON (recommended)
  7. Map Reporting Enabled: toggle ON to publish a map report so your node appears on community maps that consume MapReport packets
  8. Save

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

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.

MQTT & Internet Gateway

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

OptionHardwareProsCons
ESP32 node (simplest)Heltec V3/V4, T-BeamSingle device, no Pi needed, compactSingle-channel, limited processing
Pi + LoRa hatRaspberry Pi 4 + SX126x HATFull Linux environment via meshtasticdMore 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 nodeRaspberry Pi + RAK4631 USBEasy setup, use standard Meshtastic firmwareSingle 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.

  1. Flash with Meshtastic firmware (standard)
  2. Connect to your home or community WiFi: meshtastic --set network.wifi_enabled true --set network.wifi_ssid "YourSSID" --set network.wifi_psk "YourPassword"
  3. Configure MQTT as described in the MQTT Setup page
  4. 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.
  5. 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 & Internet Gateway

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:

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()
MQTT & Internet Gateway

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

  1. Node A sends a message over LoRa
  2. Gateway node G receives the LoRa packet and publishes it to MQTT
  3. MQTT broker delivers the packet to Gateway G's downlink subscription
  4. Gateway G injects the packet back into the LoRa network
  5. Node A receives its own message again as if from the MQTT cloud
  6. 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:

# 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

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

  1. Connect your Meshtastic device via USB cable
  2. Open client.meshtastic.org in Chrome or Edge
  3. Click "Connect" and select "Serial"
  4. Choose your device from the port list
  5. 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

  1. Open client.meshtastic.org in a Chromium-based browser
  2. Click "Connect" and select "Bluetooth"
  3. 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

FeatureMobile appWeb app
Node configurationYesYes (more detailed)
Channel managementYesYes
Message viewYesYes
Node mapYesYes
Configuration export/import (YAML)Channel/config sharing via URL/QR; recent apps also support config backupYes
Raw packet logLimitedFull packet log
Serial debug consoleNoYes
Firmware update (OTA)Yes (BLE OTA on supported devices)Browser flashing via USB (Web Flasher); verify current per-platform support
Full module config accessPartialYes

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):

  1. Connect to device via web app
  2. Navigate to Config → Export Config
  3. 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:

The serial console is accessed via Tools → Serial Console in the web app. Output can be copied and shared when reporting bugs.

Advanced Features

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

Advanced Features

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:

  1. Open the Node List and tap the target node.
  2. Tap the three-dot menu or the node detail view.
  3. Select Traceroute.
  4. 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

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.

Advanced Features

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:

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

  1. Open the Meshtastic app and connect to your node.
  2. Go to Radio Configuration → Modules → Neighbor Info.
  3. Toggle Enabled to on.
  4. Optionally adjust the Update Interval (in seconds; minimum 14400 / 4 hours). Shorter intervals give fresher data but increase channel utilization.
  5. 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.)

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.

Integrations & Automation

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:

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:

Setup Overview

  1. Install the gateway and its Python dependencies (follow the project's own README; for a manual stack: pip install meshtastic paho-mqtt aprslib).
  2. Clone the gateway repository and copy the example config file.
  3. Edit the config: enter your callsign, APRS-IS passcode, APRS-IS server (e.g. rotate.aprs2.net:14580), and MQTT broker address.
  4. Test manually using the command documented by the gateway project.
  5. Install as a systemd service for automatic start on boot:
    /etc/systemd/system/aprstastic.service
    Set Restart=on-failure and RestartSec=10 to 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.

Integrations & Automation

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 classTransportTypical 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

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:

Error Handling

Integrations & Automation

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:

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.

Integrations & Automation

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

To share or back up a flow, select all nodes with Ctrl+A, then go to Menu → Export → Clipboard. The resulting JSON can be imported on any Node-RED instance. Store your flow exports in version control alongside your Meshtastic configuration.

Security & Privacy

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:

  1. Set a custom channel name (different from "LongFast" or "Default").
  2. 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.
  3. 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:

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:

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.

Security & Privacy

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

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:

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:

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:

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.

Channel Management

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:

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

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.

Channel Management

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

  1. Open the Meshtastic app and navigate to Channels.
  2. Select the channel you want to share.
  3. Tap the Share (QR icon) button.
  4. 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:

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 Management

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:

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:

  1. Channel name - must match exactly (case-sensitive). The PRIMARY channel name also auto-derives the default frequency slot unless you explicitly override the slot.
  2. PSK - share via QR code, not as a raw string.
  3. 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.
  4. 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:

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.

Network Diagnostics and Health Monitoring

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 RangeNetwork HealthInterpretation
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:

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).

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 −80Very strong (rule of thumb). Nodes are physically close or have excellent antennas.
−80 to −100Normal operational range for most deployments.
−100 to −115Weak but decodable at high spreading factors.
< −120Conservative 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:

Spotting a Congested Network

The most common congestion warning signs:

  1. 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.
  2. 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.
  3. 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.
  4. 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.

Network Diagnostics and Health Monitoring

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:

--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:

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:

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

  1. Start with --info to get baseline node count and channelUtilization. If utilization > 25%, immediately check individual node position intervals.
  2. Run --nodes and flag any node where LastHeard is more than 2 hours old during an active session. These are likely ghost nodes.
  3. Run --listen for 5 - 10 minutes during peak mesh activity to count duplicate packet IDs and identify high-frequency transmitters.
  4. Export config with --export-config and compare hop_limit across nodes. A community mesh should generally use a maximum hop limit of 3 (the default).

Serial vs TCP: When to Use Each

MethodBest ForLimitations
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
Network Diagnostics and Health Monitoring

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:

  1. The originating node sets the hop_limit field (default 3, maximum 7).
  2. 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_limit by 1.
  3. A node will not relay a packet whose hop_limit has reached 0.
  4. 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:

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:

To appear on meshmap.net your node must:

  1. Have GPS or a fixed position configured.
  2. Have MQTT enabled pointing to mqtt.meshtastic.org or a community server that feeds into the map.
  3. 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:

  1. 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.
  2. Via traceroute: run traceroute to several different destination nodes. If the same relay node ID appears in every path, that node is a bottleneck.
  3. 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:

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.

Network Diagnostics and Health Monitoring

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

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

  1. 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
  2. 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
  3. 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
  4. 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

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

  1. 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-nodedb

    Caution: --reset-nodedb removes 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.

  2. 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.

  3. Note on node-info broadcast cadence:

    The device.node_info_broadcast_secs setting 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-node as 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

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:

Solutions

  1. 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.

  2. 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
  3. Identify and temporarily disable the node that triggered the loop.

    During a --listen session, 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.

  4. 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

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

  1. 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.

  2. 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.

  3. 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.

  4. 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
Network Diagnostics and Health Monitoring

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:

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:

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

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

Meshtastic Python API

Complete guide to the Meshtastic Python library: connection, messaging, automation, and API reference.

Meshtastic Python API

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:

  1. Opens the serial port at 115200 baud.
  2. Performs a handshake to confirm the device is running Meshtastic firmware.
  3. 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:

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()
Meshtastic Python API

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

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.

ParameterTypeDescription
debugOutfile-likeStream for debug output. Default None.
noProtoboolSkip protocol handshake (testing only). Default False.
noNodesboolSkip 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 / PathTypeDescription
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:

ModuleKey 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 VersionFirmware CompatibilityNotes
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

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:

FieldSourceNotes
Battery level (%)Hardware ADCAccuracy varies by board; some boards always report 100% if battery not detected
Voltage (V)Hardware ADCUseful for deriving SoC for LiFePO4 packs
Channel utilization (%)Radio statsPercentage of airtime used; over 25% indicates congestion
Air utilization TX (%)Radio statsPercentage of time this node was transmitting
Uptime (seconds)System clockResets on power cycle

Environment Telemetry

Requires an external I2C sensor. Supported sensors include (accuracy figures are from each sensor's manufacturer datasheet):

SensorMeasuresI2C AddressNotes
BME280Temp, Humidity, Pressure0x76 or 0x77Most popular; inexpensive; not suitable for air quality
BME680 / BME688 (BME68x)Temp, Humidity, Pressure, gas resistance0x76 or 0x77Reports 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
SHT31Temp, Humidity0x44 or 0x45High accuracy; ±0.3°C, ±2% RH (per Sensirion SHT3x datasheet)
MCP9808Temperature only0x18±0.25°C accuracy (per Microchip MCP9808 datasheet, which is address-selectable across 0x18-0x1F); Meshtastic auto-detects it at 0x18
SHTC3Temp, Humidity0x70Low 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:

Meshtastic Modules and Plugins

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

  1. 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.
  2. The server periodically sends a heartbeat advertising itself to the mesh.
  3. A client that was offline requests history from the Store and Forward server (on Android, by sending the text command SF to the server; on the Apple/connected app this happens automatically).
  4. 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

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.

Meshtastic Modules and Plugins

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:

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

  1. Place the sender node at your repeater location or test deployment point. Ensure it has GPS lock and is transmitting.
  2. Configure an ESP32-based portable node as the receiver (so it can save the CSV to flash).
  3. Drive or walk through your intended coverage area.
  4. 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.
  5. 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.

RSSISNRConnection Quality
-80 to -100 dBm>5 dBExcellent - reliable delivery
-100 to -115 dBm0 to 5 dBGood - occasional packet loss
-115 to -125 dBm-5 to 0 dBMarginal (preset-dependent)
Below -125 dBmnear 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.

Meshtastic Modules and Plugins

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:

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.

Meshtastic Modules and Plugins

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

ModeDescriptionUse Case
DEFAULTSame as SIMPLE - a raw (dumb) UART byte tunnelGeneric byte bridging
SIMPLERaw byte UART tunnel (no framing); requires a channel named serialBridging arbitrary bytes between two nodes
TEXTMSGSends/receives strings as text messages on the default text channelSimple text message bridging
PROTOProtobuf framingProgrammatic Arduino/ESP32 integration / full access
NMEAOutputs NMEA sentences from node GPSFeeding position to chartplotters
CALTOPOCalTopo-compatible position formatSAR 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

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

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:

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

Source: meshtastic.org/docs/overview/encryption/ and meshtastic.org/blog/introducing-new-public-key-cryptography-in-v2_5/. Verified 2026-05-03.

Encryption and Privacy

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:

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

  1. Create a channel with a random PSK and name it "admin" (or any name you choose)
  2. Designate that channel as the admin channel (the security admin-channel setting)
  3. Add this channel to all nodes you want to manage
  4. 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

Meshtastic Configuration Reference

Comprehensive reference for all settings under Config > Device, Config > Position, and Config > Network in the Meshtastic firmware.

Meshtastic Configuration Reference

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:

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:

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:

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:

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:

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:

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:

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:

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.

Meshtastic Configuration Reference

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:

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:

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:

  1. 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
  2. If the node has not moved, it waits up to the configured Broadcast Interval before broadcasting
  3. 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 CaseRecommended IntervalRationale
Vehicle tracker (active event)60 - 120 secondsFrequent updates needed for real-time tracking
Hiking/walking node120 - 300 secondsBalance between track fidelity and airtime
Fixed CLIENT node900 - 1800 secondsPosition doesn't change; reduce overhead
Fixed ROUTER/infrastructure3600 - 10800 secondsMinimal 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:


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).


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.

FlagData AddedUse When
ALTITUDEInclude an altitude value (if available)Mountainous terrain, aviation, 3D positioning
ALTITUDE_MSLInterpret the altitude value as height above mean sea level (vs ellipsoid); a modifier on ALTITUDE, not a separate fieldWhen sea-level altitude is specifically needed
GEOIDAL_SEPARATIONDifference between WGS84 ellipsoid and geoidPrecision surveying; not needed for most uses
DOPDilution of Precision (accuracy estimate)When position accuracy qualification is important
HVDOPWhen DOP is enabled, send separate HDOP and VDOP instead of a single PDOPPrecision tracking applications
SATINVIEWNumber of GPS satellites in viewDiagnostics, signal quality assessment
SEQ_NOSequence number (incremented per packet)When multiple position sources must be ordered
TIMESTAMPTimestamp of the GPS fixWhen time-of-fix (vs time-of-transmission) matters
HEADINGCourse over ground in degreesVehicle and vessel tracking, direction of travel
SPEEDSpeed over groundVehicle 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 ModeApproximate Current Draw (estimate)Use When
Continuous (always on)30 - 50 mA continuousReal-time tracking where every second matters
Duty-cycle (120s interval)2 - 8 mA averageStandard mobile node
Duty-cycle (600s interval)0.5 - 2 mA averageLow-power mobile node
DISABLED (fixed position)0 mA for GPSFixed infrastructure nodes
Meshtastic Configuration Reference

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:


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:

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:

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:

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.

ServerDescriptionUse When
meshtastic.pool.ntp.orgDefault NTP pool used by the firmwareGeneral use, internet-connected nodes
time.cloudflare.comCloudflare NTP (anycast, fast)Reliable alternative with good global coverage
time.google.comGoogle Public NTPReliable alternative
time.nist.govNIST time serverWhen US government standard time is needed
192.168.x.x (local)Your own local NTP serverIsolated 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:

Setup requirements:

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:

Field Deployment - No Internet

For a node deployed at an event or emergency operation without internet access:

Ethernet-Connected Infrastructure

For a fixed ROUTER node on a rack or network closet:

Local Web Access

When the node is connected to your WiFi or Ethernet network, you can reach the Meshtastic web client in a browser:

Meshtastic Apps and Interfaces

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

Connecting to a Node

  1. Enable Bluetooth on your phone
  2. Power on your Meshtastic device
  3. Open the app - it should automatically scan for nearby Meshtastic devices
  4. Tap your device in the "Connect a Radio" list
  5. 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):

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

Meshtastic Apps and Interfaces

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

Connecting Your Node

iOS Bluetooth handling differs from Android:

  1. Enable Bluetooth on your iPhone/iPad
  2. Open the Meshtastic app - it will scan for nearby BLE devices
  3. 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.)
  4. Tap to connect. iOS may require granting Bluetooth permission on first use.
  5. 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 123456 on 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

FeatureAndroidiOS
MessagingYesYes
Node mapYesYes
Full Radio ConfigYesYes (improving with each release)
Background BLEYesNo (iOS limitation)
USB Serial connectionYes (with OTG)Limited
WiFi TCP connectionYesYes
Export/Import configYesYes
Range test module controlYesYes
Apple Watch companionN/AYes (basic)

iOS-Specific Tips

Meshtastic Apps and Interfaces

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

Connecting via Web Serial

  1. Open client.meshtastic.org in Chrome or Edge (not Firefox)
  2. Click "New Connection" → "Serial"
  3. Select your device's COM port or /dev/tty device
  4. 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

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:

SettingDefaultNotes
adc_multiplier_override0 (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):

RolePower savingRelative power level
CLIENTOptional (user-set)Regular
CLIENT_MUTEOptional (user-set)Low
ROUTERForce-enabled on ESP32 (LoRa radio in standby, wakes on packets)High
REPEATER (deprecated)YesHigh

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

Power Configuration

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

SettingDefaultDescription
power.is_power_savingfalseEnable aggressive power saving (see below)
power.adc_multiplier_override0Override ADC calibration for voltage reading (float, typically ~2-6; 0 = use firmware default)
power.wait_bluetooth_secs60Keep BLE active for N seconds after last connection
power.ls_secs0Light 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_secs10Minimum 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:

Light Sleep vs Deep Sleep

Meshtastic supports power-saving sleep behavior on ESP32 hardware:

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.

ChipIdle CurrentLoRa RXLoRa TX @22 dBmWiFi 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 mAN/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.

# 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

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
LoRa Radio Configuration

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

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:

  1. Announce the planned change at least 1 week in advance
  2. Document the exact new preset value
  3. Schedule a cutover time (e.g., Sunday midnight when traffic is lowest)
  4. Have all operators change their nodes simultaneously
  5. Verify connectivity after the change
  6. 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.