Giter Club home page Giter Club logo

Comments (24)

pan- avatar pan- commented on June 30, 2024

@yogeshk19 This is not really the place where these kind of questions should be answered but I will answer yours.

You do not subscribe to notification by calling gattClient::onHVX. Calling this function will register a callback which will be called when the client receive a notification from the server. To receive such event, the client should first indicate to the server that it is interested by notifications. It should register to notification for the characteristics is interested in.

In a GATT server, a characteristic is composed of several attributes:

  • The characteristic declaration: It indicates to a client discovering the server that this attribute is the beginning of a characteristic. It contains the UUID of the characteristic, its properties as well as the handle of its value.
  • The characteristic value. It is the actual value of a characteristic. This is the attribute read by DiscoveredCharacteristic::read.
  • Descriptors: It is a set of attribute containing metadata around the characteristics. It can contains things like user description or unit. If the characteristic emit notification and/or indication it will also contains a CCCD (Client Characteristic Configuration Descriptor). If the client is interested by notifications or indication it should indicate it to the server by writing the CCCD attribute.

During a discovery, the discovery process returns DiscoveredCharacteristic objects. Once the discovery is terminated the client can register to a notification for a given characteristic:

  • First, descriptors of the characteristic has to be discovered because descriptors can be stored in any order (often the CCCD is the first attribute after the characteristic value attribute but it is not guaranteed.). The member function DiscoveredCharacteristic::discoverDescriptor handle this task.
  • If the UUID of a DiscoveredCharacteristicDescriptor is equal to BLE_UUID_DESCRIPTOR_CLIENT_CHAR_CONFIG then this descriptor is the CCCD.
  • Write the CCCD using GattClient::write 1 for notification or 2 for indication or 3 for notification and indications.

from ble.

yogeshk19 avatar yogeshk19 commented on June 30, 2024

Thanks a lot Vincent for taking the time to respond. Sorry I posted my question here, however posting on the mbed site didn't elicit any response and I accidentally stumbled into the issues for this github repository for the BLE, and saw one of the issues were around the notifications. So I am assuming the solution you have provided supersedes and is better solution than the hacky solution, i.e. basically that was provided for the mbed nrf51 in order for the client to subscribe to notifications. Is there any samples in the mbed site that uses the solution you have suggested? So I can close my question in the mbed site with the solution you have suggested?

Thanks,
Yogesh

from ble.

pan- avatar pan- commented on June 30, 2024

@yogeshk19 I don't have source code that I can show you at hand but out of my head it can be realized with something like this:

struct EnableNotificationParams { 
    const DiscoveredCharacteristic& characteristic;
    bool success;
}; 

typedef FunctionPointerWithContext<const EnableNotificationParams&> EnableNotificationCallback_t;

struct EnableNotificationProcedure { 
    static bool launch(
        const DiscoveredCharacteristic& characteristic, 
        const EnableNotificationCallback_t& cb) { 
        EnableNotificationProcedure* proc = new EnableNotificationProcedure( 
            characteristic, cb
        );

        return proc->start();
    }

private:
    EnableNotificationProcedure(
        const DiscoveredCharacteristic& characteristic, 
        const EnableNotificationCallback_t& cb) : 
        _char(characteristic), _cb(cb), _CCCD(0) { 
    }

    bool start() { 
        if (!_char.notify()) { 
            delete this;
            return false;
        }

        ble_error_t err = discoverCharacteristicDescriptors( 
            _char, 
            asFunction(&EnableNotificationProcedure::whenDescriptorDiscovered),
            asFunction(&EnableNotificationProcedure::whenDiscoveryEnd)
        )

        if (err) { 
            delete this;
            return false;
        }

        return true;
    }

    void whenDescriptorDiscovered(CharacteristicDescriptorDiscovery::DiscoveryCallbackParams_t& p) { 
        if (p.descriptor.getUUID() == BLE_UUID_DESCRIPTOR_CLIENT_CHAR_CONFIG) { 
            _CCCD = p.descriptor.getAttributeHandle();
            client()->terminateCharacteristicDescriptorDiscovery(_char);
        }
    }

    void whenDiscoveryEnd(CharacteristicDescriptorDiscovery::TerminationCallbackParams_t& p) { 
        // if an error occur report the error and terminate the procedure.
        if (p.status) { 
            EnableNotificationParams res = { _char, false };
            _cb(res);
            delete this;
            return;
        }

        // otherwise launch write the descriptor
        // first register the write callback 
        client()->onDataWritten(
            asFunction(&EnableNotificationProcedure::whenDataWritten)
        );

        uint16_t notification_enabled = 1; 
        ble_error_t err = client()->write( 
            GattClient::GATT_OP_WRITE_REQ,
            _char.getConnectionHandle(),
            _CCCD,
            sizeof(notification_enabled),
            reinterpret_cast<const uint8_t*>(&notification_enabled)
        );

        // if an error occur, notify the client and terminate
        if (err) { 
            EnableNotificationParams res = { _char, false };
            _cb(res);
            // remove the callback registered for data write 
            client()->onDataWritten().detach(
                asFunction(&EnableNotificationProcedure::whenDataWritten)
            );
            delete this;
            return;
        }
    }

