-
-
Notifications
You must be signed in to change notification settings - Fork 8.2k
Common BLE API #5051
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
Common BLE API #5051
Conversation
If you want a reference to look at re BT classic api compatibility, zephyr supports some classic in their stack, so perhaps their application api would be useful: https://docs.zephyrproject.org/latest/reference/bluetooth/index.html While that doesn't list a2dp there is code for it in the repo: https://docs.zephyrproject.org/apidoc/latest/a2dp_8h.html As first skim though, it looks like there's a separate C interface/functions for each classic profile, which sits alongside the ble gattc etc top level functions. There may not be much compatibility/interacting between them at all. |
Thanks @jimmo for the hard work. I'm fully supportive of the approach to the API that is proposed here, building and improving on the work done by @aykevl . The API is flat (a set of functions, no classes), minimal and powerful, reflecting closely the actual BLE spec. This means it can be implemented with minimal code, yet allows to do (almost) anything that the BLE spec allows. Using the API here for the core functionality, it's then possible to write wrappers in Python to expose a more user-friendly API, eg to provide a simple way to make peripherals like heart-rate monitors. It's also possible to make wrappers to provide compatibility with other BLE APIs, eg to match how BLE would work on unix (compatibility with |
Hi @jimmo, Have you already written some example helper functions you mentioned for advertising data? |
extmod/modbluetooth.c
Outdated
STATIC mp_obj_t bluetooth_active(size_t n_args, const mp_obj_t *args) { | ||
// TODO: Should active(False) clear the IRQ? | ||
//self->irq_handler = mp_const_none; | ||
//self->irq_trigger = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think irq should be cleared.
If it was bluetooth_reset then yes, but something to stop and start the bluetooth stack shouldn't necessarily reset configuration.
|
||
void mp_bluetooth_disable(void) { | ||
ble_state = BLE_STATE_OFF; | ||
mp_hal_pin_low(pyb_pin_BT_REG_ON); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function should possibly also call ble_hs_shutdown()
https://mynewt.apache.org/latest/network/docs/ble_hs/ble_att.html#c.ble_hs_shutdown
This line could be surrounded by a #ifdef pyb_pin_BT_REG_ON
for non-pybd boards that don't have such an enable pin.
} | ||
|
||
bool mp_bluetooth_is_enabled(void) { | ||
return ble_state == BLE_STATE_ACTIVE; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a nimble function ble_hs_is_enabled()
that looks like it would fit here too: https://mynewt.apache.org/latest/network/docs/ble_hs/ble_att.html#c.ble_hs_is_enabled
ports/stm32/nimble/hci_uart.c
Outdated
} |
|
|
void nimble_uart_process(void) { | ||
int host_wake = mp_hal_pin_read(pyb_pin_BT_HOST_WAKE); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suspect the host_wake pin functionality would possibly be better in the cywbt file, I think this kind of hardware interrupt pin might be unique to the cyw chip.
Zephyr HCI controller at least doesn't appear to have anything like this, nor have I been able to find any other uart hci radio's with this. They all seem to rely on just the uart port.
@jimmo The changes I made to also support generic HCI radio (nrf52 running zephyr) are on my branch here, on top of your changes: https://github.com/andrewleech/micropython/tree/common-ble-api-anl |
entry = MP_OBJ_TO_PTR(elem->value); | ||
os_mbuf_append(ctxt->om, entry->data, entry->data_len); | ||
|
||
return 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's common on other bluetooth stacks to allow for a callback here when a read request comes in. On NRF devices this is exposed as a callback event BLE_GATTS_EVT_RW_AUTHORIZE_REQUEST.
It's officially used to provide the application with a chance to authorise or deny access to the characteristic based on whether it's paired etc.
It's also commonly used to allow the application to change the value of the characteristic each read. This can be then used to stream data out the characteristic reliably, instead of notifications which are far more unreliable as they don't have the same handshaking to ensure they get through.
The problem in this case is your other events are soft handlers, whereas this would need to be hard / inline to allow the callback to change the return value of this function.
It appears to only support one service currently? If I run
But only the last service registered shows on mv nrf ble scanner app once connected. |
FYI I've made a quick python util function to create appropriate advertising data to include |
I've added support for multiple services to the head of my extension branch, see in andrewleech@72b2d17 It required a fair bit of turning the code around, not sure it's the best option. It's quite possibly better to make the one add service function take a larger structure of all the services and their matching chars in the one call, rather than having the extra handles tracking dict I've got in my attempt. |
Hello, I am new to BLE on ESP32 and I'm wondering if I could get some clarification on this. I am quite excited by the prospect of being able to use BLE with Micropython, since so many of the sensors these days seem to have really long battery life with BLE (like a year or more on a button battery). It seems like we can use some basic BLE but I'm a bit confused as to how it is implemented. I need to set up an ESP32 board as a client to monitor a few BLE sensors, nothing terribly complicated. So is BLE contained within a version of Micropython that I can just burn and install on my board? If so, is there a particular version (link?) I need to download? Or is the BLE component a separate library module that I would install in the LIB sub-folder on my ESP32? If so, is there a link for that? Or is there some other thing I would have to do (compile something)? Any help around this would be greatly appreciated; these are probably really stupid questions, but I really am confused. Regards, AB |
Hi @rf-ir-learner, this PR doesn't yet have support for esp32, the stm32 support has been proofed out first. I know Esp32 and others are being worked on, but for a feature as important as Bluetooth @jimmo and others are putting in a lot of effort behind the scenes planning it all to try to ensure its done right the first time. |
It's probably not wise to rely on the underlying BLE stack (nimble, nordic, bluedroid) being able to add services "later on". In other words, the common denominator here would be to assume that all services must be registered at the same time. In that case Alternatively, it might be possible to keep
But that seems a bit flaky. |
260bf77
to
00f3e37
Compare
I recently went through some API churn on this with One problem I encountered with this approach was that @tannewt has suggested that the service/characteristic/descriptor tree be described in a more declarative way, usable for both local and remote attributes. These service trees would then be passed to a peripheral or central object. In the case of a peripheral, the tree components would be instantiated and registered with the BLE stack. In the case of a central, the user would, for example, use a property in the declarative tree to set or get the value of a remote characteristic, and the internal logic would check that a matching characteristic had been found during discovery. Because a BLE stack is so complicated, we decided to name the low-level module (I could critique the BLE protocol, which has a multitude of role-changing chameleon objects: it is more of an n-dimensional basket-weave of orthogonal concepts than a "stack". But it is what it is and we have to figure out how to present it in a usable way.) |
Thank you for the feedback @andrewleech & @dpgeorge. I have updated this branch, incorporating all of the improvements from @andrewleech (with some modifications). I have also re-implemented adding multiple services based on @dpgeorge's suggestion. An example of adding multiple services now looks like:
This allows you to modify the service list multiple times. |
@jimmo I see our comments just crossed, and that you create the tree in the call. If you add descriptors at some point, then you'll need further nesting in the tuple of tuples. |
For something like BLE HID, I have also had to add specification of security modes, and values for an attributes for an attribute's max length and whether it is fixed length. I also added the ability to specify an initial value for an attribute, which is common for read-only descriptor values. |
Thanks for the feedback also @dhalbert -- sorry I just missed your first comment. Yes, descriptors would be an optional third entry in the characteristic tuple (and in the returned value handles tree), but this does start to get a bit messy. Not to mention other properties (default values, security, etc). (Edit: just saw your next comment :) Some thought required here - I will follow up with @dpgeorge ) We have some additional constraints due to using Nimble as the BLE stack, which requires all the services to be defined at once. See apache/mynewt-nimble#556 I need to spend some more time looking into your implementation, but I'm not sure that it would be possible currently to provide the _bleio API on top of Nimble. Obviously we'd prefer not to be forced into a particular API design by the underlying stack...
Thank you -- this is really useful!
This was our intention too (this is our low-level API). Our goal initially was to avoid any classes at all (hence the tuples) and then match the BLE gatts/gattc operation directly. I hope it should be still possible to make this match at the high-level API.
:) Helps me feel better about how long this is taking! |
@jimmo Thanks for your reply and for your work on this API. Your comments and approach have also given me some new ideas.
It's not so far from positional tuples to namedtuples to declarative classes: I think you may end up moving in that direction for convenience and readability as you need to add yet more properties for the attributes.
I only found out about the existence of Nimble midway through working on I think a key thing may be to separate the declarative and operational attribute classes. I have been assuming they would be the same, but it causes more state to need to be kept internally to track the object life cycle, and whether it is local or remote. If you look at something like the iOS BLE API, there's almost a Cartesian product of classes for objects and their roles, and maybe we could avoid that. |
00f3e37
to
f3b4e4b
Compare
I've added another commit to address Nimble memory management (root pointers are tracked for nimble malloc, and it works across soft reset). |
af7ccc1
to
d6e3675
Compare
I've implemented descriptor support and basic support for (gatts) writing of longer values. Fixed a few other crashes and cleaned up some debugging/logging code. Now able to make the PYBD act as a HID keyboard for my Android phone. |
The other thing I haven't looked at yet is pairing & bonding. "Just works" Pairing will likely work already, though I'm not sure about nimble's "LE Secure" support for using it safely. For other pairing (eg. Pin) another immediate/interrupt style event will likely be needed. Bonding support will presumably need since functionality exposed/wired up to write the bonding info to flash. If anyone else if a little lost in the wild range of different ble security options, this blog is a good primer: https://medium.com/rtone-iot-security/deep-dive-into-bluetooth-le-security-d2301d640bfc |
I'm happy to do that during merge if it proves too difficult. |
03a3fb5
to
f24b62a
Compare
I agree. This isn't just a matter of moving the files due to some stm32 specific stuff in nimble/, but this was a good refactoring to do anyway. Done (with some substantial rebasing and rewriting of history, which was worth doing anyway too).
Done. It simplifies the code and reduces code size. I should have just done it this way to start.
Actually what I meant here is something like
Good idea. Done. |
a2157e3
to
7ddbfec
Compare
Tag nimble_1_1_0_tag.
7ddbfec
to
fafa9d3
Compare
@dpgeorge any progress on nimble integration on stm32wb? |
small issue, on the PYBOARD-D, before pairing/connecting the device is advertised as a ESP32 device (dont recall the actual ID), after pairing/connecting appears as 'PYBD' |
If you're using the code snippet above, it will advertise as "esp32hr", but the device name is hardcoded in the firmware to I've added an item to #5186 to make this device name configurable. |
Oops, how careless of me, i copied the code snippet verbatim and did not even notice that
… On 8 Oct 2019, at 07:50, Jim Mussared ***@***.***> wrote:
small issue, on the PYBOARD-D, before pairing/connecting the device is advertised as a ESP32 device (dont recall the actual ID), after pairing/connecting appears as 'PYBD'
If you're using the code snippet above, it will advertise as "esp32hr", but the device name is hardcoded in the firmware to PYBD. (and ESP32 on STM32) which you will see on connection.
I've added an item to #5186 to make this device name configurable.
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub, or mute the thread.
|
This PR is based on #4589 by @aykevl and #4893 by @dpgeorge
It implements a Python API for working with BLE devices in both central and peripheral role (including scanning and advertising), and is implemented with Nimble for STM32. I have WIP ESP32 support (based on #4859, but including central role) almost ready to go, although it might be an option to use Nimble on ESP32 instead. NRF is also in progress using the SD (but could potentially use Nimble also!).
The API is lower-level than #4589 -- it more closely matches the underlying BLE spec. The idea is that specific use cases (beacon, beacon detector, UART, etc) can be implemented with higher-level Python wrappers.
Some outstanding questions:
bluetooth
), class (Bluetooth
), and method names (e.g. 'characteristics' vs 'chrs'). I hope that a future BT Classic implementation can share some of this so some thought required about whether to rename anything to be BLE specific.Registering a service (e.g. NUS):
Scanning:
Advertising:
The methods available on the
Bluetooth
object are:All events are raised via the irq handler. On all ports this is a "soft" handler (i.e. you can allocate memory). The events (and their corresponding data tuple) are:
As a peripheral (i.e. GATT server), you can use the
gatts_*
methods to read/write/notify/indicate a characteristic (using the characteristic handle returned fromgatts_add_service
, and optionally a conn_handle from IRQ_CENTRAL_CONNECT)As a central, you can obtain a connection handle and characteristic handle using the GATT client methods (
gattc_*
) and use gattc_read/write, or receive notifications by IRQ_PERIPHERAL_NOTIFY.