mirror of
https://github.com/pablorevilla-meshtastic/meshview.git
synced 2026-10-31 03:01:58 +01:00
add altitude to table
This commit is contained in:
1 parent
3e1d95ed2c
commit
93fc31efd4
6 files changed
+94
No files matched your search
@@ -0,0 +1,29 @@
|
||||
"""add last altitude to node
|
||||
|
||||
Revision ID: 44612e74a798
|
||||
Revises: 4d2a7c9e1b30
|
||||
Create Date: 2026-10-28 10:37:11.340792
|
||||
|
||||
"""
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
import sqlalchemy as sa
|
||||
|
||||
from alembic import op
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = '44612e74a798'
|
||||
down_revision: str | None = '4d2a7c9e1b30'
|
||||
branch_labels: str | Sequence[str] | None = None
|
||||
depends_on: str | Sequence[str] | None = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
with op.batch_alter_table("node") as batch_op:
|
||||
batch_op.add_column(sa.Column("last_altitude", sa.BigInteger(), nullable=True))
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
with op.batch_alter_table("node") as batch_op:
|
||||
batch_op.drop_column("last_altitude")
|
||||
@@ -73,6 +73,7 @@ Response Example
|
||||
"role": "client",
|
||||
"last_lat": 377749000,
|
||||
"last_long": -1224194000,
|
||||
"last_altitude": 125,
|
||||
"channel": "main",
|
||||
"is_mqtt_gateway": true,
|
||||
"first_seen_us": 1736283723456789,
|
||||
@@ -82,6 +83,12 @@ Response Example
|
||||
}
|
||||
```
|
||||
|
||||
`last_altitude` is the latest reported altitude in meters above sea level, or `null`
|
||||
when unknown. It updates with valid node locations from position packets and map reports.
|
||||
Position packets without altitude preserve the previous value. Map reports lack altitude
|
||||
field presence, so zero values in map reports also preserve the previous value.
|
||||
Existing nodes receive altitude as new reports arrive.
|
||||
|
||||
---
|
||||
|
||||
## 2. Packets API
|
||||
|
||||
@@ -21,6 +21,7 @@ class Node(Base):
|
||||
role: Mapped[str] = mapped_column(nullable=True)
|
||||
last_lat: Mapped[int] = mapped_column(BigInteger, nullable=True)
|
||||
last_long: Mapped[int] = mapped_column(BigInteger, nullable=True)
|
||||
last_altitude: Mapped[int | None] = mapped_column(BigInteger, nullable=True)
|
||||
channel: Mapped[str] = mapped_column(nullable=True)
|
||||
is_mqtt_gateway: Mapped[bool] = mapped_column(nullable=True)
|
||||
first_seen_us: Mapped[int] = mapped_column(BigInteger, nullable=True)
|
||||
|
||||
@@ -224,6 +224,9 @@ async def process_envelope(topic, env):
|
||||
if has_location:
|
||||
node.last_lat = map_report.latitude_i
|
||||
node.last_long = map_report.longitude_i
|
||||
# MapReport has no presence flag for altitude.
|
||||
if map_report.altitude != 0:
|
||||
node.last_altitude = map_report.altitude
|
||||
node.firmware = map_report.firmware_version
|
||||
node.last_seen_us = now_us
|
||||
if node.first_seen_us is None:
|
||||
@@ -244,6 +247,9 @@ async def process_envelope(topic, env):
|
||||
if has_location:
|
||||
node.last_lat = map_report.latitude_i
|
||||
node.last_long = map_report.longitude_i
|
||||
# MapReport has no presence flag for altitude.
|
||||
if map_report.altitude != 0:
|
||||
node.last_altitude = map_report.altitude
|
||||
session.add(node)
|
||||
except Exception as e:
|
||||
print(f"Error processing MAP_REPORT_APP: {e}")
|
||||
@@ -427,6 +433,8 @@ async def process_envelope(topic, env):
|
||||
now_us = int(time.time() * 1_000_000)
|
||||
node.last_lat = position.latitude_i
|
||||
node.last_long = position.longitude_i
|
||||
if position.HasField("altitude"):
|
||||
node.last_altitude = position.altitude
|
||||
node.last_seen_us = now_us
|
||||
if node.first_seen_us is None:
|
||||
node.first_seen_us = now_us
|
||||
|
||||
@@ -276,6 +276,7 @@ async def api_nodes(request):
|
||||
"role": n.role,
|
||||
"last_lat": getattr(n, "last_lat", None),
|
||||
"last_long": getattr(n, "last_long", None),
|
||||
"last_altitude": n.last_altitude,
|
||||
"channel": n.channel,
|
||||
"is_mqtt_gateway": getattr(n, "is_mqtt_gateway", None),
|
||||
# "last_update": n.last_update.isoformat(),
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
"""Persist reported altitudes without losing them when a report omits altitude."""
|
||||
|
||||
import pytest
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
|
||||
|
||||
from meshtastic.protobuf.mesh_pb2 import Position
|
||||
from meshtastic.protobuf.mqtt_pb2 import MapReport, ServiceEnvelope
|
||||
from meshtastic.protobuf.portnums_pb2 import PortNum
|
||||
from meshview import mqtt_database, mqtt_store
|
||||
from meshview.models import Base, Node
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("report_type", [Position, MapReport])
|
||||
async def test_altitude_updates(monkeypatch, report_type):
|
||||
engine = create_async_engine("sqlite+aiosqlite:///:memory:")
|
||||
sessions = async_sessionmaker(engine, expire_on_commit=False)
|
||||
monkeypatch.setattr(mqtt_database, "async_session", sessions, raising=False)
|
||||
monkeypatch.setattr(mqtt_store, "MQTT_GATEWAY_CACHE", set())
|
||||
port = PortNum.POSITION_APP if report_type is Position else PortNum.MAP_REPORT_APP
|
||||
try:
|
||||
async with engine.begin() as conn:
|
||||
await conn.run_sync(Base.metadata.create_all)
|
||||
cases = [
|
||||
(None, 200000000, None),
|
||||
(125, 200000000, 125),
|
||||
(None, 200000000, 125),
|
||||
(-30, 200000000, -30),
|
||||
(900, 0, -30), # Invalid location must not update altitude.
|
||||
(0, 200000000, 0 if report_type is Position else -30),
|
||||
]
|
||||
for packet_id, (altitude, coordinate, expected) in enumerate(cases, start=1):
|
||||
report = report_type(latitude_i=coordinate, longitude_i=coordinate)
|
||||
if altitude is not None:
|
||||
report.altitude = altitude
|
||||
env = ServiceEnvelope(gateway_id="!00000002", channel_id="test")
|
||||
env.packet.id = packet_id
|
||||
setattr(env.packet, "from", 123)
|
||||
env.packet.decoded.portnum = port
|
||||
env.packet.decoded.payload = report.SerializeToString()
|
||||
await mqtt_store.process_envelope("test", env)
|
||||
async with sessions() as session:
|
||||
node = (await session.execute(select(Node).where(Node.node_id == 123))).scalar_one()
|
||||
assert node.last_altitude == expected
|
||||
assert node.to_dict()["last_altitude"] == expected
|
||||
finally:
|
||||
await engine.dispose()
|
||||
Reference in new issue
Block a user