Thanks to visit codestin.com
Credit goes to github.com

Skip to content

No automatic pre discovery #40

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

Closed
wants to merge 8 commits into from

Conversation

Jeroen88
Copy link

Optional no automatic discovery of characteristics and descriptors, leading to faster connections, less heap usage and less drainage of the peripheral's battery. Characteristics are retrieved only when needed.

Calling BLEClient *pClient = BLEDevice::createClient() leads to the current behavior. However calling BLEClient *pClient = BLEDevice::createClient(false) does retrieve all services associated with this peripheral when calling pClient->connect() but it does not retrieve the characteristics for each service nor the descriptors for each characteristic! This saves a lot of connection time. Heap memory is also saved, because the characteristics and descriptors are not stored in their vectors. Moreover, because the peripheral does not need to send these characteristics and descriptors this saves battery life for the peripheral.

When pClient->getService(...uuid...)->getCharacteristic(...uuid...) is called, first the characteristic is looked up in the vector. If found that characteristic is returned. If not found, it is retrieved once and stored into the vector (if present in the peripheral of course).

For my use case, a LYWSD03MMC temperature / humidity sensor, this leads to the storage of 2 characteristics only (the payload characteristic and the battery characteristic) and no descriptors. Without this PR a total of 32 characteristics are read and also 32 descriptors. In my case this PR saves heap memory, connection time and battery life for 32 - 2 + 32 = 60 objects!

This PR is not yet thoroughly tested (in fact I only tested a few getCharacteristic() and a canRead()), but since it is acting almost the same as with pre retrieval of the characteristics, I think it should work.

It is easy to see the behavior by calling Serial.printf("%s\n", pClient->toString().c_str()) each time a new getCharacteristic() is performed. At first a client is populated with services only. The client is 'growing' with new characteristics after each discovery.

One drawback is that without automatic pre discovery the descriptors can not be read. For retrieving descriptors also 'on the fly' and end handle is needed. This end handle is one less than the handle for the next characteristic belonging to a service, but since not all characteristics are retrieved, I am not aware of a way to find the end handle in another way. If someone has a good suggestion: welcome!

Jeroen88 added 6 commits May 9, 2020 13:29
…24 bytes of heap for a small device (LYWSD03MMC)
…teristicVector instead) saving in total (compared to the current master) 1,508 bytes of program memory and 6,500 bytes of heap for a small device (LYWSD03MMC)
…eading to faster connections, less heap usage and less drainage of the peripheral's battery. Characteristics are retrieved only when needed
@Jeroen88
Copy link
Author

This PR needs PR #37

@Jeroen88
Copy link
Author

@h2zero this is the last 'big' change directed to memory usage and performance that I want to suggest. I have a few other ideas, so maybe I do a few more PRs.

What I do want to do is add a lot more const to class functions for passing a const this pointer, This allows for temporary pointers and references that can be reused, I want to do e.g.

const NimBLERemoteCharacteristic *pCharacteristic = pClient->getService(NimBLEUUID(0x00010203, 0x0405, 0x0607, 0x08090a0b0c0d1912))->getCharacteristic(NimBLEUUID(0x00010203, 0x0405, 0x0607, 0x08090a0b0c0d2b12));
if(pCharacteristic->canRead()) {
  std::string value = pCharacteristic->readValue();
}

Now the compiler forbids this because of lack of constness. But I will wait with this until you have processed my other PRs

@h2zero
Copy link
Owner

h2zero commented May 10, 2020

Yes, I wanted to do some implementation of this at some point, as you noticed a lot of resources are wasted on attributes we may not want, current implementation was a "get it working" one 😄. NimBLE does provide a lot more API's for things, we could even skip discovery all together and just read by uuid etc.. but this is a good start.

@chegewara
Copy link

Bluedroid is providing discovery services from cache, which should be implemented, so each next connection and discovery should be faster. To be honest i didnt test it so i dont know if this is implemented properly. This is similar to android (and maybe iOS) behavior.

