From 31c7dad72fdf672a38ac099d8e90611a584ccbf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laura=20Kl=C3=BCnder?= Date: Sun, 31 Mar 2024 15:26:44 +0200 Subject: [PATCH] add ibeacon scan code to c3nav.js and endpoint --- src/c3nav/editor/forms.py | 4 +- src/c3nav/routing/api/positioning.py | 9 ++-- src/c3nav/routing/locator.py | 6 +-- src/c3nav/routing/schemas.py | 25 +++++++++- src/c3nav/site/static/site/js/c3nav.js | 64 +++++++++++++++++++------- 5 files changed, 82 insertions(+), 26 deletions(-) diff --git a/src/c3nav/editor/forms.py b/src/c3nav/editor/forms.py index b5e2e7fa..d6d3dec8 100644 --- a/src/c3nav/editor/forms.py +++ b/src/c3nav/editor/forms.py @@ -25,7 +25,7 @@ from c3nav.mapdata.models import GraphEdge, LocationGroup from c3nav.mapdata.models.access import AccessPermission from c3nav.mapdata.models.geometry.space import ObstacleGroup from c3nav.mapdata.models.theme import ThemeLocationGroupBackgroundColor, ThemeObstacleGroupBackgroundColor -from c3nav.routing.schemas import LocateRequestPeerSchema +from c3nav.routing.schemas import LocateRequestWifiPeerSchema class EditorFormBase(I18nModelFormMixin, ModelForm): @@ -340,7 +340,7 @@ class EditorFormBase(I18nModelFormMixin, ModelForm): item['rssi'] = item['level'] del item['level'] try: - LocateRequestPeerSchema.model_validate(item) + LocateRequestWifiPeerSchema.model_validate(item) except PydanticValidationError as e: raise ValidationError(str(e)) scan_data.append(item) diff --git a/src/c3nav/routing/api/positioning.py b/src/c3nav/routing/api/positioning.py index e4bb22ce..40968077 100644 --- a/src/c3nav/routing/api/positioning.py +++ b/src/c3nav/routing/api/positioning.py @@ -11,14 +11,17 @@ 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 BSSIDSchema, LocateRequestPeerSchema +from c3nav.routing.schemas import BSSIDSchema, LocateRequestWifiPeerSchema, LocateRequestIBeaconPeerSchema positioning_api_router = APIRouter(tags=["positioning"]) class LocateRequestSchema(BaseSchema): - peers: list[LocateRequestPeerSchema] = APIField( - title="list of visible/measured location beacons", + wifi_peers: list[LocateRequestWifiPeerSchema] = APIField( + title="list of visible/measured wifi location beacons", + ) + ibeacon_peers: list[LocateRequestIBeaconPeerSchema] = APIField( + title="list of visible/measured location iBeacons", ) diff --git a/src/c3nav/routing/locator.py b/src/c3nav/routing/locator.py index 7f7f7fd1..166c9b93 100644 --- a/src/c3nav/routing/locator.py +++ b/src/c3nav/routing/locator.py @@ -12,7 +12,7 @@ from c3nav.mapdata.models import MapUpdate, Space from c3nav.mapdata.models.geometry.space import RangingBeacon from c3nav.mapdata.utils.locations import CustomLocation from c3nav.routing.router import Router -from c3nav.routing.schemas import LocateRequestPeerSchema +from c3nav.routing.schemas import LocateRequestWifiPeerSchema try: from asgiref.local import Local as LocalContext @@ -146,7 +146,7 @@ class Locator: cls.cached.data = cls.load_nocache(update) 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) 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)] } - 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) if not scan_data: return None diff --git a/src/c3nav/routing/schemas.py b/src/c3nav/routing/schemas.py index 455bdbdd..718e3467 100644 --- a/src/c3nav/routing/schemas.py +++ b/src/c3nav/routing/schemas.py @@ -1,7 +1,10 @@ from typing import Annotated, Union +from uuid import UUID +from annotated_types import Lt from pydantic import Field as APIField from pydantic import NegativeInt, PositiveInt +from pydantic.types import NonNegativeInt, PositiveFloat, NonNegativeFloat from c3nav.api.schema import BaseSchema 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")] -class LocateRequestPeerSchema(BaseSchema): +class LocateRequestWifiPeerSchema(BaseSchema): bssid: BSSIDSchema = APIField( title="BSSID", description="BSSID of the peer", @@ -61,3 +64,23 @@ class LocateRequestPeerSchema(BaseSchema): description="standard deviation of measurements in meters", 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" + ) diff --git a/src/c3nav/site/static/site/js/c3nav.js b/src/c3nav/site/static/site/js/c3nav.js index cbc7a617..3a4ab4d9 100644 --- a/src/c3nav/site/static/site/js/c3nav.js +++ b/src/c3nav/site/static/site/js/c3nav.js @@ -258,6 +258,7 @@ c3nav = { if (window.mobileclient) { c3nav.startWifiScanning(); + c3nav.startBLEScanning(); } 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) { 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) { peers = peers.filter(peer => c3nav.ssids.includes(peer.ssid)); } @@ -1857,28 +1858,53 @@ c3nav = { 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) { c3nav._set_user_location(null); } else { - if (c3nav._no_wifi_count > 5) { - c3nav._no_wifi_count = 0; + if (c3nav._no_scan_count > 5) { + c3nav._no_scan_count = 0; c3nav._set_user_location(null); } else { - c3nav._no_wifi_count++; + c3nav._no_scan_count++; } } 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)) .catch(() => { c3nav._set_user_location(null); - c3nav._last_wifi_scan = Date.now() + 20000 + c3nav._last_scan = Date.now() + 20000 }); }, _current_user_location: null, @@ -2027,6 +2053,10 @@ function nearby_stations_available() { c3nav._wifi_scan_results(mobileclient.getNearbyStations()); } +function ibeacon_results_available() { + c3nav._ibeacon_scan_results(mobileclient.getNearbyBeacons()); +} + function openInModal(location) { c3nav.open_modal(); $.get(location, c3nav._modal_loaded).fail(c3nav._modal_error);