diff --git a/platypush/plugins/bluetooth/_ble/_manager.py b/platypush/plugins/bluetooth/_ble/_manager.py index 367c544e9..9e8635616 100644 --- a/platypush/plugins/bluetooth/_ble/_manager.py +++ b/platypush/plugins/bluetooth/_ble/_manager.py @@ -227,6 +227,35 @@ class BLEManager(BaseBluetoothManager): 'Error while disconnecting from %s: %s', conn.device.address, e ) + def _dbus_disconnect(self, device: BLEDevice): + """ + Disconnect from a device using the DBus API (only available on Linux). + This may be required if there is an active connection to the device that is not owned by the + """ + entity = self._cache.get(device.address) + assert entity, f'Unknown device: "{device.address}"' + path = device.details.get('path') + assert path, f'The device "{device.address}" has no reported system path' + assert path.startswith('/org/bluez/'), ( + f'The device "{device.address}" is not a BlueZ device. Programmatic ' + 'system disconnection is only supported on Linux' + ) + + try: + import pydbus + + bus = pydbus.SystemBus() + dbus_device = bus.get('org.bluez', path) + dbus_device.Disconnect() + except Exception as e: + raise AssertionError( + f'Could not disconnect from {device.address}: {e}' + ) from e + + if entity.connected: + entity.connected = False + self.notify(BluetoothDeviceDisconnectedEvent, entity) + @override def connect( self, @@ -279,7 +308,11 @@ class BLEManager(BaseBluetoothManager): # Check if there are any active connections connection = self._connections.get(dev.address, None) - assert connection, f'No active connections to the device {device} were found' + if not connection: + # If there are no active connections in this process, try to + # disconnect through the DBus API + self._dbus_disconnect(dev) + return # Set the close event and wait for any connection thread to terminate if connection.close_event: diff --git a/platypush/plugins/bluetooth/_plugin.py b/platypush/plugins/bluetooth/_plugin.py index 104a31058..6a17a5824 100644 --- a/platypush/plugins/bluetooth/_plugin.py +++ b/platypush/plugins/bluetooth/_plugin.py @@ -57,6 +57,7 @@ class BluetoothPlugin(RunnablePlugin, EnumSwitchEntityManager): * **bleak** (``pip install bleak``) * **bluetooth-numbers** (``pip install bluetooth-numbers``) * **TheengsDecoder** (``pip install TheengsDecoder``) + * **pydbus** (``pip install pydbus``) * **pybluez** (``pip install git+https://github.com/pybluez/pybluez``) * **PyOBEX** (``pip install git+https://github.com/BlackLight/PyOBEX``) diff --git a/platypush/plugins/bluetooth/manifest.yaml b/platypush/plugins/bluetooth/manifest.yaml index ccd60db49..17937ad9a 100644 --- a/platypush/plugins/bluetooth/manifest.yaml +++ b/platypush/plugins/bluetooth/manifest.yaml @@ -17,6 +17,7 @@ manifest: - bleak - bluetooth-numbers - TheengsDecoder + - pydbus - git+https://github.com/pybluez/pybluez - git+https://github.com/BlackLight/PyOBEX package: platypush.plugins.bluetooth diff --git a/setup.py b/setup.py index 83dc025a7..035103943 100755 --- a/setup.py +++ b/setup.py @@ -178,6 +178,7 @@ setup( 'bleak', 'bluetooth-numbers', 'TheengsDecoder', + 'pydbus', 'pybluez @ https://github.com/pybluez/pybluez/tarball/master', 'PyOBEX @ https://github.com/BlackLight/PyOBEX/tarball/master', ],