@h2zero
Copy link
Owner

h2zero commented May 10, 2020

@chegewara unfortunately NimBLE doesn't provide this, the developers wanted to leave that up to the app to handle. I have implemented it here by providing a parameter to connect() which if set to false will not rediscover if the client already has the database.

@h2zero
Copy link
Owner

h2zero commented May 17, 2020

I'm going to take a look into the stack for this one, I like the idea of not pre-fetching all the attributes if possible but I need to see how NimBLE handles that. Ideally we could just get the services/characteristics we want and not the whole database but if NimBLE just fetches all and filters the results that would be bad for time/battery if looking up more than one. Also need a way to get the descriptors.

@Jeroen88
Copy link
Author

Ideally we could just get the services/characteristics we want and not the whole database

That would be nice indeed. I ran into that I did not find a way to get the handle of just one service, only to discover all of them. Then I decided to have the client discover all services on connection (like it does now) and then stop discovering characteristics and descriptors. But if you know a way to retrieve just one service at a time that would also save the discovery (and the heap memory) for unneeded services.

NimBLE does provide a lot more API's for things, we could even skip discovery all together and just read by uuid etc..

Yes I studied the function to read by uuid as well, but I came to the conclusion that you still need handles for that, and the only way to get the handles was by reading the characteristic (and the services) first.

Also need a way to get the descriptors

I can't imagine that this is impossible, but I ran into the problem that the end handle used for retrieving the descriptors of a remote characteristic is the start handle of the next characteristic minus 1 in the current implementation. Since only the needed characteristics are read I could not determine the end handle, but I am sure you know a way!

@Jeroen88
Copy link
Author

... taking up discussion from #53

The connection is much faster without the building of the database. If the stack builds the full database pon first connection, I would expect that connecting would take much longer. So I guess the stack does not build the database, also because a fucntion like ble_gattc_disc_svc_by_uuid exists.

@h2zero
Copy link
Owner

h2zero commented May 20, 2020

That’s where the question come from though. The stack may just discover all until it finds that uuid then stop, if the handle is early in the discovery it will be quick. But if that uuid has a handle of say 0xFFF0 then it will have discovered the whole thing and only reported back the one. That’s assuming that’s how it works, my initial investigation says it is but more work required.

@h2zero
Copy link
Owner

h2zero commented May 20, 2020

Here is a great description of how this works if you’re interested.

@Jeroen88
Copy link
Author

Here is a great description of how this works if you’re interested.

I'll have a look,thnx!

@h2zero
Copy link
Owner

h2zero commented May 20, 2020

I got the answer to my question, looked in the core spec and indeed services and characteristics are discoverable individually! So then the problem becomes how to get the descriptors... I'll have to look into that tonight, I might have it wrong in the library anyway.

@Jeroen88
Copy link
Author

I got the answer to my question

Nice!

I'll have to look into that tonight

Also nice, I will check that tomorrow then

I might have it wrong in the library anyway

Oops 😰

@Jeroen88
Copy link
Author

I think looking up one descriptor is ble_gatts_find_dsc. Now only this has to be implemented. And of course the automatic discovery of the descriptors has to be checked...

@h2zero
Copy link
Owner

h2zero commented May 20, 2020

That is for the peripheral to get a descriptor from its local database.

I have located the source of the trouble with getting the descriptors of a characteristic without getting all of them. It’s due to NimBLE not providing an end_handle which is why I did what’s in the library to compensate. I’ll see if I can patch it and submit a PR there.

@chegewara
Copy link

They are making simply assumption that next service/characteristic handle is end of previous end_handle (or next - 1).

@h2zero
Copy link
Owner

h2zero commented May 21, 2020

Yep, I looked at the code and the core spec. Seems if you need the end handle you have to ask for the next characteristic. I made a patch to do just that.

@Jeroen88
Copy link
Author

Closed because the ideas of this PR are developed in branch disc-indv-attributes.

@Jeroen88 Jeroen88 closed this May 22, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants