Files
StarPilot/openpilot/starpilot/controllers/tests/test_selfie.py
T
firestar5683 fda22037d7 Wheel Controls
Route controller input and cruise actions through shared transport and mode-action owners.

Co-authored-by: Dom <168790843+firestar5683@users.noreply.github.com>
2026-10-14 20:02:39 -05:00

127 lines
4.6 KiB
Python

import threading
from types import SimpleNamespace
from opendbc.car.honda.interface import CarInterface
from opendbc.car.honda.values import CAR
from openpilot.starpilot.controllers.selfie import SelfieCapture, authority
from openpilot.starpilot.controllers.wheel_actions import cp_fingerprint
from openpilot.starpilot.galaxy.camera_snapshot import SnapshotUnavailable
from openpilot.starpilot.galaxy.sentry_events import SentryEvents
from openpilot.starpilot.sentry_mode.storage import EventStore
class Camera:
def __init__(self):
self.entered = threading.Event()
self.release = threading.Event()
self.done = threading.Event()
def capture(self, name, *, permitted):
assert name == 'cabin'
self.entered.set()
assert self.release.wait(2)
if not permitted():
raise SnapshotUnavailable('Authority lost')
return b'\xff\xd8fixture\xff\xd9'
class Store(EventStore):
def __init__(self, root):
super().__init__(root)
self.done = threading.Event()
def record(self, *args, **kwargs):
try:
return super().record(*args, **kwargs)
finally:
self.done.set()
def test_async_capture_records_real_index_and_galaxy_image(tmp_path):
camera, store = Camera(), Store(tmp_path / 'events')
owner = SelfieCapture(camera=camera, store=store, clock=lambda: 100)
assert owner.submit(lambda: True)
assert camera.entered.wait(2)
assert owner.busy
assert not owner.submit(lambda: True)
camera.release.set()
assert store.done.wait(2)
owner.worker.join(2)
assert not owner.busy
events = SentryEvents(store).snapshot()['events']
assert len(events) == 1
assert events[0]['kind'] == 'selfie'
assert events[0]['images'] == ['cabin']
assert SentryEvents(store).image(events[0]['eventId'], 'cabin') == b'\xff\xd8fixture\xff\xd9'
def test_denied_or_revoked_capture_never_publishes_event(tmp_path):
camera, store = Camera(), Store(tmp_path / 'events')
owner = SelfieCapture(camera=camera, store=store)
allowed = [False]
assert not owner.submit(lambda: allowed[0])
assert not camera.entered.is_set()
allowed[0] = True
assert owner.submit(lambda: allowed[0])
assert camera.entered.wait(2)
allowed[0] = False
camera.release.set()
owner.worker.join(2)
assert not owner.busy
assert SentryEvents(store).snapshot()['events'] == []
def test_actual_factory_identity_and_fresh_drive_sources_admit_only_same_session():
cp = CarInterface.get_non_essential_params(CAR.HONDA_CIVIC)
drive, now = 1_000_000_000, 2_000_000_000
class Messages:
def __getitem__(self, name):
return self.data[name]
sm = Messages()
sm.data = {'deviceState': SimpleNamespace(started=True, startedMonoTime=drive),
'carState': SimpleNamespace(canValid=True, canTimeout=False)}
sm.logMonoTime = dict.fromkeys(sm.data, now - 10_000_000)
sm.recv_time = dict.fromkeys(sm.data, (now - 10_000_000) / 1e9)
sm.seen = sm.alive = sm.valid = dict.fromkeys(sm.data, True)
args = {'drive_id': drive, 'fingerprint': cp_fingerprint(cp), 'now_ns': now}
assert authority(sm, cp, **args)
sm.data['deviceState'].startedMonoTime = drive + 1
assert not authority(sm, cp, **args)
sm.data['deviceState'].startedMonoTime = drive
sm.data['carState'].canValid = False
assert not authority(sm, cp, **args)
sm.data['carState'].canValid = True
assert not authority(sm, cp, **{**args, 'now_ns': now + 300_000_000})
cp.dashcamOnly = True
assert not authority(sm, cp, **args)
def test_selfie_is_local_even_when_motion_notifications_are_configured(tmp_path):
from openpilot.starpilot.sentry_mode.notifications import NotificationOwner
store = EventStore(tmp_path / 'events')
calls, clock = [], [1.]
owner = NotificationOwner(tmp_path / 'notifications', store,
clock=lambda: clock[0], transport=lambda *args: calls.append(args) or 200)
try:
owner.action({'action': 'configure', 'channel': 'ntfy', 'enabled': True,
'url': 'https://example.test/events', 'token': ''})
store.record('selfie', 100, permitted=lambda: True, images={'cabin': b'\xff\xd8fixture\xff\xd9'})
clock[0] = store.snapshot()["events"][0]["wallTimeNs"] / 1e9 + 1
owner.tick()
assert not calls
assert owner.state['jobs'] == {}
finally:
owner.close()
def test_no_fresh_camera_frame_releases_worker_without_record(tmp_path):
class MissingCamera:
def capture(self, name, *, permitted):
raise SnapshotUnavailable('No fresh camera frame')
store = EventStore(tmp_path / 'events')
owner = SelfieCapture(camera=MissingCamera(), store=store)
assert owner.submit(lambda: True)
owner.worker.join(2)
assert not owner.busy and owner.last_result is None
assert SentryEvents(store).snapshot()['events'] == []