Skip to content

Add CoT (Cursor-on-Target) export for ATAK/WinTAK/TAK Server - #247

Open
Poid-ZA wants to merge 2 commits into
smittix:mainfrom
Poid-ZA:feat/cot-export
Open

Poid-ZA wants to merge 2 commits into
smittix:mainfrom
Poid-ZA:feat/cot-export

Conversation

@Poid-ZA

@Poid-ZA Poid-ZA commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Closes #179.

Adds optional CoT (Cursor-on-Target) export for ADS-B, AIS, and APRS position events, so they can be piped into ATAK, WinTAK, or a TAK Server.

What this does

  • New utils/cot.py: converts a decoded position event into CoT XML and sends it via UDP (default: the ATAK SA multicast group 239.2.3.1:6969) or TCP to a configured host/port.
  • Hooked into the shared utils/event_pipeline.py, alongside the existing alerts/recording/MQTT dispatch, so ADS-B, AIS, and APRS events all get exported for free without touching each route file individually.
  • Type mapping: ADS-B aircraft → a-f-A (uid ICAO-<icao>), AIS vessels → a-f-S (uid MMSI-<mmsi>), APRS stations → a-f-G-U-C (uid APRS-<callsign>).
  • Follows the exact same optional/env-gated pattern as the existing MQTT export (utils/mqtt.py): disabled by default, zero behavior change unless configured, no new external dependency (pure stdlib socket/XML — no pytak, to keep it consistent with this codebase's synchronous/gevent model rather than pytak's asyncio design).

Configuration (all optional, unset = disabled)

  • INTERCEPT_COT_HOST — destination host or multicast group (required to enable)
  • INTERCEPT_COT_PORT — destination port (default 6969)
  • INTERCEPT_COT_PROTO — udp or tcp (default udp)
  • INTERCEPT_COT_STALE_SECONDS — how long a client should treat the event as valid (default 60)

Scope

Deliberately limited to outbound CoT broadcast/TAK-server send for ADS-B + AIS + APRS (the three modes with lat/lon position data today). A bidirectional pyTAK router was floated in #179 but is a separate, larger feature — happy to follow up if there's interest.

Testing

New tests/test_cot.py — unit tests for XML generation (per-mode type/uid mapping, missing-position handling, unknown-mode handling) and the UDP/TCP send paths (mocked socket, verifies no exception on send failure). All passing locally via pytest tests/test_cot.py.

I've been running this on my own deployment; happy to adjust the type mapping or config surface if you'd prefer something different.

Closes smittix#179. Publishes decoded ADS-B, AIS, and APRS position events as
CoT XML to a configured UDP/TCP destination (defaults to the ATAK SA
multicast group 239.2.3.1:6969, or point it at a TAK Server), following
the same optional/env-gated export pattern already used by the MQTT
export (utils/mqtt.py): disabled by default, no external dependency,
hooked into the shared event pipeline so all three modes get it for
free.

New env vars: INTERCEPT_COT_HOST (unset = disabled), INTERCEPT_COT_PORT,
INTERCEPT_COT_PROTO, INTERCEPT_COT_STALE_SECONDS.
Meshtastic was the only mode not wired into the shared event pipeline
(no alerts, recording, MQTT, or now CoT). This hooks POSITION_APP
packet handling into utils.event_pipeline.process_event(), the same
call already made for ADS-B/AIS/APRS, and adds a "meshtastic" entry to
cot.py's mode map (CoT type a-f-G-U-C, uid MESH-<node id>).

Position updates from mesh nodes now flow through the same
alerts/recording/MQTT/CoT infrastructure as every other mode, so a
Meshtastic node with GPS (e.g. the Heltec T114) shows up on
ATAK/WinTAK alongside tracked aircraft/vessels/APRS stations when CoT
export is enabled.
@smittix

smittix commented Sep 23, 2026

Copy link
Copy Markdown
Owner

Thanks — this is well-built. Opt-in behind INTERCEPT_COT_HOST, no socket created when unset, send errors swallowed and then wrapped again in event_pipeline so CoT can never break streaming, and 140 lines of tests covering each mode, missing positions, unknown modes, UDP, TCP and send failures. Purely additive, so the blast radius on anyone not using it is nil. That's the right shape for an optional exporter.

Two things I'd like to resolve before merging.

1. TCP opens a new connection per event.

with socket.create_connection((cfg.COT_HOST, cfg.COT_PORT), timeout=5) as sock:
    sock.sendall(payload)

publish() is called synchronously from the event pipeline, so on a busy ADS-B feed this is a connect/teardown per aircraft update — hundreds a minute — and if the TAK server is down or unreachable, every event blocks its caller for up to 5 seconds before the exception is swallowed. That would stall event processing across the board, not just CoT.

UDP (the default) doesn't have this problem, so this only bites TCP users, but they're likely the ones pointing at a real TAK Server. A persistent connection reused across events, reconnecting on failure, would fix it; a bounded queue with a sender thread would be even better, at more complexity. Happy with either, or with a documented caveat if you'd rather keep it simple for now.

2. lat/lon go into the XML unescaped and uncoerced.

f'<point lat="{lat}" lon="{lon}" hae="{hae_val}" ce="9999999" le="9999999"/>'

uid and callsign correctly go through escape(), but the numeric fields are interpolated raw. They're normally floats so this is latent rather than live, but these dicts come from several different decoders and one of them handing over a string would produce malformed XML with no clear failure. A float() coercion on lat/lon/hae would make it airtight and reject junk early — and would pair well with the existing None check just above.

Neither is a blocker on the default UDP path; I'd just rather fix them while the code is fresh in your head than discover them from a bug report.

@smittix

smittix commented Sep 28, 2026

Copy link
Copy Markdown
Owner

Thanks for this — reviewed and it looks solid. utils/cot.py is clean (ruff passes) and it ships with tests (tests/test_cot.py, 10 passing locally), CoT export is opt-in (disabled when COT_HOST is empty), and it covers ADS-B/AIS/APRS. This addresses #179. The branch is behind main now, so it'll need a rebase so CI can run against current main before merge — could you rebase onto latest main? Happy to merge once it's green.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature request - CoT Integration (ATAK, WinTAK, TAK Server etc)

2 participants