ATAK / CoT Integration β
MeshMonitor understands the Meshtastic ATAK plugin wire format and can re-publish mesh positions as a Cursor-on-Target (CoT) feed that ATAK and WinTAK clients subscribe to directly. The integration ships in three phases, all tracked under issue #3691:
- Packet decoding β ATAK plugin packets are decoded in the Packet Monitor and ATAK GeoChat messages are persisted into Messages.
- Map contacts β ATAK device positions render as team-colored markers on MeshMonitor's maps.
- CoT feed β a streaming TCP server that pushes mesh nodes and ATAK contacts to ATAK/WinTAK as native CoT
<event>XML.
Meshtastic sources only
ATAK's plugin protocol (TAKPacket, portnum 72) is Meshtastic-specific β it does not exist on MeshCore. Phases 1 and 2 below apply to Meshtastic sources only. The Phase 3 CoT feed is the exception: it also synthesizes CoT events for positioned MeshCore nodes, since the feed's job is publishing everything MeshMonitor knows, not just what arrived over the ATAK plugin.
Phase 1 β Packet decoding & GeoChat messages β
Meshtastic's official ATAK plugin uses TAKPacket (portnum 72, ATAK_PLUGIN) to carry position reports (PLI), chat (GeoChat), delivery receipts, and arbitrary detail payloads. MeshMonitor decodes this in the Packet Monitor:
| Packet Type | What you see |
|---|---|
ATAK_PLUGIN (72) | [ATAK PLI β¦] for position reports, [ATAK GeoChat β¦] for chat (also delivered to Messages), [ATAK detail β¦], or [ATAK GeoChat receipt] β full decoded TAKPacket in the detail view |
ATAK_PLUGIN_V2 (78) | Firmware 2.8+ rich CoT (TAKPacketV2, zstd dictionary compression) β fully decoded: [ATAK V2 PLI β¦], [ATAK V2 GeoChat β¦], [ATAK V2 marker/route/CASEVAC/EMERGENCY/β¦] with the full decoded packet in the detail view. See "ATAK V2 decoding" below. |
ATAK_FORWARDER (257) | Third-party ATAK Forwarder packets β identified by name only, not decoded (it's not the official plugin format). |
ATAK GeoChat messages are also persisted into MeshMonitor's Messages β as a channel message or a DM depending on the mesh envelope, with the text prefixed [ATAK <callsign>] and push notifications enabled. Compressed (is_compressed=true) GeoChat text is labeled rather than decoded (unishox2 decompression is out of scope).
Phase 2 β ATAK contacts on the map β
Every ATAK PLI (position) report is persisted as a per-source ATAK contact and rendered as a team-colored callsign marker on the per-source Nodes map, the Dashboard map, and the Map Analysis canvas.
- Toggle: Show ATAK Contacts in the Map Features panel (default off).
- Marker color follows the ATAK team color (Cyan, Red, Green, β¦); the popup shows callsign, team, role, battery, course, speed, altitude (HAE), and last-seen time.
- Stale after 15 minutes without a fresh report β the marker dims and the popup shows a STALE badge.
- Retained for 24 hours after the last report, then purged.
- Contacts without a valid position (e.g. a Null Island fix) are stored but not plotted.
- Served at
GET /api/sources/:id/atak/contacts(requiresnodes:readon the source).
See Interactive Maps for how ATAK contacts fit alongside the other map layers.
ATAK V2 decoding (portnum 78) β
The V2 protocol (TAKPacketV2, used by the official plugin for rich CoT β shapes, routes, markers, CASEVAC, emergency alerts, TAKTALK, aircraft tracks) compresses each packet with zstd against a shared pre-trained dictionary. The wire payload is [1 flags byte][zstd body]; flags bits 0β5 select the dictionary (0 = non-aircraft, 1 = aircraft), and 0xFF means the body is an uncompressed TAKPacketV2 protobuf. Firmware forwards these payloads untouched, so MeshMonitor decompresses them itself.
What MeshMonitor does with a decoded V2 packet:
- Implicit PLI (no payload variant = position report): upserts the same per-source ATAK contact as a V1 PLI (Phase 2 above), including the map marker and the CoT feed. V2 wire units are normalized on ingest (speed cm/s β m/s, course degreesΓ100 β degrees, altitude
9999999= "no altitude" dropped). - GeoChat: persisted to Messages exactly like V1 GeoChat (channel/DM routing from the mesh envelope,
[ATAK <callsign>]prefix, push notifications). V2 chat text is never unishox2-compressed, so there is no labeled-but-undecoded case. - Rich CoT variants (shapes, markers, routes, range & bearing, CASEVAC, emergency, task, TAKTALK, aircraft, raw detail): decoded and visible in the Packet Monitor (variant-labeled preview + full JSON in the detail view). Dedicated map/feed surfaces for these are a follow-up.
Dictionaries. The two zstd dictionaries ship in the meshtastic/TAKPacket-SDK repository (GPL-3.0) and are vendored as the takpacket-sdk/ git submodule β consumed as runtime data files, the same posture as the GPL-3.0 protobufs/ submodule. No SDK code is compiled into MeshMonitor; the decoder is implemented against the published wire spec (takpacket-sdk/WIRE_FORMAT.md). If the submodule is missing at runtime (or the zstd-napi native module can't load), V2 packets gracefully degrade to the pre-4.14 labeled-but-undecoded behavior β uncompressed (0xFF) packets still decode.
Baremetal deployments must initialize the submodule (git submodule update --init --recursive); the Docker image and LXC template include the dictionaries automatically.
Phase 3 β CoT feed (ATAK/WinTAK network input) β
MeshMonitor can run a plaintext TCP server that streams CoT <event> XML β the format ATAK and WinTAK natively consume as a "network input." Point ATAK at MeshMonitor's IP and port, and it sees every ATAK contact and every positioned mesh node as a map contact, with no REST polling or app plugin required.
What's in the feed β
- ATAK contacts β every contact from the Phase 2
atak_contactstable, across all sources. - Meshtastic nodes β every positioned node across all Meshtastic sources, including nodes learned over MQTT.
- MeshCore nodes β every positioned node across all MeshCore sources (MeshCore has no native ATAK wire format, so these are synthesized server-side rather than decoded from a plugin packet).
A node that also has an ATAK contact (e.g. a phone running both the mesh radio and the ATAK plugin) appears as two CoT events β the EUD and the mesh radio are different real-world things, so they are deliberately not de-duplicated.
Private position overrides are never included. If a node's position has been overridden and marked private, it is excluded from the feed entirely.
Delivery model β
- A full snapshot is sent to each client immediately on connect.
- The full snapshot is then re-sent to every connected client every 30 seconds. ATAK de-duplicates events by
uidand honors each event'sstaletime, so a periodic resend of unchanged events is a cheap, reliable refresh β no per-event push/diffing logic is needed. - Events go stale (and ATAK will grey/drop them) after:
- 15 minutes for ATAK contacts (matches the map behavior in Phase 2).
- 60 minutes for mesh nodes (Meshtastic and MeshCore).
- The server is receive-only: inbound bytes from ATAK clients are discarded. MeshMonitor does not send TAKPackets back onto the mesh.
- Up to 16 concurrent clients.
Enabling the feed β
- Sign in as an admin and open Settings β ATAK / CoT Feed.
- Check Enable CoT feed.
- Set Feed port (default
8088). - Save. The feed binds on
0.0.0.0:<port>inside the container immediately β no restart required.
The feed is off by default.
Adding MeshMonitor as an ATAK network input β
In ATAK (or WinTAK):
- Open Settings β Network Connections (or Import β Network Import, depending on ATAK version).
- Add a new Streaming TAK Server / Network Input.
- Protocol: TCP.
- Host: the IP or hostname where MeshMonitor is reachable.
- Port: the value from Feed port above (default
8088). - No credentials β the feed has no authentication (see the security note below).
Once connected, ATAK contacts and mesh nodes should appear on the map within a few seconds (immediate snapshot on connect), refreshing every 30 seconds.
Security: plaintext, unauthenticated, trusted networks only
The CoT feed has no encryption and no authentication. Anyone who can reach the configured port can read every mesh node's live position and every ATAK contact's position. It binds on 0.0.0.0 (not localhost) because ATAK EUDs are typically remote.
- Keep the feed off unless you're actively using it.
- Only expose the port on a trusted network (LAN, VPN) β do not port-forward it directly to the internet.
- TLS support is a deferred follow-up, not currently available.
Docker Compose Example β
services:
meshmonitor:
image: ghcr.io/yeraze/meshmonitor:latest
container_name: meshmonitor
ports:
- "8080:3001"
- "8088:8088" # ATAK/CoT feed (enable in Settings after first boot)
volumes:
- meshmonitor-data:/data
restart: unless-stopped
volumes:
meshmonitor-data:The CoT feed is off by default β publish the port in your compose file, then enable it in Settings β ATAK / CoT Feed as described above.
Kubernetes/Helm Example β
service:
type: LoadBalancer
ports:
- name: http
port: 80
targetPort: 3001
- name: atak-cot
port: 8088
targetPort: 8088After the pod starts, open Settings β ATAK / CoT Feed and enable it.