补齐设备HTTP定位坐标归一化
This commit is contained in:
@@ -130,6 +130,19 @@ class LocationService(DatabaseServiceBase):
|
|||||||
)
|
)
|
||||||
return device_identity, None
|
return device_identity, None
|
||||||
|
|
||||||
|
payload = self._normalized_location_payload(
|
||||||
|
coord_type=self._get_explicit_coord_type(payload),
|
||||||
|
latitude=payload.lat,
|
||||||
|
longitude=payload.lng,
|
||||||
|
accuracy_m=payload.accuracy_m,
|
||||||
|
altitude_m=payload.altitude_m,
|
||||||
|
speed_mps=payload.speed_mps,
|
||||||
|
heading_deg=payload.heading_deg,
|
||||||
|
source=payload.source,
|
||||||
|
battery_pct=payload.battery_pct,
|
||||||
|
device_time=payload.device_time,
|
||||||
|
)
|
||||||
|
|
||||||
db_session = await self.get_session()
|
db_session = await self.get_session()
|
||||||
try:
|
try:
|
||||||
dao = LocationDAO(db_session)
|
dao = LocationDAO(db_session)
|
||||||
@@ -191,6 +204,60 @@ class LocationService(DatabaseServiceBase):
|
|||||||
finally:
|
finally:
|
||||||
await db_session.close()
|
await db_session.close()
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class _LocationPayload:
|
||||||
|
coord_type: str
|
||||||
|
lat: float
|
||||||
|
lng: float
|
||||||
|
accuracy_m: int | None
|
||||||
|
altitude_m: float | None
|
||||||
|
speed_mps: float | None
|
||||||
|
heading_deg: int | None
|
||||||
|
source: int
|
||||||
|
battery_pct: int | None
|
||||||
|
device_time: datetime
|
||||||
|
|
||||||
|
def _normalized_location_payload(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
coord_type: str | None,
|
||||||
|
latitude: float,
|
||||||
|
longitude: float,
|
||||||
|
accuracy_m: int | None,
|
||||||
|
altitude_m: float | None,
|
||||||
|
speed_mps: float | None,
|
||||||
|
heading_deg: int | None,
|
||||||
|
source: int,
|
||||||
|
battery_pct: int | None,
|
||||||
|
device_time: datetime,
|
||||||
|
) -> _LocationPayload:
|
||||||
|
normalized_lat, normalized_lng, normalized_coord_type = normalize_location_coordinate(
|
||||||
|
latitude=float(latitude),
|
||||||
|
longitude=float(longitude),
|
||||||
|
coord_type=coord_type,
|
||||||
|
)
|
||||||
|
return self._LocationPayload(
|
||||||
|
coord_type=normalized_coord_type,
|
||||||
|
lat=normalized_lat,
|
||||||
|
lng=normalized_lng,
|
||||||
|
accuracy_m=accuracy_m,
|
||||||
|
altitude_m=altitude_m,
|
||||||
|
speed_mps=speed_mps,
|
||||||
|
heading_deg=heading_deg,
|
||||||
|
source=source,
|
||||||
|
battery_pct=battery_pct,
|
||||||
|
device_time=device_time,
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_explicit_coord_type(payload: DeviceLocationReportRequest) -> str | None:
|
||||||
|
fields_set = getattr(payload, "model_fields_set", None)
|
||||||
|
if fields_set is None:
|
||||||
|
fields_set = getattr(payload, "__fields_set__", set())
|
||||||
|
if "coord_type" not in fields_set:
|
||||||
|
return None
|
||||||
|
return payload.coord_type
|
||||||
|
|
||||||
async def report_mqtt_device_location(
|
async def report_mqtt_device_location(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
@@ -215,19 +282,6 @@ class LocationService(DatabaseServiceBase):
|
|||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class _MQTTLocationPayload:
|
|
||||||
coord_type: str
|
|
||||||
lat: float
|
|
||||||
lng: float
|
|
||||||
accuracy_m: int | None
|
|
||||||
altitude_m: float | None
|
|
||||||
speed_mps: float | None
|
|
||||||
heading_deg: int | None
|
|
||||||
source: int
|
|
||||||
battery_pct: int | None
|
|
||||||
device_time: datetime
|
|
||||||
|
|
||||||
source_value = self._normalize_location_source(source)
|
source_value = self._normalize_location_source(source)
|
||||||
|
|
||||||
db_session = await self.get_session()
|
db_session = await self.get_session()
|
||||||
@@ -237,16 +291,10 @@ class LocationService(DatabaseServiceBase):
|
|||||||
if not binding_row or binding_row["child_id"] is None:
|
if not binding_row or binding_row["child_id"] is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
normalized_lat, normalized_lng, normalized_coord_type = normalize_location_coordinate(
|
payload = self._normalized_location_payload(
|
||||||
|
coord_type=coord_type,
|
||||||
latitude=float(latitude),
|
latitude=float(latitude),
|
||||||
longitude=float(longitude),
|
longitude=float(longitude),
|
||||||
coord_type=coord_type,
|
|
||||||
)
|
|
||||||
|
|
||||||
payload = _MQTTLocationPayload(
|
|
||||||
coord_type=normalized_coord_type,
|
|
||||||
lat=normalized_lat,
|
|
||||||
lng=normalized_lng,
|
|
||||||
accuracy_m=accuracy_m,
|
accuracy_m=accuracy_m,
|
||||||
altitude_m=altitude_m,
|
altitude_m=altitude_m,
|
||||||
speed_mps=speed_mps,
|
speed_mps=speed_mps,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from banban.schemas.location import DeviceLocationReportRequest
|
||||||
from banban.service.location import (
|
from banban.service.location import (
|
||||||
LocationService,
|
LocationService,
|
||||||
normalize_location_coordinate,
|
normalize_location_coordinate,
|
||||||
@@ -49,3 +50,68 @@ async def test_mqtt_location_report_ignores_zero_zero_before_normalization(monke
|
|||||||
|
|
||||||
assert row is None
|
assert row is None
|
||||||
assert sessions == []
|
assert sessions == []
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_http_location_report_without_coord_type_converts_before_persist(monkeypatch):
|
||||||
|
service = LocationService()
|
||||||
|
captured = {}
|
||||||
|
|
||||||
|
class FakeDeviceIdentity:
|
||||||
|
child_id = 9
|
||||||
|
child_name = "child"
|
||||||
|
|
||||||
|
class FakeSession:
|
||||||
|
async def close(self):
|
||||||
|
captured["closed"] = True
|
||||||
|
|
||||||
|
class FakeLocationDAO:
|
||||||
|
def __init__(self, session):
|
||||||
|
captured["session"] = session
|
||||||
|
|
||||||
|
async def report_device_location(self, *, device_id, child_id, payload):
|
||||||
|
captured["device_id"] = device_id
|
||||||
|
captured["child_id"] = child_id
|
||||||
|
captured["payload"] = payload
|
||||||
|
return {
|
||||||
|
"child_id": child_id,
|
||||||
|
"device_id": device_id,
|
||||||
|
"coord_type": payload.coord_type,
|
||||||
|
"lat": payload.lat,
|
||||||
|
"lng": payload.lng,
|
||||||
|
"should_resolve_address": False,
|
||||||
|
}
|
||||||
|
|
||||||
|
async def fake_authenticate_device_identity(*, device_id, serial_number):
|
||||||
|
captured["auth"] = (device_id, serial_number)
|
||||||
|
return FakeDeviceIdentity()
|
||||||
|
|
||||||
|
async def fake_get_session():
|
||||||
|
return FakeSession()
|
||||||
|
|
||||||
|
monkeypatch.setattr(
|
||||||
|
"banban.service.location.im_service.authenticate_device_identity",
|
||||||
|
fake_authenticate_device_identity,
|
||||||
|
)
|
||||||
|
monkeypatch.setattr("banban.service.location.LocationDAO", FakeLocationDAO)
|
||||||
|
monkeypatch.setattr(service, "get_session", fake_get_session)
|
||||||
|
|
||||||
|
_, row = await service.report_device_location(
|
||||||
|
device_id="TalkingQ_device001",
|
||||||
|
serial_number="SN001",
|
||||||
|
payload=DeviceLocationReportRequest(
|
||||||
|
lat=30.223122166666666,
|
||||||
|
lng=120.25837883333334,
|
||||||
|
source=0,
|
||||||
|
device_time="2026-06-29T00:00:00",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
persisted_payload = captured["payload"]
|
||||||
|
assert captured["auth"] == ("TalkingQ_device001", "SN001")
|
||||||
|
assert captured["child_id"] == 9
|
||||||
|
assert captured["closed"] is True
|
||||||
|
assert row["coord_type"] == "gcj02"
|
||||||
|
assert persisted_payload.coord_type == "gcj02"
|
||||||
|
assert persisted_payload.lat == pytest.approx(30.22066658053894)
|
||||||
|
assert persisted_payload.lng == pytest.approx(120.26284572518867)
|
||||||
|
|||||||
Reference in New Issue
Block a user