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,373 @@
|
|
|
1
|
+
"""Service helpers for the northbound API."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from datetime import timezone
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any
|
|
10
|
+
from typing import Callable
|
|
11
|
+
from typing import Dict
|
|
12
|
+
from typing import List
|
|
13
|
+
from typing import Optional
|
|
14
|
+
|
|
15
|
+
from reticulum_telemetry_hub.api.models import ChatMessage
|
|
16
|
+
from reticulum_telemetry_hub.api.models import Client
|
|
17
|
+
from reticulum_telemetry_hub.api.models import FileAttachment
|
|
18
|
+
from reticulum_telemetry_hub.api.models import IdentityStatus
|
|
19
|
+
from reticulum_telemetry_hub.api.models import ReticulumInfo
|
|
20
|
+
from reticulum_telemetry_hub.api.models import Subscriber
|
|
21
|
+
from reticulum_telemetry_hub.api.models import Topic
|
|
22
|
+
from reticulum_telemetry_hub.api.service import ReticulumTelemetryHubAPI
|
|
23
|
+
from reticulum_telemetry_hub.lxmf_telemetry.telemetry_controller import (
|
|
24
|
+
TelemetryController,
|
|
25
|
+
)
|
|
26
|
+
from reticulum_telemetry_hub.reticulum_server import command_text
|
|
27
|
+
from reticulum_telemetry_hub.reticulum_server.event_log import EventLog
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _load_supported_commands_doc() -> Optional[str]:
|
|
31
|
+
doc_path = Path(__file__).resolve().parents[2] / "docs" / "supportedCommands.md"
|
|
32
|
+
try:
|
|
33
|
+
return doc_path.read_text(encoding="utf-8")
|
|
34
|
+
except OSError:
|
|
35
|
+
return None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _build_help_fallback(doc_text: str) -> str:
|
|
39
|
+
public_commands: list[str] = []
|
|
40
|
+
protected_commands: list[str] = []
|
|
41
|
+
section: Optional[str] = None
|
|
42
|
+
for line in doc_text.splitlines():
|
|
43
|
+
stripped = line.strip()
|
|
44
|
+
if stripped.lower().startswith("public commands"):
|
|
45
|
+
section = "public"
|
|
46
|
+
continue
|
|
47
|
+
if stripped.lower().startswith("protected commands"):
|
|
48
|
+
section = "protected"
|
|
49
|
+
continue
|
|
50
|
+
if stripped.startswith("| `"):
|
|
51
|
+
parts = [part.strip() for part in stripped.split("|")]
|
|
52
|
+
if len(parts) < 2:
|
|
53
|
+
continue
|
|
54
|
+
command_cell = parts[1].replace("`", "").strip()
|
|
55
|
+
if not command_cell:
|
|
56
|
+
continue
|
|
57
|
+
if section == "protected":
|
|
58
|
+
protected_commands.append(command_cell)
|
|
59
|
+
else:
|
|
60
|
+
public_commands.append(command_cell)
|
|
61
|
+
if not public_commands and not protected_commands:
|
|
62
|
+
return "# Command list\n\nCommand documentation not available."
|
|
63
|
+
lines = ["# Command list", ""]
|
|
64
|
+
if public_commands:
|
|
65
|
+
lines.append("Public:")
|
|
66
|
+
lines.extend([f"- {command}" for command in public_commands])
|
|
67
|
+
lines.append("")
|
|
68
|
+
if protected_commands:
|
|
69
|
+
lines.append("Protected:")
|
|
70
|
+
lines.extend([f"- {command}" for command in protected_commands])
|
|
71
|
+
return "\n".join(lines).strip() + "\n"
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _build_examples_fallback(doc_text: str) -> str:
|
|
75
|
+
marker = "Public commands:"
|
|
76
|
+
start = doc_text.find(marker)
|
|
77
|
+
if start == -1:
|
|
78
|
+
return doc_text
|
|
79
|
+
snippet = doc_text[start:].strip()
|
|
80
|
+
return f"# Command examples\n\n{snippet}\n"
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@dataclass
|
|
84
|
+
class NorthboundServices:
|
|
85
|
+
"""Aggregate services needed by the northbound API."""
|
|
86
|
+
|
|
87
|
+
api: ReticulumTelemetryHubAPI
|
|
88
|
+
telemetry: TelemetryController
|
|
89
|
+
event_log: EventLog
|
|
90
|
+
started_at: datetime
|
|
91
|
+
command_manager: Optional[Any] = None
|
|
92
|
+
routing_provider: Optional[Callable[[], List[str]]] = None
|
|
93
|
+
message_dispatcher: Optional[
|
|
94
|
+
Callable[[str, Optional[str], Optional[str], Optional[dict]], ChatMessage | None]
|
|
95
|
+
] = None
|
|
96
|
+
|
|
97
|
+
def help_text(self) -> str:
|
|
98
|
+
"""Return the Help command text.
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
str: Markdown formatted help content.
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
if not self.command_manager:
|
|
105
|
+
doc_text = _load_supported_commands_doc()
|
|
106
|
+
if doc_text:
|
|
107
|
+
return _build_help_fallback(doc_text)
|
|
108
|
+
return "# Command list\n\nCommand manager is not configured."
|
|
109
|
+
return command_text.build_help_text(self.command_manager)
|
|
110
|
+
|
|
111
|
+
def examples_text(self) -> str:
|
|
112
|
+
"""Return the Examples command text.
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
str: Markdown formatted examples content.
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
if not self.command_manager:
|
|
119
|
+
doc_text = _load_supported_commands_doc()
|
|
120
|
+
if doc_text:
|
|
121
|
+
return _build_examples_fallback(doc_text)
|
|
122
|
+
return "# Command examples\n\nCommand manager is not configured."
|
|
123
|
+
return command_text.build_examples_text(self.command_manager)
|
|
124
|
+
|
|
125
|
+
def status_snapshot(self) -> Dict[str, object]:
|
|
126
|
+
"""Return the current status snapshot.
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
Dict[str, object]: Status payload for the dashboard.
|
|
130
|
+
"""
|
|
131
|
+
|
|
132
|
+
uptime = datetime.now(timezone.utc) - self.started_at
|
|
133
|
+
chat_stats = self.api.chat_message_stats()
|
|
134
|
+
return {
|
|
135
|
+
"uptime_seconds": int(uptime.total_seconds()),
|
|
136
|
+
"clients": len(self.api.list_clients()),
|
|
137
|
+
"topics": len(self.api.list_topics()),
|
|
138
|
+
"subscribers": len(self.api.list_subscribers()),
|
|
139
|
+
"files": len(self.api.list_files()),
|
|
140
|
+
"images": len(self.api.list_images()),
|
|
141
|
+
"chat": {
|
|
142
|
+
"sent": chat_stats.get("sent", 0),
|
|
143
|
+
"failed": chat_stats.get("failed", 0),
|
|
144
|
+
"received": chat_stats.get("delivered", 0),
|
|
145
|
+
},
|
|
146
|
+
"telemetry": self.telemetry.telemetry_stats(),
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
def list_events(self, limit: Optional[int] = None) -> List[Dict[str, object]]:
|
|
150
|
+
"""Return recent events.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
limit (Optional[int]): Optional limit for returned events.
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
List[Dict[str, object]]: Event entries.
|
|
157
|
+
"""
|
|
158
|
+
|
|
159
|
+
return self.event_log.list_events(limit=limit)
|
|
160
|
+
|
|
161
|
+
def record_event(
|
|
162
|
+
self, event_type: str, message: str, metadata: Optional[Dict[str, object]] = None
|
|
163
|
+
) -> Dict[str, object]:
|
|
164
|
+
"""Record an event entry.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
event_type (str): Event category.
|
|
168
|
+
message (str): Human readable description.
|
|
169
|
+
metadata (Optional[Dict[str, object]]): Optional structured data.
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
Dict[str, object]: Event entry payload.
|
|
173
|
+
"""
|
|
174
|
+
|
|
175
|
+
return self.event_log.add_event(event_type, message, metadata=metadata)
|
|
176
|
+
|
|
177
|
+
def list_clients(self) -> List[Client]:
|
|
178
|
+
"""Return connected clients.
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
List[Client]: Client entries.
|
|
182
|
+
"""
|
|
183
|
+
|
|
184
|
+
return self.api.list_clients()
|
|
185
|
+
|
|
186
|
+
def list_topics(self) -> List[Topic]:
|
|
187
|
+
"""Return topics.
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
List[Topic]: Topic entries.
|
|
191
|
+
"""
|
|
192
|
+
|
|
193
|
+
return self.api.list_topics()
|
|
194
|
+
|
|
195
|
+
def list_subscribers(self) -> List[Subscriber]:
|
|
196
|
+
"""Return subscribers.
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
List[Subscriber]: Subscriber entries.
|
|
200
|
+
"""
|
|
201
|
+
|
|
202
|
+
return self.api.list_subscribers()
|
|
203
|
+
|
|
204
|
+
def list_files(self) -> List[FileAttachment]:
|
|
205
|
+
"""Return file attachments.
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
List[FileAttachment]: File records.
|
|
209
|
+
"""
|
|
210
|
+
|
|
211
|
+
return self.api.list_files()
|
|
212
|
+
|
|
213
|
+
def list_images(self) -> List[FileAttachment]:
|
|
214
|
+
"""Return image attachments.
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
List[FileAttachment]: Image records.
|
|
218
|
+
"""
|
|
219
|
+
|
|
220
|
+
return self.api.list_images()
|
|
221
|
+
|
|
222
|
+
def list_identity_statuses(self) -> List[IdentityStatus]:
|
|
223
|
+
"""Return identity moderation statuses.
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
List[IdentityStatus]: Identity status records.
|
|
227
|
+
"""
|
|
228
|
+
|
|
229
|
+
return self.api.list_identity_statuses()
|
|
230
|
+
|
|
231
|
+
def list_chat_messages(
|
|
232
|
+
self,
|
|
233
|
+
*,
|
|
234
|
+
limit: int = 200,
|
|
235
|
+
direction: Optional[str] = None,
|
|
236
|
+
topic_id: Optional[str] = None,
|
|
237
|
+
destination: Optional[str] = None,
|
|
238
|
+
source: Optional[str] = None,
|
|
239
|
+
) -> List[ChatMessage]:
|
|
240
|
+
"""Return persisted chat messages."""
|
|
241
|
+
|
|
242
|
+
return self.api.list_chat_messages(
|
|
243
|
+
limit=limit,
|
|
244
|
+
direction=direction,
|
|
245
|
+
topic_id=topic_id,
|
|
246
|
+
destination=destination,
|
|
247
|
+
source=source,
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
def store_uploaded_attachment(
|
|
251
|
+
self,
|
|
252
|
+
*,
|
|
253
|
+
content: bytes,
|
|
254
|
+
filename: str,
|
|
255
|
+
media_type: Optional[str],
|
|
256
|
+
category: str,
|
|
257
|
+
topic_id: Optional[str] = None,
|
|
258
|
+
) -> FileAttachment:
|
|
259
|
+
"""Persist an uploaded attachment to storage."""
|
|
260
|
+
|
|
261
|
+
return self.api.store_uploaded_attachment(
|
|
262
|
+
content=content,
|
|
263
|
+
filename=filename,
|
|
264
|
+
media_type=media_type,
|
|
265
|
+
category=category,
|
|
266
|
+
topic_id=topic_id,
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
def resolve_attachments(
|
|
270
|
+
self,
|
|
271
|
+
*,
|
|
272
|
+
file_ids: list[int],
|
|
273
|
+
image_ids: list[int],
|
|
274
|
+
) -> list[FileAttachment]:
|
|
275
|
+
"""Resolve stored attachment records by ID."""
|
|
276
|
+
|
|
277
|
+
attachments: list[FileAttachment] = []
|
|
278
|
+
for file_id in file_ids:
|
|
279
|
+
attachments.append(self.api.retrieve_file(file_id))
|
|
280
|
+
for image_id in image_ids:
|
|
281
|
+
attachments.append(self.api.retrieve_image(image_id))
|
|
282
|
+
return attachments
|
|
283
|
+
|
|
284
|
+
def dump_routing(self) -> Dict[str, List[str]]:
|
|
285
|
+
"""Return connected destinations.
|
|
286
|
+
|
|
287
|
+
Returns:
|
|
288
|
+
Dict[str, List[str]]: Routing summary payload.
|
|
289
|
+
"""
|
|
290
|
+
|
|
291
|
+
if self.routing_provider:
|
|
292
|
+
return {"destinations": list(self.routing_provider())}
|
|
293
|
+
return {"destinations": [client.identity for client in self.api.list_clients()]}
|
|
294
|
+
|
|
295
|
+
def telemetry_entries(
|
|
296
|
+
self, *, since: int, topic_id: Optional[str] = None
|
|
297
|
+
) -> List[Dict[str, object]]:
|
|
298
|
+
"""Return telemetry entries for REST responses.
|
|
299
|
+
|
|
300
|
+
Args:
|
|
301
|
+
since (int): Unix timestamp in seconds.
|
|
302
|
+
topic_id (Optional[str]): Optional topic filter.
|
|
303
|
+
|
|
304
|
+
Returns:
|
|
305
|
+
List[Dict[str, object]]: Telemetry entries.
|
|
306
|
+
"""
|
|
307
|
+
|
|
308
|
+
return self.telemetry.list_telemetry_entries(since=since, topic_id=topic_id)
|
|
309
|
+
|
|
310
|
+
def app_info(self) -> ReticulumInfo:
|
|
311
|
+
"""Return application metadata.
|
|
312
|
+
|
|
313
|
+
Returns:
|
|
314
|
+
ReticulumInfo: Application info snapshot.
|
|
315
|
+
"""
|
|
316
|
+
|
|
317
|
+
return self.api.get_app_info()
|
|
318
|
+
|
|
319
|
+
def send_message(
|
|
320
|
+
self,
|
|
321
|
+
content: str,
|
|
322
|
+
*,
|
|
323
|
+
topic_id: Optional[str] = None,
|
|
324
|
+
destination: Optional[str] = None,
|
|
325
|
+
) -> None:
|
|
326
|
+
"""Dispatch a message from northbound into the core hub."""
|
|
327
|
+
|
|
328
|
+
if not self.message_dispatcher:
|
|
329
|
+
raise RuntimeError("Message dispatch is not configured")
|
|
330
|
+
self.message_dispatcher(content, topic_id, destination, None)
|
|
331
|
+
|
|
332
|
+
def send_chat_message(
|
|
333
|
+
self,
|
|
334
|
+
*,
|
|
335
|
+
content: str,
|
|
336
|
+
scope: str,
|
|
337
|
+
topic_id: Optional[str],
|
|
338
|
+
destination: Optional[str],
|
|
339
|
+
attachments: list[FileAttachment],
|
|
340
|
+
) -> ChatMessage:
|
|
341
|
+
"""Send a chat message via the core hub."""
|
|
342
|
+
|
|
343
|
+
if not self.message_dispatcher:
|
|
344
|
+
raise RuntimeError("Message dispatch is not configured")
|
|
345
|
+
chat_attachments = [
|
|
346
|
+
self.api.chat_attachment_from_file(item) for item in attachments
|
|
347
|
+
]
|
|
348
|
+
fields = {"attachments": attachments}
|
|
349
|
+
if scope:
|
|
350
|
+
fields["scope"] = scope
|
|
351
|
+
message = self.message_dispatcher(content, topic_id, destination, fields)
|
|
352
|
+
if message is None:
|
|
353
|
+
message = ChatMessage(
|
|
354
|
+
direction="outbound",
|
|
355
|
+
scope=scope,
|
|
356
|
+
state="failed",
|
|
357
|
+
content=content,
|
|
358
|
+
source=None,
|
|
359
|
+
destination=destination,
|
|
360
|
+
topic_id=topic_id,
|
|
361
|
+
attachments=chat_attachments,
|
|
362
|
+
)
|
|
363
|
+
return self.api.record_chat_message(message)
|
|
364
|
+
return message
|
|
365
|
+
|
|
366
|
+
def reload_config(self) -> ReticulumInfo:
|
|
367
|
+
"""Reload configuration from disk.
|
|
368
|
+
|
|
369
|
+
Returns:
|
|
370
|
+
ReticulumInfo: Updated configuration snapshot.
|
|
371
|
+
"""
|
|
372
|
+
|
|
373
|
+
return self.api.reload_config()
|