diff --git a/openpilot/starpilot/system/starport/auto_connect.py b/openpilot/starpilot/system/starport/auto_connect.py index 35bdc3ee30..51355b52d8 100644 --- a/openpilot/starpilot/system/starport/auto_connect.py +++ b/openpilot/starpilot/system/starport/auto_connect.py @@ -42,12 +42,12 @@ class AutoConnectPolicy: def start_refused(self, now: float) -> None: self._not_before = now + START_REFUSED_WAIT - def decide(self, now: float, *, enabled: bool, onroad: bool, car_link: bool, ready: bool, running: bool) -> str | None: + def decide(self, now: float, *, enabled: bool, onroad: bool, car_link: bool, ready: bool, running: bool, linked: bool = False) -> str | None: """Return "start", "stop" or None.""" if onroad and not self._was_onroad: self.suppressed = False # a new drive self._was_onroad = onroad - present = onroad or car_link + present = onroad or car_link or (running and linked) if running: if present: diff --git a/openpilot/starpilot/system/starport/car_profiles.py b/openpilot/starpilot/system/starport/car_profiles.py index faeda26273..f04b7b4a9d 100644 --- a/openpilot/starpilot/system/starport/car_profiles.py +++ b/openpilot/starpilot/system/starport/car_profiles.py @@ -2,13 +2,13 @@ import re DEFAULTS = {"connect_delay_s": 0.0, "max_window": 2, "phone_services": False, "phone_frames": False, - "bootstrap_experiments": False} + "bootstrap_experiments": False, "stay_while_projecting": False} FINGERPRINT_KEYS = ("head_unit_make", "head_unit_model", "car_make", "car_model", "car_year") RULES: list[dict] = [ {"id": "aawireless", "label": "AAWireless", "match": {}, - "tuning": {"max_window": 30, "phone_services": True, "phone_frames": True}}, + "tuning": {"max_window": 30, "phone_services": True, "phone_frames": True, "stay_while_projecting": True}}, {"id": "unofficial", "label": "Unofficial dongle", "match": {}, "tuning": {"bootstrap_experiments": True}}, ] diff --git a/openpilot/starpilot/system/starport/supervisor.py b/openpilot/starpilot/system/starport/supervisor.py index bad64b1df4..82ff4dd80b 100644 --- a/openpilot/starpilot/system/starport/supervisor.py +++ b/openpilot/starpilot/system/starport/supervisor.py @@ -930,7 +930,8 @@ class Supervisor: self._set(error=f'Waiting for selected car: {error}'[:300]) car_link = bool(car_device and car_device["connected"]) or now - self._car_seen_at < CAR_LINK_HOLD onroad = self._onroad() - action = self.auto.decide(now, enabled=True, onroad=onroad, car_link=car_link, + linked = car_profiles.tuning(self.config)["stay_while_projecting"] and self._status.get("state") in ("streaming", "suspended") + action = self.auto.decide(now, enabled=True, onroad=onroad, car_link=car_link, linked=linked, ready=adapter_ready and bool(device and device["paired"]) and (not companion or car_link), running=running) self._apply_auto_action(now, action, onroad) diff --git a/openpilot/starpilot/system/starport/tests/test_auto_connect.py b/openpilot/starpilot/system/starport/tests/test_auto_connect.py index d86663f50c..73b88f451b 100644 --- a/openpilot/starpilot/system/starport/tests/test_auto_connect.py +++ b/openpilot/starpilot/system/starport/tests/test_auto_connect.py @@ -19,8 +19,8 @@ def identity(tmp_path_factory): return make_identity(tmp_path_factory.mktemp("identity")) -def decide(policy, now, onroad=False, car_link=False, ready=True, running=False, enabled=True): - return policy.decide(now, enabled=enabled, onroad=onroad, car_link=car_link, ready=ready, running=running) +def decide(policy, now, onroad=False, car_link=False, ready=True, running=False, enabled=True, linked=False): + return policy.decide(now, enabled=enabled, onroad=onroad, car_link=car_link, ready=ready, running=running, linked=linked) # -------------------------------------------------------------------- policy @@ -45,6 +45,16 @@ def test_policy_stops_after_car_gone(): assert decide(policy, 100) is None # stopped, car still gone: no restart +def test_policy_keeps_a_projecting_session_when_the_car_link_drops(): + policy = AutoConnectPolicy() + assert decide(policy, 0, car_link=True) == "start" + assert decide(policy, 1, car_link=True, running=True) is None + for now in range(2, 400, 10): + assert decide(policy, now, running=True, linked=True) is None + assert decide(policy, 400, running=True) is None # stream ended: grace period starts + assert decide(policy, 400 + auto_connect.STOP_AFTER, running=True) == "stop" + + def test_policy_leaves_sessions_that_never_saw_the_car(): policy = AutoConnectPolicy() for now in range(0, 300, 10):