    void whenDataWritten(const GattWriteCallbackParams* p) { 
        // filter other write 
        if (p.connHandle != _char.getConnectionHandle() || p.handle != _CCCD) { 
            return;
        }

        // TODO: test write status when properly implemented by BLE API!
        // for now, it always succeed :(
        EnableNotificationParams res = { _char, true };
        _cb(res);
        client()->onDataWritten().detach(
            asFunction(&EnableNotificationProcedure::whenDataWritten)
        );
        delete this;
    }

    // client accessor
    GattClient* client() { 
        return _char.getGattClient();
    }

    // transform a member function of this class into a FunctionPointerWithContext 
    // for a the instance of this.
    template<typename ContextType>
    FunctionPointerWithContext<ContextType> asFunction(
        void (EnableNotificationProcedure::*member)(ContextType context)) {
        return FunctionPointerWithContext<ContextType>(this, member);
    }

    DiscoveredCharacteristic _char;
    EnableNotificationCallback_t _cb;
    GattAttribute::Handle_t _CCCD;
}


/**
 * Enable the notification on a discovered characteristic. 
 * This is an asynchronous process, the result of the operation will be known via a callback.
 */
bool enableNotification(
    const DiscoveredCharacteristic& characteristic, 
    const EnableNotificationCallback_t& cb) { 
    return EnableNotificationProcedure::launch(characteristic, cb);
}

I didn't tested the code; I hope you get the idea.

from ble.

yogeshk19 avatar yogeshk19 commented on June 30, 2024

Thanks a lot Vincent for the sample code. This helps a lot. However I can't get it to compile especially using the asFunction, which provides a functionpointerwithcontext. I get the following compilation error. Thanks once again for helping me out, I will see if I can make it work without the methods being instance members.

Error I get for the following line of code...

ble_error_t err = client()->discoverCharacteristicDescriptors( 
            _char, 
            asFunction(&EnableNotificationProcedure::whenDescriptorDiscovered),
            asFunction(&EnableNotificationProcedure::whenDiscoveryEnd)
        )

Error: No suitable user-defined conversion from "FunctionPointerWithContext<CharacteristicDescriptorDiscovery::DiscoveryCallbackParams_t &>" to "const CharacteristicDescriptorDiscovery::DiscoveryCallback_t" exists in "Common/Device.h", Line: 57, Col: 18

Thanks,
Yogesh

from ble.

yogeshk19 avatar yogeshk19 commented on June 30, 2024

Hi Vincent,

I was looking at https://github.com/ARMmbed/ble/blob/master/ble/CharacteristicDescriptorDiscovery.h and was wondering if the typedef's for the callback should be a reference instead of a pointer and may be that is why the code won't compile even if I make those member functions i called earlier static members. Almost all the methods expecting a callback method as an input parameter, is not passed as a reference, except for the definition below.

/**
     * Initiate a GATT Characteristic Descriptor Discovery procedure for descriptors within this characteristic.
     *
     * @param[in] onDescriptorDiscovered This callback will be called every time a descriptor is discovered
     * @param[in] onTermination This callback will be called when the discovery process is over.
     *
     * @return BLE_ERROR_NONE if descriptor discovery is launched successfully; else an appropriate error.
     */
    ble_error_t discoverDescriptors(const CharacteristicDescriptorDiscovery::DiscoveryCallback_t& onDescriptorDiscovered,
                                    const CharacteristicDescriptorDiscovery::TerminationCallback_t& onTermination) const;

/**
     * @brief Callback type for when a matching characteristic descriptor is found during
     * characteristic descriptor discovery.
     *
     * @param param A pointer to a DiscoveryCallbackParams_t object which will remain
     * valid for the lifetime of the callback. Memory for this object is owned by
     * the BLE_API eventing framework. The application can safely make a persistent
     * shallow-copy of this object in order to work with the service beyond the
     * callback.
     */
    typedef FunctionPointerWithContext<const DiscoveryCallbackParams_t*> DiscoveryCallback_t;

    //should this be instead.
   // typedef FunctionPointerWithContext<const DiscoveryCallbackParams_t &> DiscoveryCallback_t;

    /**
     * @brief Callback type for when characteristic descriptor discovery terminates.
     *
     * @param param A pointer to a TerminationCallbackParams_t object which will remain
     * valid for the lifetime of the callback. Memory for this object is owned by
     * the BLE_API eventing framework. The application can safely make a persistent
     * shallow-copy of this object in order to work with the service beyond the
     * callback.
     */
    typedef FunctionPointerWithContext<const TerminationCallbackParams_t*> TerminationCallback_t;

 //should this be instead.
  // typedef FunctionPointerWithContext<const TerminationCallbackParams_t &> TerminationCallback_t;

from ble.

pan- avatar pan- commented on June 30, 2024

@yogeshk19 I'm sorry, the definition of member function callback whenDescriptorDiscovered and whenDiscoveryEnd doesn't match the signature expected by the function which discover characteristic descriptors.
Pointer to const should be used instead of references. Data structure type should be ok though.

