Skip to content

CotEvent Object Model

Stefan Gofferje edited this page Aug 26, 2026 · 1 revision

CotEvent Object Model

CotEvent is one Cursor-on-Target message. It models the event envelope and the position as typed fields; the whole <detail> block stays available as a raw element so nothing is ever lost.

Creating events

You can construct one from scratch:

from takstream import CotEvent

event = CotEvent(cot_type="a-f-G-U-C", lat=48.208, lon=16.373)

Identity conveniences go straight into the detail block, and the same names are readable/writable properties afterwards:

event = CotEvent(
    cot_type="a-f-G-U-C",
    lat=48.208, lon=16.373,
    callsign="MYCALL", team="Cyan", role="RTO", remarks="hello",
)
event.callsign = "RENAMED"   # updates detail/contact/@callsign

or parse one from XML:

event = CotEvent.from_xml("<event .../>...")  # str, bytes or an Element

To change the type after construction: event.set_cot_type("a-f-A").

Type segments

The CoT type string is dash-separated; two accessors pick the interesting parts for military SA types:

CotEvent(cot_type="a-f-G-U-C").affil   # 'f' - friendly
CotEvent(cot_type="a-f-G-U-C").domain  # 'G' - ground (A air, S sea surface, U subsurface)

They are plain segment accessors: on non-military types (b-t-f, u-d-*, ...) the segments exist but mean something else entirely.

Fields

Field Type Description
cot_type str CoT type string, e.g. a-f-G-U-C. Required.
uid str Unique identifier; a UUID4 is generated if you omit it
how str | None How the event was produced (m-g, h-g-i-g-o, ...)
lat, lon float Position, WGS-84 decimal degrees
hae, ce, le float Height and error figures in meters
time, start, stale datetime | None Timestamps, always UTC-aware
extra_attrs dict[str, str] Non-modelled event attributes, kept verbatim
detail Element | None The raw detail block, kept verbatim

Unknown values use the CoT sentinel 9999999 (exposed as takstream.UNKNOWN). A positionless marker really does travel with 9999999 in all three fields - that is not a parsing bug, that is CoT.

Note

detail is the actual xml.etree.ElementTree.Element. You can poke around in it freely — anything you find there survives serialization, which is exactly the point of keeping it raw.

Position handling

Per spec the position lives in a dedicated <point lat=... lon=.../> child element. Some producers instead stuff lat/lon into the <event> attributes (legacy style). Parsing accepts both, with the <point> child taking precedence. Serialization always emits a proper <point> child.

Empty numeric attributes happen on real feeds (I have seen them from NodeRED feeders); they are treated as "not present" rather than crashing your application.

Timestamps

Timestamps come back timezone-aware (UTC). Both ATAK-style microsecond format and second-precision format (common on ADS-B gateways) are accepted, as are numeric UTC offsets.

If a timestamp cannot be parsed it is left as None — silently pretending "now" is how stale-marker bugs are born.

event.age()       # timedelta since time/start
event.is_stale()  # False when stale is unknown, never lies to you

Serialization

xml_text = event.to_xml()

Missing timestamps get sensible defaults: time/start become now, stale becomes now + 120 s (see takstream.DEFAULT_STALE_SECONDS). Pass fill_defaults=False if you want full control.

Round-trip fidelity is tested against a corpus of real ATAK exports: parse -> serialize -> re-parse preserves every modelled value, every extra attribute and the complete detail tree.

Clone this wiki locally