# Network Diagnostics and Health Monitoring

Tools and techniques for understanding the health of your Meshtastic mesh.

# Reading Network Statistics in the Meshtastic App

Understanding what your Meshtastic network is actually doing requires knowing how to read the statistics the app surfaces. The mobile app (Android and iOS) and the web client all expose channel utilization, airtime, packet counters, and radio-level metrics. This page explains every number, what healthy ranges look like, and how to spot a congested network before it starts dropping messages.

## Where to Find Network Statistics

### Android App

Open the Meshtastic Android app and navigate to **Settings → Radio Configuration → Device**. Channel utilization and airtime metrics appear at the top of the device status screen. 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 roughly every 15 seconds.

## Channel Utilization %

Channel utilization measures what fraction of available airtime on the LoRa channel has been consumed by transmitted packets over a rolling window (typically the last 5 or 15 minutes depending on firmware version).

<table id="bkmrk-utilization-rangenet"> <thead> <tr><th>Utilization Range</th><th>Network Health</th><th>Interpretation</th></tr> </thead> <tbody> <tr><td>0 - 15%</td><td>Healthy</td><td>Plenty of spare capacity. All traffic should get through.</td></tr> <tr><td>15 - 25%</td><td>Moderate</td><td>Noticeable load. Start auditing position broadcast intervals.</td></tr> <tr><td>25 - 50%</td><td>High</td><td>Collisions increasing. Expect occasional packet loss.</td></tr> <tr><td>50 - 100%</td><td>Congested</td><td>Severe contention. Most non-acked packets will be lost.</td></tr> </tbody></table>

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. A single packet at SF10 / BW125 / CR 4/5 takes approximately 370 ms on air. At the default LongFast preset you can fit roughly 40 such packets per minute before hitting 25% utilization - a limit that is easily exceeded on a busy community mesh.

## Air Utilization

Air utilization is closely related but specifically counts the fraction of time the radio was *actively transmitting* (TX duty cycle). Regulators in many countries impose limits (e.g., 1% or 10% duty cycle per hour in EU 868 MHz sub-bands). Meshtastic's firmware enforces these limits internally; when the limit is approached the device will delay or drop outgoing packets to stay compliant. If you see the air utilization climbing toward your regional limit, reduce your transmit frequency.

## Packet Counters

The firmware maintains several packet counters that help diagnose forwarding behavior:

- **Packets Rx**: Total valid LoRa packets received (passed CRC check).
- **Packets Rx Bad**: Packets received with a failed CRC. A high ratio of bad packets to good packets indicates RF interference or a node with a damaged antenna.
- **Packets Tx**: Packets this node has transmitted (original or forwarded).
- **Packets Tx Relay**: Subset of Tx that were relayed on behalf of another node. A router node should have a high relay ratio. A client node with a very high relay count may be flooded because its hop limit is too generous.
- **Packets Rx Duplicate**: Packets seen more than once. Some duplication is normal in mesh (same packet arrives via multiple paths); more than 10 - 20% of total Rx suggests a routing loop or misconfigured hop limits.

## SNR - Signal-to-Noise Ratio

SNR is the most important single-number indicator of link quality. It is reported in **decibels (dB)** and expresses how far above the noise floor the received signal sits. LoRa can decode packets at negative SNR values because spread-spectrum processing gain allows it to recover signal from noise.

<table id="bkmrk-snr-%28db%29link-quality"> <thead> <tr><th>SNR (dB)</th><th>Link Quality</th><th>Notes</th></tr> </thead> <tbody> <tr><td>≥ 5</td><td>Excellent</td><td>Strong link. Consider a lower spreading factor for shorter air time.</td></tr> <tr><td>0 to 5</td><td>Good</td><td>Reliable communications. Typical for in-range nodes.</td></tr> <tr><td>−5 to 0</td><td>Marginal</td><td>Packet loss possible. Occasional retransmit expected.</td></tr> <tr><td>−10 to −5</td><td>Weak</td><td>Frequent packet loss. Borderline path - consider a relay.</td></tr> <tr><td>&lt; −10</td><td>Very Weak</td><td>Near the LoRa sensitivity floor. Most packets will be lost.</td></tr> </tbody></table>

