Fix BLE companion transport reliability

This commit is contained in:
2026-08-01 00:46:59 +02:00
parent 7bbf7aa4cc
commit 13c9f5d983
4 changed files with 136 additions and 57 deletions
+35 -30
View File
@@ -21,8 +21,6 @@ DBUS_PROPERTIES = "org.freedesktop.DBus.Properties"
GATT_MANAGER = "org.bluez.GattManager1"
GATT_SERVICE = "org.bluez.GattService1"
GATT_CHARACTERISTIC = "org.bluez.GattCharacteristic1"
AD_MANAGER = "org.bluez.LEAdvertisingManager1"
ADVERTISEMENT = "org.bluez.LEAdvertisement1"
SERVICE_UUID = "6c7a0001-7c6d-4f74-9bb0-c5a4e5efc001"
RX_UUID = "6c7a0002-7c6d-4f74-9bb0-c5a4e5efc001"
@@ -103,19 +101,30 @@ class Characteristic(dbus.service.Object):
class TxCharacteristic(Characteristic):
def __init__(self, bus, service):
super().__init__(bus, 1, TX_UUID, ["notify"], service)
super().__init__(bus, 1, TX_UUID, ["indicate"], service)
self.notifying = False
self.queue = deque()
self.sending = False
@dbus.service.method(GATT_CHARACTERISTIC, in_signature="a{sv}")
def StartNotify(self, _options):
@dbus.service.method(GATT_CHARACTERISTIC)
def StartNotify(self):
self.notifying = True
if self.queue and not self.sending:
self.sending = True
GLib.idle_add(self._send_next)
@dbus.service.method(GATT_CHARACTERISTIC, in_signature="a{sv}")
def StopNotify(self, _options):
@dbus.service.method(GATT_CHARACTERISTIC)
def StopNotify(self):
self.notifying = False
self.queue.clear()
self.sending = False
@dbus.service.method(GATT_CHARACTERISTIC)
def Confirm(self):
if self.queue:
GLib.idle_add(self._send_next)
else:
self.sending = False
def send(self, message):
encoded = (json.dumps(message, separators=(",", ":")) + "\n").encode("utf-8")
@@ -123,7 +132,7 @@ class TxCharacteristic(Characteristic):
self.queue.append(encoded[offset:offset + NOTIFY_CHUNK_BYTES])
if not self.sending:
self.sending = True
GLib.timeout_add(20, self._send_next)
GLib.idle_add(self._send_next)
def _send_next(self):
if not self.notifying or not self.queue:
@@ -132,7 +141,7 @@ class TxCharacteristic(Characteristic):
chunk = self.queue.popleft()
value = dbus.Array([dbus.Byte(byte) for byte in chunk], signature="y")
self.PropertiesChanged(GATT_CHARACTERISTIC, {"Value": value}, [])
return bool(self.queue)
return False
class RxCharacteristic(Characteristic):
@@ -216,26 +225,10 @@ def call_api(method, path, body, token):
raise ProtocolError("PI_SERVICE_UNAVAILABLE", "The Pi companion service is unavailable") from error
class Advertisement(dbus.service.Object):
def __init__(self, bus):
self.path = "/org/pi_car_companion/advertisement0"
super().__init__(bus, self.path)
@dbus.service.method(DBUS_PROPERTIES, in_signature="s", out_signature="a{sv}")
def GetAll(self, interface):
if interface != ADVERTISEMENT:
raise InvalidArgs()
return {"Type": "peripheral", "ServiceUUIDs": dbus.Array([SERVICE_UUID], signature="s"), "LocalName": "Pi Car"}
@dbus.service.method(ADVERTISEMENT)
def Release(self):
pass
def find_adapter(bus):
objects = dbus.Interface(bus.get_object(BLUEZ, "/"), DBUS_OM).GetManagedObjects()
for path, interfaces in objects.items():
if GATT_MANAGER in interfaces and AD_MANAGER in interfaces:
if GATT_MANAGER in interfaces:
return path
raise RuntimeError("No Bluetooth LE adapter with GATT server support was found")
@@ -253,10 +246,22 @@ def main():
service.add_characteristic(rx)
service.add_characteristic(tx)
application.add_service(service)
advertisement = Advertisement(bus)
dbus.Interface(adapter, GATT_MANAGER).RegisterApplication(application.path, {}, reply_handler=lambda: None, error_handler=lambda error: (_ for _ in ()).throw(error))
dbus.Interface(adapter, AD_MANAGER).RegisterAdvertisement(advertisement.path, {}, reply_handler=lambda: None, error_handler=lambda error: (_ for _ in ()).throw(error))
GLib.MainLoop().run()
loop = GLib.MainLoop()
registration_errors = []
def registration_failed(error):
registration_errors.append(error)
loop.quit()
dbus.Interface(adapter, GATT_MANAGER).RegisterApplication(
application.path,
{},
reply_handler=lambda: None,
error_handler=registration_failed,
)
loop.run()
if registration_errors:
raise registration_errors[0]
if __name__ == "__main__":