-
-
Notifications
You must be signed in to change notification settings - Fork 179
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
Conversation
… 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
…ce(const NimBLEAddress &address)
…ce(const NimBLEAddress &address)
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. |
I'll try to merge #37 tonight, could you cleanup the commented out code? |
@h2zero I updated this PR with the latest master |
Perfect, thanks! |
@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. |
No not necessary because it does not add new functionality. I introduced it to maybe replace the current callback in the future |
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. |
Removed it completely, because:
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. |
@h2zero Updating to latest master |
src/NimBLERemoteCharacteristic.cpp
Outdated
m_semaphoreReadCharEvt.take("readValue"); | ||
std::string value = m_value; | ||
*timestamp = m_timestamp; | ||
m_semaphoreReadCharEvt.give(1); |
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 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().
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.
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;
}
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 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?
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.
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.
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 am a semaphore zero and you a h(2z)ero+e, what would you suggest?
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.
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.
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.
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...
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.
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.
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 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.
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.
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!
Not really a stress test, but getting the value updated from the notification every 12s for half an hour now without any problems! |
Is any action from my side needed or will you push this PR? |
Nothing you need to do unless you wanted to change/add something else. Otherwise I will merge shortly. |
No further plans! |
This PR:
m_value
in theNimBLERemoteCharacteristic
from the stack callbackstd::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!NimBLERemoteCharacteristic
. The timestamp is set after a::readValue()
and after a notification.void notifyCallbackPlain(BLERemoteCharacteristic* pBLERemoteCharacteristic, bool isNotify)
. In this new callback theuint8_t *
pointer to the data and the length parameters are omitted, because they can be read from the characteristic itself, bypBLERemoteCharacteristic->getValue()
.NimBLEadvertisedDevice
. The timestamp is set each time the device is encountered during a scanNimBLEadvertisedDevice::getTimestamp()
to access the last time the device was scannedWith this PR:
duration == 0
). The callback still keeps on having the same behavior.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 notificationThis 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 usinggetValue()
. Or you can access the latest value at any time by getting a reference to the characteristic and issue agetValue()