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,841 @@
1
+ """Generic SQLAlchemy models for telemetry sensors without dedicated schema."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Iterable, Optional
6
+
7
+ from msgpack import packb, unpackb
8
+ from sqlalchemy import Float, ForeignKey, Integer, JSON, String
9
+ from sqlalchemy.orm import Mapped, mapped_column, relationship
10
+
11
+ from .. import Base
12
+ from .sensor import Sensor
13
+ from .sensor_enum import (
14
+ SID_CUSTOM,
15
+ SID_FUEL,
16
+ SID_LXMF_PROPAGATION,
17
+ SID_NVM,
18
+ SID_POWER_CONSUMPTION,
19
+ SID_POWER_PRODUCTION,
20
+ SID_PROCESSOR,
21
+ SID_RAM,
22
+ SID_RNS_TRANSPORT,
23
+ SID_TANK,
24
+ )
25
+
26
+ DEFAULT_LABEL = "__default__"
27
+
28
+
29
+ class RawSensor(Sensor):
30
+ """Base class for sensors that simply persist packed payloads."""
31
+
32
+ __abstract__ = True
33
+
34
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
35
+ super().__init__(*args, **kwargs)
36
+ sid = getattr(type(self), "SID", None)
37
+ if sid is not None:
38
+ self.sid = sid
39
+
40
+ def pack(self) -> Any: # type: ignore[override]
41
+ if self.data is None:
42
+ return None
43
+
44
+ cached = getattr(self, "_cached", None)
45
+ if cached is None:
46
+ cached = unpackb(self.data, strict_map_key=False)
47
+ setattr(self, "_cached", cached)
48
+ return cached
49
+
50
+ def unpack(self, packed: Any) -> Any: # type: ignore[override]
51
+ setattr(self, "_cached", packed)
52
+ self.data = None if packed is None else packb(packed, use_bin_type=True)
53
+ return packed
54
+
55
+
56
+ def _build_sensor_class(name: str, sid: int):
57
+ return type(
58
+ name,
59
+ (RawSensor,),
60
+ {
61
+ "SID": sid,
62
+ "__module__": __name__,
63
+ "__mapper_args__": {"polymorphic_identity": sid, "with_polymorphic": "*"},
64
+ },
65
+ )
66
+
67
+
68
+ class _CollectionEntry(Base):
69
+ """Common columns shared across collection sensor entries."""
70
+
71
+ __abstract__ = True
72
+
73
+ id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
74
+ sensor_id: Mapped[int] = mapped_column(ForeignKey("Sensor.id", ondelete="CASCADE"))
75
+ type_label: Mapped[str] = mapped_column(
76
+ String, nullable=False, default=DEFAULT_LABEL
77
+ )
78
+
79
+ @staticmethod
80
+ def pack_label(label: str) -> Any:
81
+ return 0x00 if label == DEFAULT_LABEL else label
82
+
83
+ @staticmethod
84
+ def normalize_label(raw: Any) -> Optional[str]:
85
+ if raw is None:
86
+ return DEFAULT_LABEL
87
+ if isinstance(raw, str):
88
+ return raw
89
+ if isinstance(raw, (bytes, bytearray)):
90
+ try:
91
+ return raw.decode()
92
+ except UnicodeDecodeError:
93
+ return None
94
+ if isinstance(raw, int):
95
+ return DEFAULT_LABEL if raw == 0 else None
96
+ return None
97
+
98
+ @classmethod
99
+ def from_packed(cls, label: str, values: Any) -> _CollectionEntry:
100
+ raise NotImplementedError
101
+
102
+ def pack_values(self) -> list[Any]:
103
+ raise NotImplementedError
104
+
105
+
106
+ class _CollectionSensor(Sensor):
107
+ """Mixin implementing helpers shared by collection-based sensors."""
108
+
109
+ __abstract__ = True
110
+
111
+ entry_model: type[_CollectionEntry]
112
+
113
+ def _get_or_create_entry(self, label: str) -> _CollectionEntry:
114
+ for entry in self.entries: # type: ignore[attr-defined]
115
+ if entry.type_label == label:
116
+ return entry
117
+ entry = self.entry_model(type_label=label)
118
+ entry.sensor = self # type: ignore[attr-defined]
119
+ self.entries.append(entry) # type: ignore[attr-defined]
120
+ return entry
121
+
122
+ def _remove_entry(self, label: str) -> bool:
123
+ for entry in list(self.entries): # type: ignore[attr-defined]
124
+ if entry.type_label == label:
125
+ self.entries.remove(entry) # type: ignore[attr-defined]
126
+ return True
127
+ return False
128
+
129
+ def _pack_entries(self) -> Optional[list[list[Any]]]:
130
+ packed: list[list[Any]] = []
131
+ for entry in self.entries: # type: ignore[attr-defined]
132
+ packed.append([entry.pack_label(entry.type_label), entry.pack_values()])
133
+ return packed or None
134
+
135
+ def _unpack_entries(self, packed: Any) -> Optional[dict[Any, list[Any]]]:
136
+ self.entries[:] = [] # type: ignore[attr-defined]
137
+ if packed is None:
138
+ return None
139
+
140
+ unpacked: dict[Any, list[Any]] = {}
141
+ for record in packed:
142
+ if not isinstance(record, (list, tuple)) or len(record) < 2:
143
+ continue
144
+ raw_label, values = record[0], record[1]
145
+ label = self.entry_model.normalize_label(raw_label)
146
+ if label is None:
147
+ continue
148
+ entry = self.entry_model.from_packed(label, values)
149
+ entry.sensor = self # type: ignore[attr-defined]
150
+ self.entries.append(entry) # type: ignore[attr-defined]
151
+ unpacked[self.entry_model.pack_label(label)] = entry.pack_values()
152
+ return unpacked
153
+
154
+
155
+ class PowerConsumption(_CollectionSensor):
156
+ __tablename__ = "PowerConsumption"
157
+ SID = SID_POWER_CONSUMPTION
158
+
159
+ id: Mapped[int] = mapped_column(ForeignKey("Sensor.id"), primary_key=True)
160
+ entries: Mapped[list["PowerConsumptionEntry"]] = relationship(
161
+ "PowerConsumptionEntry",
162
+ back_populates="sensor",
163
+ cascade="all, delete-orphan",
164
+ order_by="PowerConsumptionEntry.id",
165
+ )
166
+
167
+ def __init__(self) -> None:
168
+ super().__init__(stale_time=5)
169
+ self.sid = SID_POWER_CONSUMPTION
170
+
171
+ def update_consumer(
172
+ self,
173
+ power: Optional[float],
174
+ type_label: Any = None,
175
+ custom_icon: Optional[str] = None,
176
+ ) -> bool:
177
+ label = PowerConsumptionEntry.normalize_label(type_label)
178
+ if label is None:
179
+ return False
180
+ entry = self._get_or_create_entry(label)
181
+ entry.power = power
182
+ entry.custom_icon = custom_icon
183
+ return True
184
+
185
+ def remove_consumer(self, type_label: Any = None) -> bool:
186
+ label = PowerConsumptionEntry.normalize_label(type_label)
187
+ if label is None:
188
+ return False
189
+ return self._remove_entry(label)
190
+
191
+ def pack(self): # type: ignore[override]
192
+ return self._pack_entries()
193
+
194
+ def unpack(self, packed: Any): # type: ignore[override]
195
+ return self._unpack_entries(packed)
196
+
197
+ __mapper_args__ = {
198
+ "polymorphic_identity": SID_POWER_CONSUMPTION,
199
+ "with_polymorphic": "*",
200
+ }
201
+
202
+
203
+ class PowerConsumptionEntry(_CollectionEntry):
204
+ __tablename__ = "PowerConsumptionEntry"
205
+
206
+ power: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
207
+ custom_icon: Mapped[Optional[str]] = mapped_column(String, nullable=True)
208
+ sensor: Mapped[PowerConsumption] = relationship(
209
+ "PowerConsumption", back_populates="entries"
210
+ )
211
+
212
+ def pack_values(self) -> list[Any]:
213
+ return [self.power, self.custom_icon]
214
+
215
+ @classmethod
216
+ def from_packed(cls, label: str, values: Any) -> "PowerConsumptionEntry":
217
+ power = None
218
+ custom_icon = None
219
+ if isinstance(values, (list, tuple)):
220
+ if values:
221
+ power = values[0]
222
+ if len(values) > 1:
223
+ custom_icon = values[1]
224
+ return cls(type_label=label, power=power, custom_icon=custom_icon)
225
+
226
+
227
+ PowerConsumption.entry_model = PowerConsumptionEntry # type: ignore[attr-defined]
228
+
229
+
230
+ class PowerProduction(_CollectionSensor):
231
+ __tablename__ = "PowerProduction"
232
+ SID = SID_POWER_PRODUCTION
233
+
234
+ id: Mapped[int] = mapped_column(ForeignKey("Sensor.id"), primary_key=True)
235
+ entries: Mapped[list["PowerProductionEntry"]] = relationship(
236
+ "PowerProductionEntry",
237
+ back_populates="sensor",
238
+ cascade="all, delete-orphan",
239
+ order_by="PowerProductionEntry.id",
240
+ )
241
+
242
+ def __init__(self) -> None:
243
+ super().__init__(stale_time=5)
244
+ self.sid = SID_POWER_PRODUCTION
245
+
246
+ def update_producer(
247
+ self,
248
+ power: Optional[float],
249
+ type_label: Any = None,
250
+ custom_icon: Optional[str] = None,
251
+ ) -> bool:
252
+ label = PowerProductionEntry.normalize_label(type_label)
253
+ if label is None:
254
+ return False
255
+ entry = self._get_or_create_entry(label)
256
+ entry.power = power
257
+ entry.custom_icon = custom_icon
258
+ return True
259
+
260
+ def remove_producer(self, type_label: Any = None) -> bool:
261
+ label = PowerProductionEntry.normalize_label(type_label)
262
+ if label is None:
263
+ return False
264
+ return self._remove_entry(label)
265
+
266
+ def pack(self): # type: ignore[override]
267
+ return self._pack_entries()
268
+
269
+ def unpack(self, packed: Any): # type: ignore[override]
270
+ return self._unpack_entries(packed)
271
+
272
+ __mapper_args__ = {
273
+ "polymorphic_identity": SID_POWER_PRODUCTION,
274
+ "with_polymorphic": "*",
275
+ }
276
+
277
+
278
+ class PowerProductionEntry(_CollectionEntry):
279
+ __tablename__ = "PowerProductionEntry"
280
+
281
+ power: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
282
+ custom_icon: Mapped[Optional[str]] = mapped_column(String, nullable=True)
283
+ sensor: Mapped[PowerProduction] = relationship(
284
+ "PowerProduction", back_populates="entries"
285
+ )
286
+
287
+ def pack_values(self) -> list[Any]:
288
+ return [self.power, self.custom_icon]
289
+
290
+ @classmethod
291
+ def from_packed(cls, label: str, values: Any) -> "PowerProductionEntry":
292
+ power = None
293
+ custom_icon = None
294
+ if isinstance(values, (list, tuple)):
295
+ if values:
296
+ power = values[0]
297
+ if len(values) > 1:
298
+ custom_icon = values[1]
299
+ return cls(type_label=label, power=power, custom_icon=custom_icon)
300
+
301
+
302
+ PowerProduction.entry_model = PowerProductionEntry # type: ignore[attr-defined]
303
+
304
+
305
+ class Processor(_CollectionSensor):
306
+ __tablename__ = "Processor"
307
+ SID = SID_PROCESSOR
308
+
309
+ id: Mapped[int] = mapped_column(ForeignKey("Sensor.id"), primary_key=True)
310
+ entries: Mapped[list["ProcessorEntry"]] = relationship(
311
+ "ProcessorEntry",
312
+ back_populates="sensor",
313
+ cascade="all, delete-orphan",
314
+ order_by="ProcessorEntry.id",
315
+ )
316
+
317
+ def __init__(self) -> None:
318
+ super().__init__(stale_time=5)
319
+ self.sid = SID_PROCESSOR
320
+
321
+ def update_entry(
322
+ self,
323
+ current_load: Optional[float] = None,
324
+ load_avgs: Optional[Iterable[Optional[float]]] = None,
325
+ clock: Optional[float] = None,
326
+ type_label: Any = None,
327
+ ) -> bool:
328
+ label = ProcessorEntry.normalize_label(type_label)
329
+ if label is None:
330
+ return False
331
+ entry = self._get_or_create_entry(label)
332
+ entry.current_load = current_load
333
+ if load_avgs is None:
334
+ entry.load_avg_1m = None
335
+ entry.load_avg_5m = None
336
+ entry.load_avg_15m = None
337
+ else:
338
+ try:
339
+ avg_list = list(load_avgs)
340
+ except TypeError:
341
+ avg_list = []
342
+ entry.load_avg_1m = avg_list[0] if len(avg_list) > 0 else None
343
+ entry.load_avg_5m = avg_list[1] if len(avg_list) > 1 else None
344
+ entry.load_avg_15m = avg_list[2] if len(avg_list) > 2 else None
345
+ entry.clock = clock
346
+ return True
347
+
348
+ def remove_entry(self, type_label: Any = None) -> bool:
349
+ label = ProcessorEntry.normalize_label(type_label)
350
+ if label is None:
351
+ return False
352
+ return self._remove_entry(label)
353
+
354
+ def pack(self): # type: ignore[override]
355
+ return self._pack_entries()
356
+
357
+ def unpack(self, packed: Any): # type: ignore[override]
358
+ return self._unpack_entries(packed)
359
+
360
+ __mapper_args__ = {
361
+ "polymorphic_identity": SID_PROCESSOR,
362
+ "with_polymorphic": "*",
363
+ }
364
+
365
+
366
+ class ProcessorEntry(_CollectionEntry):
367
+ __tablename__ = "ProcessorEntry"
368
+
369
+ current_load: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
370
+ load_avg_1m: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
371
+ load_avg_5m: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
372
+ load_avg_15m: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
373
+ clock: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
374
+ sensor: Mapped[Processor] = relationship("Processor", back_populates="entries")
375
+
376
+ def pack_values(self) -> list[Any]:
377
+ load_avgs = None
378
+ if any(
379
+ value is not None
380
+ for value in (self.load_avg_1m, self.load_avg_5m, self.load_avg_15m)
381
+ ):
382
+ load_avgs = [self.load_avg_1m, self.load_avg_5m, self.load_avg_15m]
383
+ return [self.current_load, load_avgs, self.clock]
384
+
385
+ @classmethod
386
+ def from_packed(cls, label: str, values: Any) -> "ProcessorEntry":
387
+ current_load = None
388
+ load_avg_1m = None
389
+ load_avg_5m = None
390
+ load_avg_15m = None
391
+ clock = None
392
+ if isinstance(values, (list, tuple)):
393
+ if values:
394
+ current_load = values[0]
395
+ if len(values) > 1 and isinstance(values[1], (list, tuple)):
396
+ avgs = list(values[1])
397
+ load_avg_1m = avgs[0] if len(avgs) > 0 else None
398
+ load_avg_5m = avgs[1] if len(avgs) > 1 else None
399
+ load_avg_15m = avgs[2] if len(avgs) > 2 else None
400
+ if len(values) > 2:
401
+ clock = values[2]
402
+ return cls(
403
+ type_label=label,
404
+ current_load=current_load,
405
+ load_avg_1m=load_avg_1m,
406
+ load_avg_5m=load_avg_5m,
407
+ load_avg_15m=load_avg_15m,
408
+ clock=clock,
409
+ )
410
+
411
+
412
+ Processor.entry_model = ProcessorEntry # type: ignore[attr-defined]
413
+
414
+
415
+ class RandomAccessMemory(_CollectionSensor):
416
+ __tablename__ = "RandomAccessMemory"
417
+ SID = SID_RAM
418
+
419
+ id: Mapped[int] = mapped_column(ForeignKey("Sensor.id"), primary_key=True)
420
+ entries: Mapped[list["RandomAccessMemoryEntry"]] = relationship(
421
+ "RandomAccessMemoryEntry",
422
+ back_populates="sensor",
423
+ cascade="all, delete-orphan",
424
+ order_by="RandomAccessMemoryEntry.id",
425
+ )
426
+
427
+ def __init__(self) -> None:
428
+ super().__init__(stale_time=5)
429
+ self.sid = SID_RAM
430
+
431
+ def update_entry(
432
+ self,
433
+ capacity: Optional[float] = None,
434
+ used: Optional[float] = None,
435
+ type_label: Any = None,
436
+ ) -> bool:
437
+ label = RandomAccessMemoryEntry.normalize_label(type_label)
438
+ if label is None:
439
+ return False
440
+ entry = self._get_or_create_entry(label)
441
+ entry.capacity = capacity
442
+ entry.used = used
443
+ return True
444
+
445
+ def remove_entry(self, type_label: Any = None) -> bool:
446
+ label = RandomAccessMemoryEntry.normalize_label(type_label)
447
+ if label is None:
448
+ return False
449
+ return self._remove_entry(label)
450
+
451
+ def pack(self): # type: ignore[override]
452
+ return self._pack_entries()
453
+
454
+ def unpack(self, packed: Any): # type: ignore[override]
455
+ return self._unpack_entries(packed)
456
+
457
+ __mapper_args__ = {
458
+ "polymorphic_identity": SID_RAM,
459
+ "with_polymorphic": "*",
460
+ }
461
+
462
+
463
+ class RandomAccessMemoryEntry(_CollectionEntry):
464
+ __tablename__ = "RandomAccessMemoryEntry"
465
+
466
+ capacity: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
467
+ used: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
468
+ sensor: Mapped[RandomAccessMemory] = relationship(
469
+ "RandomAccessMemory", back_populates="entries"
470
+ )
471
+
472
+ def pack_values(self) -> list[Any]:
473
+ return [self.capacity, self.used]
474
+
475
+ @classmethod
476
+ def from_packed(cls, label: str, values: Any) -> "RandomAccessMemoryEntry":
477
+ capacity = None
478
+ used = None
479
+ if isinstance(values, (list, tuple)):
480
+ if values:
481
+ capacity = values[0]
482
+ if len(values) > 1:
483
+ used = values[1]
484
+ return cls(type_label=label, capacity=capacity, used=used)
485
+
486
+
487
+ RandomAccessMemory.entry_model = RandomAccessMemoryEntry # type: ignore[attr-defined]
488
+
489
+
490
+ class NonVolatileMemory(_CollectionSensor):
491
+ __tablename__ = "NonVolatileMemory"
492
+ SID = SID_NVM
493
+
494
+ id: Mapped[int] = mapped_column(ForeignKey("Sensor.id"), primary_key=True)
495
+ entries: Mapped[list["NonVolatileMemoryEntry"]] = relationship(
496
+ "NonVolatileMemoryEntry",
497
+ back_populates="sensor",
498
+ cascade="all, delete-orphan",
499
+ order_by="NonVolatileMemoryEntry.id",
500
+ )
501
+
502
+ def __init__(self) -> None:
503
+ super().__init__(stale_time=5)
504
+ self.sid = SID_NVM
505
+
506
+ def update_entry(
507
+ self,
508
+ capacity: Optional[float] = None,
509
+ used: Optional[float] = None,
510
+ type_label: Any = None,
511
+ ) -> bool:
512
+ label = NonVolatileMemoryEntry.normalize_label(type_label)
513
+ if label is None:
514
+ return False
515
+ entry = self._get_or_create_entry(label)
516
+ entry.capacity = capacity
517
+ entry.used = used
518
+ return True
519
+
520
+ def remove_entry(self, type_label: Any = None) -> bool:
521
+ label = NonVolatileMemoryEntry.normalize_label(type_label)
522
+ if label is None:
523
+ return False
524
+ return self._remove_entry(label)
525
+
526
+ def pack(self): # type: ignore[override]
527
+ return self._pack_entries()
528
+
529
+ def unpack(self, packed: Any): # type: ignore[override]
530
+ return self._unpack_entries(packed)
531
+
532
+ __mapper_args__ = {
533
+ "polymorphic_identity": SID_NVM,
534
+ "with_polymorphic": "*",
535
+ }
536
+
537
+
538
+ class NonVolatileMemoryEntry(_CollectionEntry):
539
+ __tablename__ = "NonVolatileMemoryEntry"
540
+
541
+ capacity: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
542
+ used: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
543
+ sensor: Mapped[NonVolatileMemory] = relationship(
544
+ "NonVolatileMemory", back_populates="entries"
545
+ )
546
+
547
+ def pack_values(self) -> list[Any]:
548
+ return [self.capacity, self.used]
549
+
550
+ @classmethod
551
+ def from_packed(cls, label: str, values: Any) -> "NonVolatileMemoryEntry":
552
+ capacity = None
553
+ used = None
554
+ if isinstance(values, (list, tuple)):
555
+ if values:
556
+ capacity = values[0]
557
+ if len(values) > 1:
558
+ used = values[1]
559
+ return cls(type_label=label, capacity=capacity, used=used)
560
+
561
+
562
+ NonVolatileMemory.entry_model = NonVolatileMemoryEntry # type: ignore[attr-defined]
563
+
564
+
565
+ class Custom(_CollectionSensor):
566
+ __tablename__ = "Custom"
567
+ SID = SID_CUSTOM
568
+
569
+ id: Mapped[int] = mapped_column(ForeignKey("Sensor.id"), primary_key=True)
570
+ entries: Mapped[list["CustomEntry"]] = relationship(
571
+ "CustomEntry",
572
+ back_populates="sensor",
573
+ cascade="all, delete-orphan",
574
+ order_by="CustomEntry.id",
575
+ )
576
+
577
+ def __init__(self) -> None:
578
+ super().__init__(stale_time=5)
579
+ self.sid = SID_CUSTOM
580
+
581
+ def update_entry(
582
+ self,
583
+ value: Any = None,
584
+ type_label: Any = None,
585
+ custom_icon: Optional[str] = None,
586
+ ) -> bool:
587
+ label = CustomEntry.normalize_label(type_label)
588
+ if label is None:
589
+ return False
590
+ entry = self._get_or_create_entry(label)
591
+ entry.value = value
592
+ entry.custom_icon = custom_icon
593
+ return True
594
+
595
+ def remove_entry(self, type_label: Any = None) -> bool:
596
+ label = CustomEntry.normalize_label(type_label)
597
+ if label is None:
598
+ return False
599
+ return self._remove_entry(label)
600
+
601
+ def pack(self): # type: ignore[override]
602
+ return self._pack_entries()
603
+
604
+ def unpack(self, packed: Any): # type: ignore[override]
605
+ return self._unpack_entries(packed)
606
+
607
+ __mapper_args__ = {
608
+ "polymorphic_identity": SID_CUSTOM,
609
+ "with_polymorphic": "*",
610
+ }
611
+
612
+
613
+ class CustomEntry(_CollectionEntry):
614
+ __tablename__ = "CustomEntry"
615
+
616
+ value: Mapped[Any] = mapped_column(JSON, nullable=True)
617
+ custom_icon: Mapped[Optional[str]] = mapped_column(String, nullable=True)
618
+ sensor: Mapped[Custom] = relationship("Custom", back_populates="entries")
619
+
620
+ def pack_values(self) -> list[Any]:
621
+ return [self.value, self.custom_icon]
622
+
623
+ @classmethod
624
+ def from_packed(cls, label: str, values: Any) -> "CustomEntry":
625
+ value = None
626
+ custom_icon = None
627
+ if isinstance(values, (list, tuple)):
628
+ if values:
629
+ value = values[0]
630
+ if len(values) > 1:
631
+ custom_icon = values[1]
632
+ return cls(type_label=label, value=value, custom_icon=custom_icon)
633
+
634
+
635
+ Custom.entry_model = CustomEntry # type: ignore[attr-defined]
636
+
637
+
638
+ class Tank(_CollectionSensor):
639
+ __tablename__ = "Tank"
640
+ SID = SID_TANK
641
+
642
+ id: Mapped[int] = mapped_column(ForeignKey("Sensor.id"), primary_key=True)
643
+ entries: Mapped[list["TankEntry"]] = relationship(
644
+ "TankEntry",
645
+ back_populates="sensor",
646
+ cascade="all, delete-orphan",
647
+ order_by="TankEntry.id",
648
+ )
649
+
650
+ def __init__(self) -> None:
651
+ super().__init__(stale_time=5)
652
+ self.sid = SID_TANK
653
+
654
+ def update_entry(
655
+ self,
656
+ capacity: Optional[float] = None,
657
+ level: Optional[float] = None,
658
+ unit: Optional[str] = None,
659
+ type_label: Any = None,
660
+ custom_icon: Optional[str] = None,
661
+ ) -> bool:
662
+ label = TankEntry.normalize_label(type_label)
663
+ if label is None:
664
+ return False
665
+ if unit is not None and not isinstance(unit, str):
666
+ return False
667
+ entry = self._get_or_create_entry(label)
668
+ entry.capacity = capacity
669
+ entry.level = level
670
+ entry.unit = unit
671
+ entry.custom_icon = custom_icon
672
+ return True
673
+
674
+ def remove_entry(self, type_label: Any = None) -> bool:
675
+ label = TankEntry.normalize_label(type_label)
676
+ if label is None:
677
+ return False
678
+ return self._remove_entry(label)
679
+
680
+ def pack(self): # type: ignore[override]
681
+ return self._pack_entries()
682
+
683
+ def unpack(self, packed: Any): # type: ignore[override]
684
+ return self._unpack_entries(packed)
685
+
686
+ __mapper_args__ = {
687
+ "polymorphic_identity": SID_TANK,
688
+ "with_polymorphic": "*",
689
+ }
690
+
691
+
692
+ class TankEntry(_CollectionEntry):
693
+ __tablename__ = "TankEntry"
694
+
695
+ capacity: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
696
+ level: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
697
+ unit: Mapped[Optional[str]] = mapped_column(String, nullable=True)
698
+ custom_icon: Mapped[Optional[str]] = mapped_column(String, nullable=True)
699
+ sensor: Mapped[Tank] = relationship("Tank", back_populates="entries")
700
+
701
+ def pack_values(self) -> list[Any]:
702
+ return [self.capacity, self.level, self.unit, self.custom_icon]
703
+
704
+ @classmethod
705
+ def from_packed(cls, label: str, values: Any) -> "TankEntry":
706
+ capacity = None
707
+ level = None
708
+ unit = None
709
+ custom_icon = None
710
+ if isinstance(values, (list, tuple)):
711
+ if values:
712
+ capacity = values[0]
713
+ if len(values) > 1:
714
+ level = values[1]
715
+ if len(values) > 2:
716
+ unit = values[2]
717
+ if len(values) > 3:
718
+ custom_icon = values[3]
719
+ return cls(
720
+ type_label=label,
721
+ capacity=capacity,
722
+ level=level,
723
+ unit=unit,
724
+ custom_icon=custom_icon,
725
+ )
726
+
727
+
728
+ Tank.entry_model = TankEntry # type: ignore[attr-defined]
729
+
730
+
731
+ class Fuel(_CollectionSensor):
732
+ __tablename__ = "Fuel"
733
+ SID = SID_FUEL
734
+
735
+ id: Mapped[int] = mapped_column(ForeignKey("Sensor.id"), primary_key=True)
736
+ entries: Mapped[list["FuelEntry"]] = relationship(
737
+ "FuelEntry",
738
+ back_populates="sensor",
739
+ cascade="all, delete-orphan",
740
+ order_by="FuelEntry.id",
741
+ )
742
+
743
+ def __init__(self) -> None:
744
+ super().__init__(stale_time=5)
745
+ self.sid = SID_FUEL
746
+
747
+ def update_entry(
748
+ self,
749
+ capacity: Optional[float] = None,
750
+ level: Optional[float] = None,
751
+ unit: Optional[str] = None,
752
+ type_label: Any = None,
753
+ custom_icon: Optional[str] = None,
754
+ ) -> bool:
755
+ label = FuelEntry.normalize_label(type_label)
756
+ if label is None:
757
+ return False
758
+ if unit is not None and not isinstance(unit, str):
759
+ return False
760
+ entry = self._get_or_create_entry(label)
761
+ entry.capacity = capacity
762
+ entry.level = level
763
+ entry.unit = unit
764
+ entry.custom_icon = custom_icon
765
+ return True
766
+
767
+ def remove_entry(self, type_label: Any = None) -> bool:
768
+ label = FuelEntry.normalize_label(type_label)
769
+ if label is None:
770
+ return False
771
+ return self._remove_entry(label)
772
+
773
+ def pack(self): # type: ignore[override]
774
+ return self._pack_entries()
775
+
776
+ def unpack(self, packed: Any): # type: ignore[override]
777
+ return self._unpack_entries(packed)
778
+
779
+ __mapper_args__ = {
780
+ "polymorphic_identity": SID_FUEL,
781
+ "with_polymorphic": "*",
782
+ }
783
+
784
+
785
+ class FuelEntry(_CollectionEntry):
786
+ __tablename__ = "FuelEntry"
787
+
788
+ capacity: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
789
+ level: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
790
+ unit: Mapped[Optional[str]] = mapped_column(String, nullable=True)
791
+ custom_icon: Mapped[Optional[str]] = mapped_column(String, nullable=True)
792
+ sensor: Mapped[Fuel] = relationship("Fuel", back_populates="entries")
793
+
794
+ def pack_values(self) -> list[Any]:
795
+ return [self.capacity, self.level, self.unit, self.custom_icon]
796
+
797
+ @classmethod
798
+ def from_packed(cls, label: str, values: Any) -> "FuelEntry":
799
+ capacity = None
800
+ level = None
801
+ unit = None
802
+ custom_icon = None
803
+ if isinstance(values, (list, tuple)):
804
+ if values:
805
+ capacity = values[0]
806
+ if len(values) > 1:
807
+ level = values[1]
808
+ if len(values) > 2:
809
+ unit = values[2]
810
+ if len(values) > 3:
811
+ custom_icon = values[3]
812
+ return cls(
813
+ type_label=label,
814
+ capacity=capacity,
815
+ level=level,
816
+ unit=unit,
817
+ custom_icon=custom_icon,
818
+ )
819
+
820
+
821
+ Fuel.entry_model = FuelEntry # type: ignore[attr-defined]
822
+
823
+
824
+ __all__ = [
825
+ "Custom",
826
+ "CustomEntry",
827
+ "Fuel",
828
+ "FuelEntry",
829
+ "NonVolatileMemory",
830
+ "NonVolatileMemoryEntry",
831
+ "PowerConsumption",
832
+ "PowerConsumptionEntry",
833
+ "PowerProduction",
834
+ "PowerProductionEntry",
835
+ "Processor",
836
+ "ProcessorEntry",
837
+ "RandomAccessMemory",
838
+ "RandomAccessMemoryEntry",
839
+ "Tank",
840
+ "TankEntry",
841
+ ]