The minimum decodable SNR depends on the spreading factor. At SF7 the floor is approximately −7.5 dB; at SF12 (LongSlow) it is −20 dB. The default LongFast preset uses SF11/BW250, giving a floor around −17.5 dB.

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

<table id="bkmrk-rssi-%28dbm%29interpreta"> <thead> <tr><th>RSSI (dBm)</th><th>Interpretation</th></tr> </thead> <tbody> <tr><td>−60 to −80</td><td>Very strong. Nodes are physically close or have excellent antennas.</td></tr> <tr><td>−80 to −100</td><td>Normal operational range for most deployments.</td></tr> <tr><td>−100 to −115</td><td>Weak but decodable at high spreading factors.</td></tr> <tr><td>&lt; −120</td><td>At or below sensitivity floor. Packet loss very likely.</td></tr> </tbody></table>

## What Healthy Numbers Look Like

A well-configured community mesh with 10 - 30 nodes should exhibit:

- Channel utilization consistently below 15%.
- SNR between −5 and +10 dB on all primary relay links.
- RSSI above −115 dBm on relay links.
- Duplicate packet ratio below 10%.
- Zero or near-zero "Rx Bad" packets (CRC failures) unless there is known interference.

## Spotting a Congested Network

The most common congestion warning signs:

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.

# 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.9 or later
pip install meshtastic

# 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/tty.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 requires the `bleak` extras package on some platforms:

```
pip install "meshtastic[ble]"
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 JSON-structured summary of the connected node, 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,
 "role": "CLIENT",
 "positionFlags": 811,
 "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" },
 "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 ... }
Channels: [ { "index": 0, "role": "PRIMARY", "settings": { ... } }, ... ]

```

Key fields to check during diagnostics:

- `channelUtilization`: percentage of channel used in the last 5 minutes.
- `airUtilTx`: TX duty cycle percentage.
- `snr`: last-heard SNR from each remote node (dB).
- `lastHeard`: Unix timestamp. Subtract from current time to see how stale a node entry is.
- `firmwareVersion`: compare against the latest release to check if updates are needed.

## --nodes: Node Table

```
meshtastic --nodes

```

Prints a compact tabular summary of all nodes in the mesh database:

```
 N Num User AKA Hardware Latitude Longitude Altitude Battery SNR LastHeard
 1 2864434397 WB5ABC W5 TLORA_V2 32.3456 -97.0123 312m 87% 6.5 2024-04-25 10:31:22
 2 3109276812 K5ZZZ K5 RAK4631 32.3512 -97.0099 289m 72% 2.1 2024-04-25 10:29:55
 3 4012345678 N5XYZ N5 HELTEC_V3 32.3390 -97.0210 278m -- -4.7 2024-04-25 09:15:10

```

