From dff446fe5e3c7215abf291b39845e8081ff53b76 Mon Sep 17 00:00:00 2001 From: firestar5683 <168790843+firestar5683@users.noreply.github.com> Date: Tue, 1 Sep 2026 14:32:13 -0500 Subject: [PATCH] Fix Bluetooth pairing --- launch_env.sh | 2 +- starpilot/system/bluetooth/bluez.py | 7 +++++- starpilot/system/bluetooth/daemon.py | 20 ++++++++++++---- .../system/bluetooth/tests/test_bluetooth.py | 24 +++++++++++++++++-- system/hardware/tici/agnos.json | 20 ++++++++-------- 5 files changed, 55 insertions(+), 18 deletions(-) diff --git a/launch_env.sh b/launch_env.sh index f2c15f65b..79e9cf9ee 100755 --- a/launch_env.sh +++ b/launch_env.sh @@ -21,7 +21,7 @@ fi export QCOM_PRIORITY=12 if [ -z "$AGNOS_VERSION" ]; then - export AGNOS_VERSION="19.6.15" + export AGNOS_VERSION="19.6.16" fi if [ -z "$AGNOS_ACCEPTED_VERSIONS" ]; then diff --git a/starpilot/system/bluetooth/bluez.py b/starpilot/system/bluetooth/bluez.py index a3765c775..f58c95a0d 100644 --- a/starpilot/system/bluetooth/bluez.py +++ b/starpilot/system/bluetooth/bluez.py @@ -38,6 +38,7 @@ class PairingAgent: self._response: tuple[bool, str] | None = None self._generation = 0 self._auto_accept_paths: set[str] = set() + self._auto_accept_incoming = False @property def prompt(self) -> dict[str, Any] | None: @@ -94,9 +95,13 @@ class PairingAgent: else: self._auto_accept_paths.discard(device_path) + def set_auto_accept_incoming(self, enabled: bool) -> None: + with self._condition: + self._auto_accept_incoming = enabled + def auto_accept(self, kind: str, device_path: str) -> bool: with self._condition: - return kind in {"confirmation", "authorization"} and device_path in self._auto_accept_paths + return kind in {"confirmation", "authorization"} and (self._auto_accept_incoming or device_path in self._auto_accept_paths) class BlueZClient: def __init__(self): diff --git a/starpilot/system/bluetooth/daemon.py b/starpilot/system/bluetooth/daemon.py index b755322d8..60499bfb3 100644 --- a/starpilot/system/bluetooth/daemon.py +++ b/starpilot/system/bluetooth/daemon.py @@ -58,12 +58,21 @@ class BluetoothController: self._radio.start() self._bluez = self._bluez_factory() self._bluez.set_powered(True) + self._bluez.agent.set_auto_accept_incoming(self._offroad()) try: self._bluez.set_discoverable(True) except Exception as error: cloudlog.warning(f"Bluetooth discoverability setup failed: {error}") return self._bluez + def initialize(self) -> None: + if not self.params.get_bool("BluetoothEnabled"): + return + try: + self._client() + except Exception: + cloudlog.exception("Bluetooth initialization failed") + def _reset_client(self) -> None: with self._lock: if self._bluez is not None: @@ -96,6 +105,7 @@ class BluetoothController: try: result.update(self._client().status()) result["available"] = True + self._bluez.agent.set_auto_accept_incoming(result["offroad"]) prompt = result.get("prompt") if prompt is not None and self._pairing_address: prompt["address"] = self._pairing_address @@ -111,9 +121,9 @@ class BluetoothController: if command in OFFROAD_COMMANDS and not self._offroad(): raise RuntimeError("Bluetooth settings can only be changed offroad") - def _pair_worker(self, address: str, device_path: str | None = None) -> None: + def _pair_worker(self, address: str) -> None: try: - self._client().pair(address, device_path) + self._client().pair(address) status = self._client().device_for_address(address) if status.get("audio") and not self.params.get("BluetoothAudioAddress", encoding="utf-8"): self.params.put("BluetoothAudioAddress", address) @@ -189,10 +199,11 @@ class BluetoothController: elif command == "pair": if self._pairing_address: raise RuntimeError("Another Bluetooth device is already pairing") - device = self._client().device_for_address(address) + self._client().device_for_address(address) + self._scan_deadline = 0.0 self._pairing_address = address self._pairing_error = "" - threading.Thread(target=self._pair_worker, args=(address, device["path"]), daemon=True).start() + threading.Thread(target=self._pair_worker, args=(address,), daemon=True).start() elif command == "connect": self._client().connect(address) elif command == "disconnect": @@ -290,6 +301,7 @@ def main() -> None: except FileNotFoundError: pass controller = BluetoothController() + threading.Thread(target=controller.initialize, daemon=True).start() threading.Thread(target=controller.maintain_connections, daemon=True).start() try: with BluetoothServer(BLUETOOTH_SOCKET_PATH, controller) as server: diff --git a/starpilot/system/bluetooth/tests/test_bluetooth.py b/starpilot/system/bluetooth/tests/test_bluetooth.py index 503855be4..6880e51ac 100644 --- a/starpilot/system/bluetooth/tests/test_bluetooth.py +++ b/starpilot/system/bluetooth/tests/test_bluetooth.py @@ -39,6 +39,9 @@ class FakeAgent: def __init__(self): self.responses = [] + def set_auto_accept_incoming(self, _enabled): + pass + def respond(self, prompt_id, accepted, value): self.responses.append((prompt_id, accepted, value)) return prompt_id == "prompt" @@ -230,6 +233,9 @@ def test_desktop_fake_bluetooth_cannot_activate_on_device(monkeypatch, tmp_path) def test_pairing_agent_accept_reject_and_timeout(): agent = PairingAgent() + agent.set_auto_accept_incoming(True) + assert agent.request("confirmation", "/incoming", "123456") == (True, "") + agent.set_auto_accept_incoming(False) result = [] worker = threading.Thread(target=lambda: result.append(agent.request("confirmation", "/device", "123456", timeout=1.0))) worker.start() @@ -253,6 +259,18 @@ def test_disabled_status_does_not_start_radio_or_bluez(): assert radio.starts == 0 and created == [] +def test_enabled_initialization_registers_bluetooth_agent_without_ui_poll(): + params = FakeParams(IsOffroad=True, BluetoothEnabled=True) + radio = FakeRadio() + created = [] + controller = BluetoothController(params, lambda: created.append(FakeBlueZ()) or created[-1], radio) + + controller.initialize() + + assert radio.starts == 1 and len(created) == 1 + assert created[0].powered + + def test_power_pair_audio_and_offroad_enforcement(): params = FakeParams(IsOffroad=True, BluetoothEnabled=False) radio = FakeRadio() @@ -401,7 +419,7 @@ def test_scan_stops_after_timeout(): assert not client.discovering and controller._scan_deadline == 0.0 -def test_pair_stops_discovery_before_starting_pair(): +def test_pair_keeps_discovery_until_pair_starts(): params = FakeParams(IsOffroad=True, BluetoothEnabled=True) client = FakeBlueZ() controller = BluetoothController(params, lambda: client, FakeRadio()) @@ -412,7 +430,9 @@ def test_pair_stops_discovery_before_starting_pair(): while not any(action[0] == "pair" for action in client.actions) and time.monotonic() < deadline: time.sleep(0.01) - assert client.actions.index(("stop_scan", "")) < client.actions.index(("pair", client.device["address"])) + pair_index = client.actions.index(("pair", client.device["address"])) + assert client.actions[-1] == ("stop_scan", "") + assert pair_index < len(client.actions) - 1 def test_audio_queue_is_nonblocking_and_falls_back(): diff --git a/system/hardware/tici/agnos.json b/system/hardware/tici/agnos.json index ab125ac52..7c90fd551 100644 --- a/system/hardware/tici/agnos.json +++ b/system/hardware/tici/agnos.json @@ -56,30 +56,30 @@ }, { "name": "boot", - "url": "https://files.firestar.link/x/n2jco20tm4oh/boot23.img.xz", + "url": "https://files.firestar.link/x/83cmdtzb2sav/boot25.img.xz", "fallback_urls": [ - "https://files-east.firestar.link/x/rjthk2ij2j8a/boot23.img.xz" + "https://files-east.firestar.link/x/o3r7dpay7yj5/boot25.img.xz" ], - "hash": "e0cae2edf9953ba5b94ffd868d74a9b8ab4fa253625e7120641b49d6e7052586", - "hash_raw": "e0cae2edf9953ba5b94ffd868d74a9b8ab4fa253625e7120641b49d6e7052586", + "hash": "846d9e41fe0eb3543a01cb9a55a6bb540e4097dd838ada890674b654bd97a49a", + "hash_raw": "846d9e41fe0eb3543a01cb9a55a6bb540e4097dd838ada890674b654bd97a49a", "size": 48302080, "sparse": false, "full_check": true, "has_ab": true, - "ondevice_hash": "e0cae2edf9953ba5b94ffd868d74a9b8ab4fa253625e7120641b49d6e7052586" + "ondevice_hash": "846d9e41fe0eb3543a01cb9a55a6bb540e4097dd838ada890674b654bd97a49a" }, { "name": "system", - "url": "https://files.firestar.link/x/5y3tzu7fic02/system23.img.xz", + "url": "https://files.firestar.link/x/ser56g04xioj/system25.img.xz", "fallback_urls": [ - "https://files-east.firestar.link/x/nuxu72xr6z2y/system23.img.xz" + "https://files-east.firestar.link/x/90fsutnuzy13/system25.img.xz" ], - "hash": "a896e86181114e32b028dc08a27c16571a9f58a120af50018d0a410323013ce7", - "hash_raw": "a896e86181114e32b028dc08a27c16571a9f58a120af50018d0a410323013ce7", + "hash": "f81ac06c0634ed0dadd5b4778f3b5052a2779d69669b7e70e21505c951ce6ccc", + "hash_raw": "f81ac06c0634ed0dadd5b4778f3b5052a2779d69669b7e70e21505c951ce6ccc", "size": 4718592000, "sparse": false, "full_check": false, "has_ab": true, - "ondevice_hash": "a896e86181114e32b028dc08a27c16571a9f58a120af50018d0a410323013ce7" + "ondevice_hash": "f81ac06c0634ed0dadd5b4778f3b5052a2779d69669b7e70e21505c951ce6ccc" } ]