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,68 @@
1
+ """SQLAlchemy model for the Magnetic Field sensor."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Optional
6
+
7
+ from sqlalchemy import Float, ForeignKey
8
+ from sqlalchemy.orm import Mapped, mapped_column
9
+
10
+ from .sensor import Sensor
11
+ from .sensor_enum import SID_MAGNETIC_FIELD
12
+
13
+
14
+ class MagneticField(Sensor):
15
+ __tablename__ = "MagneticField"
16
+
17
+ id: Mapped[int] = mapped_column(ForeignKey("Sensor.id"), primary_key=True)
18
+ x: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
19
+ y: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
20
+ z: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
21
+
22
+ def __init__(
23
+ self,
24
+ stale_time: float | None = 1,
25
+ data: Any | None = None,
26
+ active: bool = False,
27
+ synthesized: bool = False,
28
+ last_update: float = 0,
29
+ last_read: float = 0,
30
+ ) -> None:
31
+ super().__init__(
32
+ stale_time=stale_time,
33
+ data=data,
34
+ active=active,
35
+ synthesized=synthesized,
36
+ last_update=last_update,
37
+ last_read=last_read,
38
+ )
39
+ self.sid = SID_MAGNETIC_FIELD
40
+
41
+ def pack(self): # type: ignore[override]
42
+ if self.x is None and self.y is None and self.z is None:
43
+ return None
44
+ return [self.x, self.y, self.z]
45
+
46
+ def unpack(self, packed: Any): # type: ignore[override]
47
+ if packed is None:
48
+ self.x = None
49
+ self.y = None
50
+ self.z = None
51
+ return None
52
+
53
+ try:
54
+ self.x = packed[0]
55
+ self.y = packed[1]
56
+ self.z = packed[2]
57
+ except (IndexError, TypeError):
58
+ self.x = None
59
+ self.y = None
60
+ self.z = None
61
+ return None
62
+
63
+ return {"x": self.x, "y": self.y, "z": self.z}
64
+
65
+ __mapper_args__ = {
66
+ "polymorphic_identity": SID_MAGNETIC_FIELD,
67
+ "with_polymorphic": "*",
68
+ }
@@ -0,0 +1,53 @@
1
+ """SQLAlchemy model for the Physical Link sensor."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Optional
6
+
7
+ from sqlalchemy import Float, ForeignKey
8
+ from sqlalchemy.orm import Mapped, mapped_column
9
+
10
+ from .sensor import Sensor
11
+ from .sensor_enum import SID_PHYSICAL_LINK
12
+
13
+
14
+ class PhysicalLink(Sensor):
15
+ __tablename__ = "PhysicalLink"
16
+
17
+ id: Mapped[int] = mapped_column(ForeignKey("Sensor.id"), primary_key=True)
18
+ rssi: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
19
+ snr: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
20
+ q: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
21
+
22
+ def __init__(self) -> None:
23
+ super().__init__(stale_time=5)
24
+ self.sid = SID_PHYSICAL_LINK
25
+
26
+ def pack(self): # type: ignore[override]
27
+ if self.rssi is None and self.snr is None and self.q is None:
28
+ return None
29
+ return [self.rssi, self.snr, self.q]
30
+
31
+ def unpack(self, packed: Any): # type: ignore[override]
32
+ if packed is None:
33
+ self.rssi = None
34
+ self.snr = None
35
+ self.q = None
36
+ return None
37
+
38
+ try:
39
+ self.rssi = packed[0]
40
+ self.snr = packed[1]
41
+ self.q = packed[2]
42
+ except (IndexError, TypeError):
43
+ self.rssi = None
44
+ self.snr = None
45
+ self.q = None
46
+ return None
47
+
48
+ return {"rssi": self.rssi, "snr": self.snr, "q": self.q}
49
+
50
+ __mapper_args__ = {
51
+ "polymorphic_identity": SID_PHYSICAL_LINK,
52
+ "with_polymorphic": "*",
53
+ }
@@ -0,0 +1,37 @@
1
+ """SQLAlchemy model for the Pressure sensor."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Optional
6
+
7
+ from sqlalchemy import Float, ForeignKey
8
+ from sqlalchemy.orm import Mapped, mapped_column
9
+
10
+ from .sensor import Sensor
11
+ from .sensor_enum import SID_PRESSURE
12
+
13
+
14
+ class Pressure(Sensor):
15
+ __tablename__ = "Pressure"
16
+
17
+ id: Mapped[int] = mapped_column(ForeignKey("Sensor.id"), primary_key=True)
18
+ mbar: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
19
+
20
+ def __init__(self) -> None:
21
+ super().__init__(stale_time=5)
22
+ self.sid = SID_PRESSURE
23
+
24
+ def pack(self): # type: ignore[override]
25
+ return self.mbar
26
+
27
+ def unpack(self, packed: Any): # type: ignore[override]
28
+ if packed is None:
29
+ self.mbar = None
30
+ return None
31
+ self.mbar = packed
32
+ return {"mbar": self.mbar}
33
+
34
+ __mapper_args__ = {
35
+ "polymorphic_identity": SID_PRESSURE,
36
+ "with_polymorphic": "*",
37
+ }
@@ -0,0 +1,37 @@
1
+ """SQLAlchemy model for the Proximity sensor."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Optional
6
+
7
+ from sqlalchemy import Boolean, ForeignKey
8
+ from sqlalchemy.orm import Mapped, mapped_column
9
+
10
+ from .sensor import Sensor
11
+ from .sensor_enum import SID_PROXIMITY
12
+
13
+
14
+ class Proximity(Sensor):
15
+ __tablename__ = "Proximity"
16
+
17
+ id: Mapped[int] = mapped_column(ForeignKey("Sensor.id"), primary_key=True)
18
+ triggered: Mapped[Optional[bool]] = mapped_column(Boolean, nullable=True)
19
+
20
+ def __init__(self) -> None:
21
+ super().__init__(stale_time=1)
22
+ self.sid = SID_PROXIMITY
23
+
24
+ def pack(self): # type: ignore[override]
25
+ return self.triggered
26
+
27
+ def unpack(self, packed: Any): # type: ignore[override]
28
+ if packed is None:
29
+ self.triggered = None
30
+ return None
31
+ self.triggered = packed
32
+ return {"triggered": self.triggered}
33
+
34
+ __mapper_args__ = {
35
+ "polymorphic_identity": SID_PROXIMITY,
36
+ "with_polymorphic": "*",
37
+ }
@@ -0,0 +1,75 @@
1
+ """SQLAlchemy model for the Received sensor."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Optional
6
+
7
+ from sqlalchemy import Float, ForeignKey, LargeBinary
8
+ from sqlalchemy.orm import Mapped, mapped_column
9
+
10
+ from .sensor import Sensor
11
+ from .sensor_enum import SID_RECEIVED
12
+
13
+
14
+ class Received(Sensor):
15
+ __tablename__ = "Received"
16
+
17
+ id: Mapped[int] = mapped_column(ForeignKey("Sensor.id"), primary_key=True)
18
+ by: Mapped[Optional[bytes]] = mapped_column(LargeBinary, nullable=True)
19
+ via: Mapped[Optional[bytes]] = mapped_column(LargeBinary, nullable=True)
20
+ geodesic_distance: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
21
+ euclidian_distance: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
22
+
23
+ def __init__(self) -> None:
24
+ super().__init__(stale_time=5)
25
+ self.sid = SID_RECEIVED
26
+
27
+ def pack(self): # type: ignore[override]
28
+ if (
29
+ self.by is None
30
+ and self.via is None
31
+ and self.geodesic_distance is None
32
+ and self.euclidian_distance is None
33
+ ):
34
+ return None
35
+
36
+ return [
37
+ self.by,
38
+ self.via,
39
+ self.geodesic_distance,
40
+ self.euclidian_distance,
41
+ ]
42
+
43
+ def unpack(self, packed: Any): # type: ignore[override]
44
+ if packed is None:
45
+ self.by = None
46
+ self.via = None
47
+ self.geodesic_distance = None
48
+ self.euclidian_distance = None
49
+ return None
50
+
51
+ try:
52
+ self.by = packed[0]
53
+ self.via = packed[1]
54
+ self.geodesic_distance = packed[2]
55
+ self.euclidian_distance = packed[3]
56
+ except (IndexError, TypeError):
57
+ self.by = None
58
+ self.via = None
59
+ self.geodesic_distance = None
60
+ self.euclidian_distance = None
61
+ return None
62
+
63
+ return {
64
+ "by": self.by,
65
+ "via": self.via,
66
+ "distance": {
67
+ "geodesic": self.geodesic_distance,
68
+ "euclidian": self.euclidian_distance,
69
+ },
70
+ }
71
+
72
+ __mapper_args__ = {
73
+ "polymorphic_identity": SID_RECEIVED,
74
+ "with_polymorphic": "*",
75
+ }
@@ -0,0 +1,209 @@
1
+ """SQLAlchemy model for Reticulum transport telemetry."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from msgpack import packb, unpackb
8
+ from sqlalchemy import Boolean, Float, ForeignKey, Integer, LargeBinary
9
+ from sqlalchemy.orm import Mapped, mapped_column
10
+
11
+ from .sensor import Sensor
12
+ from .sensor_enum import SID_RNS_TRANSPORT
13
+
14
+
15
+ def _encode_payload(payload: Any) -> bytes | None:
16
+ """Serialize ``payload`` with msgpack if it is not ``None``."""
17
+
18
+ if payload is None:
19
+ return None
20
+ return packb(payload, use_bin_type=True)
21
+
22
+
23
+ def _decode_payload(blob: bytes | None) -> Any:
24
+ """Deserialize msgpack ``blob`` into Python objects."""
25
+
26
+ if blob is None:
27
+ return None
28
+ return unpackb(blob, strict_map_key=False)
29
+
30
+
31
+ class RNSTransport(Sensor):
32
+ """Telemetry sensor describing the local Reticulum transport state."""
33
+
34
+ __tablename__ = "RNSTransport"
35
+
36
+ SID = SID_RNS_TRANSPORT
37
+
38
+ id: Mapped[int] = mapped_column(
39
+ ForeignKey("Sensor.id", ondelete="CASCADE"), primary_key=True
40
+ )
41
+ transport_enabled: Mapped[bool] = mapped_column(Boolean, default=False)
42
+ transport_identity: Mapped[bytes | None] = mapped_column(LargeBinary, nullable=True)
43
+ transport_uptime: Mapped[int | None] = mapped_column(Integer, nullable=True)
44
+ traffic_rxb: Mapped[int | None] = mapped_column(Integer, nullable=True)
45
+ traffic_txb: Mapped[int | None] = mapped_column(Integer, nullable=True)
46
+ speed_rx: Mapped[float | None] = mapped_column(Float, nullable=True)
47
+ speed_tx: Mapped[float | None] = mapped_column(Float, nullable=True)
48
+ speed_rx_inst: Mapped[float | None] = mapped_column(Float, nullable=True)
49
+ speed_tx_inst: Mapped[float | None] = mapped_column(Float, nullable=True)
50
+ memory_used: Mapped[float | None] = mapped_column(Float, nullable=True)
51
+ interface_count: Mapped[int | None] = mapped_column(Integer, nullable=True)
52
+ link_count: Mapped[int | None] = mapped_column(Integer, nullable=True)
53
+
54
+ interfaces_blob: Mapped[bytes | None] = mapped_column(LargeBinary, nullable=True)
55
+ path_table_blob: Mapped[bytes | None] = mapped_column(LargeBinary, nullable=True)
56
+ ifstats_blob: Mapped[bytes | None] = mapped_column(LargeBinary, nullable=True)
57
+ extra_blob: Mapped[bytes | None] = mapped_column(LargeBinary, nullable=True)
58
+
59
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
60
+ if "stale_time" not in kwargs:
61
+ kwargs["stale_time"] = 60
62
+ super().__init__(*args, **kwargs)
63
+ self.sid = self.SID
64
+
65
+ @property
66
+ def interfaces(self) -> Any:
67
+ """Return the decoded interface list or mapping."""
68
+
69
+ return _decode_payload(self.interfaces_blob)
70
+
71
+ @interfaces.setter
72
+ def interfaces(self, value: Any) -> None:
73
+ self.interfaces_blob = _encode_payload(value)
74
+
75
+ @property
76
+ def path_table(self) -> Any:
77
+ return _decode_payload(self.path_table_blob)
78
+
79
+ @path_table.setter
80
+ def path_table(self, value: Any) -> None:
81
+ self.path_table_blob = _encode_payload(value)
82
+
83
+ @property
84
+ def ifstats(self) -> Any:
85
+ return _decode_payload(self.ifstats_blob)
86
+
87
+ @ifstats.setter
88
+ def ifstats(self, value: Any) -> None:
89
+ self.ifstats_blob = _encode_payload(value)
90
+
91
+ @property
92
+ def extras(self) -> Any:
93
+ return _decode_payload(self.extra_blob)
94
+
95
+ @extras.setter
96
+ def extras(self, value: Any) -> None:
97
+ self.extra_blob = _encode_payload(value)
98
+
99
+ def _populate_counts(self, interfaces: Any) -> None:
100
+ if self.interface_count is not None or interfaces is None:
101
+ return
102
+ if hasattr(interfaces, "__len__"):
103
+ try:
104
+ self.interface_count = len(interfaces) # type: ignore[arg-type]
105
+ return
106
+ except TypeError:
107
+ pass
108
+ try:
109
+ self.interface_count = sum(1 for _ in interfaces) # type: ignore[arg-type]
110
+ except TypeError:
111
+ pass
112
+
113
+ def pack(self) -> dict[str, Any] | None: # type: ignore[override]
114
+ extras = self.extras or {}
115
+ payload: dict[str, Any] = dict(extras)
116
+
117
+ interfaces = self.interfaces
118
+ path_table = self.path_table
119
+ ifstats = self.ifstats
120
+
121
+ if ifstats is not None and interfaces is not None:
122
+ if isinstance(ifstats, dict) and "interfaces" not in ifstats:
123
+ merged = dict(ifstats)
124
+ merged["interfaces"] = interfaces
125
+ ifstats = merged
126
+ elif interfaces is not None:
127
+ ifstats = {"interfaces": interfaces}
128
+
129
+ interface_count = self.interface_count
130
+ if interface_count is None and isinstance(interfaces, list):
131
+ interface_count = len(interfaces)
132
+
133
+ payload.update(
134
+ {
135
+ "transport_enabled": bool(self.transport_enabled),
136
+ "transport_identity": (
137
+ bytes(self.transport_identity) if self.transport_identity else None
138
+ ),
139
+ "transport_uptime": self.transport_uptime,
140
+ "traffic_rxb": self.traffic_rxb,
141
+ "traffic_txb": self.traffic_txb,
142
+ "speed_rx": self.speed_rx,
143
+ "speed_tx": self.speed_tx,
144
+ "speed_rx_inst": self.speed_rx_inst,
145
+ "speed_tx_inst": self.speed_tx_inst,
146
+ "memory_used": self.memory_used,
147
+ "interface_count": interface_count,
148
+ "link_count": self.link_count,
149
+ }
150
+ )
151
+
152
+ if interfaces is not None:
153
+ payload["interfaces"] = interfaces
154
+ if path_table is not None:
155
+ payload["path_table"] = path_table
156
+ if ifstats is not None:
157
+ payload["ifstats"] = ifstats
158
+
159
+ return payload
160
+
161
+ def unpack(self, packed: Any) -> Any: # type: ignore[override]
162
+ if packed is None or not isinstance(packed, dict):
163
+ return None
164
+
165
+ data = dict(packed)
166
+
167
+ interfaces = data.pop("interfaces", None)
168
+ ifstats = data.pop("ifstats", None)
169
+ path_table = data.pop("path_table", None)
170
+
171
+ if interfaces is None and isinstance(ifstats, dict):
172
+ maybe_interfaces = ifstats.get("interfaces")
173
+ if maybe_interfaces is not None:
174
+ interfaces = maybe_interfaces
175
+
176
+ self.interfaces = interfaces
177
+ self.ifstats = ifstats
178
+ self.path_table = path_table
179
+
180
+ self.transport_enabled = bool(
181
+ data.pop("transport_enabled", self.transport_enabled)
182
+ )
183
+ self.transport_identity = data.pop(
184
+ "transport_identity", self.transport_identity
185
+ )
186
+ self.transport_uptime = data.pop("transport_uptime", self.transport_uptime)
187
+ self.traffic_rxb = data.pop("traffic_rxb", self.traffic_rxb)
188
+ self.traffic_txb = data.pop("traffic_txb", self.traffic_txb)
189
+ self.speed_rx = data.pop("speed_rx", self.speed_rx)
190
+ self.speed_tx = data.pop("speed_tx", self.speed_tx)
191
+ self.speed_rx_inst = data.pop("speed_rx_inst", self.speed_rx_inst)
192
+ self.speed_tx_inst = data.pop("speed_tx_inst", self.speed_tx_inst)
193
+ self.memory_used = data.pop("memory_used", self.memory_used)
194
+ self.interface_count = data.pop("interface_count", self.interface_count)
195
+ self.link_count = data.pop("link_count", self.link_count)
196
+
197
+ self._populate_counts(interfaces)
198
+
199
+ self.extras = data if data else None
200
+
201
+ return packed
202
+
203
+ __mapper_args__ = {
204
+ "polymorphic_identity": SID_RNS_TRANSPORT,
205
+ "with_polymorphic": "*",
206
+ }
207
+
208
+
209
+ __all__ = ["RNSTransport"]
@@ -0,0 +1,65 @@
1
+ from sqlalchemy import Column, ForeignKey, Integer, Float, Boolean, BLOB
2
+ from msgpack import packb, unpackb
3
+ from sqlalchemy.orm import DeclarativeMeta, relationship, Mapped, mapped_column
4
+
5
+ from .. import Base
6
+
7
+
8
+ class SensorDeclarativeMeta(DeclarativeMeta):
9
+ """Custom declarative metaclass that fills in mapper defaults."""
10
+
11
+ def __init__(cls, classname, bases, dict_, **kwargs):
12
+ if classname != "Sensor":
13
+ mapper_args = dict_.get("__mapper_args__")
14
+ if mapper_args is None or "polymorphic_identity" not in mapper_args:
15
+ mapper_args = dict(mapper_args or {})
16
+ mapper_args.setdefault("with_polymorphic", "*")
17
+ mapper_args.setdefault("polymorphic_identity", classname)
18
+ cls.__mapper_args__ = mapper_args
19
+ super().__init__(classname, bases, dict_, **kwargs)
20
+
21
+
22
+ class Sensor(Base, metaclass=SensorDeclarativeMeta):
23
+ __tablename__ = "Sensor"
24
+
25
+ id = Column(Integer, primary_key=True, autoincrement=True)
26
+ sid = Column(Integer, nullable=False, default=0x00)
27
+ stale_time = Column(Float, nullable=True)
28
+ data = Column(BLOB, nullable=True)
29
+ synthesized = Column(Boolean, default=False)
30
+ telemeter_id: Mapped[int] = mapped_column(ForeignKey("Telemeter.id"))
31
+ telemeter = relationship("Telemeter", back_populates="sensors")
32
+
33
+ def __init__(
34
+ self,
35
+ stale_time=None,
36
+ data=None,
37
+ active=False,
38
+ synthesized=False,
39
+ last_update=0,
40
+ last_read=0,
41
+ ):
42
+ self.stale_time = stale_time
43
+ self.data = data
44
+ self.active = active
45
+ self.synthesized = synthesized
46
+ self.last_update = last_update
47
+ self.last_read = last_read
48
+
49
+ def packb(self):
50
+ return packb(self.pack())
51
+
52
+ def unpackb(self, packed):
53
+ return unpackb(self.unpack(packed))
54
+
55
+ def pack(self):
56
+ return self.data
57
+
58
+ def unpack(self, packed):
59
+ return packed
60
+
61
+ __mapper_args__ = {
62
+ "polymorphic_identity": "Sensor",
63
+ "with_polymorphic": "*",
64
+ "polymorphic_on": sid,
65
+ }
@@ -0,0 +1,27 @@
1
+ SID_NONE = 0x00
2
+ SID_TIME = 0x01
3
+ SID_LOCATION = 0x02
4
+ SID_PRESSURE = 0x03
5
+ SID_BATTERY = 0x04
6
+ SID_PHYSICAL_LINK = 0x05
7
+ SID_ACCELERATION = 0x06
8
+ SID_TEMPERATURE = 0x07
9
+ SID_HUMIDITY = 0x08
10
+ SID_MAGNETIC_FIELD = 0x09
11
+ SID_AMBIENT_LIGHT = 0x0A
12
+ SID_GRAVITY = 0x0B
13
+ SID_ANGULAR_VELOCITY = 0x0C
14
+ SID_PROXIMITY = 0x0E
15
+ SID_INFORMATION = 0x0F
16
+ SID_RECEIVED = 0x10
17
+ SID_POWER_CONSUMPTION = 0x11
18
+ SID_POWER_PRODUCTION = 0x12
19
+ SID_PROCESSOR = 0x13
20
+ SID_RAM = 0x14
21
+ SID_NVM = 0x15
22
+ SID_TANK = 0x16
23
+ SID_FUEL = 0x17
24
+ SID_LXMF_PROPAGATION = 0x18
25
+ SID_RNS_TRANSPORT = 0x19
26
+ SID_CONNECTION_MAP = 0x1A
27
+ SID_CUSTOM = 0xFF
@@ -0,0 +1,58 @@
1
+ from .sensor_enum import *
2
+ from .acceleration import Acceleration
3
+ from .ambient_light import AmbientLight
4
+ from .angular_velocity import AngularVelocity
5
+ from .battery import Battery
6
+ from .gravity import Gravity
7
+ from .humidity import Humidity
8
+ from .information import Information
9
+ from .location import Location
10
+ from .magnetic_field import MagneticField
11
+ from .physical_link import PhysicalLink
12
+ from .pressure import Pressure
13
+ from .proximity import Proximity
14
+ from .received import Received
15
+ from .temperature import Temperature
16
+ from .time import Time
17
+ from .connection_map import ConnectionMap
18
+ from .generic import (
19
+ Custom,
20
+ Fuel,
21
+ NonVolatileMemory,
22
+ PowerConsumption,
23
+ PowerProduction,
24
+ Processor,
25
+ RandomAccessMemory,
26
+ Tank,
27
+ )
28
+ from .lxmf_propagation import LXMFPropagation
29
+ from .rns_transport import RNSTransport
30
+
31
+ sid_mapping = {
32
+ SID_TIME: Time,
33
+ SID_LOCATION: Location,
34
+ SID_PRESSURE: Pressure,
35
+ SID_BATTERY: Battery,
36
+ SID_PHYSICAL_LINK: PhysicalLink,
37
+ SID_ACCELERATION: Acceleration,
38
+ SID_TEMPERATURE: Temperature,
39
+ SID_HUMIDITY: Humidity,
40
+ SID_MAGNETIC_FIELD: MagneticField,
41
+ SID_AMBIENT_LIGHT: AmbientLight,
42
+ SID_GRAVITY: Gravity,
43
+ SID_ANGULAR_VELOCITY: AngularVelocity,
44
+ SID_PROXIMITY: Proximity,
45
+ SID_INFORMATION: Information,
46
+ SID_RECEIVED: Received,
47
+ SID_POWER_CONSUMPTION: PowerConsumption,
48
+ SID_POWER_PRODUCTION: PowerProduction,
49
+ SID_PROCESSOR: Processor,
50
+ SID_RAM: RandomAccessMemory,
51
+ SID_NVM: NonVolatileMemory,
52
+ SID_TANK: Tank,
53
+ SID_FUEL: Fuel,
54
+ SID_LXMF_PROPAGATION: LXMFPropagation,
55
+ SID_RNS_TRANSPORT: RNSTransport,
56
+ SID_CONNECTION_MAP: ConnectionMap,
57
+ SID_CUSTOM: Custom,
58
+ }
@@ -0,0 +1,37 @@
1
+ """SQLAlchemy model for the Temperature sensor."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Optional
6
+
7
+ from sqlalchemy import Float, ForeignKey
8
+ from sqlalchemy.orm import Mapped, mapped_column
9
+
10
+ from .sensor import Sensor
11
+ from .sensor_enum import SID_TEMPERATURE
12
+
13
+
14
+ class Temperature(Sensor):
15
+ __tablename__ = "Temperature"
16
+
17
+ id: Mapped[int] = mapped_column(ForeignKey("Sensor.id"), primary_key=True)
18
+ c: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
19
+
20
+ def __init__(self) -> None:
21
+ super().__init__(stale_time=5)
22
+ self.sid = SID_TEMPERATURE
23
+
24
+ def pack(self): # type: ignore[override]
25
+ return self.c
26
+
27
+ def unpack(self, packed: Any): # type: ignore[override]
28
+ if packed is None:
29
+ self.c = None
30
+ return None
31
+ self.c = packed
32
+ return {"c": self.c}
33
+
34
+ __mapper_args__ = {
35
+ "polymorphic_identity": SID_TEMPERATURE,
36
+ "with_polymorphic": "*",
37
+ }