micropython / micropython-lib

Core Python libraries ported to MicroPython
Other
2.42k stars 1k forks source link

Unable to save service discovery during bonding makes it impossible to get data from some bonded devices on reconnect #903

Open brianreinhold opened 3 months ago

brianreinhold commented 3 months ago

A BLE peripheral that bonds also assumes that the services and characteristics and descriptors discovered during the bonding connection are saved by the client such that on a reconnect, after encryption, service discovery is unnecessary and the peripheral can notify and/or indicate it's data. There are several health devices that do this.

With the aioble library, one is forced to do service discovery every connection in order to subscribe to notifications/indications. However, service discovery is painfully slow, and long before it is done the health device has already indicated/notified its data and by the time one has subscribed (if the device does not disconnect) the data has already been evented and nothing is received.

How to work around this?

Is this a possible work-a-round?

Edit the code so that the start, end, and value handles are made public instead of private Save these handles and UUID for each service, characteristic, and descriptor discovered Recreate the ClientService, ClientCharacteristic, and ClientDescriptor for each of the saved items (of interest) Change the subscribe method such that it uses the saved ClientDescriptor instead of doing further discovery operations to get the Descriptor.

I do not know if that is possible as the devil is always in the details. But to those who are familiar with the inner workings of aioble, is this something one could do, or is it far more complicated that that?

andrewleech commented 3 months ago

Ah, yeah that's a tricky issue. I haven't had to deal with a device that assumes everything is cached and sends notifications / events immediately after connection like that!

I'm thinking there will be more needed at the C level to support this, in particular the exposing CCCD for caching will certainly be needed to maintain the subscriptions to characteristics. I started work on that a few years ago but didn't have the resources to finalise and test it.

Oh wait, CCCD is more about the device / server end isn't it, you're referring more to the client / central side wanting to cache the characteristics. I'm really not as familar with that side, I've only developed devices myself.

I'm not sure if the central side stack needs to be informed about the characterisics to set up buffers or anything? Maybe if it's just characteristic identifiers then exposing them to be saved / resored in the json file would be enough; certainly that makes sense to start with to see if it works?

brianreinhold commented 3 months ago

I can't logon to github from this device so I have to respond this way.

Yes I am thinking about bonded reconnects. A client needs to be able to read/write/enable/disable characteristics/descriptors without having to do service discovery (which is painfully slow). A server that has bonded to a client that has enabled the 0x2902 descriptor shall not have to re-enable the descriptor in order to receive notifications/indications on a reconnect. So this code

# Write to the Client Characteristic Configuration to subscribe to
# notify/indications for this characteristic.
async def subscribe(self, notify: bool=True, indicate: bool=False):
    # Ensure that the generated notifications are dispatched in case the app
    # hasn't awaited on notified/indicated yet.
    self._register_with_connection()
    if cccd := await self.descriptor(bluetooth.UUID(_CCCD_UUID)):
        await cccd.write(struct.pack("<H", _CCCD_NOTIFY * notify + _CCCD_INDICATE * indicate))
    else:
        raise ValueError("CCCD not found")

would only need to register for the notifications/indications on a bonded reconnect.

If one can create the ClientService, ClientCharacteristic, and ClientDescriptor objects from the saved handles and properties would this be sufficient to write and read a characteristic/descriptor and to receive indications/notifications ... without doing any service discovery process. I think that if one can do this re-creation, the rest of the objects can be completed ... but I do not know, especially objects like the ClientDiscovery (only necessary for service discovery?).

I am actually having a lot of difficulty with bonded health devices on reconnects because of the necessity of redoing service discovery and its slowness. Some thermometers allow you to keep taking measurements but you will lose the first as they have evented before one can subscribe. This is NOT very user friendly to an elderly clientelle!

Looking at the btstack C code, this is the information I need to get and cache

typedef struct { uint16_t start_group_handle; uint16_t end_group_handle; uint16_t uuid16; uint8_t uuid128[16]; } gatt_client_service_t;

