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,506 @@
|
|
|
1
|
+
"""GeoChat-specific data structures for ATAK Cursor on Target payloads."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import xml.etree.ElementTree as ET
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class ChatHierarchyContact:
|
|
12
|
+
"""Represents a contact entry inside a chat hierarchy."""
|
|
13
|
+
|
|
14
|
+
uid: str
|
|
15
|
+
name: str
|
|
16
|
+
|
|
17
|
+
@classmethod
|
|
18
|
+
def from_xml(cls, elem: ET.Element) -> "ChatHierarchyContact":
|
|
19
|
+
"""Create a :class:`ChatHierarchyContact` from a ``<contact>`` element."""
|
|
20
|
+
|
|
21
|
+
return cls(uid=elem.get("uid", ""), name=elem.get("name", ""))
|
|
22
|
+
|
|
23
|
+
def to_element(self) -> ET.Element:
|
|
24
|
+
"""Return an XML element representing the hierarchy contact."""
|
|
25
|
+
|
|
26
|
+
return ET.Element("contact", {"uid": self.uid, "name": self.name})
|
|
27
|
+
|
|
28
|
+
def to_dict(self) -> dict:
|
|
29
|
+
"""Return a serialisable representation of the hierarchy contact."""
|
|
30
|
+
|
|
31
|
+
return {"uid": self.uid, "name": self.name}
|
|
32
|
+
|
|
33
|
+
@classmethod
|
|
34
|
+
def from_dict(cls, data: dict) -> "ChatHierarchyContact":
|
|
35
|
+
"""Create a :class:`ChatHierarchyContact` from a dictionary."""
|
|
36
|
+
|
|
37
|
+
return cls(uid=data.get("uid", ""), name=data.get("name", ""))
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass
|
|
41
|
+
class ChatHierarchyGroup:
|
|
42
|
+
"""Represents nested groups under the chat hierarchy."""
|
|
43
|
+
|
|
44
|
+
uid: str
|
|
45
|
+
name: str
|
|
46
|
+
contacts: list[ChatHierarchyContact] = field(default_factory=list)
|
|
47
|
+
groups: list["ChatHierarchyGroup"] = field(default_factory=list)
|
|
48
|
+
|
|
49
|
+
@classmethod
|
|
50
|
+
def from_xml(cls, elem: ET.Element) -> "ChatHierarchyGroup":
|
|
51
|
+
"""Create a :class:`ChatHierarchyGroup` from a ``<group>`` element."""
|
|
52
|
+
|
|
53
|
+
contacts = [
|
|
54
|
+
ChatHierarchyContact.from_xml(item) for item in elem.findall("contact")
|
|
55
|
+
]
|
|
56
|
+
child_groups = [
|
|
57
|
+
ChatHierarchyGroup.from_xml(item) for item in elem.findall("group")
|
|
58
|
+
]
|
|
59
|
+
return cls(
|
|
60
|
+
uid=elem.get("uid", ""),
|
|
61
|
+
name=elem.get("name", ""),
|
|
62
|
+
contacts=contacts,
|
|
63
|
+
groups=child_groups,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
def to_element(self) -> ET.Element:
|
|
67
|
+
"""Return an XML element representing the hierarchy group."""
|
|
68
|
+
|
|
69
|
+
element = ET.Element("group", {"uid": self.uid, "name": self.name})
|
|
70
|
+
for contact in self.contacts:
|
|
71
|
+
element.append(contact.to_element())
|
|
72
|
+
for group in self.groups:
|
|
73
|
+
element.append(group.to_element())
|
|
74
|
+
return element
|
|
75
|
+
|
|
76
|
+
def to_dict(self) -> dict:
|
|
77
|
+
"""Return a serialisable representation of the hierarchy group."""
|
|
78
|
+
|
|
79
|
+
data: dict = {"uid": self.uid, "name": self.name}
|
|
80
|
+
if self.contacts:
|
|
81
|
+
data["contacts"] = [contact.to_dict() for contact in self.contacts]
|
|
82
|
+
if self.groups:
|
|
83
|
+
data["groups"] = [group.to_dict() for group in self.groups]
|
|
84
|
+
return data
|
|
85
|
+
|
|
86
|
+
@classmethod
|
|
87
|
+
def from_dict(cls, data: dict) -> "ChatHierarchyGroup":
|
|
88
|
+
"""Create a :class:`ChatHierarchyGroup` from a dictionary."""
|
|
89
|
+
|
|
90
|
+
contacts_data = data.get("contacts", [])
|
|
91
|
+
groups_data = data.get("groups", [])
|
|
92
|
+
contacts = [ChatHierarchyContact.from_dict(item) for item in contacts_data]
|
|
93
|
+
groups = [ChatHierarchyGroup.from_dict(item) for item in groups_data]
|
|
94
|
+
return cls(
|
|
95
|
+
uid=data.get("uid", ""),
|
|
96
|
+
name=data.get("name", ""),
|
|
97
|
+
contacts=contacts,
|
|
98
|
+
groups=groups,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@dataclass
|
|
103
|
+
class ChatHierarchy:
|
|
104
|
+
"""Root chat hierarchy container."""
|
|
105
|
+
|
|
106
|
+
groups: list[ChatHierarchyGroup] = field(default_factory=list)
|
|
107
|
+
|
|
108
|
+
@classmethod
|
|
109
|
+
def from_xml(cls, elem: ET.Element) -> "ChatHierarchy":
|
|
110
|
+
"""Create a :class:`ChatHierarchy` from a ``<hierarchy>`` element."""
|
|
111
|
+
|
|
112
|
+
groups = [ChatHierarchyGroup.from_xml(item) for item in elem.findall("group")]
|
|
113
|
+
return cls(groups=groups)
|
|
114
|
+
|
|
115
|
+
def to_element(self) -> ET.Element:
|
|
116
|
+
"""Return an XML hierarchy element."""
|
|
117
|
+
|
|
118
|
+
element = ET.Element("hierarchy")
|
|
119
|
+
for group in self.groups:
|
|
120
|
+
element.append(group.to_element())
|
|
121
|
+
return element
|
|
122
|
+
|
|
123
|
+
def to_dict(self) -> dict:
|
|
124
|
+
"""Return a serialisable representation of the hierarchy."""
|
|
125
|
+
|
|
126
|
+
return {"groups": [group.to_dict() for group in self.groups]}
|
|
127
|
+
|
|
128
|
+
@classmethod
|
|
129
|
+
def from_dict(cls, data: dict) -> "ChatHierarchy":
|
|
130
|
+
"""Create a :class:`ChatHierarchy` from a dictionary."""
|
|
131
|
+
|
|
132
|
+
groups_data = data.get("groups", [])
|
|
133
|
+
return cls(groups=[ChatHierarchyGroup.from_dict(item) for item in groups_data])
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@dataclass
|
|
137
|
+
class ChatGroup:
|
|
138
|
+
"""Participants and identifiers for a GeoChat room."""
|
|
139
|
+
|
|
140
|
+
chat_id: str
|
|
141
|
+
uid0: str
|
|
142
|
+
chatroom: Optional[str] = None
|
|
143
|
+
uid1: str = ""
|
|
144
|
+
uid2: str = ""
|
|
145
|
+
|
|
146
|
+
@classmethod
|
|
147
|
+
def from_xml(cls, elem: ET.Element) -> "ChatGroup":
|
|
148
|
+
"""Create a :class:`ChatGroup` from a ``<chatgrp>`` element."""
|
|
149
|
+
|
|
150
|
+
return cls(
|
|
151
|
+
chatroom=elem.get("chatroom"),
|
|
152
|
+
chat_id=elem.get("id", ""),
|
|
153
|
+
uid0=elem.get("uid0", ""),
|
|
154
|
+
uid1=elem.get("uid1", ""),
|
|
155
|
+
uid2=elem.get("uid2", ""),
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
def to_element(self) -> ET.Element:
|
|
159
|
+
"""Return an XML element describing the chat group."""
|
|
160
|
+
|
|
161
|
+
attrib = {
|
|
162
|
+
"id": self.chat_id,
|
|
163
|
+
"uid0": self.uid0,
|
|
164
|
+
"uid1": self.uid1,
|
|
165
|
+
}
|
|
166
|
+
if self.chatroom:
|
|
167
|
+
attrib["chatroom"] = self.chatroom
|
|
168
|
+
if self.uid2:
|
|
169
|
+
attrib["uid2"] = self.uid2
|
|
170
|
+
return ET.Element("chatgrp", attrib)
|
|
171
|
+
|
|
172
|
+
def to_dict(self) -> dict:
|
|
173
|
+
"""Return a serialisable representation of the chat group."""
|
|
174
|
+
|
|
175
|
+
data = {
|
|
176
|
+
"chat_id": self.chat_id,
|
|
177
|
+
"uid0": self.uid0,
|
|
178
|
+
"uid1": self.uid1,
|
|
179
|
+
}
|
|
180
|
+
if self.chatroom:
|
|
181
|
+
data["chatroom"] = self.chatroom
|
|
182
|
+
if self.uid2:
|
|
183
|
+
data["uid2"] = self.uid2
|
|
184
|
+
return data
|
|
185
|
+
|
|
186
|
+
@classmethod
|
|
187
|
+
def from_dict(cls, data: dict) -> "ChatGroup":
|
|
188
|
+
"""Create a :class:`ChatGroup` from a dictionary."""
|
|
189
|
+
|
|
190
|
+
return cls(
|
|
191
|
+
chatroom=data.get("chatroom"),
|
|
192
|
+
chat_id=data.get("chat_id", ""),
|
|
193
|
+
uid0=data.get("uid0", ""),
|
|
194
|
+
uid1=data.get("uid1", ""),
|
|
195
|
+
uid2=data.get("uid2", ""),
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
@dataclass
|
|
200
|
+
class Remarks:
|
|
201
|
+
"""Represents annotated remarks content."""
|
|
202
|
+
|
|
203
|
+
text: str
|
|
204
|
+
source: Optional[str] = None
|
|
205
|
+
source_id: Optional[str] = None
|
|
206
|
+
to: Optional[str] = None
|
|
207
|
+
time: Optional[str] = None
|
|
208
|
+
|
|
209
|
+
@classmethod
|
|
210
|
+
def from_xml(cls, elem: ET.Element) -> "Remarks":
|
|
211
|
+
"""Create a :class:`Remarks` from a ``<remarks>`` element."""
|
|
212
|
+
|
|
213
|
+
return cls(
|
|
214
|
+
text=elem.text or "",
|
|
215
|
+
source=elem.get("source"),
|
|
216
|
+
source_id=elem.get("sourceID"),
|
|
217
|
+
to=elem.get("to"),
|
|
218
|
+
time=elem.get("time"),
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
def to_element(self) -> ET.Element:
|
|
222
|
+
"""Return an XML element with optional metadata."""
|
|
223
|
+
|
|
224
|
+
attrib: dict[str, str] = {}
|
|
225
|
+
if self.source is not None:
|
|
226
|
+
attrib["source"] = self.source
|
|
227
|
+
if self.source_id is not None:
|
|
228
|
+
attrib["sourceID"] = self.source_id
|
|
229
|
+
if self.to is not None:
|
|
230
|
+
attrib["to"] = self.to
|
|
231
|
+
if self.time is not None:
|
|
232
|
+
attrib["time"] = self.time
|
|
233
|
+
element = ET.Element("remarks", attrib)
|
|
234
|
+
element.text = self.text
|
|
235
|
+
return element
|
|
236
|
+
|
|
237
|
+
def to_dict(self) -> dict:
|
|
238
|
+
"""Return a serialisable representation of the remarks."""
|
|
239
|
+
|
|
240
|
+
data: dict = {"text": self.text}
|
|
241
|
+
if self.source is not None:
|
|
242
|
+
data["source"] = self.source
|
|
243
|
+
if self.source_id is not None:
|
|
244
|
+
data["source_id"] = self.source_id
|
|
245
|
+
if self.to is not None:
|
|
246
|
+
data["to"] = self.to
|
|
247
|
+
if self.time is not None:
|
|
248
|
+
data["time"] = self.time
|
|
249
|
+
return data
|
|
250
|
+
|
|
251
|
+
@classmethod
|
|
252
|
+
def from_dict(cls, data: dict) -> "Remarks":
|
|
253
|
+
"""Create a :class:`Remarks` from a dictionary."""
|
|
254
|
+
|
|
255
|
+
return cls(
|
|
256
|
+
text=data.get("text", ""),
|
|
257
|
+
source=data.get("source"),
|
|
258
|
+
source_id=data.get("source_id"),
|
|
259
|
+
to=data.get("to"),
|
|
260
|
+
time=data.get("time"),
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
@dataclass
|
|
265
|
+
class MartiDest:
|
|
266
|
+
"""Represents a MARTI destination element."""
|
|
267
|
+
|
|
268
|
+
callsign: Optional[str] = None
|
|
269
|
+
|
|
270
|
+
@classmethod
|
|
271
|
+
def from_xml(cls, elem: ET.Element) -> "MartiDest":
|
|
272
|
+
"""Create a :class:`MartiDest` from a ``<dest>`` element."""
|
|
273
|
+
|
|
274
|
+
return cls(callsign=elem.get("callsign", ""))
|
|
275
|
+
|
|
276
|
+
def to_element(self) -> ET.Element:
|
|
277
|
+
"""Return an XML element representing the destination."""
|
|
278
|
+
|
|
279
|
+
attrib: dict[str, str] = {}
|
|
280
|
+
if self.callsign:
|
|
281
|
+
attrib["callsign"] = self.callsign
|
|
282
|
+
return ET.Element("dest", attrib)
|
|
283
|
+
|
|
284
|
+
def to_dict(self) -> dict:
|
|
285
|
+
"""Return a serialisable representation of the destination."""
|
|
286
|
+
|
|
287
|
+
return {"callsign": self.callsign}
|
|
288
|
+
|
|
289
|
+
@classmethod
|
|
290
|
+
def from_dict(cls, data: dict) -> "MartiDest":
|
|
291
|
+
"""Create a :class:`MartiDest` from a dictionary."""
|
|
292
|
+
|
|
293
|
+
return cls(callsign=data.get("callsign"))
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
@dataclass
|
|
297
|
+
class Marti:
|
|
298
|
+
"""Represents MARTI routing details."""
|
|
299
|
+
|
|
300
|
+
dest: Optional[MartiDest] = None
|
|
301
|
+
|
|
302
|
+
@classmethod
|
|
303
|
+
def from_xml(cls, elem: ET.Element) -> "Marti":
|
|
304
|
+
"""Create a :class:`Marti` from a ``<marti>`` element."""
|
|
305
|
+
|
|
306
|
+
dest_el = elem.find("dest")
|
|
307
|
+
return cls(dest=MartiDest.from_xml(dest_el) if dest_el is not None else None)
|
|
308
|
+
|
|
309
|
+
def to_element(self) -> Optional[ET.Element]:
|
|
310
|
+
"""Return a MARTI element when routing information exists."""
|
|
311
|
+
|
|
312
|
+
if self.dest is None:
|
|
313
|
+
return None
|
|
314
|
+
element = ET.Element("marti")
|
|
315
|
+
element.append(self.dest.to_element())
|
|
316
|
+
return element
|
|
317
|
+
|
|
318
|
+
def to_dict(self) -> dict:
|
|
319
|
+
"""Return a serialisable representation of the MARTI details."""
|
|
320
|
+
|
|
321
|
+
if self.dest is None:
|
|
322
|
+
return {}
|
|
323
|
+
return {"dest": self.dest.to_dict()}
|
|
324
|
+
|
|
325
|
+
@classmethod
|
|
326
|
+
def from_dict(cls, data: dict) -> "Marti":
|
|
327
|
+
"""Create a :class:`Marti` from a dictionary."""
|
|
328
|
+
|
|
329
|
+
dest_data = data.get("dest")
|
|
330
|
+
dest = MartiDest.from_dict(dest_data) if isinstance(dest_data, dict) else None
|
|
331
|
+
return cls(dest=dest)
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
@dataclass
|
|
335
|
+
class ServerDestination:
|
|
336
|
+
"""Represents an empty ``__serverdestination`` marker element."""
|
|
337
|
+
|
|
338
|
+
@staticmethod
|
|
339
|
+
def to_element() -> ET.Element:
|
|
340
|
+
"""Return an empty ``__serverdestination`` element."""
|
|
341
|
+
|
|
342
|
+
return ET.Element("__serverdestination")
|
|
343
|
+
|
|
344
|
+
@staticmethod
|
|
345
|
+
def to_dict() -> dict:
|
|
346
|
+
"""Return an empty mapping representing the marker."""
|
|
347
|
+
|
|
348
|
+
return {}
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
@dataclass
|
|
352
|
+
class Chat: # pylint: disable=too-many-instance-attributes
|
|
353
|
+
"""Metadata describing the GeoChat parent and room."""
|
|
354
|
+
|
|
355
|
+
parent: Optional[str] = None
|
|
356
|
+
id: Optional[str] = None
|
|
357
|
+
chatroom: Optional[str] = None
|
|
358
|
+
sender_callsign: Optional[str] = None
|
|
359
|
+
group_owner: Optional[str] = None
|
|
360
|
+
message_id: Optional[str] = None
|
|
361
|
+
chat_group: Optional[ChatGroup] = None
|
|
362
|
+
hierarchy: Optional[ChatHierarchy] = None
|
|
363
|
+
|
|
364
|
+
@classmethod
|
|
365
|
+
def from_xml(cls, elem: ET.Element) -> "Chat":
|
|
366
|
+
"""Create a :class:`Chat` from an XML ``<__chat>`` element."""
|
|
367
|
+
|
|
368
|
+
chat_group_el = elem.find("chatgrp")
|
|
369
|
+
hierarchy_el = elem.find("hierarchy")
|
|
370
|
+
return cls(
|
|
371
|
+
parent=elem.get("parent"),
|
|
372
|
+
id=elem.get("id"),
|
|
373
|
+
chatroom=elem.get("chatroom"),
|
|
374
|
+
sender_callsign=elem.get("senderCallsign"),
|
|
375
|
+
group_owner=elem.get("groupOwner"),
|
|
376
|
+
message_id=elem.get("messageId"),
|
|
377
|
+
chat_group=(
|
|
378
|
+
ChatGroup.from_xml(chat_group_el) if chat_group_el is not None else None
|
|
379
|
+
),
|
|
380
|
+
hierarchy=(
|
|
381
|
+
ChatHierarchy.from_xml(hierarchy_el)
|
|
382
|
+
if hierarchy_el is not None
|
|
383
|
+
else None
|
|
384
|
+
),
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
def to_element(self) -> ET.Element:
|
|
388
|
+
"""Return an XML element representing the chat metadata."""
|
|
389
|
+
|
|
390
|
+
attrib = {}
|
|
391
|
+
if self.parent is not None:
|
|
392
|
+
attrib["parent"] = self.parent
|
|
393
|
+
if self.id is not None:
|
|
394
|
+
attrib["id"] = self.id
|
|
395
|
+
if self.chatroom is not None:
|
|
396
|
+
attrib["chatroom"] = self.chatroom
|
|
397
|
+
if self.sender_callsign is not None:
|
|
398
|
+
attrib["senderCallsign"] = self.sender_callsign
|
|
399
|
+
if self.group_owner is not None:
|
|
400
|
+
attrib["groupOwner"] = self.group_owner
|
|
401
|
+
if self.message_id is not None:
|
|
402
|
+
attrib["messageId"] = self.message_id
|
|
403
|
+
element = ET.Element("__chat", attrib)
|
|
404
|
+
if self.chat_group:
|
|
405
|
+
element.append(self.chat_group.to_element())
|
|
406
|
+
if self.hierarchy:
|
|
407
|
+
element.append(self.hierarchy.to_element())
|
|
408
|
+
return element
|
|
409
|
+
|
|
410
|
+
def to_dict(self) -> dict:
|
|
411
|
+
"""Return a serialisable representation of the chat details."""
|
|
412
|
+
|
|
413
|
+
data: dict = {}
|
|
414
|
+
if self.parent is not None:
|
|
415
|
+
data["parent"] = self.parent
|
|
416
|
+
if self.id is not None:
|
|
417
|
+
data["id"] = self.id
|
|
418
|
+
if self.chatroom is not None:
|
|
419
|
+
data["chatroom"] = self.chatroom
|
|
420
|
+
if self.sender_callsign is not None:
|
|
421
|
+
data["sender_callsign"] = self.sender_callsign
|
|
422
|
+
if self.group_owner is not None:
|
|
423
|
+
data["group_owner"] = self.group_owner
|
|
424
|
+
if self.message_id is not None:
|
|
425
|
+
data["message_id"] = self.message_id
|
|
426
|
+
if self.chat_group:
|
|
427
|
+
data["chat_group"] = self.chat_group.to_dict()
|
|
428
|
+
if self.hierarchy:
|
|
429
|
+
data["hierarchy"] = self.hierarchy.to_dict()
|
|
430
|
+
return data
|
|
431
|
+
|
|
432
|
+
@classmethod
|
|
433
|
+
def from_dict(cls, data: dict) -> "Chat":
|
|
434
|
+
"""Create a :class:`Chat` from a dictionary."""
|
|
435
|
+
|
|
436
|
+
chat_group = None
|
|
437
|
+
if "chat_group" in data:
|
|
438
|
+
chat_group = ChatGroup.from_dict(data["chat_group"])
|
|
439
|
+
hierarchy = None
|
|
440
|
+
if "hierarchy" in data:
|
|
441
|
+
hierarchy = ChatHierarchy.from_dict(data["hierarchy"])
|
|
442
|
+
return cls(
|
|
443
|
+
parent=data.get("parent"),
|
|
444
|
+
id=data.get("id"),
|
|
445
|
+
chatroom=data.get("chatroom"),
|
|
446
|
+
sender_callsign=data.get("sender_callsign"),
|
|
447
|
+
group_owner=data.get("group_owner"),
|
|
448
|
+
message_id=data.get("message_id"),
|
|
449
|
+
chat_group=chat_group,
|
|
450
|
+
hierarchy=hierarchy,
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
@dataclass
|
|
455
|
+
class Link:
|
|
456
|
+
"""Relationship metadata for GeoChat participants."""
|
|
457
|
+
|
|
458
|
+
uid: str
|
|
459
|
+
type: str
|
|
460
|
+
relation: str
|
|
461
|
+
production_time: Optional[str] = None
|
|
462
|
+
parent_callsign: Optional[str] = None
|
|
463
|
+
|
|
464
|
+
@classmethod
|
|
465
|
+
def from_xml(cls, elem: ET.Element) -> "Link":
|
|
466
|
+
"""Create a :class:`Link` from a ``<link>`` element."""
|
|
467
|
+
|
|
468
|
+
return cls(
|
|
469
|
+
uid=elem.get("uid", ""),
|
|
470
|
+
type=elem.get("type", ""),
|
|
471
|
+
relation=elem.get("relation", ""),
|
|
472
|
+
production_time=elem.get("production_time"),
|
|
473
|
+
parent_callsign=elem.get("parent_callsign"),
|
|
474
|
+
)
|
|
475
|
+
|
|
476
|
+
def to_element(self) -> ET.Element:
|
|
477
|
+
"""Return an XML element for the participant link."""
|
|
478
|
+
|
|
479
|
+
attrib = {"uid": self.uid, "type": self.type, "relation": self.relation}
|
|
480
|
+
if self.production_time is not None:
|
|
481
|
+
attrib["production_time"] = self.production_time
|
|
482
|
+
if self.parent_callsign is not None:
|
|
483
|
+
attrib["parent_callsign"] = self.parent_callsign
|
|
484
|
+
return ET.Element("link", attrib)
|
|
485
|
+
|
|
486
|
+
def to_dict(self) -> dict:
|
|
487
|
+
"""Return a serialisable representation of the link."""
|
|
488
|
+
|
|
489
|
+
data = {"uid": self.uid, "type": self.type, "relation": self.relation}
|
|
490
|
+
if self.production_time is not None:
|
|
491
|
+
data["production_time"] = self.production_time
|
|
492
|
+
if self.parent_callsign is not None:
|
|
493
|
+
data["parent_callsign"] = self.parent_callsign
|
|
494
|
+
return data
|
|
495
|
+
|
|
496
|
+
@classmethod
|
|
497
|
+
def from_dict(cls, data: dict) -> "Link":
|
|
498
|
+
"""Create a :class:`Link` from a dictionary."""
|
|
499
|
+
|
|
500
|
+
return cls(
|
|
501
|
+
uid=data.get("uid", ""),
|
|
502
|
+
type=data.get("type", ""),
|
|
503
|
+
relation=data.get("relation", ""),
|
|
504
|
+
production_time=data.get("production_time"),
|
|
505
|
+
parent_callsign=data.get("parent_callsign"),
|
|
506
|
+
)
|