mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-06 20:43:46 +08:00
fda22037d7
Route controller input and cruise actions through shared transport and mode-action owners. Co-authored-by: Dom <168790843+firestar5683@users.noreply.github.com>
127 lines
4.6 KiB
Python
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'] == []
|