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,409 @@
|
|
|
1
|
+
"""Runtime helpers for configuring locally collected telemetry snapshots."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from configparser import ConfigParser
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from datetime import datetime, timezone
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any, Callable, Mapping, MutableMapping, Protocol
|
|
10
|
+
|
|
11
|
+
from reticulum_telemetry_hub.config.manager import HubConfigurationManager
|
|
12
|
+
from reticulum_telemetry_hub.lxmf_telemetry.model.persistance.sensors.sensor import (
|
|
13
|
+
Sensor,
|
|
14
|
+
)
|
|
15
|
+
from reticulum_telemetry_hub.lxmf_telemetry.model.persistance.sensors.sensor_mapping import (
|
|
16
|
+
sid_mapping,
|
|
17
|
+
)
|
|
18
|
+
from reticulum_telemetry_hub.lxmf_telemetry.model.persistance.sensors.sensor_enum import (
|
|
19
|
+
SID_ACCELERATION,
|
|
20
|
+
SID_AMBIENT_LIGHT,
|
|
21
|
+
SID_ANGULAR_VELOCITY,
|
|
22
|
+
SID_BATTERY,
|
|
23
|
+
SID_CONNECTION_MAP,
|
|
24
|
+
SID_CUSTOM,
|
|
25
|
+
SID_FUEL,
|
|
26
|
+
SID_GRAVITY,
|
|
27
|
+
SID_HUMIDITY,
|
|
28
|
+
SID_INFORMATION,
|
|
29
|
+
SID_LOCATION,
|
|
30
|
+
SID_LXMF_PROPAGATION,
|
|
31
|
+
SID_MAGNETIC_FIELD,
|
|
32
|
+
SID_NVM,
|
|
33
|
+
SID_PHYSICAL_LINK,
|
|
34
|
+
SID_POWER_CONSUMPTION,
|
|
35
|
+
SID_POWER_PRODUCTION,
|
|
36
|
+
SID_PRESSURE,
|
|
37
|
+
SID_PROCESSOR,
|
|
38
|
+
SID_PROXIMITY,
|
|
39
|
+
SID_RAM,
|
|
40
|
+
SID_RECEIVED,
|
|
41
|
+
SID_RNS_TRANSPORT,
|
|
42
|
+
SID_TANK,
|
|
43
|
+
SID_TEMPERATURE,
|
|
44
|
+
SID_TIME,
|
|
45
|
+
)
|
|
46
|
+
from reticulum_telemetry_hub.lxmf_telemetry.model.persistance.telemeter import Telemeter
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _utcnow() -> datetime:
|
|
50
|
+
return datetime.now(timezone.utc).replace(tzinfo=None)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
SnapshotMutator = Callable[[Telemeter, dict[int, Any]], None]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class TelemetryPlugin(Protocol):
|
|
57
|
+
"""Protocol describing telemetry plugins that can customize snapshots."""
|
|
58
|
+
|
|
59
|
+
def setup(self, manager: "TelemeterManager") -> None: ...
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass
|
|
63
|
+
class StaticInformationConfig:
|
|
64
|
+
"""Configuration describing synthesized Information sensor contents."""
|
|
65
|
+
|
|
66
|
+
enabled: bool = True
|
|
67
|
+
contents: str = ""
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@dataclass
|
|
71
|
+
class StaticLocationConfig:
|
|
72
|
+
"""Configuration describing synthesized Location sensor coordinates."""
|
|
73
|
+
|
|
74
|
+
enabled: bool = True
|
|
75
|
+
latitude: float | None = None
|
|
76
|
+
longitude: float | None = None
|
|
77
|
+
altitude: float | None = None
|
|
78
|
+
speed: float | None = None
|
|
79
|
+
bearing: float | None = None
|
|
80
|
+
accuracy: float | None = None
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
SENSOR_NAME_TO_SID = {
|
|
84
|
+
"time": SID_TIME,
|
|
85
|
+
"location": SID_LOCATION,
|
|
86
|
+
"pressure": SID_PRESSURE,
|
|
87
|
+
"battery": SID_BATTERY,
|
|
88
|
+
"physical_link": SID_PHYSICAL_LINK,
|
|
89
|
+
"acceleration": SID_ACCELERATION,
|
|
90
|
+
"temperature": SID_TEMPERATURE,
|
|
91
|
+
"humidity": SID_HUMIDITY,
|
|
92
|
+
"magnetic_field": SID_MAGNETIC_FIELD,
|
|
93
|
+
"ambient_light": SID_AMBIENT_LIGHT,
|
|
94
|
+
"gravity": SID_GRAVITY,
|
|
95
|
+
"angular_velocity": SID_ANGULAR_VELOCITY,
|
|
96
|
+
"proximity": SID_PROXIMITY,
|
|
97
|
+
"information": SID_INFORMATION,
|
|
98
|
+
"received": SID_RECEIVED,
|
|
99
|
+
"power_consumption": SID_POWER_CONSUMPTION,
|
|
100
|
+
"power_production": SID_POWER_PRODUCTION,
|
|
101
|
+
"processor": SID_PROCESSOR,
|
|
102
|
+
"ram": SID_RAM,
|
|
103
|
+
"nvm": SID_NVM,
|
|
104
|
+
"tank": SID_TANK,
|
|
105
|
+
"fuel": SID_FUEL,
|
|
106
|
+
"lxmf_propagation": SID_LXMF_PROPAGATION,
|
|
107
|
+
"rns_transport": SID_RNS_TRANSPORT,
|
|
108
|
+
"connection_map": SID_CONNECTION_MAP,
|
|
109
|
+
"custom": SID_CUSTOM,
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
DEFAULT_SENSOR_ORDER = (
|
|
113
|
+
"time",
|
|
114
|
+
"location",
|
|
115
|
+
"information",
|
|
116
|
+
"battery",
|
|
117
|
+
"pressure",
|
|
118
|
+
"temperature",
|
|
119
|
+
"humidity",
|
|
120
|
+
"magnetic_field",
|
|
121
|
+
"ambient_light",
|
|
122
|
+
"gravity",
|
|
123
|
+
"angular_velocity",
|
|
124
|
+
"acceleration",
|
|
125
|
+
"proximity",
|
|
126
|
+
"physical_link",
|
|
127
|
+
"received",
|
|
128
|
+
"power_consumption",
|
|
129
|
+
"power_production",
|
|
130
|
+
"processor",
|
|
131
|
+
"ram",
|
|
132
|
+
"nvm",
|
|
133
|
+
"tank",
|
|
134
|
+
"fuel",
|
|
135
|
+
"lxmf_propagation",
|
|
136
|
+
"rns_transport",
|
|
137
|
+
"connection_map",
|
|
138
|
+
"custom",
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
@dataclass
|
|
143
|
+
class TelemetryRuntimeConfig:
|
|
144
|
+
"""Runtime configuration controlling synthesized sensors and toggles."""
|
|
145
|
+
|
|
146
|
+
enabled_sensors: MutableMapping[int, bool] = field(default_factory=dict)
|
|
147
|
+
static_information: StaticInformationConfig | None = None
|
|
148
|
+
static_location: StaticLocationConfig | None = None
|
|
149
|
+
|
|
150
|
+
@classmethod
|
|
151
|
+
def from_manager(
|
|
152
|
+
cls,
|
|
153
|
+
manager: HubConfigurationManager | None,
|
|
154
|
+
*,
|
|
155
|
+
filename: str = "telemetry.ini",
|
|
156
|
+
) -> "TelemetryRuntimeConfig":
|
|
157
|
+
if manager is None:
|
|
158
|
+
return cls()
|
|
159
|
+
|
|
160
|
+
telemetry_filename = manager.runtime_config.telemetry_filename or filename
|
|
161
|
+
|
|
162
|
+
if manager.config_parser.has_section("telemetry"):
|
|
163
|
+
return cls.from_section(manager.config_parser["telemetry"])
|
|
164
|
+
|
|
165
|
+
path = Path(manager.storage_path) / telemetry_filename
|
|
166
|
+
return cls.from_file(path)
|
|
167
|
+
|
|
168
|
+
@classmethod
|
|
169
|
+
def from_file(cls, path: Path | None) -> "TelemetryRuntimeConfig":
|
|
170
|
+
if path is None:
|
|
171
|
+
return cls()
|
|
172
|
+
|
|
173
|
+
parser = ConfigParser()
|
|
174
|
+
if path.exists():
|
|
175
|
+
parser.read(path)
|
|
176
|
+
|
|
177
|
+
if parser.has_section("telemetry"):
|
|
178
|
+
section: Mapping[str, str] = parser["telemetry"]
|
|
179
|
+
else:
|
|
180
|
+
section = {}
|
|
181
|
+
|
|
182
|
+
return cls.from_section(section)
|
|
183
|
+
|
|
184
|
+
@classmethod
|
|
185
|
+
def from_section(cls, section: Mapping[str, str]) -> "TelemetryRuntimeConfig":
|
|
186
|
+
if section is None:
|
|
187
|
+
section = {}
|
|
188
|
+
|
|
189
|
+
enabled: MutableMapping[int, bool] = {}
|
|
190
|
+
for name, sid in SENSOR_NAME_TO_SID.items():
|
|
191
|
+
flag_key = f"enable_{name}"
|
|
192
|
+
if flag_key in section:
|
|
193
|
+
enabled[sid] = _get_bool(section, flag_key, True)
|
|
194
|
+
|
|
195
|
+
info_cfg = None
|
|
196
|
+
info_text = section.get("static_information", "").strip()
|
|
197
|
+
if info_text:
|
|
198
|
+
info_enabled = _get_bool(section, "enable_information", True)
|
|
199
|
+
info_cfg = StaticInformationConfig(enabled=info_enabled, contents=info_text)
|
|
200
|
+
|
|
201
|
+
location_cfg = None
|
|
202
|
+
if _get_bool(section, "synthesize_location", False):
|
|
203
|
+
latitude = _get_float(section, "location_latitude")
|
|
204
|
+
longitude = _get_float(section, "location_longitude")
|
|
205
|
+
if latitude is not None and longitude is not None:
|
|
206
|
+
location_cfg = StaticLocationConfig(
|
|
207
|
+
enabled=True,
|
|
208
|
+
latitude=latitude,
|
|
209
|
+
longitude=longitude,
|
|
210
|
+
altitude=_get_float(section, "location_altitude"),
|
|
211
|
+
speed=_get_float(section, "location_speed"),
|
|
212
|
+
bearing=_get_float(section, "location_bearing"),
|
|
213
|
+
accuracy=_get_float(section, "location_accuracy"),
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
return cls(
|
|
217
|
+
enabled_sensors=enabled,
|
|
218
|
+
static_information=info_cfg,
|
|
219
|
+
static_location=location_cfg,
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
def is_enabled(self, sid: int) -> bool:
|
|
223
|
+
return self.enabled_sensors.get(sid, True)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def _get_bool(section: Mapping[str, str], key: str, default: bool) -> bool:
|
|
227
|
+
value = section.get(key)
|
|
228
|
+
if value is None:
|
|
229
|
+
return default
|
|
230
|
+
return str(value).strip().lower() in {"1", "true", "yes", "on"}
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def _get_float(section: Mapping[str, str], key: str) -> float | None:
|
|
234
|
+
value = section.get(key)
|
|
235
|
+
if value is None:
|
|
236
|
+
return None
|
|
237
|
+
try:
|
|
238
|
+
return float(value)
|
|
239
|
+
except (TypeError, ValueError):
|
|
240
|
+
return None
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
class TelemeterManager:
|
|
244
|
+
"""Own a long-lived ``Telemeter`` configured via hub settings."""
|
|
245
|
+
|
|
246
|
+
def __init__(
|
|
247
|
+
self,
|
|
248
|
+
*,
|
|
249
|
+
config_manager: HubConfigurationManager | None = None,
|
|
250
|
+
config: TelemetryRuntimeConfig | None = None,
|
|
251
|
+
telemeter: Telemeter | None = None,
|
|
252
|
+
) -> None:
|
|
253
|
+
self._config_manager = config_manager
|
|
254
|
+
self._config = config or TelemetryRuntimeConfig.from_manager(config_manager)
|
|
255
|
+
self._telemeter = telemeter or Telemeter(peer_dest="")
|
|
256
|
+
self._sensors: MutableMapping[int, Sensor] = {}
|
|
257
|
+
self._sensor_order: list[int] = []
|
|
258
|
+
self._enabled: MutableMapping[int, bool] = {}
|
|
259
|
+
self._mutators: list[SnapshotMutator] = []
|
|
260
|
+
self._initialize_default_sensors()
|
|
261
|
+
|
|
262
|
+
@property
|
|
263
|
+
def telemeter(self) -> Telemeter:
|
|
264
|
+
return self._telemeter
|
|
265
|
+
|
|
266
|
+
def get_sensor(self, sid_or_name: int | str) -> Sensor | None:
|
|
267
|
+
sid = self._normalize_sid(sid_or_name)
|
|
268
|
+
return self._sensors.get(sid)
|
|
269
|
+
|
|
270
|
+
def enable_sensor(self, sid_or_name: int | str) -> None:
|
|
271
|
+
sid = self._normalize_sid(sid_or_name)
|
|
272
|
+
if sid not in self._sensors:
|
|
273
|
+
self._add_sensor_instance(sid)
|
|
274
|
+
self._enabled[sid] = True
|
|
275
|
+
|
|
276
|
+
def disable_sensor(self, sid_or_name: int | str) -> None:
|
|
277
|
+
sid = self._normalize_sid(sid_or_name)
|
|
278
|
+
self._enabled[sid] = False
|
|
279
|
+
|
|
280
|
+
def add_sensor(self, sensor: Sensor, *, enabled: bool = True) -> Sensor:
|
|
281
|
+
sid = sensor.sid
|
|
282
|
+
self._sensors[sid] = sensor
|
|
283
|
+
if sid not in self._sensor_order:
|
|
284
|
+
self._sensor_order.append(sid)
|
|
285
|
+
if sensor not in self._telemeter.sensors:
|
|
286
|
+
self._telemeter.sensors.append(sensor)
|
|
287
|
+
self._enabled[sid] = enabled
|
|
288
|
+
return sensor
|
|
289
|
+
|
|
290
|
+
def add_snapshot_mutator(self, mutator: SnapshotMutator) -> None:
|
|
291
|
+
self._mutators.append(mutator)
|
|
292
|
+
|
|
293
|
+
def register_plugin(
|
|
294
|
+
self, plugin: TelemetryPlugin | Callable[["TelemeterManager"], None]
|
|
295
|
+
) -> None:
|
|
296
|
+
if hasattr(plugin, "setup"):
|
|
297
|
+
plugin.setup(self) # type: ignore[attr-defined]
|
|
298
|
+
elif callable(plugin):
|
|
299
|
+
plugin(self)
|
|
300
|
+
else: # pragma: no cover - defensive guard
|
|
301
|
+
raise TypeError(
|
|
302
|
+
"Telemetry plugins must be callables or expose a setup() method"
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
def snapshot(self) -> dict[int, Any]:
|
|
306
|
+
"""Pack enabled sensors and return a telemetry snapshot."""
|
|
307
|
+
|
|
308
|
+
payload: dict[int, Any] = {}
|
|
309
|
+
now = _utcnow()
|
|
310
|
+
self._synthesize_information()
|
|
311
|
+
self._synthesize_location(now)
|
|
312
|
+
|
|
313
|
+
for sid in self._sensor_order:
|
|
314
|
+
if not self._enabled.get(sid, True):
|
|
315
|
+
continue
|
|
316
|
+
sensor = self._sensors.get(sid)
|
|
317
|
+
if sensor is None:
|
|
318
|
+
continue
|
|
319
|
+
if sid == SID_TIME and hasattr(sensor, "utc"):
|
|
320
|
+
sensor.utc = now
|
|
321
|
+
packed = sensor.pack()
|
|
322
|
+
if packed is None:
|
|
323
|
+
continue
|
|
324
|
+
payload[sid] = packed
|
|
325
|
+
|
|
326
|
+
for mutator in self._mutators:
|
|
327
|
+
mutator(self._telemeter, payload)
|
|
328
|
+
|
|
329
|
+
return payload
|
|
330
|
+
|
|
331
|
+
# ------------------------------------------------------------------ #
|
|
332
|
+
# private helpers
|
|
333
|
+
# ------------------------------------------------------------------ #
|
|
334
|
+
def _initialize_default_sensors(self) -> None:
|
|
335
|
+
for name in DEFAULT_SENSOR_ORDER:
|
|
336
|
+
sid = SENSOR_NAME_TO_SID.get(name)
|
|
337
|
+
if sid is None:
|
|
338
|
+
continue
|
|
339
|
+
self._add_sensor_instance(sid)
|
|
340
|
+
|
|
341
|
+
def _add_sensor_instance(self, sid: int) -> None:
|
|
342
|
+
sensor_cls = sid_mapping.get(sid)
|
|
343
|
+
if sensor_cls is None:
|
|
344
|
+
return
|
|
345
|
+
sensor = sensor_cls()
|
|
346
|
+
self._sensors[sid] = sensor
|
|
347
|
+
self._sensor_order.append(sid)
|
|
348
|
+
self._telemeter.sensors.append(sensor)
|
|
349
|
+
self._enabled[sid] = self._config.is_enabled(sid)
|
|
350
|
+
|
|
351
|
+
def _normalize_sid(self, sid_or_name: int | str) -> int:
|
|
352
|
+
if isinstance(sid_or_name, int):
|
|
353
|
+
return sid_or_name
|
|
354
|
+
if isinstance(sid_or_name, str):
|
|
355
|
+
key = sid_or_name.strip().lower()
|
|
356
|
+
if key in SENSOR_NAME_TO_SID:
|
|
357
|
+
return SENSOR_NAME_TO_SID[key]
|
|
358
|
+
raise KeyError(f"Unknown telemetry sensor '{sid_or_name}'")
|
|
359
|
+
raise TypeError("Sensor identifiers must be int or str")
|
|
360
|
+
|
|
361
|
+
def _synthesize_information(self) -> None:
|
|
362
|
+
cfg = self._config.static_information
|
|
363
|
+
if cfg is None or not cfg.enabled or not cfg.contents:
|
|
364
|
+
return
|
|
365
|
+
self.enable_sensor(SID_INFORMATION)
|
|
366
|
+
sensor = self._sensors.get(SID_INFORMATION)
|
|
367
|
+
if sensor is None:
|
|
368
|
+
return
|
|
369
|
+
setattr(sensor, "contents", cfg.contents)
|
|
370
|
+
setattr(sensor, "synthesized", True)
|
|
371
|
+
|
|
372
|
+
def _synthesize_location(self, timestamp: datetime) -> None:
|
|
373
|
+
cfg = self._config.static_location
|
|
374
|
+
if cfg is None or not cfg.enabled:
|
|
375
|
+
return
|
|
376
|
+
if cfg.latitude is None or cfg.longitude is None:
|
|
377
|
+
return
|
|
378
|
+
self.enable_sensor(SID_LOCATION)
|
|
379
|
+
sensor = self._sensors.get(SID_LOCATION)
|
|
380
|
+
if sensor is None:
|
|
381
|
+
return
|
|
382
|
+
defaults: Mapping[str, float] = {
|
|
383
|
+
"altitude": 0.0,
|
|
384
|
+
"speed": 0.0,
|
|
385
|
+
"bearing": 0.0,
|
|
386
|
+
"accuracy": 0.0,
|
|
387
|
+
}
|
|
388
|
+
setattr(sensor, "latitude", cfg.latitude)
|
|
389
|
+
setattr(sensor, "longitude", cfg.longitude)
|
|
390
|
+
setattr(
|
|
391
|
+
sensor,
|
|
392
|
+
"altitude",
|
|
393
|
+
cfg.altitude if cfg.altitude is not None else defaults["altitude"],
|
|
394
|
+
)
|
|
395
|
+
setattr(
|
|
396
|
+
sensor, "speed", cfg.speed if cfg.speed is not None else defaults["speed"]
|
|
397
|
+
)
|
|
398
|
+
setattr(
|
|
399
|
+
sensor,
|
|
400
|
+
"bearing",
|
|
401
|
+
cfg.bearing if cfg.bearing is not None else defaults["bearing"],
|
|
402
|
+
)
|
|
403
|
+
setattr(
|
|
404
|
+
sensor,
|
|
405
|
+
"accuracy",
|
|
406
|
+
cfg.accuracy if cfg.accuracy is not None else defaults["accuracy"],
|
|
407
|
+
)
|
|
408
|
+
setattr(sensor, "last_update", timestamp)
|
|
409
|
+
setattr(sensor, "synthesized", True)
|