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
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
from sqlalchemy import Column, Integer, String, ForeignKey, BLOB
|
|
2
|
-
from sqlalchemy.orm import relationship
|
|
3
|
-
from . import Base
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class Appearance(Base):
|
|
7
|
-
|
|
8
|
-
__tablename__ = "Appearance"
|
|
9
|
-
|
|
10
|
-
id = Column(Integer, primary_key=True, autoincrement=True)
|
|
11
|
-
icon = Column(String, nullable=False)
|
|
12
|
-
foreground = Column(String, nullable=False)
|
|
13
|
-
background = Column(String, nullable=False)
|
|
14
|
-
peer_id = Column(String, ForeignKey("Peer.destination_hash"))
|
|
15
|
-
peer = relationship("Peer", back_populates="appearance")
|
|
16
|
-
|
|
17
|
-
def __init__(self, peer, icon
|
|
18
|
-
self.peer = peer
|
|
19
|
-
self.icon = icon
|
|
1
|
+
from sqlalchemy import Column, Integer, String, ForeignKey, BLOB
|
|
2
|
+
from sqlalchemy.orm import relationship
|
|
3
|
+
from . import Base
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Appearance(Base):
|
|
7
|
+
|
|
8
|
+
__tablename__ = "Appearance"
|
|
9
|
+
|
|
10
|
+
id = Column(Integer, primary_key=True, autoincrement=True)
|
|
11
|
+
icon = Column(String, nullable=False)
|
|
12
|
+
foreground = Column(String, nullable=False)
|
|
13
|
+
background = Column(String, nullable=False)
|
|
14
|
+
peer_id = Column(String, ForeignKey("Peer.destination_hash"))
|
|
15
|
+
peer = relationship("Peer", back_populates="appearance")
|
|
16
|
+
|
|
17
|
+
def __init__(self, peer, icon="Default"):
|
|
18
|
+
self.peer = peer
|
|
19
|
+
self.icon = icon
|
|
@@ -1,13 +1,17 @@
|
|
|
1
|
-
from . import Base
|
|
2
|
-
from typing import TYPE_CHECKING, Optional
|
|
3
|
-
from sqlalchemy import Column, Integer, String
|
|
4
|
-
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
1
|
+
from . import Base
|
|
2
|
+
from typing import TYPE_CHECKING, Optional
|
|
3
|
+
from sqlalchemy import Column, Integer, String
|
|
4
|
+
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from .telemeter import Telemeter
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Peer(Base):
|
|
11
|
+
__tablename__ = "Peer"
|
|
12
|
+
|
|
13
|
+
destination_hash: Mapped[str] = mapped_column(
|
|
14
|
+
String, nullable=False, primary_key=True
|
|
15
|
+
)
|
|
16
|
+
telemeters = relationship("Telemeter")
|
|
17
|
+
# appearance = relationship("Appearance", back_populates='peer')
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""SQLAlchemy models for LXMF telemetry sensors."""
|
|
2
|
+
|
|
3
|
+
from .acceleration import Acceleration
|
|
4
|
+
from .ambient_light import AmbientLight
|
|
5
|
+
from .angular_velocity import AngularVelocity
|
|
6
|
+
from .battery import Battery
|
|
7
|
+
from .connection_map import ConnectionMap
|
|
8
|
+
from .generic import (
|
|
9
|
+
Custom,
|
|
10
|
+
Fuel,
|
|
11
|
+
NonVolatileMemory,
|
|
12
|
+
PowerConsumption,
|
|
13
|
+
PowerProduction,
|
|
14
|
+
Processor,
|
|
15
|
+
RandomAccessMemory,
|
|
16
|
+
Tank,
|
|
17
|
+
)
|
|
18
|
+
from .gravity import Gravity
|
|
19
|
+
from .humidity import Humidity
|
|
20
|
+
from .information import Information
|
|
21
|
+
from .location import Location
|
|
22
|
+
from .magnetic_field import MagneticField
|
|
23
|
+
from .lxmf_propagation import LXMFPropagation, LXMFPropagationPeer
|
|
24
|
+
from .physical_link import PhysicalLink
|
|
25
|
+
from .pressure import Pressure
|
|
26
|
+
from .proximity import Proximity
|
|
27
|
+
from .received import Received
|
|
28
|
+
from .rns_transport import RNSTransport
|
|
29
|
+
from .sensor import Sensor
|
|
30
|
+
from .sensor_enum import *
|
|
31
|
+
from .sensor_mapping import sid_mapping
|
|
32
|
+
from .temperature import Temperature
|
|
33
|
+
from .time import Time
|
|
34
|
+
|
|
35
|
+
__all__ = [
|
|
36
|
+
"Acceleration",
|
|
37
|
+
"AmbientLight",
|
|
38
|
+
"AngularVelocity",
|
|
39
|
+
"Battery",
|
|
40
|
+
"ConnectionMap",
|
|
41
|
+
"Custom",
|
|
42
|
+
"Fuel",
|
|
43
|
+
"Gravity",
|
|
44
|
+
"Humidity",
|
|
45
|
+
"Information",
|
|
46
|
+
"Location",
|
|
47
|
+
"LXMFPropagation",
|
|
48
|
+
"LXMFPropagationPeer",
|
|
49
|
+
"MagneticField",
|
|
50
|
+
"NonVolatileMemory",
|
|
51
|
+
"PhysicalLink",
|
|
52
|
+
"PowerConsumption",
|
|
53
|
+
"PowerProduction",
|
|
54
|
+
"Processor",
|
|
55
|
+
"Proximity",
|
|
56
|
+
"Pressure",
|
|
57
|
+
"RandomAccessMemory",
|
|
58
|
+
"Received",
|
|
59
|
+
"RNSTransport",
|
|
60
|
+
"Sensor",
|
|
61
|
+
"Temperature",
|
|
62
|
+
"Time",
|
|
63
|
+
"Tank",
|
|
64
|
+
"sid_mapping",
|
|
65
|
+
]
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""SQLAlchemy model for the Acceleration 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_ACCELERATION
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Acceleration(Sensor):
|
|
15
|
+
__tablename__ = "Acceleration"
|
|
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_ACCELERATION
|
|
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_ACCELERATION,
|
|
67
|
+
"with_polymorphic": "*",
|
|
68
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""SQLAlchemy model for the Ambient Light 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_AMBIENT_LIGHT
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class AmbientLight(Sensor):
|
|
15
|
+
__tablename__ = "AmbientLight"
|
|
16
|
+
|
|
17
|
+
id: Mapped[int] = mapped_column(ForeignKey("Sensor.id"), primary_key=True)
|
|
18
|
+
lux: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
|
|
19
|
+
|
|
20
|
+
def __init__(self) -> None:
|
|
21
|
+
super().__init__(stale_time=1)
|
|
22
|
+
self.sid = SID_AMBIENT_LIGHT
|
|
23
|
+
|
|
24
|
+
def pack(self): # type: ignore[override]
|
|
25
|
+
return self.lux
|
|
26
|
+
|
|
27
|
+
def unpack(self, packed: Any): # type: ignore[override]
|
|
28
|
+
if packed is None:
|
|
29
|
+
self.lux = None
|
|
30
|
+
return None
|
|
31
|
+
self.lux = packed
|
|
32
|
+
return {"lux": self.lux}
|
|
33
|
+
|
|
34
|
+
__mapper_args__ = {
|
|
35
|
+
"polymorphic_identity": SID_AMBIENT_LIGHT,
|
|
36
|
+
"with_polymorphic": "*",
|
|
37
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""SQLAlchemy model for the Angular Velocity 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_ANGULAR_VELOCITY
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class AngularVelocity(Sensor):
|
|
15
|
+
__tablename__ = "AngularVelocity"
|
|
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_ANGULAR_VELOCITY
|
|
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_ANGULAR_VELOCITY,
|
|
67
|
+
"with_polymorphic": "*",
|
|
68
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""SQLAlchemy model for the Battery sensor."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Optional
|
|
6
|
+
|
|
7
|
+
from sqlalchemy import Boolean, Float, ForeignKey
|
|
8
|
+
from sqlalchemy.orm import Mapped, mapped_column
|
|
9
|
+
|
|
10
|
+
from .sensor import Sensor
|
|
11
|
+
from .sensor_enum import SID_BATTERY
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Battery(Sensor):
|
|
15
|
+
__tablename__ = "Battery"
|
|
16
|
+
|
|
17
|
+
id: Mapped[int] = mapped_column(ForeignKey("Sensor.id"), primary_key=True)
|
|
18
|
+
charge_percent: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
|
|
19
|
+
charging: Mapped[Optional[bool]] = mapped_column(Boolean, nullable=True)
|
|
20
|
+
temperature: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
|
|
21
|
+
|
|
22
|
+
def __init__(self) -> None:
|
|
23
|
+
super().__init__(stale_time=10)
|
|
24
|
+
self.sid = SID_BATTERY
|
|
25
|
+
|
|
26
|
+
def pack(self): # type: ignore[override]
|
|
27
|
+
if (
|
|
28
|
+
self.charge_percent is None
|
|
29
|
+
and self.charging is None
|
|
30
|
+
and self.temperature is None
|
|
31
|
+
):
|
|
32
|
+
return None
|
|
33
|
+
|
|
34
|
+
charge = None if self.charge_percent is None else round(self.charge_percent, 1)
|
|
35
|
+
return [charge, self.charging, self.temperature]
|
|
36
|
+
|
|
37
|
+
def unpack(self, packed: Any): # type: ignore[override]
|
|
38
|
+
if packed is None:
|
|
39
|
+
self.charge_percent = None
|
|
40
|
+
self.charging = None
|
|
41
|
+
self.temperature = None
|
|
42
|
+
return None
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
self.charge_percent = (
|
|
46
|
+
None if packed[0] is None else round(float(packed[0]), 1)
|
|
47
|
+
)
|
|
48
|
+
self.charging = packed[1] if len(packed) > 1 else None
|
|
49
|
+
if len(packed) > 2:
|
|
50
|
+
self.temperature = packed[2]
|
|
51
|
+
else:
|
|
52
|
+
self.temperature = None
|
|
53
|
+
except (IndexError, TypeError, ValueError):
|
|
54
|
+
self.charge_percent = None
|
|
55
|
+
self.charging = None
|
|
56
|
+
self.temperature = None
|
|
57
|
+
return None
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
"charge_percent": self.charge_percent,
|
|
61
|
+
"charging": self.charging,
|
|
62
|
+
"temperature": self.temperature,
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
__mapper_args__ = {
|
|
66
|
+
"polymorphic_identity": SID_BATTERY,
|
|
67
|
+
"with_polymorphic": "*",
|
|
68
|
+
}
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
"""Connection map sensor models."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Optional
|
|
6
|
+
|
|
7
|
+
from sqlalchemy import Float, ForeignKey, Integer, JSON, String, UniqueConstraint
|
|
8
|
+
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
9
|
+
|
|
10
|
+
from .. import Base
|
|
11
|
+
from .sensor import Sensor
|
|
12
|
+
from .sensor_enum import SID_CONNECTION_MAP
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
_UNSET = object()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ConnectionMap(Sensor):
|
|
19
|
+
"""Sensor representing a set of maps populated with connection points."""
|
|
20
|
+
|
|
21
|
+
__tablename__ = "ConnectionMap"
|
|
22
|
+
|
|
23
|
+
id: Mapped[int] = mapped_column(ForeignKey("Sensor.id"), primary_key=True)
|
|
24
|
+
maps: Mapped[list["ConnectionMapMap"]] = relationship(
|
|
25
|
+
"ConnectionMapMap",
|
|
26
|
+
back_populates="sensor",
|
|
27
|
+
cascade="all, delete-orphan",
|
|
28
|
+
order_by="ConnectionMapMap.id",
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
SID = SID_CONNECTION_MAP
|
|
32
|
+
|
|
33
|
+
def __init__(self) -> None:
|
|
34
|
+
super().__init__()
|
|
35
|
+
self.sid = SID_CONNECTION_MAP
|
|
36
|
+
|
|
37
|
+
def ensure_map(
|
|
38
|
+
self, map_name: str, label: Optional[str] = None
|
|
39
|
+
) -> "ConnectionMapMap":
|
|
40
|
+
"""Return an existing map entry or create a new one."""
|
|
41
|
+
|
|
42
|
+
for entry in self.maps:
|
|
43
|
+
if entry.map_name == map_name:
|
|
44
|
+
if label is not None:
|
|
45
|
+
entry.label = label
|
|
46
|
+
return entry
|
|
47
|
+
|
|
48
|
+
entry = ConnectionMapMap(map_name=map_name, label=label)
|
|
49
|
+
entry.sensor = self
|
|
50
|
+
return entry
|
|
51
|
+
|
|
52
|
+
def add_point(
|
|
53
|
+
self,
|
|
54
|
+
map_name: str,
|
|
55
|
+
point_hash: str,
|
|
56
|
+
*,
|
|
57
|
+
latitude: float | None | object = _UNSET,
|
|
58
|
+
longitude: float | None | object = _UNSET,
|
|
59
|
+
altitude: float | None | object = _UNSET,
|
|
60
|
+
point_type: str | None | object = _UNSET,
|
|
61
|
+
name: str | None | object = _UNSET,
|
|
62
|
+
signals: dict[str, Any] | None | object = _UNSET,
|
|
63
|
+
**extra_signals: Any,
|
|
64
|
+
) -> "ConnectionMapPoint":
|
|
65
|
+
"""Add or update a connection point within a map."""
|
|
66
|
+
|
|
67
|
+
entry = self.ensure_map(map_name)
|
|
68
|
+
point = entry.get_point(point_hash)
|
|
69
|
+
if point is None:
|
|
70
|
+
point = ConnectionMapPoint(point_hash=point_hash)
|
|
71
|
+
point.map = entry
|
|
72
|
+
|
|
73
|
+
if latitude is not _UNSET:
|
|
74
|
+
point.latitude = latitude # type: ignore[assignment]
|
|
75
|
+
if longitude is not _UNSET:
|
|
76
|
+
point.longitude = longitude # type: ignore[assignment]
|
|
77
|
+
if altitude is not _UNSET:
|
|
78
|
+
point.altitude = altitude # type: ignore[assignment]
|
|
79
|
+
if point_type is not _UNSET:
|
|
80
|
+
point.point_type = point_type # type: ignore[assignment]
|
|
81
|
+
if name is not _UNSET:
|
|
82
|
+
point.name = name # type: ignore[assignment]
|
|
83
|
+
|
|
84
|
+
if signals is not _UNSET or extra_signals:
|
|
85
|
+
if signals is _UNSET:
|
|
86
|
+
merged_signals: dict[str, Any] = dict(point.signals or {})
|
|
87
|
+
elif signals is None:
|
|
88
|
+
merged_signals = {}
|
|
89
|
+
else:
|
|
90
|
+
merged_signals = {k: v for k, v in signals.items() if v is not None}
|
|
91
|
+
for key, value in extra_signals.items():
|
|
92
|
+
if value is not None:
|
|
93
|
+
merged_signals[key] = value
|
|
94
|
+
point.signals = merged_signals or None
|
|
95
|
+
|
|
96
|
+
return point
|
|
97
|
+
|
|
98
|
+
def pack(self) -> Optional[dict[str, Any]]: # type: ignore[override]
|
|
99
|
+
maps_payload: dict[str, dict[str, Any]] = {}
|
|
100
|
+
for entry in self.maps:
|
|
101
|
+
points_payload: dict[str, dict[str, Any]] = {}
|
|
102
|
+
for point in entry.points:
|
|
103
|
+
points_payload[point.point_hash] = point.to_payload()
|
|
104
|
+
maps_payload[entry.map_name] = entry.to_payload(points_payload)
|
|
105
|
+
|
|
106
|
+
if not maps_payload:
|
|
107
|
+
return None
|
|
108
|
+
|
|
109
|
+
return {"maps": maps_payload}
|
|
110
|
+
|
|
111
|
+
def unpack(self, packed: Any) -> Optional[dict[str, Any]]: # type: ignore[override]
|
|
112
|
+
self.maps[:] = []
|
|
113
|
+
|
|
114
|
+
if not isinstance(packed, dict):
|
|
115
|
+
return None
|
|
116
|
+
|
|
117
|
+
maps_payload = packed.get("maps")
|
|
118
|
+
if not isinstance(maps_payload, dict):
|
|
119
|
+
return None
|
|
120
|
+
|
|
121
|
+
normalized: dict[str, dict[str, Any]] = {}
|
|
122
|
+
for map_name, payload in maps_payload.items():
|
|
123
|
+
if not isinstance(map_name, str):
|
|
124
|
+
continue
|
|
125
|
+
|
|
126
|
+
label = None
|
|
127
|
+
points_data: Any = None
|
|
128
|
+
if isinstance(payload, dict):
|
|
129
|
+
label = payload.get("label")
|
|
130
|
+
points_data = payload.get("points")
|
|
131
|
+
|
|
132
|
+
entry = ConnectionMapMap(map_name=map_name, label=label)
|
|
133
|
+
entry.sensor = self
|
|
134
|
+
|
|
135
|
+
if isinstance(points_data, dict):
|
|
136
|
+
for point_hash, point_payload in points_data.items():
|
|
137
|
+
if not isinstance(point_hash, str) or not isinstance(
|
|
138
|
+
point_payload, dict
|
|
139
|
+
):
|
|
140
|
+
continue
|
|
141
|
+
point = ConnectionMapPoint.from_payload(point_hash, point_payload)
|
|
142
|
+
point.map = entry
|
|
143
|
+
normalized[map_name] = entry.to_payload(entry.pack_points())
|
|
144
|
+
|
|
145
|
+
return {"maps": normalized} if normalized else None
|
|
146
|
+
|
|
147
|
+
__mapper_args__ = {
|
|
148
|
+
"polymorphic_identity": SID_CONNECTION_MAP,
|
|
149
|
+
"with_polymorphic": "*",
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class ConnectionMapMap(Base):
|
|
154
|
+
"""ORM model representing a named map."""
|
|
155
|
+
|
|
156
|
+
__tablename__ = "ConnectionMapMap"
|
|
157
|
+
__table_args__ = (UniqueConstraint("sensor_id", "map_name"),)
|
|
158
|
+
|
|
159
|
+
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
|
160
|
+
sensor_id: Mapped[int] = mapped_column(
|
|
161
|
+
ForeignKey("ConnectionMap.id", ondelete="CASCADE")
|
|
162
|
+
)
|
|
163
|
+
map_name: Mapped[str] = mapped_column(String, nullable=False)
|
|
164
|
+
label: Mapped[Optional[str]] = mapped_column(String, nullable=True)
|
|
165
|
+
|
|
166
|
+
sensor: Mapped[ConnectionMap] = relationship("ConnectionMap", back_populates="maps")
|
|
167
|
+
points: Mapped[list["ConnectionMapPoint"]] = relationship(
|
|
168
|
+
"ConnectionMapPoint",
|
|
169
|
+
back_populates="map",
|
|
170
|
+
cascade="all, delete-orphan",
|
|
171
|
+
order_by="ConnectionMapPoint.id",
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
def get_point(self, point_hash: str) -> Optional["ConnectionMapPoint"]:
|
|
175
|
+
for point in self.points:
|
|
176
|
+
if point.point_hash == point_hash:
|
|
177
|
+
return point
|
|
178
|
+
return None
|
|
179
|
+
|
|
180
|
+
def to_payload(self, points: dict[str, dict[str, Any]]) -> dict[str, Any]:
|
|
181
|
+
payload: dict[str, Any] = {"points": points}
|
|
182
|
+
if self.label is not None:
|
|
183
|
+
payload["label"] = self.label
|
|
184
|
+
return payload
|
|
185
|
+
|
|
186
|
+
def pack_points(self) -> dict[str, dict[str, Any]]:
|
|
187
|
+
return {point.point_hash: point.to_payload() for point in self.points}
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
class ConnectionMapPoint(Base):
|
|
191
|
+
"""ORM model for individual map points."""
|
|
192
|
+
|
|
193
|
+
__tablename__ = "ConnectionMapPoint"
|
|
194
|
+
__table_args__ = (UniqueConstraint("map_id", "point_hash"),)
|
|
195
|
+
|
|
196
|
+
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
|
197
|
+
map_id: Mapped[int] = mapped_column(
|
|
198
|
+
ForeignKey("ConnectionMapMap.id", ondelete="CASCADE")
|
|
199
|
+
)
|
|
200
|
+
point_hash: Mapped[str] = mapped_column(String, nullable=False)
|
|
201
|
+
latitude: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
|
|
202
|
+
longitude: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
|
|
203
|
+
altitude: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
|
|
204
|
+
point_type: Mapped[Optional[str]] = mapped_column(String, nullable=True)
|
|
205
|
+
name: Mapped[Optional[str]] = mapped_column(String, nullable=True)
|
|
206
|
+
signals: Mapped[Optional[dict[str, Any]]] = mapped_column(JSON, nullable=True)
|
|
207
|
+
|
|
208
|
+
map: Mapped[ConnectionMapMap] = relationship(
|
|
209
|
+
"ConnectionMapMap", back_populates="points"
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
def to_payload(self) -> dict[str, Any]:
|
|
213
|
+
payload: dict[str, Any] = {}
|
|
214
|
+
if self.latitude is not None:
|
|
215
|
+
payload["lat"] = self.latitude
|
|
216
|
+
if self.longitude is not None:
|
|
217
|
+
payload["lon"] = self.longitude
|
|
218
|
+
if self.altitude is not None:
|
|
219
|
+
payload["alt"] = self.altitude
|
|
220
|
+
if self.point_type is not None:
|
|
221
|
+
payload["type"] = self.point_type
|
|
222
|
+
if self.name is not None:
|
|
223
|
+
payload["name"] = self.name
|
|
224
|
+
if self.signals:
|
|
225
|
+
payload.update(self.signals)
|
|
226
|
+
return payload
|
|
227
|
+
|
|
228
|
+
@classmethod
|
|
229
|
+
def from_payload(
|
|
230
|
+
cls, point_hash: str, payload: dict[str, Any]
|
|
231
|
+
) -> "ConnectionMapPoint":
|
|
232
|
+
signals: dict[str, Any] = {}
|
|
233
|
+
latitude = payload.get("lat")
|
|
234
|
+
longitude = payload.get("lon")
|
|
235
|
+
altitude = payload.get("alt")
|
|
236
|
+
point_type = payload.get("type")
|
|
237
|
+
name = payload.get("name")
|
|
238
|
+
|
|
239
|
+
for key, value in payload.items():
|
|
240
|
+
if key not in {"lat", "lon", "alt", "type", "name"}:
|
|
241
|
+
signals[key] = value
|
|
242
|
+
|
|
243
|
+
return cls(
|
|
244
|
+
point_hash=point_hash,
|
|
245
|
+
latitude=latitude,
|
|
246
|
+
longitude=longitude,
|
|
247
|
+
altitude=altitude,
|
|
248
|
+
point_type=point_type,
|
|
249
|
+
name=name,
|
|
250
|
+
signals=signals or None,
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
__all__ = [
|
|
255
|
+
"ConnectionMap",
|
|
256
|
+
"ConnectionMapMap",
|
|
257
|
+
"ConnectionMapPoint",
|
|
258
|
+
]
|