typedef struct { uint16_t start_handle; uint16_t value_handle; uint16_t end_handle; uint16_t properties; uint16_t uuid16; uint8_t uuid128[16]; } gatt_client_characteristic_t;

typedef struct { uint16_t handle; uint16_t uuid16; uint8_t uuid128[16]; } gatt_client_characteristic_descriptor_t;

Brian


From: Andrew Leech @.> Sent: Monday, July 22, 2024 7:50 PM To: micropython/micropython-lib @.> Cc: Brian Reinhold @.>; Author @.> Subject: Re: [micropython/micropython-lib] Unable to save service discovery during bonding makes it impossible to get data from some bonded devices on reconnect (Issue #903)

Ah, yeah that's a tricky issue. I haven't had to deal with a device that assumes everything is cached and sends notifications / events immediately after connection like that!

I'm thinking there will be more needed at the C level to support this, in particular the exposing CCCD for caching will certainly be needed to maintain the subscriptions to characteristics. I started work on that a few years ago but didn't have the resources to finalise and test it.

Oh wait, CCCD is more about the device / server end isn't it, you're referring more to the client / central side wanting to cache the characteristics. I'm really not as familar with that side, I've only developed devices myself.

I'm not sure if the central side stack needs to be informed about the characterisics to set up buffers or anything? Maybe if it's just characteristic identifiers then exposing them to be saved / resored in the json file would be enough; certainly that makes sense to start with to see if it works?

— Reply to this email directly, view it on GitHubhttps://github.com/micropython/micropython-lib/issues/903#issuecomment-2243998472, or unsubscribehttps://github.com/notifications/unsubscribe-auth/AJMDO647W45W6ZCKHQBALXTZNWLF3AVCNFSM6AAAAABLJH4YBGVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDENBTHE4TQNBXGI. You are receiving this because you authored the thread.

brianreinhold commented 3 months ago

Here is what I get from service discovery (pulse oximeter) printing out the handles. Descriptor discovery is not done so I dont have those values. I suppose I could catch them when I subscribe. I find that enabling descriptors every connection is not an issue as some devices misbehave and do not persist their enabled states even when bonded (which is a violation), but discovering the descriptor each time before enabling is a hinder and can be an issue.

2024-07-23 11:52:49 INFO bt_async_central: LNI: ================ Service discovery results: 2024-07-23 11:52:49 INFO bt_async_central: LNI: Service: UUID('46a970e0-0d5f-11e2-8b5e-0002a5d5c51b') start handle: 31 end handle: 43 2024-07-23 11:52:49 INFO bt_async_central: LNI: Characteristic: UUID('0aad7ea0-0d60-11e2-8e3c-0002a5d5c51b') value handle: 33 end handle: 34 2024-07-23 11:52:49 INFO bt_async_central: LNI: Characteristic: UUID('34e27863-76ff-4f8e-96f1-9e3993aa6199') value handle: 36 end handle: 37 2024-07-23 11:52:49 INFO bt_async_central: LNI: Characteristic: UUID('1447af80-0d60-11e2-88b6-0002a5d5c51b') value handle: 39 end handle: 40 2024-07-23 11:52:50 INFO bt_async_central: LNI: Characteristic: UUID('ec0a8f24-4d24-11e7-b114-b2f933d5fe66') value handle: 42 end handle: 43 2024-07-23 11:52:50 INFO bt_async_central: LNI: Service: UUID(0x180a) start handle: 6 end handle: 30 2024-07-23 11:52:50 INFO bt_async_central: LNI: Characteristic: UUID(0x2a24) value handle: 11 end handle: 12 2024-07-23 11:52:50 INFO bt_async_central: LNI: Characteristic: UUID(0x2a29) value handle: 8 end handle: 9 2024-07-23 11:52:50 INFO bt_async_central: LNI: Characteristic: UUID(0x2a26) value handle: 20 end handle: 21 2024-07-23 11:52:50 INFO bt_async_central: LNI: Characteristic: UUID(0x2a28) value handle: 17 end handle: 18 2024-07-23 11:52:50 INFO bt_async_central: LNI: Characteristic: UUID(0x2a25) value handle: 14 end handle: 15 2024-07-23 11:52:50 INFO bt_async_central: LNI: Characteristic: UUID(0x2a23) value handle: 26 end handle: 27 2024-07-23 11:52:51 INFO bt_async_central: LNI: Characteristic: UUID(0x2a27) value handle: 23 end handle: 24 2024-07-23 11:52:51 INFO bt_async_central: LNI: Characteristic: UUID(0x2a2a) value handle: 29 end handle: 30 2024-07-23 11:52:51 INFO bt_async_central: LNI: Service: UUID(0x1800) start handle: 1 end handle: 5 2024-07-23 11:52:51 INFO bt_async_central: LNI: Characteristic: UUID(0x2a01) value handle: 5 end handle: 7 2024-07-23 11:52:51 INFO bt_async_central: LNI: Characteristic: UUID(0x2a00) value handle: 3 end handle: 5 2024-07-23 11:52:51 INFO bt_async_central: LNI: Service: UUID(0x180f) start handle: 68 end handle: 65535 2024-07-23 11:52:51 INFO bt_async_central: LNI: Characteristic: UUID(0x2a19) value handle: 70 end handle: 65535 2024-07-23 11:52:51 INFO bt_async_central: LNI: Service: UUID(0x1805) start handle: 60 end handle: 67 2024-07-23 11:52:52 INFO bt_async_central: LNI: Characteristic: UUID(0x2a2b) value handle: 62 end handle: 64 2024-07-23 11:52:52 INFO bt_async_central: LNI: Characteristic: UUID(0x2a14) value handle: 66 end handle: 67 2024-07-23 11:52:52 INFO bt_async_central: LNI: Service: UUID(0x1822) start handle: 44 end handle: 59 2024-07-23 11:52:52 INFO bt_async_central: LNI: Characteristic: UUID(0x2a5f) value handle: 50 end handle: 52 2024-07-23 11:52:52 INFO bt_async_central: LNI: Characteristic: UUID(0x2a5e) value handle: 46 end handle: 48 2024-07-23 11:52:52 INFO bt_async_central: LNI: Characteristic: UUID(0x2a60) value handle: 54 end handle: 55 2024-07-23 11:52:52 INFO bt_async_central: LNI: Characteristic: UUID(0x2a52) value handle: 57 end handle: 59

Brian


From: Andrew Leech @.> Sent: Monday, July 22, 2024 7:50 PM To: micropython/micropython-lib @.> Cc: Brian Reinhold @.>; Author @.> Subject: Re: [micropython/micropython-lib] Unable to save service discovery during bonding makes it impossible to get data from some bonded devices on reconnect (Issue #903)

Ah, yeah that's a tricky issue. I haven't had to deal with a device that assumes everything is cached and sends notifications / events immediately after connection like that!

I'm thinking there will be more needed at the C level to support this, in particular the exposing CCCD for caching will certainly be needed to maintain the subscriptions to characteristics. I started work on that a few years ago but didn't have the resources to finalise and test it.

Oh wait, CCCD is more about the device / server end isn't it, you're referring more to the client / central side wanting to cache the characteristics. I'm really not as familar with that side, I've only developed devices myself.

I'm not sure if the central side stack needs to be informed about the characterisics to set up buffers or anything? Maybe if it's just characteristic identifiers then exposing them to be saved / resored in the json file would be enough; certainly that makes sense to start with to see if it works?

— Reply to this email directly, view it on GitHubhttps://github.com/micropython/micropython-lib/issues/903#issuecomment-2243998472, or unsubscribehttps://github.com/notifications/unsubscribe-auth/AJMDO647W45W6ZCKHQBALXTZNWLF3AVCNFSM6AAAAABLJH4YBGVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDENBTHE4TQNBXGI. You are receiving this because you authored the thread.