mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-08 13:33:42 +08:00
99 lines
3.6 KiB
Python
99 lines
3.6 KiB
Python
"""Client for starportd's local control socket (used by the settings UI)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import socket
|
|
from typing import Any
|
|
|
|
STARPORT_SOCKET_PATH = "/tmp/starpilot-starport.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 StarPortClient:
|
|
def __init__(self, socket_path: str = STARPORT_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: StarPortClient | 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 starportd 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 StarPortClient()
|
|
if client.available:
|
|
return bool(client.call("forget_receiver", address=address).get("cleared"))
|
|
from openpilot.starpilot.system.starport 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
|