-
Notifications
You must be signed in to change notification settings - Fork 455
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
Bluetooth LE Notifications not working #2231
Comments
Thanks for the debug and proposed solution! Why not throw together a PR with your suggestion, since you have a use case and came up with the fix? It may not be perfect, but if what is there now doesn't work (and the BTStack guys haven't kept up with their Arduino library so it's not going to be fixed upstream) we aren't likely to be worse off. |
Looking at the code, I think you're spot on with the fix, but the |
In the meantime, I made some modifications that might better fit to the structure of BTstacklib. I reverted the subscribeForNotifications function to its original version and added an additional function called listenForCharacteristicValueUpdates. To do this, I had to add int BLEDevice::listenForCharacteristicValueUpdates(BLECharacteristic * characteristic){
return BTstack.listenForCharacteristicValueUpdates(this, characteristic); and int BTstackManager::listenForCharacteristicValueUpdates(BLEDevice * device, BLECharacteristic * characteristic) {
gatt_client_listen_for_characteristic_value_updates(¬ification_listener, gatt_client_callback, device->getHandle(),
(gatt_client_characteristic_t*) characteristic->getCharacteristic());
return 1; to BTstacklib.cpp as well as the corresponding function definitions to BTstacklib.h To circumvent the problem with the local variable, I added the definition static gatt_client_notification_t notification_listener; at the beginning of BTstacklib.cpp In the ino-file (derived from the LECentral example), the new function is called before the original subscription function: device->listenForCharacteristicValueUpdates(&characteristics[charMeasurement].characteristic);
device->subscribeForNotifications(&characteristics[charMeasurement].characteristic); It works, but I have to state that I am by no means a proficient programmer nor am I an expert on BLE. This code was derived from a simple "copycat"-approach. Particularly, I do not understand the bookkeeping with the variable "notification_listener". In the example of the pico SDK, a function named gatt_client_stop_listening_for_characteristic_value_updates(¬ification_listener); is called at disconnect. My code works without this, but I suppose that there is a reason for calling this function. |
Found that the wrapper has been derived from the Arduino port that BlueKitchen published (a long time ago...). Therefore, posted the issue there (bluekitchen/btstack#615) hoping that they know exactliy how to implement the subscription for notifications correctly. |
Thanks also to Earle. I've been tearing my hair out over this exact problem for the last few days. I'm trying to build a rpi pico central for MIDI messages from an instrument. With the LECentral code it would connect, then nothing... |
Can either of you post a simple example that I can try locally without and with the fixes? I'm fine doing the PR here, since the Bluestack Arduino library is basically abandoned (but mostly working!). |
As i mentioned above my application is BLE MIDI. Here's my modified LECentral code:
To test it you'll need a MIDI perpheral such as a keyboard, or wind controller. The notification callback is a work in progress so it's not very flash... What I'm intending to do wit this project is to receive BLE MIDI from an instrument (wind controller) and pump it into a synth which has a USB port. This means that the microcontroller needs to be able to present a USB-MIDI port to the synth. Gonzo |
Hi The example code "server.c" sends a temperature measurement derived from an internal diode every couple of seconds through BLE notification. I modified this code (and the makefile) to send a temperature reading every 5 sec. and also output the temperature measurement and the device's address via USB. The output should be something like
repeated every 5 seconds. Then, take a second Pico device and copy the code below (derived from the LECentral example) to the arduino IDE. Change the address in the variable addrString to the address that the first Pico showed. Compile, upload and look at the output of the Serial Monitor. #include <BTstackLib.h>
#include <SPI.h>
#include <time.h>
typedef struct characteristic_summary {
UUID uuid;
const char * name;
bool found;
BLECharacteristic characteristic;
} characteristic_summary_t;
typedef enum characteristicIDs {
charMeasurement = 0,
numCharacteristics
} characteristicIDs_t;
// Enter address of Pico temperature sensor here:
char* addrString = "D8:3A:DD:20:70:07";
UUID myServiceUuid("0000181A-0000-1000-8000-00805F9B34FB"); // Micropython temperature example from Pico SDK
characteristic_summary characteristics[] = {
{ UUID("00002A6E-0000-1000-8000-00805F9B34FB"), "Measurement", false, BLECharacteristic()} // Micropython temperature example from Pico SDK
};
time_t now, old_time;
BLEDevice myBLEDevice;
BLEService myBLEService;
const int maxServices = 50; //max. 50 services should be enough
int numServicesFound = 0; //cout number of services found
BLEService foundServices[maxServices]; //max. 50 services should be enough
bool serviceFound;
bool characteristicFound = false;
bool sendCounter = false;
bool LED_status = false;
static gatt_client_notification_t notification_listener;
void setup(void) {
Serial.begin(115200);
while(!Serial){}; // wait for serial connection. Remove for real application...
pinMode(LED_BUILTIN, OUTPUT);
BTstack.setBLEAdvertisementCallback(advertisementCallback);
BTstack.setBLEDeviceConnectedCallback(deviceConnectedCallback);
BTstack.setBLEDeviceDisconnectedCallback(deviceDisconnectedCallback);
BTstack.setGATTServiceDiscoveredCallback(gattServiceDiscovered);
BTstack.setGATTCharacteristicDiscoveredCallback(gattCharacteristicDiscovered);
BTstack.setGATTCharacteristicNotificationCallback(gattCharacteristicNotification);
BTstack.setGATTCharacteristicIndicationCallback(gattCharacteristicIndication);
BTstack.setGATTCharacteristicReadCallback(gattReadCallback);
BTstack.setGATTCharacteristicWrittenCallback(gattWrittenCallback);
BTstack.setGATTCharacteristicSubscribedCallback(gattSubscribedCallback);
BTstack.setup();
BTstack.bleStartScanning();
}
/*
@section Loop
@text In the standard Arduino loop() function, BTstack's loop() is called first
If we're connected, we send the string "BTstack" plus a counter as fast as possible.
As the Bluetooth module might be busy, it's important to check the result of the
writeCharacteristicWithoutResponse() call. If it's not ok, we just try again in the
next loop iteration.
*/
/* LISTING_START(LECentralLoop): Loop */
void loop(void) {
BTstack.loop();
time(&now);
if(now >= old_time + 1){
old_time = now;
if (LED_status == false){
digitalWrite(LED_BUILTIN, HIGH);
LED_status = true;
} else {
digitalWrite(LED_BUILTIN, LOW);
LED_status = false;
}
}
}
/*
@section Advertisement Callback
@text When an Advertisement is received, we check if it contains
the UUID of the service we're interested in. Only a single service
with a 128-bit UUID can be contained in and Advertisement and not
all BLE devices provides this. Other options are to match on the
reported device name or the BD ADDR prefix.
If we found an interesting device, we try to connect to it.
*/
/* LISTING_START(LECentralAdvertisementCallback): Advertisement Callback */
void advertisementCallback(BLEAdvertisement *bleAdvertisement) {
Serial.print("Device discovered. Addr: ");
Serial.print(bleAdvertisement->getBdAddr()->getAddressString());
Serial.print(", UUID: ");
Serial.print(bleAdvertisement->getIBeaconUUID()->getUuid128String());
Serial.print(", RSSI: ");
Serial.println(bleAdvertisement->getRssi());
if (strcmp(bleAdvertisement->getBdAddr()->getAddressString(), addrString) == 0){
Serial.print("Address ");
Serial.print(addrString);
Serial.println(" found!");
BTstack.bleStopScanning();
BTstack.bleConnect(bleAdvertisement, 10000); // 10 s
}
}
/*
@section Device Connected Callback
@text At the end of bleConnect(), the device connected callback is callec.
The status argument tells if the connection timed out, or if the connection
was established successfully.
On a successful connection, a GATT Service Discovery is started.
*/
/* LISTING_START(LECentralDeviceConnectedCallback): Device Connected Callback */
void deviceConnectedCallback(BLEStatus status, BLEDevice *device) {
switch (status) {
case BLE_STATUS_OK:
Serial.println("Device connected!");
myBLEDevice = *device;
myBLEDevice.discoverGATTServices();
break;
case BLE_STATUS_CONNECTION_TIMEOUT:
Serial.println("Error while Connecting the Peripheral");
BTstack.bleStartScanning();
break;
default:
break;
}
}
/*
@section Device Disconnected Callback
@text If the connection to a device breaks, the device disconnected callback
is called. Here, we start scanning for new devices again.
*/
/* LISTING_START(LECentralDeviceDisconnectedCallback): Device Disconnected Callback */
void deviceDisconnectedCallback(BLEDevice * device) {
(void) device;
Serial.println("Disconnected, starting over..");
sendCounter = false;
characteristicFound = false;
BTstack.bleStartScanning();
}
/*
@section Service Discovered Callback
@text The service discovered callback is called for each service and after the
service discovery is complete. The status argument is provided for this.
The main information about a discovered Service is its UUID.
If we find our service, we store the reference to this service.
This allows to discover the Characteristics for our service after
the service discovery is complete.
*/
/* LISTING_START(LECentralServiceDiscoveredCallback): Service Discovered Callback */
void gattServiceDiscovered(BLEStatus status, BLEDevice *device, BLEService *bleService) {
switch (status) {
case BLE_STATUS_OK:
Serial.print("Service Discovered. UUID String:");
Serial.print(bleService->getUUID()->getUuidString());
Serial.print(", UUID128 String:");
Serial.println(bleService->getUUID()->getUuid128String());
if (bleService->matches(&myServiceUuid)) {
serviceFound = true;
Serial.println("Service located");
myBLEService = *bleService;
}
break;
case BLE_STATUS_DONE:
Serial.println("Service discovery finished");
if (serviceFound) {
Serial.println("Requested Service found. Search for Characteristics...");
device->discoverCharacteristicsForService(&myBLEService);
}
break;
default:
Serial.println("Service discovery error");
break;
}
}
/*
@section Characteristic Discovered Callback
@text Similar to the Service Discovered callback, the Characteristic Discovered
callback is called for each Characteristic found and after the discovery is complete.
The main information is again its UUID. If we find a Characteristic that we're
interested in, it's name is printed and a reference stored for later.
On discovery complete, we subscribe to a particular Characteristic to receive
Characteristic Value updates in the Notificaation Callback.
*/
void gattCharacteristicDiscovered(BLEStatus status, BLEDevice *device, BLECharacteristic *characteristic) {
switch (status) {
case BLE_STATUS_OK:
Serial.print("Characteristic Discovered: UUID String:");
Serial.print(characteristic->getUUID()->getUuidString());
Serial.print(", UUID128 String: ");
Serial.print(characteristic->getUUID()->getUuid128String());
Serial.print(", handle: 0x");
Serial.print(characteristic->getCharacteristic()->value_handle, HEX);
Serial.print(", properties: ");
Serial.println(characteristic->getCharacteristic()->properties);
int i;
for (i = 0; i < numCharacteristics; i++) {
if (characteristic->matches(&characteristics[i].uuid)) {
Serial.print("Characteristic found: ");
Serial.println(characteristics[i].name);
characteristics[i].found = 1;
characteristics[i].characteristic = *characteristic;
break;
}
}
break;
case BLE_STATUS_DONE:
Serial.print("Characteristic discovery finished, status ");
Serial.println(status, HEX);
if (characteristics[charMeasurement].found) {
Serial.println("Subscribe to Notifications");
device->subscribeForNotifications(&characteristics[charMeasurement].characteristic);
characteristicFound = true;
}
break;
default:
Serial.println("Characteristics discovery error");
break;
}
}
/*
@section Subscribed Callback
@text After the subscribe operation is complete, we get notified if it was
successful. In this example, we read the Characteristic that contains the
BD ADDR of the other device. This isn't strictly necessary as we already
know the device address from the Advertisement, but it's a common pattern
with iOS as the device address is hidden from applications.
*/
/* LISTING_START(LECentralSubscribedCallback): Subscribed Callback */
void gattSubscribedCallback(BLEStatus status, BLEDevice * device) {
(void) status;
Serial.print("In gattSubscribedCallback(). Status: ");
Serial.print(status);
Serial.print(", Handle: 0x");
Serial.println(device->getHandle());
}
/*
@section Read Callback
@text The Read callback is called with the result from a read operation.
Here, we write to the TX Characteristic next.
*/
void gattReadCallback(BLEStatus status, BLEDevice *device, uint8_t *value, uint16_t length) {
(void) status;
//(void) length;
Serial.print("Read callback. Data Length: ");
Serial.print(length);
Serial.print(", Value: ");
for(int n = 0; n < length; n++){
Serial.print(*(value + n), HEX);
Serial.print(" ");
}
Serial.println();
}
/*
@section Written Callback
@text After the write operation is complete, the Written Callback is called with
the result in the status argument. As we're done with the initial setup of the remote
device, we set the flag to write the test string as fast as possible.
*/
void gattWrittenCallback(BLEStatus status, BLEDevice *device) {
//(void) status;
(void) device;
sendCounter = true;
Serial.print("In gattWrittenCallback, status: ");
Serial.print(status);
Serial.print(", handle: 0x");
Serial.println(device->getHandle(), HEX);
}
/*
@section Notification Callback
@text Notifications for Characteristic Value Updates are delivered via the
Notification Callback. When more than one Characteristic is subscribed,
the value handle can be used to distinguish between them. The
BLECharacteristic.isValueHandle(int handle) allows to test if a value handle
belongs to a particular Characteristic.
*/
void gattCharacteristicNotification(BLEDevice *device, uint16_t value_handle, uint8_t *value, uint16_t length) {
(void) device;
(void) value_handle;
//(void) length;
Serial.print("Notification received. Length: ");
Serial.print(length);
Serial.print(", handle: 0x");
Serial.print(value_handle, HEX);
Serial.print(" ");
if (length == 2) { // Temperature for Pico temperature server example
float temp = little_endian_read_16(value, 0);
Serial.printf("Temperature %.2f degc\n", temp / 100);
} else {
printf("Unexpected length %d\n", length);
for(int n = 0; n < length; n++){
printf("%X ", *(value + n));
}
}
}
void gattCharacteristicIndication(BLEDevice *device, uint16_t value_handle, uint8_t *value, uint16_t length) {
(void) device;
//(void) value_handle;
//(void) length;
Serial.print("Indication received. Length: ");
Serial.print(length);
Serial.print(", handle:");
Serial.print(value_handle);
Serial.print(", value:");
Serial.println((const char *)value);
} The output of the program is something like:
This means that the device with the given address has been found, connection has been established and the subscription to notifications was successful (status = 0). Without any modifications to BTstack.cpp, the output stops there and the notification callback routine, that should output the temperature value, is never called. With the modifications described above (i.e. adding the gatt_client_listen_for_characteristic_value_updates() function in some or the other way, the notification works as expected and the temperature is printed out every 5 seconds:
|
Awesome, thanks for both the samples. I think with one characteristic, like you're running @ullibak , the Also, FWIW, the BTStack guys seem to say that the library code as-is is correct, w/o the add'l |
Thank you Earle! In the link that you posted, Matthias writes (quote): "Anyway, it's enough to call gatt_client_write_client_characteristic_configuration to enable notifications, no need to gatt_client_write_characteristic_descriptor manually. To get notifications, you also have to register a listener for that characteristic with gatt_client_listen_for_characteristic_value_updates." As I understand this, the gatt_client_listen_for_characteristic_value_updates() function is mandatory to get notifications. Bluekitchen also uses this function e.g. in the example just before subscribing to notifications. I suppose that it simply was forgotten when BlueKitchen created their Arduino port. |
#2241 adds that new structure to every characteristic and uses it in the listen/unlisten methods. I could not find any calls that look similar for BLE indicators, and there are no instances of their use in the BTStack examples, so I have not added anything to them. Tested w/the BLE temp sensor MCVE posted here. |
@earle: I see that you are already working on the solution. In the meantime, Matthias gave an input on how to implement a "general" listener: |
First of all, thanks to Earle for creating and maintaining this beautiful library!
I have a device that sends data through BLE notifications. I used the example BTStack -> LECentral and modified it for the service and characteristic UUIDs of my device. I was able to connect and read services and characteristics. I was also able to subscribe to notifications and the status value of the gattSubscribedCallback() function suggests that the notification was successfully registered. But the gattCharacteristicNotification() callback routine was never triggered when my device sent data.
I then compared to the BLE client and server temperature measurement example in the Pico SDK
(\pico-examples\pico_w\bt\standalone\server.c and client.c) and found that maybe something is missing in the subscribeForNotifications() function in BTStackLib.cpp
After I modified this function, the notification callback routine was triggered and everything seemed to work as expected.
This is probably not the right way to fix this issue, but could maybe be a hint to a proper solution. The same solution should then also be applied to the subscribeForIndications() function and maybe also the corresponding unsubscribe functions.
Many thanks!
The text was updated successfully, but these errors were encountered: