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.
Files changed (108) hide show
  1. reticulum_telemetry_hub/api/__init__.py +23 -0
  2. reticulum_telemetry_hub/api/models.py +323 -0
  3. reticulum_telemetry_hub/api/service.py +836 -0
  4. reticulum_telemetry_hub/api/storage.py +528 -0
  5. reticulum_telemetry_hub/api/storage_base.py +156 -0
  6. reticulum_telemetry_hub/api/storage_models.py +118 -0
  7. reticulum_telemetry_hub/atak_cot/__init__.py +49 -0
  8. reticulum_telemetry_hub/atak_cot/base.py +277 -0
  9. reticulum_telemetry_hub/atak_cot/chat.py +506 -0
  10. reticulum_telemetry_hub/atak_cot/detail.py +235 -0
  11. reticulum_telemetry_hub/atak_cot/event.py +181 -0
  12. reticulum_telemetry_hub/atak_cot/pytak_client.py +569 -0
  13. reticulum_telemetry_hub/atak_cot/tak_connector.py +848 -0
  14. reticulum_telemetry_hub/config/__init__.py +25 -0
  15. reticulum_telemetry_hub/config/constants.py +7 -0
  16. reticulum_telemetry_hub/config/manager.py +515 -0
  17. reticulum_telemetry_hub/config/models.py +215 -0
  18. reticulum_telemetry_hub/embedded_lxmd/__init__.py +5 -0
  19. reticulum_telemetry_hub/embedded_lxmd/embedded.py +418 -0
  20. reticulum_telemetry_hub/internal_api/__init__.py +21 -0
  21. reticulum_telemetry_hub/internal_api/bus.py +344 -0
  22. reticulum_telemetry_hub/internal_api/core.py +690 -0
  23. reticulum_telemetry_hub/internal_api/v1/__init__.py +74 -0
  24. reticulum_telemetry_hub/internal_api/v1/enums.py +109 -0
  25. reticulum_telemetry_hub/internal_api/v1/manifest.json +8 -0
  26. reticulum_telemetry_hub/internal_api/v1/schemas.py +478 -0
  27. reticulum_telemetry_hub/internal_api/versioning.py +63 -0
  28. reticulum_telemetry_hub/lxmf_daemon/Handlers.py +122 -0
  29. reticulum_telemetry_hub/lxmf_daemon/LXMF.py +252 -0
  30. reticulum_telemetry_hub/lxmf_daemon/LXMPeer.py +898 -0
  31. reticulum_telemetry_hub/lxmf_daemon/LXMRouter.py +4227 -0
  32. reticulum_telemetry_hub/lxmf_daemon/LXMessage.py +1006 -0
  33. reticulum_telemetry_hub/lxmf_daemon/LXStamper.py +490 -0
  34. reticulum_telemetry_hub/lxmf_daemon/__init__.py +10 -0
  35. reticulum_telemetry_hub/lxmf_daemon/_version.py +1 -0
  36. reticulum_telemetry_hub/lxmf_daemon/lxmd.py +1655 -0
  37. reticulum_telemetry_hub/lxmf_telemetry/model/fields/field_telemetry_stream.py +6 -0
  38. reticulum_telemetry_hub/lxmf_telemetry/model/persistance/__init__.py +3 -0
  39. {lxmf_telemetry → reticulum_telemetry_hub/lxmf_telemetry}/model/persistance/appearance.py +19 -19
  40. {lxmf_telemetry → reticulum_telemetry_hub/lxmf_telemetry}/model/persistance/peer.py +17 -13
  41. reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/__init__.py +65 -0
  42. reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/acceleration.py +68 -0
  43. reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/ambient_light.py +37 -0
  44. reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/angular_velocity.py +68 -0
  45. reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/battery.py +68 -0
  46. reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/connection_map.py +258 -0
  47. reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/generic.py +841 -0
  48. reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/gravity.py +68 -0
  49. reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/humidity.py +37 -0
  50. reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/information.py +42 -0
  51. reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/location.py +110 -0
  52. reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/lxmf_propagation.py +429 -0
  53. reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/magnetic_field.py +68 -0
  54. reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/physical_link.py +53 -0
  55. reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/pressure.py +37 -0
  56. reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/proximity.py +37 -0
  57. reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/received.py +75 -0
  58. reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/rns_transport.py +209 -0
  59. reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/sensor.py +65 -0
  60. reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/sensor_enum.py +27 -0
  61. reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/sensor_mapping.py +58 -0
  62. reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/temperature.py +37 -0
  63. {lxmf_telemetry → reticulum_telemetry_hub/lxmf_telemetry}/model/persistance/sensors/time.py +36 -32
  64. {lxmf_telemetry → reticulum_telemetry_hub/lxmf_telemetry}/model/persistance/telemeter.py +26 -23
  65. reticulum_telemetry_hub/lxmf_telemetry/sampler.py +229 -0
  66. reticulum_telemetry_hub/lxmf_telemetry/telemeter_manager.py +409 -0
  67. reticulum_telemetry_hub/lxmf_telemetry/telemetry_controller.py +804 -0
  68. reticulum_telemetry_hub/northbound/__init__.py +5 -0
  69. reticulum_telemetry_hub/northbound/app.py +195 -0
  70. reticulum_telemetry_hub/northbound/auth.py +119 -0
  71. reticulum_telemetry_hub/northbound/gateway.py +310 -0
  72. reticulum_telemetry_hub/northbound/internal_adapter.py +302 -0
  73. reticulum_telemetry_hub/northbound/models.py +213 -0
  74. reticulum_telemetry_hub/northbound/routes_chat.py +123 -0
  75. reticulum_telemetry_hub/northbound/routes_files.py +119 -0
  76. reticulum_telemetry_hub/northbound/routes_rest.py +345 -0
  77. reticulum_telemetry_hub/northbound/routes_subscribers.py +150 -0
  78. reticulum_telemetry_hub/northbound/routes_topics.py +178 -0
  79. reticulum_telemetry_hub/northbound/routes_ws.py +107 -0
  80. reticulum_telemetry_hub/northbound/serializers.py +72 -0
  81. reticulum_telemetry_hub/northbound/services.py +373 -0
  82. reticulum_telemetry_hub/northbound/websocket.py +855 -0
  83. reticulum_telemetry_hub/reticulum_server/__main__.py +2237 -0
  84. reticulum_telemetry_hub/reticulum_server/command_manager.py +1268 -0
  85. reticulum_telemetry_hub/reticulum_server/command_text.py +399 -0
  86. reticulum_telemetry_hub/reticulum_server/constants.py +1 -0
  87. reticulum_telemetry_hub/reticulum_server/event_log.py +357 -0
  88. reticulum_telemetry_hub/reticulum_server/internal_adapter.py +358 -0
  89. reticulum_telemetry_hub/reticulum_server/outbound_queue.py +312 -0
  90. reticulum_telemetry_hub/reticulum_server/services.py +422 -0
  91. reticulumtelemetryhub-0.143.0.dist-info/METADATA +181 -0
  92. reticulumtelemetryhub-0.143.0.dist-info/RECORD +97 -0
  93. {reticulumtelemetryhub-0.1.0.dist-info → reticulumtelemetryhub-0.143.0.dist-info}/WHEEL +1 -1
  94. reticulumtelemetryhub-0.143.0.dist-info/licenses/LICENSE +277 -0
  95. lxmf_telemetry/model/fields/field_telemetry_stream.py +0 -7
  96. lxmf_telemetry/model/persistance/__init__.py +0 -3
  97. lxmf_telemetry/model/persistance/sensors/location.py +0 -69
  98. lxmf_telemetry/model/persistance/sensors/magnetic_field.py +0 -36
  99. lxmf_telemetry/model/persistance/sensors/sensor.py +0 -44
  100. lxmf_telemetry/model/persistance/sensors/sensor_enum.py +0 -24
  101. lxmf_telemetry/model/persistance/sensors/sensor_mapping.py +0 -9
  102. lxmf_telemetry/telemetry_controller.py +0 -124
  103. reticulum_server/main.py +0 -182
  104. reticulumtelemetryhub-0.1.0.dist-info/METADATA +0 -15
  105. reticulumtelemetryhub-0.1.0.dist-info/RECORD +0 -19
  106. {lxmf_telemetry → reticulum_telemetry_hub}/__init__.py +0 -0
  107. {lxmf_telemetry/model/persistance/sensors → reticulum_telemetry_hub/lxmf_telemetry}/__init__.py +0 -0
  108. {reticulum_server → reticulum_telemetry_hub/reticulum_server}/__init__.py +0 -0
