add ibeacon scan code to c3nav.js and endpoint

This commit is contained in:
Laura Klünder 2024-03-31 15:26:44 +02:00
parent a62017f3a2
commit 31c7dad72f
5 changed files with 82 additions and 26 deletions

View file

@ -25,7 +25,7 @@ from c3nav.mapdata.models import GraphEdge, LocationGroup
from c3nav.mapdata.models.access import AccessPermission from c3nav.mapdata.models.access import AccessPermission
from c3nav.mapdata.models.geometry.space import ObstacleGroup from c3nav.mapdata.models.geometry.space import ObstacleGroup
from c3nav.mapdata.models.theme import ThemeLocationGroupBackgroundColor, ThemeObstacleGroupBackgroundColor from c3nav.mapdata.models.theme import ThemeLocationGroupBackgroundColor, ThemeObstacleGroupBackgroundColor
from c3nav.routing.schemas import LocateRequestPeerSchema from c3nav.routing.schemas import LocateRequestWifiPeerSchema
class EditorFormBase(I18nModelFormMixin, ModelForm): class EditorFormBase(I18nModelFormMixin, ModelForm):
@ -340,7 +340,7 @@ class EditorFormBase(I18nModelFormMixin, ModelForm):
item['rssi'] = item['level'] item['rssi'] = item['level']
del item['level'] del item['level']
try: try:
LocateRequestPeerSchema.model_validate(item) LocateRequestWifiPeerSchema.model_validate(item)
except PydanticValidationError as e: except PydanticValidationError as e:
raise ValidationError(str(e)) raise ValidationError(str(e))
scan_data.append(item) scan_data.append(item)

View file

@ -11,14 +11,17 @@ from c3nav.mapdata.models.access import AccessPermission
from c3nav.mapdata.schemas.models import CustomLocationSchema from c3nav.mapdata.schemas.models import CustomLocationSchema
from c3nav.mapdata.utils.cache.stats import increment_cache_key from c3nav.mapdata.utils.cache.stats import increment_cache_key
from c3nav.routing.locator import Locator from c3nav.routing.locator import Locator
from c3nav.routing.schemas import BSSIDSchema, LocateRequestPeerSchema from c3nav.routing.schemas import BSSIDSchema, LocateRequestWifiPeerSchema, LocateRequestIBeaconPeerSchema
positioning_api_router = APIRouter(tags=["positioning"]) positioning_api_router = APIRouter(tags=["positioning"])
class LocateRequestSchema(BaseSchema): class LocateRequestSchema(BaseSchema):
peers: list[LocateRequestPeerSchema] = APIField( wifi_peers: list[LocateRequestWifiPeerSchema] = APIField(
title="list of visible/measured location beacons", title="list of visible/measured wifi location beacons",
)
ibeacon_peers: list[LocateRequestIBeaconPeerSchema] = APIField(
title="list of visible/measured location iBeacons",
) )

View file

@ -12,7 +12,7 @@ from c3nav.mapdata.models import MapUpdate, Space
from c3nav.mapdata.models.geometry.space import RangingBeacon from c3nav.mapdata.models.geometry.space import RangingBeacon
from c3nav.mapdata.utils.locations import CustomLocation from c3nav.mapdata.utils.locations import CustomLocation
from c3nav.routing.router import Router from c3nav.routing.router import Router
from c3nav.routing.schemas import LocateRequestPeerSchema from c3nav.routing.schemas import LocateRequestWifiPeerSchema
try: try:
from asgiref.local import Local as LocalContext from asgiref.local import Local as LocalContext
@ -146,7 +146,7 @@ class Locator:
cls.cached.data = cls.load_nocache(update) cls.cached.data = cls.load_nocache(update)
return cls.cached.data return cls.cached.data
def convert_raw_scan_data(self, raw_scan_data: list[LocateRequestPeerSchema]) -> ScanData: def convert_raw_scan_data(self, raw_scan_data: list[LocateRequestWifiPeerSchema]) -> ScanData:
return self.convert_scan(raw_scan_data, create_peers=False) return self.convert_scan(raw_scan_data, create_peers=False)
def get_xyz(self, address: BSSID) -> tuple[int, int, int] | None: def get_xyz(self, address: BSSID) -> tuple[int, int, int] | None:
@ -160,7 +160,7 @@ class Locator:
peer: peer.xyz for peer in self.peers[:len(self.xyz)] peer: peer.xyz for peer in self.peers[:len(self.xyz)]
} }
def locate(self, raw_scan_data: list[LocateRequestPeerSchema], permissions=None): def locate(self, raw_scan_data: list[LocateRequestWifiPeerSchema], permissions=None):
scan_data = self.convert_raw_scan_data(raw_scan_data) scan_data = self.convert_raw_scan_data(raw_scan_data)
if not scan_data: if not scan_data:
return None return None

View file