I didn't try to compile the code but the algorithm should be correct.

Sorry for the brievety; I'm on mobile.

from ble.

yogeshk19 avatar yogeshk19 commented on June 30, 2024

Hi Vincent,

Thanks I was about to respond and let you know I had made the same fix. Thanks for confirming as well. I will test it and if everything works well, with your permission I could post your solution in the mbed site, so in the future people can have a working sample to play around with in order to test notifications.

Thanks,
Yogesh

from ble.

yogeshk19 avatar yogeshk19 commented on June 30, 2024

Thanks Vincent, for helping me with this and the code works very well and I get notifications now. I hope you don't mind if I post this piece of example code in the mbed site, so it could be helpful to others who need help setting up notifications in their application. I am closing this issue. Once again thanks for taking the time to provide a solution for this issue.

Thanks,
Yogesh

from ble.

pan- avatar pan- commented on June 30, 2024

Thanks I was about to respond and let you know I had made the same fix. Thanks for confirming as well. I will test it and if everything works well, with your permission I could post your solution in the mbed site, so in the future people can have a working sample to play around with in order to test notifications.

No problem, go ahead.

from ble.

yogeshk19 avatar yogeshk19 commented on June 30, 2024

@pan- The code you provided me to subscribe to notifications, is this available out of box in the latest MBED BLE API? I am trying to remove the custom code if its available in the official build, since this seems like something that should be available by default?

Moreover after I upgraded the mbed-os build to 5.8.5 the method call below has started to return the BLE_ERROR_INVALID_STATE BLE error.

client()->discoverCharacteristicDescriptors

Thanks,
Yogesh

from ble.

pan- avatar pan- commented on June 30, 2024

The code you provided me to subscribe to notifications, is this available out of box in the latest MBED BLE API?

No it is not available out of the box; it may be in a latter release but there's no plan yet. I suggest you open an issue specifically for this feature request in the mbed-os repo so it gets some traction and won't be missed.

Moreover after I upgraded the mbed-os build to 5.8.5 the method call below has started to return the BLE_ERROR_INVALID_STATE BLE error.

What was your previous version ? Could you provide some details explaining about the discovery process is made and when characteristic descriptors are discovered ?

from ble.

yogeshk19 avatar yogeshk19 commented on June 30, 2024

Thanks Vincent for taking the time to respond. The mbed-os version where it worked was 5.5.4 and doesn't return the error described above and although I haven't tested it with the versions of the mbed-os and I directly used 5.8.5 rev.

void OnCharacteristicDiscovery(const DiscoveredCharacteristic *pCharacteristic)

I launch discovery based on the service UUID and also characteristic UUID and when the above method is fired for the characteristic i am looking to listen for notifications, it goes thru' the code you had provided and this worked perfectly well until when I switched to the mbed-os version 5.8.5.

Thanks,
Yogesh

from ble.

pan- avatar pan- commented on June 30, 2024

If you launch a descriptor discovery while the characteristic discovery is still active then it may return BLE_ERROR_INVALID_STATE. Nordic stack is weird though as it caches some requests unlike the other stacks we do supports. Here's an example demonstrating how a server can be discovered: GattClient.

I cannot comment on why it started with 5.8.5 as it would require a deeper investigation.

We changed the GattClient implementation in mbed-os 5.8; instead of using the Nordic tailored one it uses the one generic for all our targets which prevents concurrent use clients requests over one connection. That is the expected behavior as GATT is a sequential protocol.

from ble.

yogeshk19 avatar yogeshk19 commented on June 30, 2024

Thanks Vincent for the example code. I am not sure I understand the GattClient implementation change in 5.8. But for now I will try out the new code example and see if it fixes the invalid state error.

Thanks,
Yogesh

from ble.

yogeshk19 avatar yogeshk19 commented on June 30, 2024

Thanks Vincent as always you rock. The code changes work against the latest MBED-OS build. Hope your team implements this permanently into the MBED BLE API as well. I am closing this issue as the other issue has been logged to integrate this into the MBED BLE API.

Thanks,
Yogesh

from ble.

 avatar commented on June 30, 2024

Hiii Yogeshk19,

would you mind sharing your code with us (or me)?
I am facing the same problems but dont seem to find a solution.

Thanks in advance,
Philipp :)

from ble.

yogeshk19 avatar yogeshk19 commented on June 30, 2024

@BleDudeAF I just used the GattClient code that Vincent attached earlier in the thread. It works well with the latest mbed OS. Let me know if this doesn't work for you, I can help you with the changes I made.

Thanks,
Yogesh

from ble.

 avatar commented on June 30, 2024

Thanks Yogesh for your fast reply.
I checked out the code you mentioned.
Somehow I got many include errors. I will try again to make it run.
Nevertheless, i oriented myself on your code but still facing some problems.
After successfully updating the cccd value of my desired characteristic, i cant get any suitable hvx event.
I just receive one weird one with the following params: connection handle: 53689073, attribute handle: 15380, length: 15380 (all dec int).
What i need is the callback from updating the value attribute (handle: 14, connection handle: 0)

