MeshMonitor v4.13.2 is out. It finishes the ATAK/CoT work, teaches MeshMonitor to respect a node that cannot transmit, adds a 3D terrain view, and catches gateways that uplink traffic their senders asked to keep off MQTT.
If you run PostgreSQL or MySQL, take this one. Those two backends re-ran every migration on each boot, and one of them rebuilt a table from scratch each time. Details below.
Fix first: PostgreSQL and MySQL replayed every migration β
The guard that says "this migration already ran" was written for SQLite only. PostgreSQL and MySQL never had it, so both replayed all 128 migrations at every start.
Most migrations do no harm when they run twice. Migration 030 does β it rebuilds route_segments from scratch. One reporting install had 865,000 rows in that table, deleted and reinserted on every restart. Both backends now keep a ledger of what they have applied, the same way SQLite does. SQLite users were never affected.
ATAK and CoT β
Three phases of work. MeshMonitor now reads both ATAK plugin formats and serves what it hears back out to ATAK clients:
- Packets decode. MeshMonitor reads ATAK plugin packets on portnum 72, and now portnum 78 as well. V2 packs each payload with zstd against a shared dictionary, which we vendor from the official TAKPacket SDK. You get readable previews β position, GeoChat, markers, routes, CASEVAC, emergency β and the full decoded packet in the detail view.
- Contacts land on the map. Positions from the ATAK plugin persist as per-source contacts and draw as team-colored callsign markers on every map. Popups carry callsign, team, role, battery, course, speed, and altitude. A contact fades with a STALE badge after 15 minutes and drops off after a day.
- ATAK and WinTAK can connect. Turn on the ATAK / CoT Feed in Settings and MeshMonitor serves Cursor-on-Target XML over TCP. Add it in ATAK as a network input and your mesh shows up as tracks: ATAK contacts, Meshtastic nodes, and MeshCore nodes, all of them, from every source. It sends a full snapshot when you connect and refreshes every 30 seconds.
The feed has no password and no encryption, and it never includes a private position override. Run it on a network you trust. See the ATAK guide for the full setup.
Receive-only nodes now work properly β
A node with lora.tx_enabled off can hear the mesh but cannot answer. MeshMonitor used to let you try anyway, then fail at the radio.
Now a single server-side guard blocks every send path β messages, traceroutes, admin commands, announcements, auto-ack β and the UI dims the controls it would have let you press, with a note saying why. The Automations source summary shows a TX badge, and importing a config reads the node's live LoRa settings instead of guessing, so an import can no longer flip TX back on by accident.
This matters for receive-only monitors, listening posts, and any node you want on the map without a voice. The Receive-only mode guide walks through it.
Catch gateways that ignore ok_to_mqtt β
A Meshtastic node can set ok_to_mqtt=false to say "do not put my traffic on MQTT." Gateways are supposed to honor it. Some do not.
MeshMonitor now spots it. A violating packet is flagged in the MQTT Packet Monitor, kept for 90 days, and rolled into an ok_to_mqtt Violations report under Analysis & Reports. You get a per-gateway view, a per-packet view, node names on every row, and CSV export. Paging and sorting run in SQL, so the report stays quick on a busy broker.
A 3D map β
Map Analysis has a 3D mode. Real terrain, a vertical exaggeration slider, and the same layers you use in 2D: node markers, neighbor links, and traceroute paths, all with matching selection behavior. Hardware without WebGL falls back to the flat map.
The Terrain Link Profile got sharper too. Antenna height seeds from the node's own reported altitude minus the ground under it, thin dashed neighbor links are finally clickable, and route segments support terrain analysis.
Smaller things worth knowing β
- Share a node as a contact. The node card has a Share contact action that draws a Meshtastic contact QR and a matching link. Scan it and the node, its name, and its public key land in the app β no retyping a key.
- NodeInfo Enrichment. A new report finds nodes whose record on one source is missing a field another source already has, ranks the best donor, and fills the gap. Copy NodeInfo also gained per-field selection and can now overwrite a field that is already set.
- MQTT hop-limit override. The embedded broker's zero-hop toggle became a number from 0 to 7, so you pick the hop budget instead of choosing between none and passthrough.
- MeshCore route names. The relay chain on a message resolves to repeater names, works as
{ROUTE_NAMES}in reply templates, and opens a route popup that maps the packet's path when every hop has a position. - Virtual Node can hand out the node's private key β if you let it. Some tools sign in as the node itself and ask the radio for its private key. The Virtual Node had no answer for that request, so those tools just reported a broken proxy. There is now a MeshCore-only Allow PKI export switch, off by default. Leave it off and the Virtual Node says "unavailable", exactly as firmware built without the feature does. Turn it on only on a network you trust: the port has no client authentication, and anyone who reads that key can become your node for good. It is a separate switch from admin commands on purpose β admin commands change your node, the key replaces it. Every export is logged.
- Auto-Ack resend attempts are yours to set, from 1 to 3.
- The Nodes tab is now the Map tab. It has always been a map with a list beside it. Old
#nodeslinks still work. - One icon set. App icons moved to a shared component, with a lint rule that blocks new hardcoded glyphs. Everything renders at the same size and weight and follows your theme.
Why every DM recipient kept getting favorited β
If you noticed nodes turning up as favorites on your radio without you touching them, this was us.
Before every PKI-encrypted direct message, MeshMonitor told the radio "here is this contact" β every time, without checking whether the radio already had it. That is not a harmless repeat. When the firmware stores a contact this way it marks the node as a favorite on purpose, to stop it being evicted from the node database. So each DM, including each automated Auto-Ack reply, re-favorited whoever it went to. The favorite then synced back and looked like it came from somewhere else.
MeshMonitor now keeps track of which peers the radio already holds a key for, and only sends the contact when it truly needs to. It drops that knowledge when the connection resets or a key repair purges the node, so a DM that really does need the key still gets it. Nothing about how favorites sync back has changed.
Plus more fixes: API tokens work on the older /api endpoints, nodes no longer get stuck hidden on the map, last-seen tracks per transport, a reply to a purged parent still shows the reply box, security warnings can be cleared once you fix them, and publishing a Helm chart stops wiping the older versions from the index.
Work you will not see β
Much of this release was cleanup. server.ts handed its inline routes to route modules. MeshtasticManager handed announce scheduling, admin-ack correlation, MQTT bridging, and device config to services of their own. Connection lifecycle runs on an explicit state machine. The frontend traded activeTab render gates for real routes, Settings collapsed 49 mirrored state hooks into one draft, and every raw fetch() in a component moved behind the API service.
None of that changes what the app does. It changes how fast we can change it.
The full changelog lists every entry. As always, most of the fixes here started as someone's careful bug report β thank you.