From 99a9025afe397a7922f657bca8a4ecb9b1fc5ae4 Mon Sep 17 00:00:00 2001 From: dirwin31 <83434411+dirwin31@users.noreply.github.com> Date: Sun, 1 Nov 2026 14:18:06 -0800 Subject: [PATCH] Report how the channel was found and what the ladder tried; wait longer for a slow car Reports carry the SDP outcome (where the RFCOMM channel came from and how many retries it took). The Discord post names the handshake options tried and how each ended. A car that once needed 70 s to be ready now gets that many stage timeouts to answer the Wi-Fi handshake, never fewer than the standard wait and capped at nine. --- openpilot/starpilot/galaxy/starport_send.py | 15 ++++++++++++++- .../starpilot/galaxy/tests/test_starport_send.py | 1 + .../starpilot/system/starport/bugsink_report.py | 12 ++++++++++++ .../starpilot/system/starport/setup_ladder.py | 8 ++++++++ openpilot/starpilot/system/starport/supervisor.py | 4 +++- .../system/starport/tests/test_bugsink_report.py | 8 ++++++++ .../system/starport/tests/test_setup_ladder.py | 10 ++++++++++ 7 files changed, 56 insertions(+), 2 deletions(-) diff --git a/openpilot/starpilot/galaxy/starport_send.py b/openpilot/starpilot/galaxy/starport_send.py index 52ad1e0371..fc1045dbe4 100644 --- a/openpilot/starpilot/galaxy/starport_send.py +++ b/openpilot/starpilot/galaxy/starport_send.py @@ -104,10 +104,22 @@ def _setup(log_dir: Path | None, logs_to_check: int = 3) -> str: return "" +def _handshake(log_dir: Path | None, logs_to_check: int = 3) -> str: + """What the setup ladder tried in the newest log that recorded it, e.g. "standard (advanced) > other_reply (worked)".""" + from openpilot.starpilot.system.starport import compat_report + from openpilot.starpilot.system.starport.bugsink_report import session_ladder + for path in compat_report.session_logs(log_dir or identity_store.LOG_DIR)[:logs_to_check]: + tried = session_ladder(compat_report.load_events(path)) + if tried: + return " > ".join(f"{item['rung']}{' learned' if item.get('learned') else ''} ({item.get('outcome', 'tried')})" for item in tried[-6:]) + return "" + + def device_summary(log_dir: Path | None = None) -> dict: from openpilot.starpilot.system.starport.bugsink_transport import device_metadata summary = device_metadata() - for key, read in (("car_fingerprint", _car_fingerprint), ("head_unit", lambda: _head_unit(log_dir)), ("setup", lambda: _setup(log_dir))): + for key, read in (("car_fingerprint", _car_fingerprint), ("head_unit", lambda: _head_unit(log_dir)), ("setup", lambda: _setup(log_dir)), + ("handshake", lambda: _handshake(log_dir))): try: summary[key] = read() except Exception as error: @@ -163,6 +175,7 @@ def send(data: bytes, name: str, note: str, summary: dict, url: str | None = Non f"Car: {summary.get('car_fingerprint') or 'unknown'}", f"Head unit: {summary.get('head_unit') or 'unknown'}", f"Setup: {summary.get('setup') or 'unknown'}", + f"Handshake: {summary.get('handshake') or 'unknown'}", f"Branch {summary.get('branch', '?')} @ {str(summary.get('commit', '?'))[:10]}", f"Note: {note.strip() or '(none)'}"] payload = {"content": "\n".join(lines)[:1900], "username": "StarPilot diagnostics", "allowed_mentions": {"parse": []}} diff --git a/openpilot/starpilot/galaxy/tests/test_starport_send.py b/openpilot/starpilot/galaxy/tests/test_starport_send.py index 8a24310d8a..59e086efee 100644 --- a/openpilot/starpilot/galaxy/tests/test_starport_send.py +++ b/openpilot/starpilot/galaxy/tests/test_starport_send.py @@ -61,6 +61,7 @@ class SendTest(unittest.TestCase): self.assertIn("Note: Dropped at the light", call["payload"]["content"]) self.assertIn("Head unit: Honda Display Audio", call["payload"]["content"]) self.assertIn("Setup: unknown", call["payload"]["content"]) # no session log recorded a setup in this fixture + self.assertIn("Handshake: unknown", call["payload"]["content"]) self.assertNotIn(SUMMARY["device_id"], call["payload"]["content"]) self.assertEqual(query(call), {"wait": "true"}) self.assertEqual(call["files"]["files[0]"], ("logs.zip", b"zip", "application/zip")) diff --git a/openpilot/starpilot/system/starport/bugsink_report.py b/openpilot/starpilot/system/starport/bugsink_report.py index 40d0d6e967..c859969afa 100644 --- a/openpilot/starpilot/system/starport/bugsink_report.py +++ b/openpilot/starpilot/system/starport/bugsink_report.py @@ -190,6 +190,17 @@ def offered_modes(events: list[dict]) -> list[str]: return modes[:MAX_OFFERED_MODES] +def session_sdp(events: list[dict]) -> dict: + """How the car's Android Auto RFCOMM channel was found: where it came from, and how many SDP retries it took.""" + found = next((event for event in events if event["event"] == "rfcomm_channel"), {}) + retries = sum(1 for event in events if event["event"] == "sdp_retry") + if not found and not retries: + return {} + return {key: value for key, value in (("channel", found.get("channel") if isinstance(found.get("channel"), int) else None), + ("source", str(found["source"])[:12] if found.get("source") else None), + ("retries", retries or None)) if value is not None} + + def session_setup(events: list[dict]) -> dict: """The setup type and profile the owner chose, as recorded when the session started.""" start = next((event for event in events if event["event"] == "session_start"), {}) @@ -265,6 +276,7 @@ def session_record(path: Path) -> dict: "phone_address_sent": bool(opened.get("phone_address")) if opened is not None else None, "strategy": session_strategy(events, setup), "ladder": session_ladder(events), + "sdp": session_sdp(events), "software": {key: str(software.get(key) or "unknown") for key in ("commit", "branch")} if isinstance(software, dict) else {}, "settings": settings if isinstance(settings, dict) else {}, "seq": _session_number(path), diff --git a/openpilot/starpilot/system/starport/setup_ladder.py b/openpilot/starpilot/system/starport/setup_ladder.py index cb68e094c6..49f696f7dc 100644 --- a/openpilot/starpilot/system/starport/setup_ladder.py +++ b/openpilot/starpilot/system/starport/setup_ladder.py @@ -239,6 +239,14 @@ class SetupLadder: entry["t_ready"] = (entry["t_ready"] + [round(float(t_ready_s), 1)])[-8:] self.save() + def ready_patience(self, address: str, stage_timeout: float = 20.0) -> int: + """Stage timeouts a slow car needs: it was ready after t_ready_s once, so the bootstrap must be willing to wait that long. + + Never less than the standard patience, and 0 (no opinion) for a car nothing is known about.""" + entry = self.cars.get(address.upper()) + times = entry["t_ready"] if entry else [] + return min(9, int(-(-max(times) // stage_timeout))) if times else 0 + def snapshot(self, address: str, adapter: bool = False) -> dict: """What the setup screen and the reports show: every rung, its state, and whether this car has a learned strategy.""" entry = self.cars.get(address.upper()) if address else None diff --git a/openpilot/starpilot/system/starport/supervisor.py b/openpilot/starpilot/system/starport/supervisor.py index 1e559175d5..7089f827ae 100644 --- a/openpilot/starpilot/system/starport/supervisor.py +++ b/openpilot/starpilot/system/starport/supervisor.py @@ -1341,7 +1341,9 @@ class Supervisor: boot = WirelessBootstrap(rfcomm, self._bootstrap_log, device_serial=config["device_name"], version_status=int(config.get("version_status", 0)), experiment="all" if car_profiles.tuning(config)["bootstrap_experiments"] or strategy.experiments else "", - modern_reply=strategy.modern_reply, patience=strategy.patience, channel_override=strategy.channel_type) + modern_reply=strategy.modern_reply, channel_override=strategy.channel_type, + # a car that once took 70 s to be ready gets more than the standard wait for the Wi-Fi handshake + patience=max(strategy.patience, self._ladder.ready_patience(address, 20.0))) self._set(state="wifi_start") result = boot.run(lambda credentials: lease.acquire(credentials, cancelled=boot.join_is_cancelled), cancelled=self._stop.is_set) except Exception as error: diff --git a/openpilot/starpilot/system/starport/tests/test_bugsink_report.py b/openpilot/starpilot/system/starport/tests/test_bugsink_report.py index 2e300c8974..8f59569e23 100644 --- a/openpilot/starpilot/system/starport/tests/test_bugsink_report.py +++ b/openpilot/starpilot/system/starport/tests/test_bugsink_report.py @@ -506,3 +506,11 @@ def test_report_shows_what_the_ladder_tried_and_what_worked(tmp_path): tags = br.drive_event([record])["tags"] assert (tags["aa.ladder_rung"], tags["aa.ladder_learned"]) == ("other_reply", "no") assert br.session_record(write_session(tmp_path, 2, STREAMED))["ladder"] == [] # older logs report nothing, without error + + +def test_report_says_how_the_rfcomm_channel_was_found(tmp_path): + events = [list(item) for item in STREAMED] + events.insert(1, (0, 1, "sdp_retry", {"error": "x"})) + events.insert(2, (0, 2, "rfcomm_channel", {"channel": 8, "source": "sdp"})) + assert br.session_record(write_session(tmp_path, 1, events))["sdp"] == {"channel": 8, "source": "sdp", "retries": 1} + assert br.session_record(write_session(tmp_path, 2, STREAMED))["sdp"] == {} diff --git a/openpilot/starpilot/system/starport/tests/test_setup_ladder.py b/openpilot/starpilot/system/starport/tests/test_setup_ladder.py index be0826f4b8..1ba7e0f6e0 100644 --- a/openpilot/starpilot/system/starport/tests/test_setup_ladder.py +++ b/openpilot/starpilot/system/starport/tests/test_setup_ladder.py @@ -211,3 +211,13 @@ def test_forgetting_a_car_forgets_what_was_learned_about_it(sup): sup._ladder.worked(RECEIVER, sl.resolve("patient", False), 10.0) assert sup.forget_receiver(RECEIVER) is True assert not sup._ladder.snapshot(RECEIVER)["learned"] and RECEIVER not in sup._ladder.cars + + +def test_a_slow_car_gets_the_patience_it_once_needed_and_an_unknown_car_gets_no_opinion(ladder): + assert ladder.ready_patience(CAR) == 0 + ladder.worked(CAR, sl.resolve("standard", False), 14.0) + assert ladder.ready_patience(CAR) == 1 + ladder.worked(CAR, sl.resolve("standard", False), 70.0) + assert ladder.ready_patience(CAR) == 4 # 70 s at 20 s per stage timeout + ladder.worked(CAR, sl.resolve("standard", False), 3000.0) + assert ladder.ready_patience(CAR) == 9 # capped