Repository navigation
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.
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/@callsignor parse one from XML:
event = CotEvent.from_xml("<event .../>...") # str, bytes or an ElementTo change the type after construction: event.set_cot_type("a-f-A").
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.
| 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.
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 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 youxml_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.