@@ -0,0 +1,178 @@
1
+ """Topic routes for the northbound API."""
2
+ # pylint: disable=import-error
3
+
4
+ from __future__ import annotations
5
+
6
+ from typing import Callable
7
+
8
+ from fastapi import Depends
9
+ from fastapi import FastAPI
10
+ from fastapi import HTTPException
11
+ from fastapi import Query
12
+ from fastapi import status
13
+
14
+ from reticulum_telemetry_hub.api.service import ReticulumTelemetryHubAPI
15
+
16
+ from .models import SubscribeTopicRequest
17
+ from .models import TopicPayload
18
+ from .serializers import build_topic
19
+ from .serializers import serialize_subscriber
20
+ from .serializers import serialize_topic
21
+ from .services import NorthboundServices
22
+
23
+
24
+ def register_topic_routes(
25
+ app: FastAPI,
26
+ *,
27
+ services: NorthboundServices,
28
+ api: ReticulumTelemetryHubAPI,
29
+ require_protected: Callable[[], None],
30
+ ) -> None:
31
+ """Register topic routes on the FastAPI app.
32
+
33
+ Args:
34
+ app (FastAPI): FastAPI application instance.
35
+ services (NorthboundServices): Aggregated services.
36
+ api (ReticulumTelemetryHubAPI): API service instance.
37
+ require_protected (Callable[[], None]): Dependency for protected routes.
38
+
39
+ Returns:
40
+ None: Routes are registered on the application.
41
+ """
42
+
43
+ @app.get("/Topic/{topic_id}")
44
+ def retrieve_topic(topic_id: str) -> dict:
45
+ """Retrieve a topic by ID.
46
+
47
+ Args:
48
+ topic_id (str): Topic identifier.
49
+
50
+ Returns:
51
+ dict: Topic payload.
52
+ """
53
+
54
+ try:
55
+ topic = api.retrieve_topic(topic_id)
56
+ except KeyError as exc:
57
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc
58
+ return serialize_topic(topic)
59
+
60
+ @app.post("/Topic", dependencies=[Depends(require_protected)])
61
+ def create_topic(payload: TopicPayload) -> dict:
62
+ """Create a new topic.
63
+
64
+ Args:
65
+ payload (TopicPayload): Topic request payload.
66
+
67
+ Returns:
68
+ dict: Created topic payload.
69
+ """
70
+
71
+ topic = build_topic(payload)
72
+ try:
73
+ created = api.create_topic(topic)
74
+ except ValueError as exc:
75
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc)) from exc
76
+ services.record_event("topic_created", f"Topic created: {created.topic_id}")
77
+ return serialize_topic(created)
78
+
79
+ @app.delete("/Topic", dependencies=[Depends(require_protected)])
80
+ def delete_topic(topic_id: str = Query(alias="id")) -> dict:
81
+ """Delete a topic.
82
+
83
+ Args:
84
+ topic_id (str): Topic identifier.
85
+
86
+ Returns:
87
+ dict: Deleted topic payload.
88
+ """
89
+
90
+ try:
91
+ topic = api.delete_topic(topic_id)
92
+ except KeyError as exc:
93
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc
94
+ services.record_event("topic_deleted", f"Topic deleted: {topic.topic_id}")
95
+ return serialize_topic(topic)
96
+
97
+ @app.get("/Topic")
98
+ def list_topics() -> list[dict]:
99
+ """List topics.
100
+
101
+ Returns:
102
+ list[dict]: Topic entries.
103
+ """
104
+
105
+ return [serialize_topic(topic) for topic in services.list_topics()]
106
+
107
+ @app.patch("/Topic", dependencies=[Depends(require_protected)])
108
+ def patch_topic(payload: TopicPayload) -> dict:
109
+ """Update a topic.
110
+
111
+ Args:
112
+ payload (TopicPayload): Topic update payload.
113
+
114
+ Returns:
115
+ dict: Updated topic payload.
116
+ """
117
+
118
+ if not payload.topic_id:
119
+ raise HTTPException(
120
+ status_code=status.HTTP_400_BAD_REQUEST,
121
+ detail="TopicID is required",
122
+ )
123
+ try:
124
+ topic = api.patch_topic(payload.topic_id, **payload.model_dump(by_alias=True, exclude_unset=True))
125
+ except KeyError as exc:
126
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc
127
+ services.record_event("topic_updated", f"Topic updated: {topic.topic_id}")
128
+ return serialize_topic(topic)
129
+
130
+ @app.post("/Topic/Subscribe")
131
+ def subscribe_topic(payload: SubscribeTopicRequest) -> dict:
132
+ """Subscribe a destination to a topic.
133
+
134
+ Args:
135
+ payload (SubscribeTopicRequest): Subscription payload.
136
+
137
+ Returns:
138
+ dict: Subscriber payload.
139
+ """
140
+
141
+ if not payload.destination:
142
+ raise HTTPException(
143
+ status_code=status.HTTP_400_BAD_REQUEST,
144
+ detail="Destination is required when authentication identity is unavailable",
145
+ )
146
+ try:
147
+ subscriber = api.subscribe_topic(
148
+ payload.topic_id,
149
+ payload.destination,
150
+ reject_tests=payload.reject_tests,
151
+ metadata=payload.metadata,
152
+ )
153
+ except KeyError as exc:
154
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc
155
+ services.record_event(
156
+ "topic_subscribed",
157
+ f"Subscriber added: {subscriber.subscriber_id}",
158
+ metadata={"topic_id": subscriber.topic_id},
159
+ )
160
+ return serialize_subscriber(subscriber)
161
+
162
+ @app.post("/Topic/Associate", dependencies=[Depends(require_protected)])
163
+ def associate_topic(payload: TopicPayload) -> dict:
164
+ """Return the topic association payload.
165
+
166
+ Args:
167
+ payload (TopicPayload): Topic association payload.
168
+
169
+ Returns:
170
+ dict: Topic association payload.
171
+ """
172
+
173
+ if not payload.topic_id:
174
+ raise HTTPException(
175
+ status_code=status.HTTP_400_BAD_REQUEST,
176
+ detail="TopicID is required",
177
+ )
178
+ return {"TopicID": payload.topic_id}
@@ -0,0 +1,107 @@
1
+ """WebSocket route registrations for the northbound API."""
2
+ # pylint: disable=import-error
3
+
4
+ from __future__ import annotations
5
+
6
+ from fastapi import FastAPI
7
+ from fastapi import WebSocket
8
+
9
+ from .auth import ApiAuth
10
+ from .services import NorthboundServices
11
+ from .websocket import EventBroadcaster
12
+ from .websocket import MessageBroadcaster
13
+ from .websocket import TelemetryBroadcaster
14
+ from .websocket import handle_message_socket
15
+ from .websocket import handle_system_socket
16
+ from .websocket import handle_telemetry_socket
17
+
18
+
19
+ def register_ws_routes(
20
+ app: FastAPI,
21
+ *,
22
+ services: NorthboundServices,
23
+ auth: ApiAuth,
24
+ event_broadcaster: EventBroadcaster,
25
+ telemetry_broadcaster: TelemetryBroadcaster,
26
+ message_broadcaster: MessageBroadcaster,
27
+ ) -> None:
28
+ """Register WebSocket routes on the FastAPI app.
29
+
30
+ Args:
31
+ app (FastAPI): FastAPI application instance.
32
+ services (NorthboundServices): Aggregated services.
33
+ auth (ApiAuth): Auth validator.
34
+ event_broadcaster (EventBroadcaster): Event broadcaster.
35
+ telemetry_broadcaster (TelemetryBroadcaster): Telemetry broadcaster.
36
+ message_broadcaster (MessageBroadcaster): Message broadcaster.
37
+
38
+ Returns:
39
+ None: Routes are registered on the application.
40
+ """
41
+
42
+ @app.websocket("/events/system")
43
+ async def system_websocket(websocket: WebSocket) -> None:
44
+ """WebSocket stream for system status and events.
45
+
46
+ Args:
47
+ websocket (WebSocket): WebSocket connection.
48
+ """
49
+
50
+ def _event_list_provider(limit: int) -> list[dict]:
51
+ """Return recent events with the provided limit.
52
+
53
+ Args:
54
+ limit (int): Maximum number of events to return.
55
+
56
+ Returns:
57
+ list[dict]: Event entries.
58
+ """
59
+
60
+ return services.list_events(limit=limit)
61
+
62
+ await handle_system_socket(
63
+ websocket,
64
+ auth=auth,
65
+ event_broadcaster=event_broadcaster,
66
+ status_provider=services.status_snapshot,
67
+ event_list_provider=_event_list_provider,
68
+ )
69
+
70
+ @app.websocket("/telemetry/stream")
71
+ async def telemetry_websocket(websocket: WebSocket) -> None:
72
+ """WebSocket stream for telemetry updates.
73
+
74
+ Args:
75
+ websocket (WebSocket): WebSocket connection.
76
+ """
77
+
78
+ def _telemetry_snapshot(since: int, topic_id: str | None) -> list[dict]:
79
+ """Return telemetry snapshots for WebSocket clients.
80
+
81
+ Args:
82
+ since (int): Unix timestamp (seconds) for the earliest entries.
83
+ topic_id (str | None): Optional topic filter.
84
+
85
+ Returns:
86
+ list[dict]: Telemetry entries.
87
+ """
88
+
89
+ return services.telemetry_entries(since=since, topic_id=topic_id)
90
+
91
+ await handle_telemetry_socket(
92
+ websocket,
93
+ auth=auth,
94
+ telemetry_broadcaster=telemetry_broadcaster,
95
+ telemetry_snapshot=_telemetry_snapshot,
96
+ )
97
+
98
+ @app.websocket("/messages/stream")
99
+ async def message_websocket(websocket: WebSocket) -> None:
100
+ """WebSocket stream for inbound/outbound messages."""
101
+
102
+ await handle_message_socket(
103
+ websocket,
104
+ auth=auth,
105
+ message_broadcaster=message_broadcaster,
106
+ message_sender=services.send_message,
107
+ )
@@ -0,0 +1,72 @@
1
+ """Serialization helpers for the northbound API."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from reticulum_telemetry_hub.api.models import Subscriber
6
+ from reticulum_telemetry_hub.api.models import Topic
7
+
8
+ from .models import SubscriberPayload
9
+ from .models import TopicPayload
10
+
11
+
12
+ def serialize_topic(topic: Topic) -> dict:
13
+ """Serialize a topic for JSON output.
14
+
15
+ Args:
16
+ topic (Topic): Topic instance.
17
+
18
+ Returns:
19
+ dict: Serialized topic payload.
20
+ """
21
+
22
+ return topic.to_dict()
23
+
24
+
25
+ def serialize_subscriber(subscriber: Subscriber) -> dict:
26
+ """Serialize a subscriber for JSON output.
27
+
28
+ Args:
29
+ subscriber (Subscriber): Subscriber instance.
30
+
31
+ Returns:
32
+ dict: Serialized subscriber payload.
33
+ """
34
+
35
+ return subscriber.to_dict()
36
+
37
+
38
+ def build_topic(payload: TopicPayload) -> Topic:
39
+ """Build a topic model from a payload.
40
+
41
+ Args:
42
+ payload (TopicPayload): Topic payload.
43
+
44
+ Returns:
45
+ Topic: Topic instance.
46
+ """
47
+
48
+ return Topic(
49
+ topic_id=payload.topic_id,
50
+ topic_name=payload.topic_name or "",
51
+ topic_path=payload.topic_path or "",
52
+ topic_description=payload.topic_description or "",
53
+ )
54
+
55
+
56
+ def build_subscriber(payload: SubscriberPayload) -> Subscriber:
57
+ """Build a subscriber model from a payload.
58
+
59
+ Args:
60
+ payload (SubscriberPayload): Subscriber payload.
61
+
62
+ Returns:
63
+ Subscriber: Subscriber instance.
64
+ """
65
+
66
+ return Subscriber(
67
+ subscriber_id=payload.subscriber_id,
68
+ destination=payload.destination or "",
69
+ topic_id=payload.topic_id,
70
+ reject_tests=payload.reject_tests,
71
+ metadata=payload.metadata or {},
72
+ )