ReticulumTelemetryHub 0.1.0__py3-none-any.whl → 0.143.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- reticulum_telemetry_hub/api/__init__.py +23 -0
- reticulum_telemetry_hub/api/models.py +323 -0
- reticulum_telemetry_hub/api/service.py +836 -0
- reticulum_telemetry_hub/api/storage.py +528 -0
- reticulum_telemetry_hub/api/storage_base.py +156 -0
- reticulum_telemetry_hub/api/storage_models.py +118 -0
- reticulum_telemetry_hub/atak_cot/__init__.py +49 -0
- reticulum_telemetry_hub/atak_cot/base.py +277 -0
- reticulum_telemetry_hub/atak_cot/chat.py +506 -0
- reticulum_telemetry_hub/atak_cot/detail.py +235 -0
- reticulum_telemetry_hub/atak_cot/event.py +181 -0
- reticulum_telemetry_hub/atak_cot/pytak_client.py +569 -0
- reticulum_telemetry_hub/atak_cot/tak_connector.py +848 -0
- reticulum_telemetry_hub/config/__init__.py +25 -0
- reticulum_telemetry_hub/config/constants.py +7 -0
- reticulum_telemetry_hub/config/manager.py +515 -0
- reticulum_telemetry_hub/config/models.py +215 -0
- reticulum_telemetry_hub/embedded_lxmd/__init__.py +5 -0
- reticulum_telemetry_hub/embedded_lxmd/embedded.py +418 -0
- reticulum_telemetry_hub/internal_api/__init__.py +21 -0
- reticulum_telemetry_hub/internal_api/bus.py +344 -0
- reticulum_telemetry_hub/internal_api/core.py +690 -0
- reticulum_telemetry_hub/internal_api/v1/__init__.py +74 -0
- reticulum_telemetry_hub/internal_api/v1/enums.py +109 -0
- reticulum_telemetry_hub/internal_api/v1/manifest.json +8 -0
- reticulum_telemetry_hub/internal_api/v1/schemas.py +478 -0
- reticulum_telemetry_hub/internal_api/versioning.py +63 -0
- reticulum_telemetry_hub/lxmf_daemon/Handlers.py +122 -0
- reticulum_telemetry_hub/lxmf_daemon/LXMF.py +252 -0
- reticulum_telemetry_hub/lxmf_daemon/LXMPeer.py +898 -0
- reticulum_telemetry_hub/lxmf_daemon/LXMRouter.py +4227 -0
- reticulum_telemetry_hub/lxmf_daemon/LXMessage.py +1006 -0
- reticulum_telemetry_hub/lxmf_daemon/LXStamper.py +490 -0
- reticulum_telemetry_hub/lxmf_daemon/__init__.py +10 -0
- reticulum_telemetry_hub/lxmf_daemon/_version.py +1 -0
- reticulum_telemetry_hub/lxmf_daemon/lxmd.py +1655 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/fields/field_telemetry_stream.py +6 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/__init__.py +3 -0
- {lxmf_telemetry → reticulum_telemetry_hub/lxmf_telemetry}/model/persistance/appearance.py +19 -19
- {lxmf_telemetry → reticulum_telemetry_hub/lxmf_telemetry}/model/persistance/peer.py +17 -13
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/__init__.py +65 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/acceleration.py +68 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/ambient_light.py +37 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/angular_velocity.py +68 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/battery.py +68 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/connection_map.py +258 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/generic.py +841 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/gravity.py +68 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/humidity.py +37 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/information.py +42 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/location.py +110 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/lxmf_propagation.py +429 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/magnetic_field.py +68 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/physical_link.py +53 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/pressure.py +37 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/proximity.py +37 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/received.py +75 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/rns_transport.py +209 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/sensor.py +65 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/sensor_enum.py +27 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/sensor_mapping.py +58 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/temperature.py +37 -0
- {lxmf_telemetry → reticulum_telemetry_hub/lxmf_telemetry}/model/persistance/sensors/time.py +36 -32
- {lxmf_telemetry → reticulum_telemetry_hub/lxmf_telemetry}/model/persistance/telemeter.py +26 -23
- reticulum_telemetry_hub/lxmf_telemetry/sampler.py +229 -0
- reticulum_telemetry_hub/lxmf_telemetry/telemeter_manager.py +409 -0
- reticulum_telemetry_hub/lxmf_telemetry/telemetry_controller.py +804 -0
- reticulum_telemetry_hub/northbound/__init__.py +5 -0
- reticulum_telemetry_hub/northbound/app.py +195 -0
- reticulum_telemetry_hub/northbound/auth.py +119 -0
- reticulum_telemetry_hub/northbound/gateway.py +310 -0
- reticulum_telemetry_hub/northbound/internal_adapter.py +302 -0
- reticulum_telemetry_hub/northbound/models.py +213 -0
- reticulum_telemetry_hub/northbound/routes_chat.py +123 -0
- reticulum_telemetry_hub/northbound/routes_files.py +119 -0
- reticulum_telemetry_hub/northbound/routes_rest.py +345 -0
- reticulum_telemetry_hub/northbound/routes_subscribers.py +150 -0
- reticulum_telemetry_hub/northbound/routes_topics.py +178 -0
- reticulum_telemetry_hub/northbound/routes_ws.py +107 -0
- reticulum_telemetry_hub/northbound/serializers.py +72 -0
- reticulum_telemetry_hub/northbound/services.py +373 -0
- reticulum_telemetry_hub/northbound/websocket.py +855 -0
- reticulum_telemetry_hub/reticulum_server/__main__.py +2237 -0
- reticulum_telemetry_hub/reticulum_server/command_manager.py +1268 -0
- reticulum_telemetry_hub/reticulum_server/command_text.py +399 -0
- reticulum_telemetry_hub/reticulum_server/constants.py +1 -0
- reticulum_telemetry_hub/reticulum_server/event_log.py +357 -0
- reticulum_telemetry_hub/reticulum_server/internal_adapter.py +358 -0
- reticulum_telemetry_hub/reticulum_server/outbound_queue.py +312 -0
- reticulum_telemetry_hub/reticulum_server/services.py +422 -0
- reticulumtelemetryhub-0.143.0.dist-info/METADATA +181 -0
- reticulumtelemetryhub-0.143.0.dist-info/RECORD +97 -0
- {reticulumtelemetryhub-0.1.0.dist-info → reticulumtelemetryhub-0.143.0.dist-info}/WHEEL +1 -1
- reticulumtelemetryhub-0.143.0.dist-info/licenses/LICENSE +277 -0
- lxmf_telemetry/model/fields/field_telemetry_stream.py +0 -7
- lxmf_telemetry/model/persistance/__init__.py +0 -3
- lxmf_telemetry/model/persistance/sensors/location.py +0 -69
- lxmf_telemetry/model/persistance/sensors/magnetic_field.py +0 -36
- lxmf_telemetry/model/persistance/sensors/sensor.py +0 -44
- lxmf_telemetry/model/persistance/sensors/sensor_enum.py +0 -24
- lxmf_telemetry/model/persistance/sensors/sensor_mapping.py +0 -9
- lxmf_telemetry/telemetry_controller.py +0 -124
- reticulum_server/main.py +0 -182
- reticulumtelemetryhub-0.1.0.dist-info/METADATA +0 -15
- reticulumtelemetryhub-0.1.0.dist-info/RECORD +0 -19
- {lxmf_telemetry → reticulum_telemetry_hub}/__init__.py +0 -0
- {lxmf_telemetry/model/persistance/sensors → reticulum_telemetry_hub/lxmf_telemetry}/__init__.py +0 -0
- {reticulum_server → reticulum_telemetry_hub/reticulum_server}/__init__.py +0 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""SQLAlchemy models for the Reticulum Community Hub API storage."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from datetime import timezone
|
|
7
|
+
|
|
8
|
+
from sqlalchemy import Boolean
|
|
9
|
+
from sqlalchemy import Column
|
|
10
|
+
from sqlalchemy import DateTime
|
|
11
|
+
from sqlalchemy import Integer
|
|
12
|
+
from sqlalchemy import JSON
|
|
13
|
+
from sqlalchemy import String
|
|
14
|
+
from sqlalchemy.orm import declarative_base
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
Base = declarative_base()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _utcnow() -> datetime:
|
|
21
|
+
"""Return the current UTC datetime with timezone information."""
|
|
22
|
+
return datetime.now(timezone.utc)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class TopicRecord(Base): # pylint: disable=too-few-public-methods
|
|
26
|
+
"""SQLAlchemy record for topics."""
|
|
27
|
+
|
|
28
|
+
__tablename__ = "topics"
|
|
29
|
+
|
|
30
|
+
id = Column(String, primary_key=True)
|
|
31
|
+
name = Column(String, nullable=False)
|
|
32
|
+
path = Column(String, nullable=False)
|
|
33
|
+
description = Column(String, nullable=True)
|
|
34
|
+
created_at = Column(DateTime(timezone=True), default=_utcnow, nullable=False)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class SubscriberRecord(Base): # pylint: disable=too-few-public-methods
|
|
38
|
+
"""SQLAlchemy record for subscribers."""
|
|
39
|
+
|
|
40
|
+
__tablename__ = "subscribers"
|
|
41
|
+
|
|
42
|
+
id = Column(String, primary_key=True)
|
|
43
|
+
destination = Column(String, nullable=False)
|
|
44
|
+
topic_id = Column(String, nullable=True)
|
|
45
|
+
reject_tests = Column(Integer, nullable=True)
|
|
46
|
+
metadata_json = Column("metadata", JSON, nullable=True)
|
|
47
|
+
created_at = Column(DateTime(timezone=True), default=_utcnow, nullable=False)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class ClientRecord(Base): # pylint: disable=too-few-public-methods
|
|
51
|
+
"""SQLAlchemy record for clients."""
|
|
52
|
+
|
|
53
|
+
__tablename__ = "clients"
|
|
54
|
+
|
|
55
|
+
identity = Column(String, primary_key=True)
|
|
56
|
+
last_seen = Column(DateTime(timezone=True), default=_utcnow, nullable=False)
|
|
57
|
+
metadata_json = Column("metadata", JSON, nullable=True)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class FileRecord(Base): # pylint: disable=too-few-public-methods
|
|
61
|
+
"""SQLAlchemy record for stored files."""
|
|
62
|
+
|
|
63
|
+
__tablename__ = "file_records"
|
|
64
|
+
|
|
65
|
+
id = Column(Integer, primary_key=True, autoincrement=True)
|
|
66
|
+
name = Column(String, nullable=False)
|
|
67
|
+
path = Column(String, nullable=False)
|
|
68
|
+
media_type = Column(String, nullable=True)
|
|
69
|
+
category = Column(String, nullable=False)
|
|
70
|
+
size = Column(Integer, nullable=False)
|
|
71
|
+
topic_id = Column(String, nullable=True)
|
|
72
|
+
created_at = Column(DateTime(timezone=True), default=_utcnow, nullable=False)
|
|
73
|
+
updated_at = Column(
|
|
74
|
+
DateTime(timezone=True), default=_utcnow, onupdate=_utcnow, nullable=False
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class ChatMessageRecord(Base): # pylint: disable=too-few-public-methods
|
|
79
|
+
"""SQLAlchemy record for persisted chat messages."""
|
|
80
|
+
|
|
81
|
+
__tablename__ = "chat_messages"
|
|
82
|
+
|
|
83
|
+
id = Column(String, primary_key=True)
|
|
84
|
+
direction = Column(String, nullable=False)
|
|
85
|
+
scope = Column(String, nullable=False)
|
|
86
|
+
state = Column(String, nullable=False)
|
|
87
|
+
content = Column(String, nullable=False)
|
|
88
|
+
source = Column(String, nullable=True)
|
|
89
|
+
destination = Column(String, nullable=True)
|
|
90
|
+
topic_id = Column(String, nullable=True)
|
|
91
|
+
attachments_json = Column("attachments", JSON, nullable=True)
|
|
92
|
+
created_at = Column(DateTime(timezone=True), default=_utcnow, nullable=False)
|
|
93
|
+
updated_at = Column(
|
|
94
|
+
DateTime(timezone=True), default=_utcnow, onupdate=_utcnow, nullable=False
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class IdentityStateRecord(Base): # pylint: disable=too-few-public-methods
|
|
99
|
+
"""SQLAlchemy record for identity moderation state."""
|
|
100
|
+
|
|
101
|
+
__tablename__ = "identity_states"
|
|
102
|
+
|
|
103
|
+
identity = Column(String, primary_key=True)
|
|
104
|
+
is_banned = Column(Boolean, nullable=False, default=False)
|
|
105
|
+
is_blackholed = Column(Boolean, nullable=False, default=False)
|
|
106
|
+
updated_at = Column(DateTime(timezone=True), default=_utcnow, nullable=False)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class IdentityAnnounceRecord(Base): # pylint: disable=too-few-public-methods
|
|
110
|
+
"""SQLAlchemy record for Reticulum announce metadata."""
|
|
111
|
+
|
|
112
|
+
__tablename__ = "identity_announces"
|
|
113
|
+
|
|
114
|
+
destination_hash = Column(String, primary_key=True)
|
|
115
|
+
display_name = Column(String, nullable=True)
|
|
116
|
+
first_seen = Column(DateTime(timezone=True), default=_utcnow, nullable=False)
|
|
117
|
+
last_seen = Column(DateTime(timezone=True), default=_utcnow, nullable=False)
|
|
118
|
+
source_interface = Column(String, nullable=True)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""ATAK COT support classes and datapack utilities."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from reticulum_telemetry_hub.atak_cot.base import Contact
|
|
6
|
+
from reticulum_telemetry_hub.atak_cot.base import Group
|
|
7
|
+
from reticulum_telemetry_hub.atak_cot.base import Point
|
|
8
|
+
from reticulum_telemetry_hub.atak_cot.base import Status
|
|
9
|
+
from reticulum_telemetry_hub.atak_cot.base import Takv
|
|
10
|
+
from reticulum_telemetry_hub.atak_cot.base import Track
|
|
11
|
+
from reticulum_telemetry_hub.atak_cot.base import Uid
|
|
12
|
+
from reticulum_telemetry_hub.atak_cot.chat import Chat
|
|
13
|
+
from reticulum_telemetry_hub.atak_cot.chat import ChatGroup
|
|
14
|
+
from reticulum_telemetry_hub.atak_cot.chat import ChatHierarchy
|
|
15
|
+
from reticulum_telemetry_hub.atak_cot.chat import ChatHierarchyContact
|
|
16
|
+
from reticulum_telemetry_hub.atak_cot.chat import ChatHierarchyGroup
|
|
17
|
+
from reticulum_telemetry_hub.atak_cot.chat import Link
|
|
18
|
+
from reticulum_telemetry_hub.atak_cot.chat import Marti
|
|
19
|
+
from reticulum_telemetry_hub.atak_cot.chat import MartiDest
|
|
20
|
+
from reticulum_telemetry_hub.atak_cot.chat import Remarks
|
|
21
|
+
from reticulum_telemetry_hub.atak_cot.detail import Detail
|
|
22
|
+
from reticulum_telemetry_hub.atak_cot.event import Event
|
|
23
|
+
from reticulum_telemetry_hub.atak_cot.event import Packable
|
|
24
|
+
from reticulum_telemetry_hub.atak_cot.event import pack_data
|
|
25
|
+
from reticulum_telemetry_hub.atak_cot.event import unpack_data
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
"Contact",
|
|
29
|
+
"Group",
|
|
30
|
+
"Point",
|
|
31
|
+
"Track",
|
|
32
|
+
"Takv",
|
|
33
|
+
"Uid",
|
|
34
|
+
"Status",
|
|
35
|
+
"Chat",
|
|
36
|
+
"ChatGroup",
|
|
37
|
+
"ChatHierarchy",
|
|
38
|
+
"ChatHierarchyContact",
|
|
39
|
+
"ChatHierarchyGroup",
|
|
40
|
+
"Link",
|
|
41
|
+
"Marti",
|
|
42
|
+
"MartiDest",
|
|
43
|
+
"Remarks",
|
|
44
|
+
"Detail",
|
|
45
|
+
"Event",
|
|
46
|
+
"Packable",
|
|
47
|
+
"pack_data",
|
|
48
|
+
"unpack_data",
|
|
49
|
+
]
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
"""Data classes representing ATAK Cursor-on-Target primitives."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
import xml.etree.ElementTree as ET
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class Point:
|
|
11
|
+
"""A geographic point element."""
|
|
12
|
+
|
|
13
|
+
lat: float
|
|
14
|
+
lon: float
|
|
15
|
+
hae: float
|
|
16
|
+
ce: float
|
|
17
|
+
le: float
|
|
18
|
+
|
|
19
|
+
@classmethod
|
|
20
|
+
def from_xml(cls, elem):
|
|
21
|
+
"""Create a :class:`Point` from an XML ``<point>`` element."""
|
|
22
|
+
|
|
23
|
+
return cls(
|
|
24
|
+
lat=float(elem.get("lat", 0)),
|
|
25
|
+
lon=float(elem.get("lon", 0)),
|
|
26
|
+
hae=float(elem.get("hae", 0)),
|
|
27
|
+
ce=float(elem.get("ce", 0)),
|
|
28
|
+
le=float(elem.get("le", 0)),
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
def to_element(self):
|
|
32
|
+
"""Return an XML element representing this point."""
|
|
33
|
+
|
|
34
|
+
attrib = {
|
|
35
|
+
"lat": str(self.lat),
|
|
36
|
+
"lon": str(self.lon),
|
|
37
|
+
"hae": str(self.hae),
|
|
38
|
+
"ce": str(self.ce),
|
|
39
|
+
"le": str(self.le),
|
|
40
|
+
}
|
|
41
|
+
return ET.Element("point", attrib)
|
|
42
|
+
|
|
43
|
+
def to_dict(self) -> dict:
|
|
44
|
+
"""Return a serialisable dictionary representation."""
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
"lat": self.lat,
|
|
48
|
+
"lon": self.lon,
|
|
49
|
+
"hae": self.hae,
|
|
50
|
+
"ce": self.ce,
|
|
51
|
+
"le": self.le,
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
@classmethod
|
|
55
|
+
def from_dict(cls, data: dict) -> "Point":
|
|
56
|
+
"""Create a :class:`Point` from a dictionary."""
|
|
57
|
+
|
|
58
|
+
return cls(
|
|
59
|
+
lat=float(data.get("lat", 0)),
|
|
60
|
+
lon=float(data.get("lon", 0)),
|
|
61
|
+
hae=float(data.get("hae", 0)),
|
|
62
|
+
ce=float(data.get("ce", 0)),
|
|
63
|
+
le=float(data.get("le", 0)),
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@dataclass
|
|
68
|
+
class Contact:
|
|
69
|
+
"""Identifies the sender of the COT message."""
|
|
70
|
+
|
|
71
|
+
callsign: str
|
|
72
|
+
endpoint: str | None = None
|
|
73
|
+
|
|
74
|
+
@classmethod
|
|
75
|
+
def from_xml(cls, elem):
|
|
76
|
+
"""Construct a :class:`Contact` from an XML ``<contact>`` element."""
|
|
77
|
+
|
|
78
|
+
return cls(callsign=elem.get("callsign", ""), endpoint=elem.get("endpoint"))
|
|
79
|
+
|
|
80
|
+
def to_element(self):
|
|
81
|
+
"""Return an XML element for the contact."""
|
|
82
|
+
|
|
83
|
+
attrib = {"callsign": self.callsign}
|
|
84
|
+
if self.endpoint:
|
|
85
|
+
attrib["endpoint"] = self.endpoint
|
|
86
|
+
return ET.Element("contact", attrib)
|
|
87
|
+
|
|
88
|
+
def to_dict(self) -> dict:
|
|
89
|
+
"""Return a serialisable representation."""
|
|
90
|
+
|
|
91
|
+
data = {"callsign": self.callsign}
|
|
92
|
+
if self.endpoint:
|
|
93
|
+
data["endpoint"] = self.endpoint
|
|
94
|
+
return data
|
|
95
|
+
|
|
96
|
+
@classmethod
|
|
97
|
+
def from_dict(cls, data: dict) -> "Contact":
|
|
98
|
+
"""Create a :class:`Contact` from a dictionary."""
|
|
99
|
+
|
|
100
|
+
return cls(callsign=data.get("callsign", ""), endpoint=data.get("endpoint"))
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@dataclass
|
|
104
|
+
class Group:
|
|
105
|
+
"""Specifies group affiliation for the sender."""
|
|
106
|
+
|
|
107
|
+
name: str
|
|
108
|
+
role: str
|
|
109
|
+
|
|
110
|
+
@classmethod
|
|
111
|
+
def from_xml(cls, elem):
|
|
112
|
+
"""Create a :class:`Group` from an XML ``<__group>`` element."""
|
|
113
|
+
|
|
114
|
+
return cls(name=elem.get("name", ""), role=elem.get("role", ""))
|
|
115
|
+
|
|
116
|
+
def to_element(self):
|
|
117
|
+
"""Return an XML element for the group affiliation."""
|
|
118
|
+
|
|
119
|
+
return ET.Element("__group", {"name": self.name, "role": self.role})
|
|
120
|
+
|
|
121
|
+
def to_dict(self) -> dict:
|
|
122
|
+
"""Return a serialisable representation."""
|
|
123
|
+
|
|
124
|
+
return {"name": self.name, "role": self.role}
|
|
125
|
+
|
|
126
|
+
@classmethod
|
|
127
|
+
def from_dict(cls, data: dict) -> "Group":
|
|
128
|
+
"""Create a :class:`Group` from a dictionary."""
|
|
129
|
+
|
|
130
|
+
return cls(name=data.get("name", ""), role=data.get("role", ""))
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@dataclass
|
|
134
|
+
class Track:
|
|
135
|
+
"""Represents movement information such as speed and bearing."""
|
|
136
|
+
|
|
137
|
+
course: float
|
|
138
|
+
speed: float
|
|
139
|
+
|
|
140
|
+
@classmethod
|
|
141
|
+
def from_xml(cls, elem):
|
|
142
|
+
"""Parse an XML ``<track>`` element into a :class:`Track`."""
|
|
143
|
+
|
|
144
|
+
return cls(
|
|
145
|
+
course=float(elem.get("course", 0)), speed=float(elem.get("speed", 0))
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
def to_element(self):
|
|
149
|
+
"""Return an XML element for the movement details."""
|
|
150
|
+
|
|
151
|
+
return ET.Element("track", {"course": str(self.course), "speed": str(self.speed)})
|
|
152
|
+
|
|
153
|
+
def to_dict(self) -> dict:
|
|
154
|
+
"""Return a serialisable representation."""
|
|
155
|
+
|
|
156
|
+
return {"course": self.course, "speed": self.speed}
|
|
157
|
+
|
|
158
|
+
@classmethod
|
|
159
|
+
def from_dict(cls, data: dict) -> "Track":
|
|
160
|
+
"""Create a :class:`Track` from a dictionary."""
|
|
161
|
+
|
|
162
|
+
return cls(
|
|
163
|
+
course=float(data.get("course", 0)), speed=float(data.get("speed", 0))
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
@dataclass
|
|
168
|
+
class Takv:
|
|
169
|
+
"""Describes the TAK client version and platform information."""
|
|
170
|
+
|
|
171
|
+
version: str
|
|
172
|
+
platform: str
|
|
173
|
+
os: str
|
|
174
|
+
device: str
|
|
175
|
+
|
|
176
|
+
@classmethod
|
|
177
|
+
def from_xml(cls, elem):
|
|
178
|
+
"""Create a :class:`Takv` from an XML ``<takv>`` element."""
|
|
179
|
+
|
|
180
|
+
return cls(
|
|
181
|
+
version=elem.get("version", ""),
|
|
182
|
+
platform=elem.get("platform", ""),
|
|
183
|
+
os=elem.get("os", ""),
|
|
184
|
+
device=elem.get("device", ""),
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
def to_element(self):
|
|
188
|
+
"""Return an XML element representing this TAK client."""
|
|
189
|
+
|
|
190
|
+
return ET.Element(
|
|
191
|
+
"takv",
|
|
192
|
+
{
|
|
193
|
+
"version": self.version,
|
|
194
|
+
"platform": self.platform,
|
|
195
|
+
"os": self.os,
|
|
196
|
+
"device": self.device,
|
|
197
|
+
},
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
def to_dict(self) -> dict:
|
|
201
|
+
"""Return a serialisable representation."""
|
|
202
|
+
|
|
203
|
+
return {
|
|
204
|
+
"version": self.version,
|
|
205
|
+
"platform": self.platform,
|
|
206
|
+
"os": self.os,
|
|
207
|
+
"device": self.device,
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
@classmethod
|
|
211
|
+
def from_dict(cls, data: dict) -> "Takv":
|
|
212
|
+
"""Create a :class:`Takv` from a dictionary."""
|
|
213
|
+
|
|
214
|
+
return cls(
|
|
215
|
+
version=data.get("version", ""),
|
|
216
|
+
platform=data.get("platform", ""),
|
|
217
|
+
os=data.get("os", ""),
|
|
218
|
+
device=data.get("device", ""),
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
@dataclass
|
|
223
|
+
class Uid:
|
|
224
|
+
"""Nested UID used by ATAK to describe the Droid identifier."""
|
|
225
|
+
|
|
226
|
+
droid: str
|
|
227
|
+
|
|
228
|
+
@classmethod
|
|
229
|
+
def from_xml(cls, elem):
|
|
230
|
+
"""Construct a :class:`Uid` from an XML ``<uid>`` element."""
|
|
231
|
+
|
|
232
|
+
return cls(droid=elem.get("Droid", ""))
|
|
233
|
+
|
|
234
|
+
def to_element(self):
|
|
235
|
+
"""Return an XML element representing the UID."""
|
|
236
|
+
|
|
237
|
+
return ET.Element("uid", {"Droid": self.droid})
|
|
238
|
+
|
|
239
|
+
def to_dict(self) -> dict:
|
|
240
|
+
"""Return a serialisable representation."""
|
|
241
|
+
|
|
242
|
+
return {"Droid": self.droid}
|
|
243
|
+
|
|
244
|
+
@classmethod
|
|
245
|
+
def from_dict(cls, data: dict) -> "Uid":
|
|
246
|
+
"""Create a :class:`Uid` from a dictionary."""
|
|
247
|
+
|
|
248
|
+
return cls(droid=data.get("Droid", ""))
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
@dataclass
|
|
252
|
+
class Status:
|
|
253
|
+
"""Represents battery status information."""
|
|
254
|
+
|
|
255
|
+
battery: float
|
|
256
|
+
|
|
257
|
+
@classmethod
|
|
258
|
+
def from_xml(cls, elem):
|
|
259
|
+
"""Construct a :class:`Status` from an XML ``<status>`` element."""
|
|
260
|
+
|
|
261
|
+
return cls(battery=float(elem.get("battery", 0)))
|
|
262
|
+
|
|
263
|
+
def to_element(self):
|
|
264
|
+
"""Return an XML element representing status."""
|
|
265
|
+
|
|
266
|
+
return ET.Element("status", {"battery": str(self.battery)})
|
|
267
|
+
|
|
268
|
+
def to_dict(self) -> dict:
|
|
269
|
+
"""Return a serialisable representation."""
|
|
270
|
+
|
|
271
|
+
return {"battery": self.battery}
|
|
272
|
+
|
|
273
|
+
@classmethod
|
|
274
|
+
def from_dict(cls, data: dict) -> "Status":
|
|
275
|
+
"""Create a :class:`Status` from a dictionary."""
|
|
276
|
+
|
|
277
|
+
return cls(battery=float(data.get("battery", 0)))
|