Do you have a clue whats going on?
Thanks for your efford,
Philipp

Edit:
every time the Server writes the characteristic value attribute, my onDataWrite gets triggered claiming that my cccd (handle 14) of the characterisitc got updated.
If i then manually read the attribute with the handle 15, i get as the "characteristic value attribute" - value 0x08. What i expect is the value 0 or 1.
Reading the ble_error_t of that read operation, it outputs also 8 (which should be BLE_ERROR_OPERATION_NOT_PERMITTED = 8)

from ble.

yogeshk19 avatar yogeshk19 commented on June 30, 2024

Hi Philip,

What version of mbed-os are you on?

Here is what I have in my code base:

OnBleInitComplete event I register for the event for receiving characteristic notifications.
ble.gattClient().onHVX(asFunctionWithContext(&BleCentral::OnSensorDataChanged));

OnBleConnection event I LaunchDiscovery for a given service & characteristics that I want to listen notifications for and onServiceDiscoveryTermination event I invoke the discoverCharacteristicDescriptors() method in the code @pan- provided early on.

This is the code I have to setup the notifications and works well for me. I am not sure about the scenario you mentioned, but if the characteristic handle gets updated, I am not sure if the notifications will fire.

@pan- may have a better idea then I do.

#ifndef BLEPERIPHERAL_H
#define BLEPERIPHERAL_H

#include <mbed.h>
#include "ble/BLE.h"
#include "ble/DiscoveredCharacteristic.h"
#include "ble/DiscoveredService.h"

namespace BleCentralApp
{   
    struct BlePeripheral 
    { 
        BlePeripheral() {
			_CCCD = 0; 
			_enabledNotification = false;
        }
          
        void setDiscoveredCharacteristic(const DiscoveredCharacteristic* pDiscoveredCharacteristic) {
            if(pDiscoveredCharacteristic != NULL) {
               _discoveredCharacteristic = *pDiscoveredCharacteristic; 
	        }
			else {
				_enabledNotification = false;
			}
        }
        
        const DiscoveredCharacteristic* getDiscoveredCharacteristic() const {
            return &_discoveredCharacteristic;
		}      
        
        bool isNotificationEnabled() {
            return _enabledNotification;  
        }

		void discoverCharacteristicDescriptors() { 
			//notification already enabled.
			if(_enabledNotification)
				return;

			if (!_discoveredCharacteristic.getProperties().notify()) { 
			    logError("Notifications are not enabled for the Discovered Characteristic.");
                return;
            }
    
            ble_error_t err = client()->discoverCharacteristicDescriptors( 
                _discoveredCharacteristic, 
                asFunction(&BlePeripheral::whenDescriptorDiscovered),
                asFunction(&BlePeripheral::whenDiscoveryEnd)
            );
    
            if (err) { 
               logError("discoverCharacteristicDescriptors call failed with Ble error: %d" , err);
            }
        }

        
    private:
        void whenDescriptorDiscovered(const CharacteristicDescriptorDiscovery::DiscoveryCallbackParams_t* p) { 
            if (p->descriptor.getUUID() == BLE_UUID_DESCRIPTOR_CLIENT_CHAR_CONFIG) { 
                _CCCD = p->descriptor.getAttributeHandle();
                client()->terminateCharacteristicDescriptorDiscovery(_discoveredCharacteristic);
            }
        }
    
        void whenDiscoveryEnd(const CharacteristicDescriptorDiscovery::TerminationCallbackParams_t* p) { 
            if (p->status) { 
                _enabledNotification = false;
                return;
            }
    
            // otherwise launch write the descriptor
            // first register the write callback 
            client()->onDataWritten(
                asFunction(&BlePeripheral::whenDataWritten)
            );
    
            uint16_t notification_enabled = 1; 
            ble_error_t err = client()->write( 
                GattClient::GATT_OP_WRITE_REQ,
                _discoveredCharacteristic.getConnectionHandle(),
                _CCCD,
                sizeof(notification_enabled),
                reinterpret_cast<const uint8_t*>(&notification_enabled)
            );
    
            if (err) { 
                _enabledNotification = false;
                
                // remove the callback registered for data write 
                client()->onDataWritten().detach(
                    asFunction(&BlePeripheral::whenDataWritten)
                );
                return;
            }

			_enabledNotification = true;
        }
    
        void whenDataWritten(const GattWriteCallbackParams* p) { 
            // filter other write 
            if (p->connHandle != _discoveredCharacteristic.getConnectionHandle() || p->handle != _CCCD) { 
                return;
            }
    		
            _enabledNotification = true;
         
            client()->onDataWritten().detach(
                asFunction(&BlePeripheral::whenDataWritten)
            );
        }
    
        // client accessor
        GattClient* client() { 
            return _discoveredCharacteristic.getGattClient();
        }
    
        // transform a member function of this class into a FunctionPointerWithContext 
        // for a the instance of this.
        template<typename ContextType>
        FunctionPointerWithContext<ContextType> asFunction(
            void (BlePeripheral::*member)(ContextType context)) {
            return FunctionPointerWithContext<ContextType>(this, member);
        }
        
