mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-09 22:13:43 +08:00
a531afd02a
Expose pairing readiness and coordinate adapter hand-offs with Android Auto. Show app pairings separately, support individual removal, and improve pairing controls on both comma screens. Add Galaxy companion endpoints and web asset manifests, preserve the plain camera look, and fix headless speed-limit shapes.
99 lines
3.6 KiB
Python
99 lines
3.6 KiB
Python
"""Client for android_autod's local control socket (used by the settings UI)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import socket
|
|
from typing import Any
|
|
|
|
ANDROID_AUTO_SOCKET_PATH = "/tmp/starpilot-android-auto.sock"
|
|
COMMAND_TIMEOUTS = {"stop": 12.0, "prepare_pairing": 20.0, "devices": 10.0, "select_receiver": 10.0, "bluetooth_action": 25.0, "bluetooth_power": 25.0,
|
|
# Opening a window may wait on a slow BlueZ right after boot, and a closed one is given time to wind down.
|
|
"phone_window": 25.0,
|
|
"forget_receiver": 15.0}
|
|
|
|
|
|
class AndroidAutoClient:
|
|
def __init__(self, socket_path: str = ANDROID_AUTO_SOCKET_PATH, timeout: float = 5.0):
|
|
self.socket_path = socket_path
|
|
self.timeout = timeout
|
|
|
|
@property
|
|
def available(self) -> bool:
|
|
return os.path.exists(self.socket_path)
|
|
|
|
def call(self, command: str, **payload: Any) -> dict[str, Any]:
|
|
if not self.available:
|
|
raise RuntimeError("Android Auto service is not running (turn on Bluetooth)")
|
|
request = json.dumps({"command": command, **payload}, separators=(",", ":")).encode() + b"\n"
|
|
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as sock:
|
|
sock.settimeout(COMMAND_TIMEOUTS.get(command, self.timeout))
|
|
sock.connect(self.socket_path)
|
|
sock.sendall(request)
|
|
response = bytearray()
|
|
while not response.endswith(b"\n"):
|
|
chunk = sock.recv(65536)
|
|
if not chunk:
|
|
break
|
|
response.extend(chunk)
|
|
if not response:
|
|
raise RuntimeError("Android Auto service returned no response")
|
|
result = json.loads(response)
|
|
if not result.get("ok", False):
|
|
raise RuntimeError(str(result.get("error", "Android Auto operation failed")))
|
|
return result
|
|
|
|
def status(self) -> dict[str, Any]:
|
|
return self.call("status").get("status", {})
|
|
|
|
def start(self) -> None:
|
|
self.call("start")
|
|
|
|
def stop(self) -> None:
|
|
self.call("stop")
|
|
|
|
def select_receiver(self, address: str, name: str = "") -> None:
|
|
self.call("select_receiver", address=address, name=name)
|
|
|
|
def set_view(self, view: str) -> None:
|
|
self.call("set_view", view=view)
|
|
|
|
def set_connection(self, connection: str) -> None:
|
|
self.call("set_connection", connection=connection)
|
|
|
|
def prepare_pairing(self) -> None:
|
|
self.call("prepare_pairing")
|
|
|
|
def set_auto_connect(self, enabled: bool) -> None:
|
|
self.call("set_auto_connect", enabled=enabled)
|
|
|
|
def devices(self) -> list[dict[str, Any]]:
|
|
return list(self.call("devices").get("devices", []))
|
|
|
|
|
|
def forget_car(address: str, client: AndroidAutoClient | None = None) -> bool:
|
|
"""Clear Android Auto's chosen car (or companion) after its pairing was deleted; True if it was the chosen car.
|
|
|
|
Goes through android_autod when it runs, so its in-memory settings stay in step. Otherwise nothing
|
|
holds the settings, and the saved file is edited directly.
|
|
"""
|
|
client = client or AndroidAutoClient()
|
|
if client.available:
|
|
return bool(client.call("forget_receiver", address=address).get("cleared"))
|
|
from openpilot.starpilot.system.android_auto import identity
|
|
address = address.strip().upper()
|
|
config = identity.load_config()
|
|
chosen = config["receiver_address"].upper() == address
|
|
companion = config["companion_address"].upper() == address
|
|
cache = dict(config["rfcomm_cache"])
|
|
if cache.pop(address, None) is None and not (chosen or companion):
|
|
return False
|
|
config["rfcomm_cache"] = cache
|
|
if chosen:
|
|
config.update(receiver_address="", receiver_name="", companion_address="", companion_name="")
|
|
elif companion:
|
|
config.update(companion_address="", companion_name="")
|
|
identity.save_config(config)
|
|
return chosen
|