@ -1,7 +1,10 @@
from typing import Annotated, Union from typing import Annotated, Union
from uuid import UUID
from annotated_types import Lt
from pydantic import Field as APIField from pydantic import Field as APIField
from pydantic import NegativeInt, PositiveInt from pydantic import NegativeInt, PositiveInt
from pydantic.types import NonNegativeInt, PositiveFloat, NonNegativeFloat
from c3nav.api.schema import BaseSchema from c3nav.api.schema import BaseSchema
from c3nav.api.utils import NonEmptyStr from c3nav.api.utils import NonEmptyStr
@ -9,7 +12,7 @@ from c3nav.api.utils import NonEmptyStr
BSSIDSchema = Annotated[str, APIField(pattern=r"^[a-z0-9]{2}(:[a-z0-9]{2}){5}$", title="BSSID")] BSSIDSchema = Annotated[str, APIField(pattern=r"^[a-z0-9]{2}(:[a-z0-9]{2}){5}$", title="BSSID")]
class LocateRequestPeerSchema(BaseSchema): class LocateRequestWifiPeerSchema(BaseSchema):
bssid: BSSIDSchema = APIField( bssid: BSSIDSchema = APIField(
title="BSSID", title="BSSID",
description="BSSID of the peer", description="BSSID of the peer",
@ -61,3 +64,23 @@ class LocateRequestPeerSchema(BaseSchema):
description="standard deviation of measurements in meters", description="standard deviation of measurements in meters",
example=1.23 example=1.23
) )
class LocateRequestIBeaconPeerSchema(BaseSchema):
uuid: UUID = APIField(
title="UUID",
description="UUID of the iBeacon",
example="a142621a-2f42-09b3-245b-e1ac6356e9b0",
)
major: Annotated[NonNegativeInt, Lt(2 ** 16)] = APIField(
title="major value of the iBeacon",
)
minor: Annotated[NonNegativeInt, Lt(2 ** 16)] = APIField(
title="minor value of the iBeacon",
)
distance: NonNegativeFloat = APIField(
title="determined iBeacon distance",
)
last_seen_ago: NonNegativeInt = APIField(
title="how many milliseconds ago this beacon was last seen"
)

View file

@ -258,6 +258,7 @@ c3nav = {
if (window.mobileclient) { if (window.mobileclient) {
c3nav.startWifiScanning(); c3nav.startWifiScanning();
c3nav.startBLEScanning();
} }
c3nav.init_completed = true; c3nav.init_completed = true;
@ -1830,19 +1831,19 @@ c3nav = {
} }
}, },
_no_wifi_count: 0, startBLEScanning: function() {
if (mobileclient.registerBeaconUuid) {
mobileclient.registerBeaconUuid("a142621a-2f42-09b3-245b-e1ac6356e9b0");
}
},
_last_scan: 0,
_last_wifi_peers: [],
_last_ibeacon_peers: [],
_no_scan_count: 0,
_wifi_scan_results: function(peers) { _wifi_scan_results: function(peers) {
peers = JSON.parse(peers); peers = JSON.parse(peers);
if (peers.length) {
c3nav._hasLocationPermission = true;
} else {
c3nav.hasLocationPermission(true);
}
var now = Date.now();
if (now-4000 < c3nav._last_wifi_scan) return;
if (c3nav.ssids) { if (c3nav.ssids) {
peers = peers.filter(peer => c3nav.ssids.includes(peer.ssid)); peers = peers.filter(peer => c3nav.ssids.includes(peer.ssid));
} }
@ -1857,28 +1858,53 @@ c3nav = {
delete peer.rtt; delete peer.rtt;
} }
} }
c3nav._last_wifi_peers = peers;
c3nav._after_scan_results();
},
_ibeacon_scan_results: function(peers) {
peers = JSON.parse(peers);
c3nav._last_ibeacon_peers = peers;
c3nav._after_scan_results();
},
_after_scan_results: function() {
has_peers = c3nav._last_wifi_peers.length || c3nav._last_ibeacon_peers.length;
if (has_peers) {
c3nav._hasLocationPermission = true;
} else {
c3nav.hasLocationPermission(true);
}
var now = Date.now();
if (now-4000 < c3nav._last_scan) return;
if (!peers.length) { if (!has_peers) {
if (!c3nav._hasLocationPermission) { if (!c3nav._hasLocationPermission) {
c3nav._set_user_location(null); c3nav._set_user_location(null);
} else { } else {
if (c3nav._no_wifi_count > 5) { if (c3nav._no_scan_count > 5) {
c3nav._no_wifi_count = 0; c3nav._no_scan_count = 0;
c3nav._set_user_location(null); c3nav._set_user_location(null);
} else { } else {
c3nav._no_wifi_count++; c3nav._no_scan_count++;
} }
} }
return; return;
} }
c3nav._no_wifi_count = 0; c3nav._no_scan_count = 0;
c3nav_api.post('positioning/locate/', {peers}) let ibeacon_peers = c3nav._last_ibeacon_peers.map(p => ({...p}));
for (let peer of ibeacon_peers) {
peer.last_seen_ago = Math.max(0, now - peer.last_seen);
}
c3nav_api.post('positioning/locate/', {
wifi_peers: c3nav._last_wifi_peers,
ibeacon_peers: ibeacon_peers,
})
.then(data => c3nav._set_user_location(data.location)) .then(data => c3nav._set_user_location(data.location))
.catch(() => { .catch(() => {
c3nav._set_user_location(null); c3nav._set_user_location(null);
c3nav._last_wifi_scan = Date.now() + 20000 c3nav._last_scan = Date.now() + 20000
}); });
}, },
_current_user_location: null, _current_user_location: null,
@ -2027,6 +2053,10 @@ function nearby_stations_available() {
c3nav._wifi_scan_results(mobileclient.getNearbyStations()); c3nav._wifi_scan_results(mobileclient.getNearbyStations());
} }
function ibeacon_results_available() {
c3nav._ibeacon_scan_results(mobileclient.getNearbyBeacons());
}
function openInModal(location) { function openInModal(location) {
c3nav.open_modal(); c3nav.open_modal();
$.get(location, c3nav._modal_loaded).fail(c3nav._modal_error); $.get(location, c3nav._modal_loaded).fail(c3nav._modal_error);