        //ble related handles and characteristics
        GattAttribute::Handle_t _CCCD;
        DiscoveredCharacteristic _discoveredCharacteristic;
		
        //flags to enable notifications.
        bool  _enabledNotification;
    };
}
#endif /* BLEPERIPHERAL_H */

Sorry If I was not much help!!

Thanks,
Yogesh

from ble.

 avatar commented on June 30, 2024

@yogeshk19 @pan-
Ok now i took some time and tried to figure it out with your given example code.
I just cant find the faulty piece of code, would it help to post it in here? I'll give it a try..
Using the hvx callback didnt work for me as i just received that weird update event (handle 15380).

/* mbed Microcontroller Library
 * Copyright (c) 2006-2015 ARM Limited
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <events/mbed_events.h>
#include <mbed.h>
#include "ble/BLE.h"
#include "ble/DiscoveredCharacteristic.h"
#include "ble/DiscoveredService.h"

#define UUID_HRM_SERVICE                0x180d
#define UUID_HRM_CHARACTERISTIC         0x2a37
#define UUID_HRM_DESCRIPTOR             0x2902

DigitalOut alivenessLED(LED1, 1);
static DiscoveredCharacteristic hrmCharacteristic;
static const char PEER_NAME[] = "RBL-BLE";

uint16_t _discovered_UUID = 0x0000;
GattAttribute::Handle_t _CCCD_handle(0);
uint8_t reed_state;

void look_for_Descriptors();
void stop();
void triggerRead(const GattWriteCallbackParams *response);
void onUpdatesCallback(const GattHVXCallbackParams *updates);
void read_characteristic(const DiscoveredCharacteristic &characteristic);
void ReadCompletitionCallback(const GattReadCallbackParams *params);

static bool flag_connected = false;
static bool flag_hrm_char_found = false;


static EventQueue eventQueue(/* event count */ 16 * EVENTS_EVENT_SIZE);

void periodicCallback(void) {
    if(flag_connected){
            alivenessLED = !alivenessLED; /* Do blinky on LED1 while we're waiting for BLE events */
    }
}

void advertisementCallback(const Gap::AdvertisementCallbackParams_t *params) {
    // parse the advertising payload, looking for data type COMPLETE_LOCAL_NAME
    // The advertising payload is a collection of key/value records where
    // byte 0: length of the record excluding this byte
    // byte 1: The key, it is the type of the data
    // byte [2..N] The value. N is equal to byte0 - 1
    uint32_t err_code;
    for (uint8_t i = 0; i < params->advertisingDataLen; ++i) {

        const uint8_t record_length = params->advertisingData[i];
        if (record_length == 0) {
            continue;
        }
        const uint8_t type = params->advertisingData[i + 1];
        const uint8_t* value = params->advertisingData + i + 2;
        const uint8_t value_length = record_length - 1;

        if(type == GapAdvertisingData::COMPLETE_LOCAL_NAME) {
            if ((value_length == sizeof(PEER_NAME)) && (memcmp(value, PEER_NAME, value_length) == 0)) {
                printf(
                    "adv peerAddr[%02x %02x %02x %02x %02x %02x] rssi %d, isScanResponse %u, AdvertisementType %u\r\n",
                    params->peerAddr[5], params->peerAddr[4], params->peerAddr[3], params->peerAddr[2],                 //peerAddr: BLE address of the device that has advertised the packet.
                    params->peerAddr[1], params->peerAddr[0], params->rssi, params->isScanResponse, params->type
                );
                err_code = BLE::Instance().gap().connect(params->peerAddr, Gap::ADDR_TYPE_RANDOM_STATIC, NULL, NULL);
                if(err_code == BLE_ERROR_NOT_IMPLEMENTED){
                      printf("BLE_ERROR_NOT_IMPLEMENTED\n");
                }else if(err_code == 0){
                  wait_ms(100);
                  printf("success\n");
                }
                break;
            }
        }
        i += record_length;
    }
}

void serviceDiscoveryCallback(const DiscoveredService *service) {
    printf("Service Discovery Callback\n");
    if (service->getUUID().shortOrLong() == UUID::UUID_TYPE_SHORT) {
        printf("Service UUID-%x attrs[%u %u]\r\n", service->getUUID().getShortUUID(), service->getStartHandle(), service->getEndHandle());
    } else {
        printf("Service UUID-");
        const uint8_t *longUUIDBytes = service->getUUID().getBaseUUID();
        for (unsigned i = 0; i < UUID::LENGTH_OF_LONG_UUID; i++) {
            printf("%02x", longUUIDBytes[i]);
        }
        printf(" attrs[%u %u]\r\n", service->getStartHandle(), service->getEndHandle());
    }
}
void onDataWrittenCallback(const GattWriteCallbackParams* params){
            printf("onDataWritten Callback. attribute handle: %u, length:%u, data:%u \n", params->handle, params->len, params->data);
            printf("hrmChar read: %u\n", hrmCharacteristic.read());
}

