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

Skip to content

Update NimBLERemoteCharacteristic value from a notification (replaces PR #27) #49

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

Merged
merged 15 commits into from
May 30, 2020

Conversation

Jeroen88
Copy link

This PR:

  • Updates the m_value in the NimBLERemoteCharacteristic from the stack callback
  • Adds std::string NimBLERemoteCharacteristic::getValue(time_t *timestamp = nullptr) to access the value that is present in the characteristic without reading it again from the remote server!
  • Adds a timestamp (epoch) to the NimBLERemoteCharacteristic. The timestamp is set after a ::readValue() and after a notification.
  • Adds a new callback void notifyCallbackPlain(BLERemoteCharacteristic* pBLERemoteCharacteristic, bool isNotify). In this new callback the uint8_t * pointer to the data and the length parameters are omitted, because they can be read from the characteristic itself, by pBLERemoteCharacteristic->getValue().
  • Adds a timestamp (epoch) to NimBLEadvertisedDevice. The timestamp is set each time the device is encountered during a scan
  • Adds NimBLEadvertisedDevice::getTimestamp() to access the last time the device was scanned

With this PR:

  • It is possible to read the service data and / or the manufacturer data without setting up callbacks for it, and without waiting for a scan to complete. This is especially useful in an infinite scan (with duration == 0). The callback still keeps on having the same behavior.
  • It is also possible to get the value from a notification, again without setting up callbacks for it. Still the callbacks work properly. Moreover: In the notify callback the value can (also) be accessed from the notification itself.
  • The API stays backward compatible, remoteCharacteristic::readValue() (and the other read functions) keep their purpose and work like before. The new ::getValue() returns the latest value set either by ::readValue() or by notification

This PR replaces PR #27. As stated by @h2zero in that PR, the device for what I created that PR, the LYWSD03MMC temperature / moisture sensor, does not comply with the BLE standards: it starts sending notifications on service ebe0ccb0-7a0a-4b0c-8a1a-6ff2997da3a6 characteristic ebe0ccc1-7a0a-4b0c-8a1a-6ff2997da3a6 right after connecting with it, without asking for that notification. With this PR you can ask that device for a notification on the service and characteristic, and even read it, and register a callback for it. That will work conform BLE standard (although connecting to the device is enough to get the notifications). In the notification callback you can read the data from the uint8_t * pointer OR from the characteristic itself using getValue(). Or you can access the latest value at any time by getting a reference to the characteristic and issue a getValue()

Jeroen88 added 5 commits May 14, 2020 10:27
… to get the latest remote characteristic and (optionally) it's timestamp. Getting the value and the timestamp is made unary using the semaphore to assure value and timestamp belong together. A new NimBLERemoteCharacteristic::registerForNotify() is introduced, passing only the remote characteristic and it's type (notify / indicate), since the value can now be retrieved from the remote characteristic itself. NiBLEClient::handleGapEvent() is adapted to set m_value and the new m_timestamp in the remote characteristic. Also the call to the new callback is added
…eded for returning the service data to be copied correctly. Added a timestamp for the moment a device was scanned
@h2zero
Copy link
Owner

h2zero commented May 17, 2020

Good work here, might be an issue with the semaphore due to upcoming changes with those but we can worry about that after. I wish I had the time you do to work on this lol, I have too many other things going on lately, only get to work on this in small increments. Looking forward to testing later.

@Jeroen88
Copy link
Author

Good work here

Thank you!

I wish I had the time you do to work on this

It would be nice if you find some time to merge #37 / #46, to change the maps into vectors. I can merge these small changes on my local master for the time being, but the other keep me from further improvements.

@h2zero
Copy link
Owner

h2zero commented May 17, 2020

I'll try to merge #37 tonight, could you cleanup the commented out code?

@Jeroen88
Copy link
Author

I can't push #37 anymore, because you merged master. I can cleanup #46 instead. But... we live in a different timezone! So next opportunity will be tomorrow to clean things up...

@Jeroen88
Copy link
Author

Jeroen88 commented May 18, 2020

@h2zero I updated this PR with the latest master

@h2zero
Copy link
Owner

h2zero commented May 18, 2020

Perfect, thanks!

@h2zero
Copy link
Owner

h2zero commented May 19, 2020

@Jeroen88 Is the new callback necessary here? I mean other than saving a couple bytes on the stack it doesn't seem to provide any functionality that the original doesn't have.

@Jeroen88
Copy link
Author

No not necessary because it does not add new functionality. I introduced it to maybe replace the current callback in the future

@h2zero
Copy link
Owner

h2zero commented May 22, 2020

What I think should be done here in this case I’d comment out the new callback related code. Then we can discuss if anything related to the current callback should be changed. The rest looks great but that part should probably have it’s own PR.

@Jeroen88
Copy link
Author

What I think should be done here in this case I’d comment out the new callback related code.

Removed it completely, because:

Then we can discuss if anything related to the current callback should be changed.

Well the change is very easy. Because with this PR the value is updated in the remote characteristic itself, also the pointer to the data and the length of the data could be taken from the characteristic itself, that is passed by the callback. This is just a minor improvement, I agree, so I leave it up to you @h2zero if you want to keep it this way or change it.

@h2zero
Copy link
Owner

h2zero commented May 23, 2020

Well the change is very easy. Because with this PR the value is updated in the remote characteristic itself, also the pointer to the data and the length of the data could be taken from the characteristic itself, that is passed by the callback. This is just a minor improvement, I agree, so I leave it up to you @h2zero if you want to keep it this way or change it.

Yes there is nothing wrong with callback itself, it's just extra code to deal with is all. Would be fine if we were to replace the existing callback, but I'd rather not break the API unless it was a significant improvement.

@Jeroen88
Copy link
Author

@h2zero Updating to latest master

m_semaphoreReadCharEvt.take("readValue");
std::string value = m_value;
*timestamp = m_timestamp;
m_semaphoreReadCharEvt.give(1);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know it seems strange but 0 == success in BLE (also 14 in NimBLE) anything else is an error value. So this should be give(0) or just give().

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually this method should probably be refactored a bit since we always return a copy of m_value .
Something like:

{
    m_semaphoreReadCharEvt.take("readValue");
    std::string value = m_value;
    m_semaphoreReadCharEvt.give();

    if(timestamp != nullptr) {
        *timestamp = m_timestamp;
    }

    return value;
}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wrote it like that on purpose. If we change it to your suggestion there is a little chance that the return value and the timestamp do not belong together, if a notification is in the middle of an update.

I do not like the extra copy of the value but I could not come up with another way. Do you?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Making a copy is necessary but the compiler should optimize it to copy directly into the callers container.

Also now that you mention the timing issue, we have no protection in the notification handler against the value being updated as getValue() is copying. The semaphore would only block if readValue() was called at the same time, but that would be odd.

Of course we cannot take a semaphore in the notification handler unless it was used with a 0 timeout then tested for success, but then we wouldn't be able to update the value if it failed.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am a semaphore zero and you a h(2z)ero+e, what would you suggest?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we come up with a reason for this?

My first thought is when we change the value in notify it creates a new string object and just changes the location pointer of m_value. If the copy constructor was called in getValue() at the same time it would still get a valid reference, at least until the original m_value destructor is called, which could be long enough to finish the copy. I could be completely wrong, worth testing though.

The other possibility is flawed testing 😄, I just realized the peripheral did not vary the data length so my test never went out of range.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the copy constructor was called in getValue() at the same time it would still get a valid reference, at least until the original m_value destructor is called, which could be long enough to finish the copy

I do not know how FreeRTOS switches tasks. We're not interrupt based, interrupts may occur everywhere. But maybe the OS has some mechanism to protect for the segfaults? idk...

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I know it does not have any such mechanism. It simply switches between tasks on a priority basis, when one task is done or waiting for an event it switches to the next highest priority task. Interrupts are handled as their own, highest priority, task that stops all others I believe, but it's been a while since I got into that.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have come up with a compromise for this, I'll push it to this PR right away, stress testing for a hour now without a problem. It uses the same semaphore with a 0 timeout in the notify callback, that does not update the value if it fails to take it, but that should only happen when reading directly at the same time, so it should not be an issue 99.99% of the time.

If you could test it later on your setup and let me know I'd really appreciate it.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you could test it later on your setup and let me know I'd really appreciate it.

Sure! But most likely not today because I am busy at the office. Tomorrow for sure!

@Jeroen88
Copy link
Author

Not really a stress test, but getting the value updated from the notification every 12s for half an hour now without any problems!

@Jeroen88
Copy link
Author

Is any action from my side needed or will you push this PR?

@h2zero
Copy link
Owner

h2zero commented May 29, 2020

Nothing you need to do unless you wanted to change/add something else. Otherwise I will merge shortly.

@Jeroen88
Copy link
Author

No further plans!

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.

2 participants