mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-08 13:33:42 +08:00
d08cd1b08b
Serve the Galaxy app key and its QR code from /api/galaxy/session to direct local connections so the Android app can import the tunnel key on its own. Let the app and the iOS app register for Sentry push alerts.
70 lines
3.0 KiB
Python
70 lines
3.0 KiB
Python
"""Bounded projection of local Sentry motion metadata."""
|
|
|
|
import json
|
|
import re
|
|
from collections.abc import Callable
|
|
from typing import Protocol, cast
|
|
|
|
from openpilot.starpilot.sentry_mode.storage import EventStore, StorageUnavailable
|
|
|
|
|
|
MAX_RESPONSE_BYTES = 128 * 1024
|
|
ID = re.compile(r"[0-9a-f]{32}\Z")
|
|
|
|
|
|
class SentryEventsUnavailable(Exception):
|
|
pass
|
|
|
|
|
|
class EventSnapshotSource(Protocol):
|
|
def snapshot(self) -> dict: ...
|
|
|
|
|
|
class EventDeletionSource(Protocol):
|
|
def delete(self, event_id: str | None = None, *, permitted: Callable[[], bool]) -> dict: ...
|
|
|
|
|
|
class SentryEvents:
|
|
def __init__(self, store: EventSnapshotSource | None = None):
|
|
self.store = store if store is not None else EventStore()
|
|
|
|
def snapshot(self) -> dict:
|
|
try:
|
|
source = self.store.snapshot()
|
|
if (type(source) is not dict or type(source.get("version")) is not int or source["version"] != 1 or
|
|
type(source.get("incomplete")) is not bool or type(source.get("capacity")) is not int or source["capacity"] != 512 or
|
|
type(source.get("events")) is not list or len(source["events"]) > 512):
|
|
raise ValueError("Invalid motion inventory")
|
|
events = []
|
|
for value in source["events"]:
|
|
if (type(value) is not dict or type(value.get("eventId")) is not str or
|
|
ID.fullmatch(value["eventId"]) is None or type(value.get("kind")) is not str or
|
|
value["kind"] not in ("warning", "alarm", "selfie") or
|
|
type(value.get("wallTimeNs")) is not int or not 0 < value["wallTimeNs"] < 2**63):
|
|
raise ValueError("Invalid motion event")
|
|
events.append({"eventId": value["eventId"], "kind": value["kind"],
|
|
"systemTimeMs": value["wallTimeNs"] // 1_000_000,
|
|
**({"simulated": True} if value.get("simulated") else {}),
|
|
"images": cast(Callable[[str], list], self.store.images)(value["eventId"]) if hasattr(self.store, "images") else []})
|
|
result = {"schemaVersion": 1, "source": "local", "scanIncomplete": source["incomplete"],
|
|
"capacity": source["capacity"], "events": events,
|
|
"storageBytes": source.get("storageBytes", 0), "storageIncomplete": source.get("storageIncomplete", False)}
|
|
if len(json.dumps(result, separators=(",", ":")).encode()) > MAX_RESPONSE_BYTES:
|
|
raise ValueError("Motion response exceeds limit")
|
|
return result
|
|
except (StorageUnavailable, OSError, ValueError, TypeError, OverflowError, RecursionError) as error:
|
|
raise SentryEventsUnavailable from error
|
|
|
|
def image(self, event_id, camera):
|
|
try:
|
|
return self.store.image(event_id, camera)
|
|
except (OSError, StorageUnavailable, AttributeError) as error:
|
|
raise SentryEventsUnavailable from error
|
|
|
|
def delete(self, event_id=None, *, permitted=lambda: True):
|
|
try:
|
|
result = cast(EventDeletionSource, self.store).delete(event_id, permitted=permitted)
|
|
return {**self.snapshot(), **result}
|
|
except (StorageUnavailable, OSError, AttributeError) as error:
|
|
raise SentryEventsUnavailable from error
|