void write_cccd(){
        uint16_t notification_enabled = BLE_HVX_NOTIFICATION;
        BLE &ble = BLE::Instance();


        ble_error_t err =  BLE::Instance(BLE::DEFAULT_INSTANCE).gattClient().write(

            GattClient::GATT_OP_WRITE_REQ,
            hrmCharacteristic.getConnectionHandle(),
            _CCCD_handle,
            sizeof(notification_enabled),
            reinterpret_cast<const uint8_t *>(&notification_enabled)

        );
        /*
        ble_error_t err = GattServer::write(
            _CCCD_handle,
            notification_enabled,
            sizeof(notification_enabled),
            false
            );*/
        if(err == BLE_ERROR_NONE){
            printf("cccd update sent successful\n");
            //ble.gattClient().onHVX(onUpdatesCallback);
        }else{
            printf("error updating: error_code [%u]\n", err);
        }
}

void characteristicDescriptorDiscoveryCallback(const CharacteristicDescriptorDiscovery::DiscoveryCallbackParams_t *charParams){

            printf("descriptor found with:\n");
            printf("connection_handle[%u] UUID[%X] attribute_Handle[%u]\r\n",
            charParams->descriptor.getConnectionHandle(),
            _discovered_UUID = charParams->descriptor.getUUID().getShortUUID(),
            _CCCD_handle = charParams->descriptor.getAttributeHandle()
            );

}

void descriptorDiscoveryTerminationCallback(const CharacteristicDescriptorDiscovery::TerminationCallbackParams_t *termParams){
BLE &ble = BLE::Instance();
            if(termParams->error_code == 0 && _discovered_UUID == BLE_UUID_DESCRIPTOR_CLIENT_CHAR_CONFIG){  // && !BLE::Instance().gattClient().isServiceDiscoveryActive()
                    printf("descriptorDiscovery terminated without errors\n");
                    printf("sending notification request now..\n");
                    write_cccd();
                    ble.gattClient().terminateServiceDiscovery();
            }else{
                    printf("no cccd found yet..\n");
                    printf("try again\n");
                    look_for_Descriptors();
            }

}

void updatehrmCharacteristic(void) {
    uint32_t err_code;
    if (!BLE::Instance().gattClient().isServiceDiscoveryActive()) {

        //hrmCharacteristic.read();
    }
}

void characteristicDiscoveryCallback(const DiscoveredCharacteristic *characteristicP) {
        BLE &ble = BLE::Instance();
        printf("    \n");
        printf("Characteristic Discovery Callback\n");
        printf("Char UUID[%x] valueAttr_Handle[%u] broadcast[%x] notification[%u] readable[%u]\r\n",
        characteristicP->getUUID().getShortUUID(),
        characteristicP->getValueHandle(),
        (uint8_t)characteristicP->getProperties().broadcast(),
        (uint8_t)characteristicP->getProperties().notify(),
        (uint8_t)characteristicP->getProperties().read());

    if (characteristicP->getUUID().getShortUUID() == UUID_HRM_CHARACTERISTIC ) { /* !ALERT! Alter this filter to suit your device. */
        hrmCharacteristic        = *characteristicP;
        flag_hrm_char_found = true;     //flag true so look_for_Descriptors gets invoked
    }
}

void look_for_Descriptors(){
    printf("    \n");
    BLE &ble = BLE::Instance();
    if(_discovered_UUID == UUID_HRM_DESCRIPTOR && !BLE::Instance().gattClient().isServiceDiscoveryActive()){
            printf("discovered UUID[%X] == cccd UUID[%X]\r\n",
                _discovered_UUID,
                UUID_HRM_DESCRIPTOR
                );
            printf("cccd with handle [%u] found!\n", _CCCD_handle);
            eventQueue.call(write_cccd);
    }else{
            printf("start looking for Descriptors now\n");
                ble.gattClient().discoverCharacteristicDescriptors(
                hrmCharacteristic,
                characteristicDescriptorDiscoveryCallback,
                descriptorDiscoveryTerminationCallback
            );
    }

}

void discoveryTerminationCallback(Gap::Handle_t connectionHandle) {
    printf("terminated Service Discovery for handle %u\r\n", connectionHandle);
        //printf("trigger update\n");
        if(flag_hrm_char_found == true && hrmCharacteristic.getProperties().notify()){
            printf("hrm Char found with notifications enabled\n");
            eventQueue.call(look_for_Descriptors);
        }else if(flag_hrm_char_found == true && !hrmCharacteristic.getProperties().notify()){
            printf("hrm Char found with notifications disabled\n");
        }
}

void connectionCallback(const Gap::ConnectionCallbackParams_t *params) {
    flag_connected = true;
    printf("connected to Peripheral with peerAddr:   0x%X\n", params->peerAddr);
    printf("connection handle: %d\n", params->handle);
    if (params->role == Gap::CENTRAL) {
        BLE &ble = BLE::Instance();
        ble.gattClient().onServiceDiscoveryTermination(discoveryTerminationCallback);
        ble.gattClient().launchServiceDiscovery(params->handle, serviceDiscoveryCallback, characteristicDiscoveryCallback);
    }
}

