python-bluezero
python-bluezero copied to clipboard
Parallel bluetooth low energy advertising and scanning for beacons
Hello ukBaz!
First many thanks for this great library!
But sadly I have some issues at the moment, when advertising as a ble peripheral and starting a subprocess which searches for iBeacons.
My code looks roughly like that:
from bluezero import peripheral
from bluezero import adapter
from bluetooth.bluetoothdevice import BluetoothDevice
from logsys.logger import Logger
import config
import subprocess
class BluetoothController:
def __init__(self):
self.adapter_address = list(adapter.Adapter.available())[0].address
self.ble_uart = peripheral.Peripheral(adapter_address=self.adapter_address,
local_name=config.BLUETOOTH_APP_NAME)
def setup_ble(self):
self.set_service_and_characteristic()
self.set_listeners()
self.ble_uart.publish()
def set_service_and_characteristic(self):
self.ble_uart.add_service(srv_id=2, uuid=config.BLUETOOTH_SERVICE_UUID, primary=True)
self.ble_uart.add_characteristic(srv_id=2, chr_id=1, uuid=config.BLUETOOTH_CHARACTERISTIC_UUID,
value=[], notifying=False,
flags=['write', 'write-without-response'],
write_callback=BluetoothDevice.uart_write,
read_callback=None,
notify_callback=None)
def set_listeners(self):
self.ble_uart.on_connect = BluetoothDevice.on_connect
self.ble_uart.on_disconnect = BluetoothDevice.on_disconnect
def scan_for_beacon(self):
Logger.debug("start beacon scan")
print("start beacon scan")
subprocess.Popen(["python", "parallel-beacon-scan/main.py"], shell=False,
stdin=None, stdout=None, stderr=None, close_fds=True)
def beacon_is_barrier_beacon(self, beacon_data):
uuid = beacon_data[0]
major = beacon_data[1]
minor = beacon_data[2]
return str(uuid) == config.BEACON_UUID and major == config.BEACON_MAJOR and minor == config.BEACON_MINOR
The scan_for_beacon
method starts a subprocess, that scans for beacons and looks like that:
from bluezero import observer
import config
class BeaconBluetoothController:
def scan_for_beacon(self):
observer.Scanner.start_beacon_scan(on_ibeacon=self.ibeacon_found)
def ibeacon_found(self, beacon_data):
if self.beacon_is_barrier_beacon(beacon_data):
print("is my beacon")
def beacon_is_barrier_beacon(self, beacon_data):
uuid = beacon_data[0]
major = beacon_data[1]
minor = beacon_data[2]
is_barrier_beacon = str(uuid) == config.BEACON_UUID and major == config.BEACON_MAJOR and minor == config.BEACON_MINOR
if is_barrier_beacon:
print(f"{uuid}.{major}.{minor}")
return is_barrier_beacon
Now, if the beaconbluetoothcontroller is started as a subprocess I get the following errors:
ERROR:dbus.connection:Exception in handler for D-Bus signal:
Traceback (most recent call last):
File "/home/pi/.local/lib/python3.9/site-packages/dbus/connection.py", line 218, in maybe_handle_message
self._handler(*args, **kwargs)
File "/home/pi/.local/lib/python3.9/site-packages/bluezero/adapter.py", line 317, in _interfaces_added
self.on_device_found(new_dev)
File "/home/pi/.local/lib/python3.9/site-packages/bluezero/observer.py", line 175, in on_device_found
rssi = bz_device_obj.RSSI
File "/home/pi/.local/lib/python3.9/site-packages/bluezero/device.py", line 219, in RSSI
return dbus_tools.get(self.remote_device_props,
File "/home/pi/.local/lib/python3.9/site-packages/bluezero/dbus_tools.py", line 407, in get
raise dbus_exception
File "/home/pi/.local/lib/python3.9/site-packages/bluezero/dbus_tools.py", line 398, in get
return dbus_prop_obj.Get(dbus_iface, prop_name)
File "/home/pi/.local/lib/python3.9/site-packages/dbus/proxies.py", line 72, in __call__
return self._proxy_method(*args, **keywords)
File "/home/pi/.local/lib/python3.9/site-packages/dbus/proxies.py", line 141, in __call__
return self._connection.call_blocking(self._named_service,
File "/home/pi/.local/lib/python3.9/site-packages/dbus/connection.py", line 634, in call_blocking
reply_message = self.send_message_with_reply_and_block(
dbus.exceptions.DBusException: org.freedesktop.DBus.Error.UnknownObject: Method "Get" with signature "ss" on interface "org.freedesktop.DBus.Properties" doesn't exist
ERROR:dbus.connection:Exception in handler for D-Bus signal:
Traceback (most recent call last):
File "/home/pi/.local/lib/python3.9/site-packages/dbus/connection.py", line 218, in maybe_handle_message
self._handler(*args, **kwargs)
File "/home/pi/.local/lib/python3.9/site-packages/bluezero/adapter.py", line 314, in _interfaces_added
new_dev = device.Device(
File "/home/pi/.local/lib/python3.9/site-packages/bluezero/device.py", line 53, in __init__
raise ValueError("Cannot find a device: " + device_addr +
ValueError: Cannot find a device: 21:7D:AE:6C:97:30 using adapter: B8:27:EB:D6:83:A2
If I start the subprocess manually, they aren't thrown. My theory is that there are some issues, when an instance that has already started a BLE process (like advertising itself as Peripheral) is starting a BLE subprocess (like scanning for beacons).
Personally the exceptions aren't that bad for me. The beacon that I'm scanning for is found, nevertheless some devices are causing some issues. But if there is a way to handle them I would be gratefully if you can tell me where my issue is.
Thanks again for your great library.
This seems like is similar to https://github.com/ukBaz/python-bluezero/issues/266 .
I suspect the issue is that the scanning is creating new devices in BlueZ which triggers the InterfacesAdded
D-Bus signal. The observer
code also removes those devices once it has the device information to work around a "feature" in BlueZ that throttles the reporting of discovered devices. Then the self.ble_uart.on_connect
is using the same InterfacesAdded
D-Bus signal.
So the investigation would need to focus on following section of code to make it more robust to the different scenarios where a InterfacesAdded
signal is triggered.
https://github.com/ukBaz/python-bluezero/blob/392a115ace80a6d7257b76708ec24b99ec9e2e5d/bluezero/adapter.py#L304-L322
If you have any feedback or insight into how this could be changed (or tested) then please share.
Thanks for the quick answer and your insight!
I have sadly no good idea whats the best way to adjust the code of yours. But if you have access to an ibeacon:
Create two python applications. One to advertise as BLE peripheral, one to scan for beacons.
When you start the peripheral application, use subprocess.Popen
to start the beacon scanner.
This should be enough to reproduce my issues.
Until this issue can be fixed. What would be the best way to handle the thrown exceptions? I tried to wrap different parts of my code in try: except:
blocks but nothing helped to catch them.
From the start of this project there has always been a philosophy about complexity; that the library would attempt to help people along the learning curve as they learnt about using Bluetooth with Python and that for some they would outgrow the library as they wanted more control.
This meant that there was an acceptance that the abstraction would bring restrictions and that is why the concept of complexity levels was used. An interesting blog around this topic is "When you need to escape an abstraction, how violent is your exit?"
The observer
and peripheral
modules are at level 10 that looks to simplify event loops and so starts them in the background without the user knowing.
As a result of this the two applications want to create and start an event loop which means they have to run them in different processes to work. You have achieved this by using subprocess
for one of them. However, that is not really how the event loop should be used. Both the "applications" should be registered to the same event loop and then the event loop started.
I have hacked together an example that starts just the one event loop: https://github.com/ukBaz/python-bluezero/pull/381/files?diff=split&w=0
When I run the ticket_380 branch I don't see the errors that you are reporting.
I suspect that what this means is that you need to access the lower level API's to make your application(s) work cleanly.
Closing because of inactivity.