use shcmea for beaconmeasurement data and fix some related things
This commit is contained in:
parent
b45fd961c0
commit
8feac6bf43
6 changed files with 67 additions and 44 deletions
|
@ -25,9 +25,9 @@ from c3nav.mapdata.forms import I18nModelFormMixin
|
|||
from c3nav.mapdata.models import GraphEdge, LocationGroup, Source, LocationGroupCategory, GraphNode, Space, \
|
||||
LocationSlug, WayType
|
||||
from c3nav.mapdata.models.access import AccessPermission, AccessRestrictionGroup, AccessRestriction
|
||||
from c3nav.mapdata.models.geometry.space import ObstacleGroup
|
||||
from c3nav.mapdata.models.geometry.space import ObstacleGroup, BeaconMeasurement
|
||||
from c3nav.mapdata.models.theme import ThemeLocationGroupBackgroundColor, ThemeObstacleGroupBackgroundColor
|
||||
from c3nav.routing.schemas import LocateRequestWifiPeerSchema
|
||||
from c3nav.routing.schemas import LocateWifiPeerSchema, BeaconMeasurementDataSchema
|
||||
|
||||
|
||||
class EditorFormBase(I18nModelFormMixin, ModelForm):
|
||||
|
@ -312,30 +312,10 @@ class EditorFormBase(I18nModelFormMixin, ModelForm):
|
|||
)
|
||||
|
||||
def clean_data(self):
|
||||
if 'wifi' not in self.cleaned_data['data']:
|
||||
data = self.cleaned_data['data']
|
||||
if not data.wifi:
|
||||
raise ValidationError(_('WiFi scan data is missing.'))
|
||||
if not isinstance(self.cleaned_data['data']["wifi"], list):
|
||||
raise ValidationError(_('WiFi scan data is not a list.'))
|
||||
|
||||
data = list()
|
||||
for scan in self.cleaned_data['data']["wifi"]:
|
||||
scan: list[dict]
|
||||
scan_data = list()
|
||||
for item in scan:
|
||||
# The app might return results without an SSID, we ignore those
|
||||
if not item.get('ssid', ''):
|
||||
continue
|
||||
# App version < 4.2.4 use level instead fo rssi
|
||||
if 'level' in item:
|
||||
item['rssi'] = item['level']
|
||||
del item['level']
|
||||
try:
|
||||
LocateRequestWifiPeerSchema.model_validate(item)
|
||||
except PydanticValidationError as e:
|
||||
raise ValidationError(str(e))
|
||||
scan_data.append(item)
|
||||
data.append(scan_data)
|
||||
|
||||
data.wifi = [[item for item in scan if item.ssid] for scan in data.wifi]
|
||||
return data
|
||||
|
||||
def clean(self):
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
# Generated by Django 5.0.8 on 2024-12-23 15:24
|
||||
|
||||
import c3nav.routing.schemas
|
||||
import django.core.serializers.json
|
||||
import django.db.models.deletion
|
||||
import django_pydantic_field.compat.django
|
||||
import django_pydantic_field.fields
|
||||
import types
|
||||
import typing
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
def forwards_func(apps, schema_editor):
|
||||
BeaconMeasurement = apps.get_model('mapdata', 'BeaconMeasurement')
|
||||
for measurement in BeaconMeasurement.objects.all():
|
||||
if isinstance(measurement.data, list):
|
||||
measurement.data = {"wifi": measurement.data}
|
||||
measurement.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mapdata', '0123_door_name_door_todo'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(forwards_func, migrations.RunPython.noop),
|
||||
migrations.AlterField(
|
||||
model_name='beaconmeasurement',
|
||||
name='data',
|
||||
field=django_pydantic_field.fields.PydanticSchemaField(config=None, encoder=django.core.serializers.json.DjangoJSONEncoder, schema=c3nav.routing.schemas.BeaconMeasurementDataSchema, verbose_name='Measurement list'),
|
||||
),
|
||||
]
|
|
@ -9,6 +9,7 @@ from django.urls import reverse
|
|||
from django.utils.functional import cached_property
|
||||
from django.utils.text import format_lazy
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django_pydantic_field.fields import SchemaField
|
||||
from shapely.geometry import CAP_STYLE, JOIN_STYLE, mapping
|
||||
|
||||
from c3nav.mapdata.fields import GeometryField, I18nField
|
||||
|
@ -21,6 +22,7 @@ from c3nav.mapdata.models.locations import SpecificLocation
|
|||
from c3nav.mapdata.utils.cache.changes import changed_geometries
|
||||
from c3nav.mapdata.utils.geometry import unwrap_geom
|
||||
from c3nav.mapdata.utils.json import format_geojson
|
||||
from c3nav.routing.schemas import BeaconMeasurementDataSchema
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from c3nav.mapdata.render.theme import ThemeColorManager
|
||||
|
@ -430,7 +432,8 @@ class BeaconMeasurement(SpaceGeometryMixin, models.Model):
|
|||
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True,
|
||||
verbose_name=_('author'))
|
||||
comment = models.TextField(null=True, blank=True, verbose_name=_('comment'))
|
||||
data = models.JSONField(_('Measurement list'), default=dict) # todo: would be nice if this used pydantic
|
||||
data: BeaconMeasurementDataSchema = SchemaField(BeaconMeasurementDataSchema,
|
||||
verbose_name=_('Measurement list'))
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Beacon Measurement')
|
||||
|
|
|
@ -12,16 +12,16 @@ from c3nav.mapdata.models.access import AccessPermission
|
|||
from c3nav.mapdata.schemas.models import CustomLocationSchema
|
||||
from c3nav.mapdata.utils.cache.stats import increment_cache_key
|
||||
from c3nav.routing.locator import Locator
|
||||
from c3nav.routing.schemas import LocateRequestWifiPeerSchema, LocateRequestIBeaconPeerSchema
|
||||
from c3nav.routing.schemas import LocateWifiPeerSchema, LocateIBeaconPeerSchema
|
||||
|
||||
positioning_api_router = APIRouter(tags=["positioning"])
|
||||
|
||||
|
||||
class LocateRequestSchema(BaseSchema):
|
||||
wifi_peers: list[LocateRequestWifiPeerSchema] = APIField(
|
||||
wifi_peers: list[LocateWifiPeerSchema] = APIField(
|
||||
title="list of visible/measured wifi location beacons",
|
||||
)
|
||||
ibeacon_peers: list[LocateRequestIBeaconPeerSchema] = APIField(
|
||||
ibeacon_peers: list[LocateIBeaconPeerSchema] = APIField(
|
||||
title="list of visible/measured location iBeacons",
|
||||
)
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ from c3nav.mapdata.models import MapUpdate, Space
|
|||
from c3nav.mapdata.utils.locations import CustomLocation
|
||||
from c3nav.mesh.utils import get_nodes_and_ranging_beacons
|
||||
from c3nav.routing.router import Router
|
||||
from c3nav.routing.schemas import LocateRequestWifiPeerSchema
|
||||
from c3nav.routing.schemas import LocateWifiPeerSchema, BeaconMeasurementDataSchema, LocateIBeaconPeerSchema
|
||||
|
||||
try:
|
||||
from asgiref.local import Local as LocalContext
|
||||
|
@ -117,33 +117,33 @@ class Locator:
|
|||
self.peers.append(peer)
|
||||
return peer_id
|
||||
|
||||
def convert_wifi_scan(self, scan_data, create_peers=False) -> ScanData:
|
||||
def convert_wifi_scan(self, scan_data: list[LocateWifiPeerSchema], create_peers=False) -> ScanData:
|
||||
result = {}
|
||||
for scan_value in scan_data:
|
||||
if settings.WIFI_SSIDS and scan_value['ssid'] not in settings.WIFI_SSIDS:
|
||||
if settings.WIFI_SSIDS and scan_value.ssid not in settings.WIFI_SSIDS:
|
||||
continue
|
||||
peer_id = self.get_peer_id(scan_value['bssid'], create=create_peers)
|
||||
peer_id = self.get_peer_id(scan_value.bssid, create=create_peers)
|
||||
if peer_id is not None:
|
||||
result[peer_id] = ScanDataValue(rssi=scan_value["rssi"], distance=scan_value.get("distance", None))
|
||||
result[peer_id] = ScanDataValue(rssi=scan_value.rssi, distance=scan_value.get("distance", None))
|
||||
return result
|
||||
|
||||
def convert_ibeacon_scan(self, scan_data, create_peers=False) -> ScanData:
|
||||
def convert_ibeacon_scan(self, scan_data: list[LocateIBeaconPeerSchema], create_peers=False) -> ScanData:
|
||||
result = {}
|
||||
for scan_value in scan_data:
|
||||
peer_id = self.get_peer_id(
|
||||
(scan_value['uuid'], scan_value['major'], scan_value['minor']),
|
||||
(scan_value.uuid, scan_value.major, scan_value.minor),
|
||||
create=create_peers
|
||||
)
|
||||
if peer_id is not None:
|
||||
result[peer_id] = ScanDataValue(ibeacon_range=scan_value["distance"])
|
||||
result[peer_id] = ScanDataValue(ibeacon_range=scan_value.distance)
|
||||
return result
|
||||
|
||||
def convert_scans(self, scans_data, create_peers=False) -> ScanData:
|
||||
def convert_scans(self, scans_data: BeaconMeasurementDataSchema, create_peers=False) -> ScanData:
|
||||
converted = []
|
||||
for scan in scans_data.get("wifi", []):
|
||||
for scan in scans_data.wifi:
|
||||
converted.append(self.convert_wifi_scan(scan, create_peers=create_peers))
|
||||
|
||||
for scan in scans_data.get("ibeacon", []):
|
||||
for scan in scans_data.ibeacon:
|
||||
converted.append(self.convert_ibeacon_scan(scan, create_peers=create_peers))
|
||||
|
||||
peer_ids = reduce(operator.or_, (frozenset(values.keys()) for values in converted), frozenset())
|
||||
|
@ -176,7 +176,7 @@ class Locator:
|
|||
cls.cached.data = cls.load_nocache(update)
|
||||
return cls.cached.data
|
||||
|
||||
def convert_raw_scan_data(self, raw_scan_data: list[LocateRequestWifiPeerSchema]) -> ScanData:
|
||||
def convert_raw_scan_data(self, raw_scan_data: list[LocateWifiPeerSchema]) -> ScanData:
|
||||
return self.convert_wifi_scan(raw_scan_data, create_peers=False)
|
||||
|
||||
def get_xyz(self, identifier: LocatorPeerIdentifier) -> tuple[int, int, int] | None:
|
||||
|
@ -191,7 +191,7 @@ class Locator:
|
|||
if isinstance(peer.identifier, MacAddress)
|
||||
}
|
||||
|
||||
def locate(self, raw_scan_data: list[LocateRequestWifiPeerSchema], permissions=None):
|
||||
def locate(self, raw_scan_data: list[LocateWifiPeerSchema], permissions=None):
|
||||
# todo: support for ibeacons
|
||||
scan_data = self.convert_raw_scan_data(raw_scan_data)
|
||||
if not scan_data:
|
||||
|
|
|
@ -10,7 +10,7 @@ from pydantic_extra_types.mac_address import MacAddress
|
|||
from c3nav.api.schema import BaseSchema
|
||||
|
||||
|
||||
class LocateRequestWifiPeerSchema(BaseSchema):
|
||||
class LocateWifiPeerSchema(BaseSchema):
|
||||
bssid: MacAddress = APIField(
|
||||
title="BSSID",
|
||||
description="BSSID of the peer",
|
||||
|
@ -25,6 +25,7 @@ class LocateRequestWifiPeerSchema(BaseSchema):
|
|||
title="RSSI",
|
||||
description="RSSI in dBm",
|
||||
example=-42,
|
||||
validation_alias="level", # App version < 4.2.4 use level instead fo rssi
|
||||
)
|
||||
frequency: Union[
|
||||
PositiveInt,
|
||||
|
@ -64,7 +65,7 @@ class LocateRequestWifiPeerSchema(BaseSchema):
|
|||
)
|
||||
|
||||
|
||||
class LocateRequestIBeaconPeerSchema(BaseSchema):
|
||||
class LocateIBeaconPeerSchema(BaseSchema):
|
||||
uuid: UUID = APIField(
|
||||
title="UUID",
|
||||
description="UUID of the iBeacon",
|
||||
|
@ -82,3 +83,8 @@ class LocateRequestIBeaconPeerSchema(BaseSchema):
|
|||
last_seen_ago: NonNegativeInt = APIField(
|
||||
title="how many milliseconds ago this beacon was last seen"
|
||||
)
|
||||
|
||||
|
||||
class BeaconMeasurementDataSchema(BaseSchema):
|
||||
wifi: list[list[LocateWifiPeerSchema]] = []
|
||||
ibeacon: list[list[LocateIBeaconPeerSchema]] = []
|
Loading…
Add table
Add a link
Reference in a new issue