2023-12-04 19:24:14 +01:00
|
|
|
from typing import Annotated, Union
|
2023-11-27 22:59:59 +01:00
|
|
|
|
2024-03-30 22:12:27 +01:00
|
|
|
from django.conf import settings
|
2023-12-03 18:25:26 +01:00
|
|
|
from django.core.exceptions import ValidationError
|
2023-11-27 22:59:59 +01:00
|
|
|
from ninja import Field as APIField
|
2023-12-03 16:37:05 +01:00
|
|
|
from ninja import Router as APIRouter
|
2024-03-31 19:36:02 +02:00
|
|
|
from pydantic_extra_types.mac_address import MacAddress
|
2023-11-24 16:15:19 +01:00
|
|
|
|
2023-12-03 21:55:08 +01:00
|
|
|
from c3nav.api.auth import auth_responses
|
2023-12-11 20:49:50 +01:00
|
|
|
from c3nav.api.schema import BaseSchema
|
2023-12-03 18:25:26 +01:00
|
|
|
from c3nav.mapdata.models.access import AccessPermission
|
2023-12-02 00:00:23 +01:00
|
|
|
from c3nav.mapdata.schemas.models import CustomLocationSchema
|
2024-12-28 22:58:25 +01:00
|
|
|
from c3nav.mapdata.tasks import update_ap_names_bssid_mapping
|
2023-12-03 18:25:26 +01:00
|
|
|
from c3nav.mapdata.utils.cache.stats import increment_cache_key
|
|
|
|
from c3nav.routing.locator import Locator
|
2024-12-23 16:26:15 +01:00
|
|
|
from c3nav.routing.schemas import LocateWifiPeerSchema, LocateIBeaconPeerSchema
|
2023-11-24 16:15:19 +01:00
|
|
|
|
|
|
|
positioning_api_router = APIRouter(tags=["positioning"])
|
|
|
|
|
|
|
|
|
2023-12-11 20:49:50 +01:00
|
|
|
class LocateRequestSchema(BaseSchema):
|
2024-12-23 16:26:15 +01:00
|
|
|
wifi_peers: list[LocateWifiPeerSchema] = APIField(
|
2024-03-31 15:26:44 +02:00
|
|
|
title="list of visible/measured wifi location beacons",
|
|
|
|
)
|
2024-12-23 16:26:15 +01:00
|
|
|
ibeacon_peers: list[LocateIBeaconPeerSchema] = APIField(
|
2024-03-31 15:26:44 +02:00
|
|
|
title="list of visible/measured location iBeacons",
|
2023-12-04 19:24:14 +01:00
|
|
|
)
|
2023-12-02 00:00:23 +01:00
|
|
|
|
|
|
|
|
2023-12-11 20:49:50 +01:00
|
|
|
class PositioningResult(BaseSchema):
|
2023-12-04 19:24:14 +01:00
|
|
|
location: Union[
|
|
|
|
Annotated[CustomLocationSchema, APIField(title="location")],
|
|
|
|
Annotated[None, APIField(title="null", description="position could not be determined")]
|
|
|
|
] = APIField(
|
|
|
|
title="location",
|
|
|
|
description="positinoing result",
|
|
|
|
)
|
2023-12-02 00:00:23 +01:00
|
|
|
|
|
|
|
|
2023-12-03 19:48:13 +01:00
|
|
|
@positioning_api_router.post('/locate/', summary="determine position",
|
|
|
|
description="determine position based on wireless measurements "
|
|
|
|
"(including ranging, if available)",
|
2023-12-02 00:00:23 +01:00
|
|
|
response={200: PositioningResult, **auth_responses})
|
2024-12-28 22:58:25 +01:00
|
|
|
def get_position(request, parameters: LocateRequestSchema):
|
2023-12-03 18:25:26 +01:00
|
|
|
try:
|
2024-12-23 22:13:32 +01:00
|
|
|
location = Locator.load().locate(parameters.wifi_peers,
|
2023-12-11 19:02:19 +01:00
|
|
|
permissions=AccessPermission.get_for_request(request))
|
2023-12-03 18:25:26 +01:00
|
|
|
if location is not None:
|
|
|
|
# todo: this will overload us probably, group these
|
|
|
|
increment_cache_key('apistats__locate__%s' % location.pk)
|
|
|
|
except ValidationError:
|
|
|
|
# todo: validation error, seriously? this shouldn't happen anyways
|
|
|
|
raise
|
|
|
|
|
2024-12-28 22:16:25 +01:00
|
|
|
if request.user_permissions.passive_ap_name_scanning:
|
|
|
|
bssid_mapping = {}
|
|
|
|
for peer in parameters.wifi_peers:
|
|
|
|
if not peer.ap_name:
|
|
|
|
continue
|
|
|
|
bssid_mapping.setdefault(peer.ap_name, set()).add(peer.bssid)
|
|
|
|
if bssid_mapping:
|
2024-12-28 23:06:22 +01:00
|
|
|
update_ap_names_bssid_mapping.delay(
|
|
|
|
map_name={name: list[bssids] for name, bssids in bssid_mapping.items()},
|
|
|
|
user=request.user
|
|
|
|
)
|
2024-12-28 22:16:25 +01:00
|
|
|
|
2023-12-03 18:25:26 +01:00
|
|
|
return {
|
2024-12-04 15:43:03 +01:00
|
|
|
"location": location
|
2023-12-03 18:25:26 +01:00
|
|
|
}
|
2023-11-24 16:15:19 +01:00
|
|
|
|
2023-11-26 17:55:23 +01:00
|
|
|
|
2024-03-30 22:12:27 +01:00
|
|
|
if settings.METRICS:
|
|
|
|
from c3nav.mapdata.metrics import APIStatsCollector
|
|
|
|
APIStatsCollector.add_stat('locate', 'location')
|
2024-03-30 20:04:11 +01:00
|
|
|
|
|
|
|
|
2023-12-03 19:48:13 +01:00
|
|
|
@positioning_api_router.get('/locate-test/', summary="debug position",
|
|
|
|
description="outputs a location for debugging purposes",
|
2023-12-02 00:00:23 +01:00
|
|
|
response={200: PositioningResult, **auth_responses})
|
2023-12-11 14:09:33 +01:00
|
|
|
def locate_test(request):
|
2023-12-03 18:25:26 +01:00
|
|
|
from c3nav.mesh.messages import MeshMessageType
|
|
|
|
from c3nav.mesh.models import MeshNode
|
|
|
|
try:
|
|
|
|
node = MeshNode.objects.prefetch_last_messages(MeshMessageType.LOCATE_RANGE_RESULTS).get(
|
|
|
|
address="d4:f9:8d:2d:0d:f1"
|
|
|
|
)
|
|
|
|
except MeshNode.DoesNotExist:
|
|
|
|
return {
|
|
|
|
"location": None
|
|
|
|
}
|
|
|
|
msg = node.last_messages[MeshMessageType.LOCATE_RANGE_RESULTS]
|
|
|
|
|
2023-12-07 02:15:32 +01:00
|
|
|
locator = Locator.load()
|
|
|
|
location = locator.locate_range(
|
|
|
|
locator.convert_raw_scan_data([
|
|
|
|
{
|
|
|
|
"bssid": r.peer,
|
|
|
|
"ssid": "",
|
|
|
|
"rssi": r.rssi,
|
|
|
|
"distance": r.distance,
|
|
|
|
}
|
2023-12-03 18:25:26 +01:00
|
|
|
for r in msg.parsed.ranges
|
|
|
|
if r.distance != 0xFFFF
|
2023-12-07 02:15:32 +01:00
|
|
|
]),
|
2023-12-03 18:25:26 +01:00
|
|
|
None
|
|
|
|
)
|
|
|
|
return {
|
2024-03-29 15:01:39 +01:00
|
|
|
"ranges": msg.parsed.model_dump(mode="json")["ranges"],
|
2023-12-03 18:25:26 +01:00
|
|
|
"datetime": msg.datetime,
|
2024-12-04 15:43:03 +01:00
|
|
|
"location": location
|
2023-12-03 18:25:26 +01:00
|
|
|
}
|
2023-11-27 22:59:59 +01:00
|
|
|
|
|
|
|
|
|
|
|
BeaconsXYZ = dict[
|
2024-03-31 19:36:02 +02:00
|
|
|
MacAddress,
|
2023-11-27 22:59:59 +01:00
|
|
|
Annotated[
|
|
|
|
tuple[
|
|
|
|
Annotated[int, APIField(title="X (in cm)")],
|
|
|
|
Annotated[int, APIField(title="Y (in cm)")],
|
|
|
|
Annotated[int, APIField(title="Z (in cm)")],
|
|
|
|
],
|
|
|
|
APIField(title="global XYZ coordinates")
|
|
|
|
]
|
2024-03-31 19:36:02 +02:00
|
|
|
]
|