The `LastHeard` column quickly shows stale nodes (entries in the node DB that haven't been heard in hours or days). These are candidates for manual removal if you are troubleshooting ghost-node problems. The SNR column shows the signal quality of the last packet heard *from* that node (not necessarily a direct link - the packet may have been relayed).

## --export-config: Full Configuration Export

```
meshtastic --export-config > node_backup_$(date +%Y%m%d).yaml

```

Exports the complete device configuration as YAML. This includes radio settings, channel definitions, module configs (telemetry intervals, MQTT settings, serial module, etc.), and device metadata. Use this to:

- Back up a node before a firmware update.
- Clone configuration from one device to another with `meshtastic --configure config.yaml`.
- Audit configuration differences between nodes in a community mesh.

## --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 <kbd>Ctrl-C</kbd> to stop. Example output:

```
TEXT_MESSAGE_APP: from=!aabbccdd, to=^all, id=0x12345678, hop_limit=3, want_ack=False
 payload: b'Hello from WB5ABC'

POSITION_APP: from=!aabbccdd, to=^all, id=0xdeadbeef, hop_limit=3, want_ack=False
 payload: Position(latitudeI=323456789, longitudeI=-970123456, altitude=312, time=1714010500)

TELEMETRY_APP: from=!ccccdddd, to=^all, id=0x87654321, hop_limit=1, want_ack=False
 payload: Telemetry(deviceMetrics=DeviceMetrics(batteryLevel=72, voltage=3.91,
 channelUtilization=11.3, airUtilTx=2.1))

```

During a `--listen` session, watch for:

- Packets arriving with `hop_limit=0` (the original TTL minus hops taken). A `hop_limit` of 0 on arrival means the packet used all of its allowed hops - the hop budget may be too low.
- Repeated identical packet IDs arriving within seconds (duplicate storm).
- High-frequency position packets from a single node ID indicating that node's smart position interval is too aggressive.

## Reading Telemetry from --listen

Every 15 minutes 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 &gt; 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

<table id="bkmrk-methodbest-forlimita"> <thead> <tr><th>Method</th><th>Best For</th><th>Limitations</th></tr> </thead> <tbody> <tr> <td>Serial (USB)</td> <td>Local bench testing, initial setup, firmware examination</td> <td>Requires physical access; occupies USB port preventing simultaneous app connection</td> </tr> <tr> <td>TCP (Wi-Fi)</td> <td>Remote diagnostics on deployed nodes, scripted automation, Raspberry Pi gateways</td> <td>Requires node to have Wi-Fi enabled and be on local network or accessible via port forward</td> </tr> <tr> <td>BLE</td> <td>Temporary field diagnostics without Wi-Fi</td> <td>Short range, platform-specific BLE stack issues, slower data rate</td> </tr> </tbody></table>

# 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).
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 firmware 2.3+ includes a built-in traceroute command that discovers the actual relay path to a destination node:

```
# 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 values shown are the signal quality measured at each relay node for the *incoming* leg of the path. A very negative SNR on one leg (below −7 dB) indicates a weak link that could cause delivery failures under poor conditions.

Important caveats for traceroute:

- The traceroute packet itself consumes channel airtime, so avoid running it repeatedly on a congested mesh.
- The path shown is the path taken by the traceroute probe - actual message traffic may take a different path because nodes relay whichever copy arrives first.
- Traceroute requires the destination node to be online and reachable.

## Visualizing the Network with meshmap.net

[meshmap.net](https://meshmap.net) is a community-maintained map that ingests position and neighbor-info telemetry from Meshtastic nodes that have MQTT upload enabled. It shows:

- Node locations on a geographic map.
- Neighbor links drawn between nodes that have directly heard each other (neighbor-info packets).
- Signal quality (SNR) color-coded on each link.
- Node metadata: firmware version, hardware model, last seen time.

To appear on meshmap.net your node must:

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.

## 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 moduleConfig.neighbor_info
# Shows:
# moduleConfig {
# neighbor_info {
# enabled: true
# update_interval: 900 # seconds between neighbor broadcasts
# }
# }

```

Setting the update interval too low (below 300 seconds) on a large mesh can noticeably increase channel utilization. 900 seconds (15 minutes) is a reasonable default.

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

- Deploy a second relay node with overlapping coverage to provide redundancy.
- Move the bottleneck node to a higher elevation to increase its coverage radius.
- Ensure the bottleneck node is configured as ROUTER role so the firmware prioritizes its relay duty and reduces sleep periods.

## Understanding Hop Count in Received Packets - Practical Example

```
import meshtastic
import meshtastic.serial_interface
from pubsub import pub

def on_receive(packet, interface):
 hop_start = packet.get("hopStart", 0)
 hop_limit = packet.get("hopLimit", 0)
 hops_taken = hop_start - hop_limit
 sender = packet.get("fromId", "unknown")
 print(f"Packet from {sender}: {hops_taken} hop(s) away, hop_limit_remaining={hop_limit}")

pub.subscribe(on_receive, "meshtastic.receive")
iface = meshtastic.serial_interface.SerialInterface()
input("Listening... press Enter to exit
")
iface.close()

```

Running this script while observing mesh traffic gives a live view of hop distances for every packet, which is invaluable for understanding how your network topology performs in practice.

# Common Network Problems and Solutions

Meshtastic networks are remarkably self-organizing, but they are not self-diagnosing. When the mesh stops working well - messages drop, nodes disappear, delivery becomes unreliable - there are a small number of root causes that account for the vast majority of problems. This page covers the four most common issues: high channel utilization, ghost nodes, routing loops, and clock skew, along with specific remediation steps for each.

## Problem 1: High Channel Utilization

### Symptoms

- Channel utilization percentage consistently above 25%.
- Messages sometimes fail to deliver even between nearby nodes.
- The node list shows many nodes as "last heard" minutes ago even though they are known to be online.
- ACKs are not received for sent messages despite short distances.

### Root Cause

The primary driver of channel utilization is **position broadcast frequency**. Every node broadcasts its GPS position on a smart interval (minimum interval) and an update interval. 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 with SF11/BW250 this is approximately 200 ms of airtime. With 30 nodes broadcasting every 900 seconds, position alone consumes roughly 30 \* 0.2 / 900 = 0.67% airtime continuously - which seems low but the packets all cluster in bursts, and additional mesh overhead (ACKs, route discovery, telemetry) multiplies the effective utilization.

### Solutions

1. **Increase minimum and maximum position broadcast intervals.**In the [Meshtastic app](https://wiki.meshamerica.com/books/hardware-guide/page/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.
    
     ```
    meshtastic --set telemetry.environment_update_interval 0
    meshtastic --set telemetry.power_update_interval 0
    ```
3. **Reduce hop limit on client nodes.**Most messages on a well-designed mesh reach their destination in 1 - 2 hops. Reducing the default hop limit from 3 to 2 cuts the number of relay transmissions by up to one-third for edge nodes.
    
     ```
    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

- The node list contains nodes with "Last Heard" timestamps hours or days in the past that never update.
- The node count shown in the app is higher than the number of known active devices.
- Sending a message to a ghost node never gets ACKed.

### Root Cause

Meshtastic stores a local database of every node it has ever heard. Nodes that leave the area, run out of battery, or are turned off permanently remain in the database indefinitely until manually cleared or the database fills and the oldest entry is evicted. These stale entries are "ghost nodes."

Ghost nodes are not just a cosmetic issue. On a network with strict channel utilization budgets, any mechanism that sends directed packets to ghost nodes (e.g., automated pings or position requests) wastes airtime. Additionally, some firmware versions attempt to route through recently-known paths, which can cause messages to fail if those paths included a now-offline ghost.

### Solutions

1. **Remove ghost nodes via the CLI:** ```
    # Remove a single node by its node number
    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. **Set a node timeout in firmware:**Firmware 2.5+ supports configuring a node-info broadcast timeout. Nodes not heard within this window are automatically evicted from the database:
    
     ```
    meshtastic --set device.node_remote_hardware_pins 0 # placeholder; see firmware release notes
    # The actual setting is device.node_info_broadcast_secs in recent firmware
    ```

## Problem 3: Routing Loops

### Symptoms

- Channel utilization spikes suddenly and stays high.
- The `--listen` output shows the same packet ID appearing many times from many different node IDs in rapid succession.
- Duplicate packet ratio in the node stats exceeds 30%.
- Battery drain on relay nodes accelerates noticeably.

### Root Cause

A routing loop in Meshtastic's managed flood protocol occurs when the duplicate-packet cache fails to suppress re-relay of a packet that has already been forwarded. This can happen due to:

- **Cache overflow**: if a burst of packets exhausts the 256-entry duplicate cache, older packet IDs fall off and a relayed copy of an evicted packet gets re-forwarded as if it were new.
- **Clock skew between nodes**: packet IDs include a timestamp component. If two nodes have clocks far apart, their caches may not recognize a duplicate.
- **Firmware bug**: certain firmware versions had cache invalidation bugs that caused loops. Updating firmware is the primary fix.

### 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](https://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 in the loop, allowing the duplicate cache to contain it:
    
     ```
    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 set multiple nodes as ROUTER\_CLIENT (deprecated; use ROUTER or REPEATER instead) role in the same RF coverage area.**ROUTER\_CLIENT (deprecated; use ROUTER or REPEATER instead) nodes relay all packets and have a larger effective cache lifetime than CLIENT nodes. Clustering multiple ROUTER\_CLIENT (deprecated; use ROUTER or REPEATER instead) nodes in the same area increases collision probability and can amplify loops.

## Problem 4: Clock Skew

### Symptoms

- Message timestamps in the app appear wrong (hours off or showing Unix epoch 0).
- Nodes show "Last Heard" timestamps in the future.
- Position packets are ignored or treated as stale even for active nodes.
- Increased duplicate packets despite low overall utilization.

### Root Cause

Meshtastic nodes use GPS time when GPS is available. Without GPS, ESP32 and nRF52 nodes rely on an internal RTC. The RTC drifts over time and resets to a default epoch when the device loses power. A node with a clock that is more than a few minutes off can cause packet ID collisions (two different packets with the same timestamp-derived ID) and incorrect "last heard" calculations.

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

<table id="bkmrk-symptom-most-likely-"> <thead> <tr> <th>Symptom</th> <th>Most Likely Cause</th> <th>First Action</th> </tr> </thead> <tbody> <tr> <td>Channel utilization &gt; 25%</td> <td>Position broadcasts too frequent</td> <td>Increase position broadcast interval to 1800 - 3600s</td> </tr> <tr> <td>Nodes in list never heard</td> <td>Ghost nodes in DB</td> <td>Remove stale nodes via app or `--remove-node`</td> </tr> <tr> <td>Same packet seen 10+ times</td> <td>Routing loop or duplicate cache overflow</td> <td>Update firmware; reduce hop\_limit to 2</td> </tr> <tr> <td>Timestamps wrong / future dates</td> <td>Clock skew (no GPS/NTP)</td> <td>Connect app or CLI to sync time from phone/PC</td> </tr> <tr> <td>ACKs missing, good SNR</td> <td>Congested channel saturating ACK window</td> <td>Reduce channel utilization; check for loops</td> </tr> </tbody></table>

# Building a Meshtastic Network Map

A network map shows you which nodes can hear each other, the quality of each link, and how messages actually route through your network. Building and maintaining an accurate map is essential for optimizing coverage and identifying problem areas.

## What to Map

A useful network map shows:

- Geographic position of all nodes (lat/lon from GPS or fixed position setting)
- Link quality between neighboring nodes (RSSI/SNR)
- Hop counts between all node pairs
- Node types (CLIENT, ROUTER, REPEATER) and roles
- Last-heard timestamps (to distinguish active vs. stale nodes)

## Using meshmap.net

meshmap.net aggregates position data from Meshtastic nodes that have position reporting and MQTT gateway enabled. It provides a global view of the Meshtastic community:

- Zoom to your region to see local node density
- Click a node for details: ID, last heard, firmware version, battery level (if reporting telemetry)
- Use the time filter to see nodes active in the last 24 hours vs. all time

**Limitation:** Only shows nodes with GPS enabled and MQTT gateway reachable. Many private community networks don't use the public MQTT server.

## 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 + custom map application
# See github.com/charleswvn/meshing-around for a self-hosted map

# Option 2: Grafana Geomap panel with InfluxDB
# Telemetry data (positions) → InfluxDB → Grafana Geomap panel
# Shows all nodes with position and telemetry data

# Option 3: Simple Python script to generate KML
import json, paho.mqtt.client as mqtt

nodes = {}
def on_message(client, userdata, msg):
 # Parse position packets and build nodes dict
 # Export to KML for Google Earth or KMZ for Google Maps
 pass
```

## Link Quality Analysis

Meshtastic's Neighbor Info module provides per-link RSSI/SNR data. Enable it on key backbone nodes:

```
# Enable Neighbor Info module:
meshtastic --set neighbor_info.enabled true
meshtastic --set neighbor_info.update_interval 900 # 15 min updates
```

With Neighbor Info data flowing through MQTT, you can build a heat map of link quality across your network. Weak links (SNR below -10 dB) are candidates for repeater insertion or antenna improvement.

## Topology Visualization Tools

- **Gephi** - Open-source network visualization. Export node list and edge list from your MQTT data; import into Gephi for force-directed layout visualization of your mesh topology.
- **NetworkX (Python)** - Build a graph of your mesh programmatically; use matplotlib for visualization; calculate graph properties (diameter, clustering coefficient, cut vertices).
- **meshview** - Community tool that parses Meshtastic position and neighbor data to produce a topology map. Check GitHub for current maintained versions.