void triggerToggledWrite(const GattReadCallbackParams *response) {
    printf("onDataRead Callback\n");
    /*
    printf("server has updated characterisitc value attribute\n");
    if (response->handle == hrmCharacteristic.getValueHandle()) {
        printf("update at: handle %u, offset %u, len %u\r\n", response->handle, response->offset, response->len);
        for (unsigned index = 0; index < response->len; index++) {
            printf("%c[%02x]", response->data[index], response->data[index]);
        }
        printf("\r\n");

        uint8_t toggledValue = response->data[0];
        printf("toggle value %u\n", toggledValue);
    }*/
}

void onUpdatesCallback(const GattHVXCallbackParams *updates){
    ble_error_t err;
    printf("update received: handle %u, type %u, len %u, data %u, %u, %u\r\n", updates->handle, updates->type, updates->len, updates->data[0],updates->data[1],updates->data[2]);
    //err = hrmCharacteristic.read(0, ReadCompletitionCallback);
   // printf("error_code reading char: %u\n",err);
    /*
    if(updates->handle == _CCCD_handle){
                const uint8_t *p_data = updates->data;
        printf("updates received: connHandle[%u]    attrHandle[%u]  Data[%u] Type[%x] Len[%u]\n",
                updates->connHandle,
                updates->handle,
                p_data,
                updates->type,
                updates->len
        );
        for (unsigned index = 0; index < updates->len; index++) {
            printf("[%02x]", updates->data[index]);
        }
        printf("\r\n");
    }*/
    //read_characteristic(hrmCharacteristic);

}
void ReadCompletitionCallback(const GattReadCallbackParams *params){
    printf("Read attr: connHandle [%u], attributeHandle [%u], offset [%u], len [%u], value [%u], status [%u]\n\r",
    params->connHandle,
    params->handle,
    params->offset,
    params->len,
    params->data[0],
    params->status
    );
    for (size_t i = 0; i <  params->len; ++i) {
            printf("0x%02X ", params->data[i]);
        }
    printf(".\r\n");
}
void read_characteristic(const DiscoveredCharacteristic &characteristic){
    ble_error_t err;
    printf("Initiating read at %u.\r\n", characteristic.getValueHandle());
    err = hrmCharacteristic.read(0, ReadCompletitionCallback);
    printf("error_code reading char: %u\n",err);
    printf("value: %u\n\r", characteristic.read());
   /* for(int i=0; i<8; i++){
        printf("value: %u, value %u\n\r", characteristic.read(i), hrmCharacteristic.read(i));
    }*/
}

void triggerRead(const GattWriteCallbackParams *response) {
    printf("onDataWrite Callback. attribute handle: %u, length:%u, data:%u \n", response->handle, response->len, response->data);
    printf("hrmChar read: %u\n", hrmCharacteristic.read());
    /*
    BLE &ble = BLE::Instance();

    printf("server has updated characterisitc attribute with handle [%u]; type of operation = %u\n", response->handle, response->writeOp);

    if(response->handle == _CCCD_handle){
        printf("handle: [%u], length: [%u]]\n",response->handle, response->len);
        for(int index = 0; index <=response->len; index++){
            printf("data(%u):[%u]\n",index,response->data[index]);
        }
        read_characteristic(hrmCharacteristic);
    }else{

    }
*/
}

void disconnectionCallback(const Gap::DisconnectionCallbackParams_t *) {
    flag_connected = false;
    printf("disconnected\r\n");
    stop(); //detach hvx callback handler and reset flags
    /* Start scanning and try to connect again */
    BLE::Instance().gap().startScan(advertisementCallback);
}

void onBleInitError(BLE &ble, ble_error_t error)
{
    /* Initialization error handling should go here */
    printf("    \n");
    printf("ble init error\n");
    printf("    \n");
}

void printMacAddress()
{
    /* Print out device MAC address to the console*/
    Gap::AddressType_t addr_type;
    Gap::Address_t address;
    BLE::Instance().gap().getAddress(&addr_type, address);
    printf("DEVICE MAC ADDRESS: ");
    for (int i = 5; i >= 1; i--){
        printf("%02x:", address[i]);
    }
    printf("%02x\r\n", address[0]);
}

void bleInitComplete(BLE::InitializationCompleteCallbackContext *params)
{
    BLE&        ble   = params->ble;
    ble_error_t error = params->error;

    if (error != BLE_ERROR_NONE) {
        /* In case of error, forward the error handling to onBleInitError */
        onBleInitError(ble, error);
        return;
    }

    /* Ensure that it is the default instance of BLE */
    if (ble.getInstanceID() != BLE::DEFAULT_INSTANCE) {
        return;
    }

    ble.gap().onDisconnection(disconnectionCallback);
    ble.gap().onConnection(connectionCallback);

    //ble.gattClient().onDataRead(triggerToggledWrite);
    //ble.gattClient().onDataWrite(triggerRead);
    ble.gattClient().onDataWrite(triggerRead);
    ble.gattClient().onHVX(onUpdatesCallback);
    ble.gattClient().onDataWritten(onDataWrittenCallback);

    // scan interval: 400ms and scan window: 400ms.
    // Every 400ms the device will scan for 400ms
    // This means that the device will scan continuously.
    ble.gap().setScanParams(400, 400);
    ble.gap().startScan(advertisementCallback);

    printMacAddress();
}

