-
-
Notifications
You must be signed in to change notification settings - Fork 8.3k
Cross-platform Bluetooth support #4589
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Anything cross-platform would start with Unix port (well, Linux for things not mandated by POSIX), that's the way to implement API covering the widest array of diverse systems. |
That's harder than you might think. The semantics of BlueZ are quite different from the semantics of all the BLE implementations for MCUs I've looked at, and requires unfortunate things like linking against dbus/glib and a separate thread to interact with dbus. Also, last time I looked BlueZ support for peripheral mode was still very immature. One example of the differences is that BLE stacks for MCUs allow you to construct a raw advertisement packet. BlueZ simply doesn't allow this, which makes the implementation much harder. In theory, you could also work directly with the hci socket on Linux, but that requires root access and does not work when you have BlueZ running at the same time (most modern Linux laptops with Bluetooth). EDIT: I think it is much easier to say "this module works on microcontrollers" and then implement support for multiple BLE stacks to make sure you're getting the cross-platform part right. This is why I've done the esp32 and nrf support at the same time. A NimBLE port would also be nice, and much easier than BlueZ. |
Forget about BlueZ, it's crappy bloatware.
There're also L2CAP sockets which shouldn't have root requirements, etc. Yep, probably can't do advertizement using them, but advertizement to Bluetooth is the same as e.g. BGP to Internet protocols. What's important is not advertizement and other "control plane" stuff, but data communication API. |
Don't confuse classic BT and BLE. The main thing that connects them is their name, and little else. Yes, you may be able to do L2CAP as non-root, but that's only a very small part of what this bluetooth package is about. While classic BT is nice to have, BLE is what is most interesting at the moment (nrf doesn't even have classic BT).
That may be so, but it's required for non-root BLE. |
Looks like you aren't very familiar with BLE stack architecture, which isn't the best premise to write anything "cross-platform", again. |
I pushed a commit to master fcace26 which should fix this. |
Many thanks @dpgeorge, this fixes the issue! I would have needed a lot of time to find that bug. import bluetooth
bt = bluetooth.Bluetooth()
bt.active(1)
bt.advertise(100, 'MicroPython')
print('----')
tx = bluetooth.Characteristic('6E400002-B5A3-F393-E0A9-E50E24DCCA9E', bluetooth.FLAG_READ|bluetooth.FLAG_NOTIFY)
rx = bluetooth.Characteristic('6E400003-B5A3-F393-E0A9-E50E24DCCA9E', bluetooth.FLAG_WRITE)
s = bt.add_service('6E400001-B5A3-F393-E0A9-E50E24DCCA9E', [tx, rx])
tx.write('foo') |
Very nice @aykevl. This is a great start to make a uniform API across targets providing BLE. Already with this limited feature set, i could envision this to be a second BLE wrapper for nrf-port for a while, and phase out the ubluepy when this gets up to a certain feature set. This will easy further development of a common API. Have you given any thoughts on how to handle events (connect / disconnect / notifications etc)? |
I think having two wrappers complicates things, for example who will handle events? Also, I think it's not really necessary to keep them both around either, as I intend to finish this PR in the next ~2 weeks.
Yes, but I'm not sure what the best way would be. One requirement for me is that there are fine-grained events, not one big event loop. That way, multiple modules could independently register a service and handle events for that service (example: an independent DFU module). For connect/disconnect, I think a global event handler would be more appropriate. EDIT: I should also mention that I am being sponsored by a company called Sensorberg. They have agreed with merging this upstream under the MIT license. |
Yeah, i did not think running them at the same time (ublupy/bluetooth). With this timeline i would guess full replacement right away should work as well as long as the basic feature set is in place. Any plans for BLE REPL replacement? :)
This sounds reasonable.
See what you mean. But i would like to challenge this a bit as well. :) |
The MVP should support NUS written in Python. I'm not sure I want to directly include support in C at the same time, but it would certainly be very useful in the future.
To be honest, I never really got the focus on these roles. There is no inherent limitation in the API to have multiple roles at the same time. While not yet supported, I don't see why you wouldn't be able to advertise while a central is connected and at the same time perform a scan for new devices and connect to them. The only limitation is the BLE stack like s110. What may be useful, however, is support for presenting as multiple devices at once. I think s132 supports that? To the outside world it looks like there are multiple devices but they're actually running on a single radio and BLE stack. I haven't investigated this area a lot. But I think they cannot be separated entirely at the API level: it's still one stack that has to be enabled. |
Another issue that I'd like to get some feedback on: how should notifications work? I see two possible implementations:
Example of the former: bt = bluetooth.Bluetooth()
bt.active(1)
bt.advertise(100, 'MicroPython')
tx = bluetooth.Characteristic('6E400002-B5A3-F393-E0A9-E50E24DCCA9E', bluetooth.FLAG_READ|bluetooth.FLAG_NOTIFY)
rx = bluetooth.Characteristic('6E400003-B5A3-F393-E0A9-E50E24DCCA9E', bluetooth.FLAG_WRITE)
s = bt.add_service('6E400001-B5A3-F393-E0A9-E50E24DCCA9E', [tx, rx])
bt.set_event_handler(handle_event)
while 1:
time.sleep(1)
tx.write('log something\n') Example of the latter: connected_devices = set()
def handle_event(code, device):
# TODO: can we even do memory allocations in an event handler?
if code == bluetooth.CONNECTED:
connected_devices.add(device)
elif code == bluetooth.DISCONNECTED:
connected_devices.remove(device)
def log(msg):
for device in connected_devices:
try:
tx.notify(msg, device)
except OSError:
pass # probably this device disconnected but the event hasn't yet triggered
bt = bluetooth.Bluetooth()
bt.active(1)
bt.advertise(100, 'MicroPython')
tx = bluetooth.Characteristic('6E400002-B5A3-F393-E0A9-E50E24DCCA9E', bluetooth.FLAG_READ|bluetooth.FLAG_NOTIFY)
rx = bluetooth.Characteristic('6E400003-B5A3-F393-E0A9-E50E24DCCA9E', bluetooth.FLAG_WRITE)
s = bt.add_service('6E400001-B5A3-F393-E0A9-E50E24DCCA9E', [tx, rx])
bt.set_event_handler(handle_event)
while 1:
time.sleep(1)
log('log something\n') While the former is a bit more implementation work and less flexible, it is more modular. There is no central module that has to manage all connections: this is all done from within the Bluetooth wrapper. While it results in a bit more code size on devices that support more than one connection, I think it is ultimately far less error prone than the latter which is easy to get slightly wrong. The former also results in less Python code which usually leads to reduced RAM consumption. Remember that many of the more interesting BLE stuff requires notifications so it isn't really optional. |
On the one hand it's nice to push code into the Python level (ie approach 2) because it gives more flexibility and results in simpler/smaller firmware. On the other hand things that are low level and a necessary part of the stack which will be implemented the same way each time are good candidates to go in the C implementation (approach 1). So I think it makes more sense to go for approach 1. Then Python code doesn't even need to know/worry whether multiple connections are supported or not, their BLE code remains the same. And there is already a lot to do on the Python side to configure and manage a BLE service, which is suitable for putting in Python-level libraries and/or helper functions, that adding more complexity in the Python code could make it difficult to maintain. For BLE stacks that only support a single connection, approach 1 should be relatively simple to implement because it's likely that GAP/GATT callback functions already exist at the C level for other purposes. (And some BLE stacks may even handle notifications automatically so approach 2 would not be possible to implement on such stacks.) I guess that for all characteristics that have a notify property there's rarely a case that they don't need to send a notification when their data changes, so having approach 1 do auto notification on characteristic change is not a big restriction. But if one day more flexibility is needed then a MANUAL_NOTIFY pseudo-property could be added which requires the user to do the notify. On a related note, does the user need to restart advertising when the connection stops, or is that also automatic? |
@dpgeorge thank you for your thoughts on the matter.
This is automatic. At the moment, advertisement restarts on a disconnect event. This should be improved so advertisements continue to work when a device connects, and only when the max number of connections has been reached should it either switch to non-connectable advertisements or stop advertising. The former makes more sense to me (think of beacons that are connectable), while the latter seems to be more common in BLE devices. |
Am I right there is no possibility to connect to the slave device directly knowing its address without scanning all available divices and matching their names/bdas??? |
Not sure what you're asking but this PR does not support central mode yet so it can't connect to any peripheral devices. If you're asking how other devices can connect to a BLE peripheral running MicroPython: that's completely up to them. They may scan first or connect directly when they know the MAC address. |
This is an example where it might be more useful if the behaviour were controlled via Python code (not via the C driver). Because then the user could select how it worked: advertising stops, continues, changes, etc. The usual case is to handle just one connection, stop adv when connected and restart when disconnected. But adv can be more general than this, after all adv (and scanning) is used as the basis of BLE mesh. |
For comparison, CircuitPython have a Edit: see also a high-level wrapper for a UART NUS service: https://github.com/adafruit/Adafruit_CircuitPython_BLE/blob/master/adafruit_ble/uart.py |
Also see the Adafruit library https://github.com/adafruit/Adafruit_CircuitPython_BLE, which implements a NUS service. We’ve tried to provide the basics in the native module, and then build the rest in Python. Edit: [oops, crossed with @dpgeorge] |
I have pushed support for notifications. Notifications are as simple as adding
That's very interesting! They seem to handle notifications inside the module as well. |
@aykevl Good work, looking forward to test your implementation on ESP32. Are you planning to do some more development or do you consider it done? Also +1 for Sensorberg for permission to merge upstream. |
I have pushed another change that adds a def callback(char, data):
print('data:', data)
rx.on_update(callback) This works on both nrf52 and nrf51. Unfortunately, I haven't yet managed to get this working on the esp32, it crashes with a LoadProhibited on this line: Line 42 in d89ce2e
When I replace Line 42 in d89ce2e
The crash for the second try is here:
Does anyone have an idea what might be going wrong on the esp32? I'm suspecting it has something to do with being handled in a different task. I have tried to increase the stack size for callbacks, but that didn't help. |
for an idea of how to implement it using api similar to "bluepy" you can take a look at: |
for reference: #4893 |
@superted6851 yes I use chrome for testing WebBluetooth. it works well with Arduino code on ESP32 https://github.com/espressif/arduino-esp32/blob/master/libraries/BLE/examples/BLE_uart/BLE_uart.ino the MicroPython code seems to transmit the data, but only once, so seems that the notification doesn't work. needs more testing to understand it anyway @dhalbert as far as I understood the NUS service are just the IDs yes and it's just publishing and subscribing data on the specified characteristics. or is there more to the NUS service ? |
@silbo could you try the PR linked just above #5051 it's a newer superset of the changes from here updated to more platforms. And yes, there's nothing special about NUS, it's just a service with characteristics labelled tx and rx to send raw data over, anyone can use it by copying the uuid's. It's become the defacto standard for fake-serial over ble. |
@andrewleech I am currently trying to compile it and flash it, but I am missing something I guess, as I am not able to import bleutooth for esp32 |
That PR contains BLE support only for STM32 |
Ah sorry, I know it's intended to be the standard api across all platforms, but missed the point that esp and nrf are still in progress there. |
@andrewleech Thanks for the update, I tried Circuitpython meanwhile on my nrf52840-dk, works great across all platforms. Adafruit has made quite cool features also. Like acting as a USB storage etc. I am in love with their new upcoming product https://www.adafruit.com/product/4333 |
Superseded by #5051 |
Could you please share firmware that you have used to run this script on ESP32 ? |
Can you share the firmware .bin file on which you have worked? |
This is PR is superseded by #5051 (nimble impl.) |
I downloaded latest firmware from micropython. In that it only supports BLE, But above code demonstrates for classic Bluetooth. I want to try with Classic Bluetooth |
I do not see any BT classic related code / example anywhere(?) |
Nimble is BLE only. See #5672 for BlueKitchen which can support bluetooth classic. However, I think that PR only implements BLE and not classic. |
There is currently no BT classic support in MicroPython. The |
I have an issue running this code on my esp32, i am using the 1.13 firmware version bt = bluetooth.Bluetooth() -------ERROR-------------------- |
As @dpgeorge pointed out in the very commit before yours it got renamed to |
Thank you very much, i wrote befote read it all, my bad |
Hello, Here is my program I have an issue Do you an example with ubluetooth module? thanks to help me |
Which is correct, as the method's name you're looking for probably is: |
Thanks, could you give me the URL to the doc. Thanks |
https://docs.micropython.org/en/latest/library/ubluetooth.html It's btw the first hit if you google for "micropython bluetooth" :) |
@JLFra wrote
this line generates a syntax error |
TRYING TO TESTimport bluetooth bt = bluetooth.BLE () print ('----') tx = bluetooth.Characteristic ('6E400002-B5A3-F393 -E0A9-E50E24DCCA9E ', bluetooth.FLAG_READ | bluetooth.FLAG_NOTIFY) s = bt.add_service ('6E300000249-E395A-E395A-E395A-E395A-E395A' , [rx]) tx.write (' foo ') bt.active (1) ELF file SHA256: 0000000000000000000000000000000000000000000000000000000000000000 Whats wrong? I just dublicate prev. code |
@zoland this PR is closed and was not merged, so the code samples here do not apply (unless you're specifically building this branch). A slightly different approach was taken for the final implementation of BLE in MicroPython. Please see https://docs.micropython.org/en/latest/library/ubluetooth.html and https://github.com/micropython/micropython/tree/master/examples/bluetooth |
…oPython-mention-in-error-messages Make error messages platform agnostic
Here is some Bluetooth support that I've been working on. It is not nearly finished, but advertising and basic setting/getting of characteristics already works.
Note: this work is being sponsored by a company called Sensorberg.