void scheduleBleEventsProcessing(BLE::OnEventsToProcessCallbackContext* context) {
    BLE &ble = BLE::Instance();
    eventQueue.call(Callback<void()>(&ble, &BLE::processEvents));
}

void stop(){
    printf("stopp\n");
    BLE &ble = BLE::Instance();
    //ble.gattClient().onHVX().detach(onUpdatesCallback);
    //ble.gattClient().onDataWritten().detach(triggerRead);
    flag_hrm_char_found = false;
    flag_connected = false;
    _discovered_UUID = 0;
    _CCCD_handle = 0;

}

int main()
{
    eventQueue.call_every(500, periodicCallback);   //Calls an event on the queue periodically

    BLE &ble = BLE::Instance();
    ble.onEventsToProcess(scheduleBleEventsProcessing);
    ble.init(bleInitComplete);

    eventQueue.dispatch_forever();

    return 0;
}

```cpp
your workflow (or flow of events) is understood.
But maybe someone can take a look on my example and figure it out within a couple of minutes.
I spent hours trying and trying, and dont seem to come to an end here.
Thank you very much for your help Yogesh! :)
Yours,
Philipp

edit:
I tried to figure it out what may cause the problem. I just refreshed the code above, what shall be concentrated on is the weird output returned by the onUpdatesCallback function.
Watching my serial monitor, it says:

update received: handle 15384, type 0, len 15384, data 4, 0, 32
update received: handle 15384, type 0, len 15384, data 4, 0, 32
update received: handle 15384, type 0, len 15384, data 4, 0, 32
update received: handle 15384, type 0, len 15384, data 4, 0, 32
update received: handle 15384, type 0, len 15384, data 4, 0, 32
...

Shouldnt the handle be 14 (for the characteristic value attribute)?
And the length seems also pretty weird...

from ble.

pan- avatar pan- commented on June 30, 2024

There is some logic error in the code posted above like it is wrong to assign the CCCD handle everytime a descriptor is discovered or the process test if the service discovery is active while a descriptor discovery is ongoing. The CCCD value written was incorrect too as a 16 bit value is required; not an 8 bit one.

Anyway, here's a corrected version that works: https://gist.github.com/pan-/585af73a8fd0ff236b9897c0432d4ef0

from ble.

 avatar commented on June 30, 2024

from ble.

 avatar commented on June 30, 2024

from ble.

pan- avatar pan- commented on June 30, 2024

Hi @BleDudeAF,

I've tried on NRF52 with the latest mbed os version; the HRM I used was based on the one we provide in our example (here), I just changed the name advertised so it get recognized by your application.

Here is my output with the code I posted above:

connected to Peer, connection handle is: 0
Service Discovery Callback
Service UUID-1800 attrs[1 7]

Characteristic Discovery Callback
Char UUID[2a00] valueAttr_Handle[3] broadcast[0] notification[0] readable[1]

Characteristic Discovery Callback
Char UUID[2a01] valueAttr_Handle[5] broadcast[0] notification[0] readable[1]

Characteristic Discovery Callback
Char UUID[2a04] valueAttr_Handle[7] broadcast[0] notification[0] readable[1]
Service Discovery Callback
Service UUID-1801 attrs[8 11]

Characteristic Discovery Callback
Char UUID[2a05] valueAttr_Handle[10] broadcast[0] notification[0] readable[0]
Service Discovery Callback
Service UUID-180d attrs[12 65535]

Characteristic Discovery Callback
Char UUID[2a37] valueAttr_Handle[14] broadcast[0] notification[1] readable[0]

Characteristic Discovery Callback
Char UUID[2a38] valueAttr_Handle[17] broadcast[0] notification[0] readable[1]
terminated Service Discovery for handle 0
hrm Char found with notifications enabled

    start looking for Descriptors of characteristic 14, range [15, 15] now
descriptor found with:
                      connection_handle[0] UUID[2902] attribute_Handle[15]
CCCD found; explicit termination of descriptors discovery
descriptorDiscovery terminated without errors
cccd with handle [15] found!
cccd update sent successful
Attribute written by client: connection = 0, handle = 15, status = 0, error code = 0
update received: handle 14
updates received: connHandle: 0, attrHandle: 14, Type: 1, Data: 00 ac
update received: handle 14
updates received: connHandle: 0, attrHandle: 14, Type: 1, Data: 00 ad
update received: handle 14
updates received: connHandle: 0, attrHandle: 14, Type: 1, Data: 00 ae
update received: handle 14
updates received: connHandle: 0, attrHandle: 14, Type: 1, Data: 00 64
update received: handle 14
updates received: connHandle: 0, attrHandle: 14, Type: 1, Data: 00 65
update received: handle 14
updates received: connHandle: 0, attrHandle: 14, Type: 1, Data: 00 66

from ble.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.