This application demonstrates detailed uses of the GattClient APIs.

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers main.cpp Source File

main.cpp

00001 
00002 /* mbed Microcontroller Library
00003  * Copyright (c) 2006-2018 ARM Limited
00004  *
00005  * Licensed under the Apache License, Version 2.0 (the "License");
00006  * you may not use this file except in compliance with the License.
00007  * You may obtain a copy of the License at
00008  *
00009  *     http://www.apache.org/licenses/LICENSE-2.0
00010  *
00011  * Unless required by applicable law or agreed to in writing, software
00012  * distributed under the License is distributed on an "AS IS" BASIS,
00013  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
00014  * See the License for the specific language governing permissions and
00015  * limitations under the License.
00016  */
00017 
00018 #include <memory>
00019 #include <new>
00020 #include <stdio.h>
00021 
00022 #include "events/EventQueue.h"
00023 #include "platform/NonCopyable.h"
00024 
00025 #include "ble/BLE.h"
00026 #include "ble/Gap.h"
00027 #include "ble/GattClient.h"
00028 #include "ble/GapAdvertisingParams.h"
00029 #include "ble/GapAdvertisingData.h"
00030 #include "ble/GattClient.h"
00031 #include "ble/DiscoveredService.h"
00032 #include "ble/DiscoveredCharacteristic.h"
00033 #include "ble/CharacteristicDescriptorDiscovery.h"
00034 
00035 #include "BLEProcess.h"
00036 
00037 /**
00038  * Handle discovery of the GATT server.
00039  *
00040  * First the GATT server is discovered in its entirety then each readable
00041  * characteristic is read and the client register to characteristic
00042  * notifications or indication when available. The client report server
00043  * indications and notification until the connection end.
00044  */
00045 class GattClientProcess : private mbed::NonCopyable<GattClientProcess>,
00046                           public ble::Gap::EventHandler,
00047                           public GattClient::EventHandler {
00048 
00049     // Internal typedef to this class type.
00050     // It is used as a shorthand to pass member function as callbacks.
00051     typedef GattClientProcess Self;
00052 
00053     typedef CharacteristicDescriptorDiscovery::DiscoveryCallbackParams_t
00054         DiscoveryCallbackParams_t;
00055 
00056     typedef CharacteristicDescriptorDiscovery::TerminationCallbackParams_t
00057         TerminationCallbackParams_t;
00058 
00059     typedef DiscoveredCharacteristic::Properties_t Properties_t;
00060 
00061 public:
00062 
00063     /**
00064      * Construct an empty client process.
00065      *
00066      * The function start() shall be called to initiate the discovery process.
00067      */
00068     GattClientProcess() :
00069         _client(NULL),
00070         _connection_handle(),
00071         _characteristics(NULL),
00072         _it(NULL),
00073         _descriptor_handle(0),
00074         _ble_interface(NULL),
00075         _event_queue(NULL) {
00076     }
00077 
00078     ~GattClientProcess()
00079     {
00080         stop();
00081     }
00082 
00083     void init(BLE &ble_interface, events::EventQueue &event_queue)
00084     {
00085         _ble_interface = &ble_interface;
00086         _event_queue = &event_queue;
00087         _client = &_ble_interface->gattClient();
00088 
00089         _ble_interface->gap().setEventHandler(this);
00090     }
00091 
00092     /**
00093      * Start the discovery process.
00094      *
00095      * @param[in] client The GattClient instance which will discover the distant
00096      * GATT server.
00097      * @param[in] connection_handle Reference of the connection to the GATT
00098      * server which will be discovered.
00099      */
00100     void start()
00101     {
00102         // setup the event handlers called during the process
00103         _client->onDataWritten().add(as_cb(&Self::when_descriptor_written));
00104         _client->onHVX().add(as_cb(&Self::when_characteristic_changed));
00105 
00106         // The discovery process will invoke when_service_discovered when a
00107         // service is discovered, when_characteristic_discovered when a
00108         // characteristic is discovered and when_service_discovery_ends once the
00109         // discovery process has ended.
00110         _client->onServiceDiscoveryTermination(as_cb(&Self::when_service_discovery_ends));
00111         ble_error_t error = _client->launchServiceDiscovery(
00112             _connection_handle,
00113             as_cb(&Self::when_service_discovered),
00114             as_cb(&Self::when_characteristic_discovered)
00115         );
00116 
00117         if (error) {
00118             printf("Error %u returned by _client->launchServiceDiscovery.\r\n", error);
00119             return;
00120         }
00121 
00122         // register as a handler for GattClient events
00123         _client->setEventHandler(this);
00124 
00125         // this might not result in a new value but if it does we will be informed through
00126         // an call in the event handler we just registered
00127         _client->negotiateAttMtu(_connection_handle);
00128 
00129         printf("Client process started: initiate service discovery.\r\n");
00130     }
00131 
00132     /**
00133      * Stop the discovery process and clean the instance.
00134      */
00135     void stop()
00136     {
00137         if (!_client) {
00138             return;
00139         }
00140 
00141         // unregister event handlers
00142         _client->onDataWritten().detach(as_cb(&Self::when_descriptor_written));
00143         _client->onHVX().detach(as_cb(&Self::when_characteristic_changed));
00144         _client->onServiceDiscoveryTermination(NULL);
00145 
00146         // remove discovered characteristics
00147         clear_characteristics();
00148 
00149         // clean up the instance
00150         _connection_handle = 0;
00151         _characteristics = NULL;
00152         _it = NULL;
00153         _descriptor_handle = 0;
00154 
00155         printf("Client process stopped.\r\n");
00156     }
00157 
00158 private:
00159     /**
00160      * Event handler invoked when a connection is established.
00161      *
00162      * This function setup the connection handle to operate on then start the
00163      * discovery process.
00164      */
00165     virtual void onConnectionComplete(const ble::ConnectionCompleteEvent &event)
00166     {
00167         _connection_handle = event.getConnectionHandle();
00168         _event_queue->call(mbed::callback(this, &Self::start));
00169     }
00170 
00171     /**
00172      * Stop the discovery process and clean the instance.
00173      */
00174     virtual void onDisconnectionComplete(const ble::DisconnectionCompleteEvent &event)
00175     {
00176         if (_client && event.getConnectionHandle() == _connection_handle) {
00177             stop();
00178         }
00179     }
00180 
00181     /**
00182      * Implementation of GattClient::EventHandler::onAttMtuChange event
00183      */
00184     virtual void onAttMtuChange(
00185         ble::connection_handle_t connectionHandle,
00186         uint16_t attMtuSize
00187     ) {
00188         printf(
00189             "ATT_MTU changed on the connection %d to a new value of %d.\r\n",
00190             connectionHandle,
00191             attMtuSize
00192             /* maximum size of an attribute written in a single operation is one less */
00193         );
00194     }
00195 
00196 private:
00197 ////////////////////////////////////////////////////////////////////////////////
00198 // Service and characteristic discovery process.
00199 
00200     /**
00201      * Handle services discovered.
00202      *
00203      * The GattClient invokes this function when a service has been discovered.
00204      *
00205      * @see GattClient::launchServiceDiscovery
00206      */
00207     void when_service_discovered(const DiscoveredService *discovered_service)
00208     {
00209         // print information of the service discovered
00210         printf("Service discovered: value = ");
00211         print_uuid(discovered_service->getUUID());
00212         printf(", start = %u, end = %u.\r\n",
00213             discovered_service->getStartHandle(),
00214             discovered_service->getEndHandle()
00215         );
00216     }
00217 
00218     /**
00219      * Handle characteristics discovered.
00220      *
00221      * The GattClient invoke this function when a characteristic has been
00222      * discovered.
00223      *
00224      * @see GattClient::launchServiceDiscovery
00225      */
00226     void when_characteristic_discovered(const DiscoveredCharacteristic *discovered_characteristic)
00227     {
00228         // print characteristics properties
00229         printf("\tCharacteristic discovered: uuid = ");
00230         print_uuid(discovered_characteristic->getUUID());
00231         printf(", properties = ");
00232         print_properties(discovered_characteristic->getProperties());
00233         printf(
00234             ", decl handle = %u, value handle = %u, last handle = %u.\r\n",
00235             discovered_characteristic->getDeclHandle(),
00236             discovered_characteristic->getValueHandle(),
00237             discovered_characteristic->getLastHandle()
00238         );
00239 
00240         // add the characteristic into the list of discovered characteristics
00241         bool success = add_characteristic(discovered_characteristic);
00242         if (!success) {
00243             printf("Error: memory allocation failure while adding the discovered characteristic.\r\n");
00244             _client->terminateServiceDiscovery();
00245             stop();
00246             return;
00247         }
00248     }
00249 
00250     /**
00251      * Handle termination of the service and characteristic discovery process.
00252      *
00253      * The GattClient invokes this function when the service and characteristic
00254      * discovery process ends.
00255      *
00256      * @see GattClient::onServiceDiscoveryTermination
00257      */
00258     void when_service_discovery_ends(Gap::Handle_t connection_handle)
00259     {
00260         if (!_characteristics) {
00261             printf("No characteristics discovered, end of the process.\r\n");
00262             return;
00263         }
00264 
00265         printf("All services and characteristics discovered, process them.\r\n");
00266 
00267         // reset iterator and start processing characteristics in order
00268         _it = NULL;
00269         _event_queue->call(mbed::callback(this, &Self::process_next_characteristic));
00270     }
00271 
00272 ////////////////////////////////////////////////////////////////////////////////
00273 // Processing of characteristics based on their properties.
00274 
00275     /**
00276      * Process the characteristics discovered.
00277      *
00278      * - If the characteristic is readable then read its value and print it. Then
00279      * - If the characteristic can emit notification or indication then discover
00280      * the characteristic CCCD and subscribe to the server initiated event.
00281      * - Otherwise skip the characteristic processing.
00282      */
00283     void process_next_characteristic(void)
00284     {
00285         if (!_it) {
00286             _it = _characteristics;
00287         } else {
00288             _it = _it->next;
00289         }
00290 
00291         while (_it) {
00292             Properties_t properties = _it->value.getProperties();
00293 
00294             if (properties.read()) {
00295                 read_characteristic(_it->value);
00296                 return;
00297             } else if(properties.notify() || properties.indicate()) {
00298                 discover_descriptors(_it->value);
00299                 return;
00300             } else {
00301                 printf(
00302                     "Skip processing of characteristic %u\r\n",
00303                     _it->value.getValueHandle()
00304                 );
00305                 _it = _it->next;
00306             }
00307         }
00308 
00309         printf("All characteristics discovered have been processed.\r\n");
00310     }
00311 
00312     /**
00313      * Initate the read of the characteristic in input.
00314      *
00315      * The completion of the operation will happens in when_characteristic_read()
00316      */
00317     void read_characteristic(const DiscoveredCharacteristic &characteristic)
00318     {
00319         printf("Initiating read at %u.\r\n", characteristic.getValueHandle());
00320         ble_error_t error = characteristic.read(
00321             0, as_cb(&Self::when_characteristic_read)
00322         );
00323 
00324         if (error) {
00325             printf(
00326                 "Error: cannot initiate read at %u due to %u\r\n",
00327                 characteristic.getValueHandle(), error
00328             );
00329             stop();
00330         }
00331     }
00332 
00333     /**
00334      * Handle the reception of a read response.
00335      *
00336      * If the characteristic can emit notification or indication then start the
00337      * discovery of the the characteristic descriptors then subscribe to the
00338      * server initiated event by writing the CCCD discovered. Otherwise start
00339      * the processing of the next characteristic discovered in the server.
00340      */
00341     void when_characteristic_read(const GattReadCallbackParams *read_event)
00342     {
00343         printf("\tCharacteristic value at %u equal to: ", read_event->handle);
00344         for (size_t i = 0; i <  read_event->len; ++i) {
00345             printf("0x%02X ", read_event->data[i]);
00346         }
00347         printf(".\r\n");
00348 
00349         Properties_t properties = _it->value.getProperties();
00350 
00351         if(properties.notify() || properties.indicate()) {
00352             discover_descriptors(_it->value);
00353         } else {
00354             process_next_characteristic();
00355         }
00356     }
00357 
00358     /**
00359      * Initiate the discovery of the descriptors of the characteristic in input.
00360      *
00361      * When a descriptor is discovered, the function when_descriptor_discovered
00362      * is invoked.
00363      */
00364     void discover_descriptors(const DiscoveredCharacteristic &characteristic)
00365     {
00366         printf("Initiating descriptor discovery of %u.\r\n", characteristic.getValueHandle());
00367 
00368         _descriptor_handle = 0;
00369         ble_error_t error = characteristic.discoverDescriptors(
00370             as_cb(&Self::when_descriptor_discovered),
00371             as_cb(&Self::when_descriptor_discovery_ends)
00372         );
00373 
00374         if (error) {
00375             printf(
00376                 "Error: cannot initiate discovery of %04X due to %u.\r\n",
00377                 characteristic.getValueHandle(), error
00378             );
00379             stop();
00380         }
00381     }
00382 
00383     /**
00384      * Handle the discovery of the characteristic descriptors.
00385      *
00386      * If the descriptor found is a CCCD then stop the discovery. Once the
00387      * process has ended subscribe to server initiated events by writing the
00388      * value of the CCCD.
00389      */
00390     void when_descriptor_discovered(const DiscoveryCallbackParams_t* event)
00391     {
00392         printf("\tDescriptor discovered at %u, UUID: ", event->descriptor.getAttributeHandle());
00393         print_uuid(event->descriptor.getUUID());
00394         printf(".\r\n");
00395 
00396         if (event->descriptor.getUUID() == BLE_UUID_DESCRIPTOR_CLIENT_CHAR_CONFIG) {
00397             _descriptor_handle = event->descriptor.getAttributeHandle();
00398             _client->terminateCharacteristicDescriptorDiscovery(
00399                 event->characteristic
00400             );
00401         }
00402     }
00403 
00404     /**
00405      * If a CCCD has been found subscribe to server initiated events by writing
00406      * its value.
00407      */
00408     void when_descriptor_discovery_ends(const TerminationCallbackParams_t *event) {
00409         // shall never happen but happen with android devices ...
00410         // process the next charateristic
00411         if (!_descriptor_handle) {
00412             printf("\tWarning: characteristic with notify or indicate attribute without CCCD.\r\n");
00413             process_next_characteristic();
00414             return;
00415         }
00416 
00417         Properties_t properties = _it->value.getProperties();
00418 
00419         uint16_t cccd_value =
00420             (properties.notify() << 0) | (properties.indicate() << 1);
00421 
00422         ble_error_t error = _client->write(
00423             GattClient::GATT_OP_WRITE_REQ,
00424             _connection_handle,
00425             _descriptor_handle,
00426             sizeof(cccd_value),
00427             reinterpret_cast<uint8_t*>(&cccd_value)
00428         );
00429 
00430         if (error) {
00431             printf(
00432                 "Error: cannot initiate write of CCCD %u due to %u.\r\n",
00433                 _descriptor_handle, error
00434             );
00435             stop();
00436         }
00437     }
00438 
00439     /**
00440      * Called when the CCCD has been written.
00441      */
00442     void when_descriptor_written(const GattWriteCallbackParams* event)
00443     {
00444         // should never happen
00445         if (!_descriptor_handle) {
00446             printf("\tError: received write response to unsolicited request.\r\n");
00447             stop();
00448             return;
00449         }
00450 
00451         printf("\tCCCD at %u written.\r\n", _descriptor_handle);
00452         _descriptor_handle = 0;
00453         process_next_characteristic();
00454     }
00455 
00456     /**
00457      * Print the updated value of the characteristic.
00458      *
00459      * This function is called when the server emits a notification or an
00460      * indication of a characteristic value the client has subscribed to.
00461      *
00462      * @see GattClient::onHVX()
00463      */
00464     void when_characteristic_changed(const GattHVXCallbackParams* event)
00465     {
00466         printf("Change on attribute %u: new value = ", event->handle);
00467         for (size_t i = 0; i < event->len; ++i) {
00468             printf("0x%02X ", event->data[i]);
00469         }
00470         printf(".\r\n");
00471     }
00472 
00473     struct DiscoveredCharacteristicNode {
00474         DiscoveredCharacteristicNode(const DiscoveredCharacteristic &c) :
00475             value(c), next(NULL) { }
00476 
00477         DiscoveredCharacteristic value;
00478         DiscoveredCharacteristicNode *next;
00479     };
00480 
00481     /**
00482      * Add a discovered characteristic into the list of discovered characteristics.
00483      */
00484     bool add_characteristic(const DiscoveredCharacteristic *characteristic)
00485     {
00486         DiscoveredCharacteristicNode* new_node =
00487             new(std::nothrow) DiscoveredCharacteristicNode(*characteristic);
00488 
00489         if (new_node == false) {
00490             printf("Error while allocating a new characteristic.\r\n");
00491             return false;
00492         }
00493 
00494         if (_characteristics == NULL) {
00495             _characteristics = new_node;
00496         } else {
00497             DiscoveredCharacteristicNode* c = _characteristics;
00498             while(c->next) {
00499                 c = c->next;
00500             }
00501             c->next = new_node;
00502         }
00503 
00504         return true;
00505     }
00506 
00507     /**
00508      * Clear the list of discovered characteristics.
00509      */
00510     void clear_characteristics(void)
00511     {
00512         DiscoveredCharacteristicNode *c= _characteristics;
00513 
00514         while (c) {
00515             DiscoveredCharacteristicNode *n = c->next;
00516             delete c;
00517             c = n;
00518         }
00519     }
00520 
00521     /**
00522      * Helper to construct an event handler from a member function of this
00523      * instance.
00524      */
00525     template<typename ContextType>
00526     FunctionPointerWithContext<ContextType> as_cb(
00527         void (Self::*member)(ContextType context)
00528     ) {
00529         return makeFunctionPointer(this, member);
00530     }
00531 
00532     /**
00533      * Print the value of a UUID.
00534      */
00535     static void print_uuid(const UUID &uuid)
00536     {
00537         const uint8_t *uuid_value = uuid.getBaseUUID();
00538 
00539         // UUIDs are in little endian, print them in big endian
00540         for (size_t i = 0; i < uuid.getLen(); ++i) {
00541             printf("%02X", uuid_value[(uuid.getLen() - 1) - i]);
00542         }
00543     }
00544 
00545     /**
00546      * Print the value of a characteristic properties.
00547      */
00548     static void print_properties(const Properties_t &properties)
00549     {
00550         const struct {
00551             bool (Properties_t::*fn)() const;
00552             const char* str;
00553         } prop_to_str[] = {
00554             { &Properties_t::broadcast, "broadcast" },
00555             { &Properties_t::read, "read" },
00556             { &Properties_t::writeWoResp, "writeWoResp" },
00557             { &Properties_t::write, "write" },
00558             { &Properties_t::notify, "notify" },
00559             { &Properties_t::indicate, "indicate" },
00560             { &Properties_t::authSignedWrite, "authSignedWrite" }
00561         };
00562 
00563         printf("[");
00564         for (size_t i = 0; i < (sizeof(prop_to_str) / sizeof(prop_to_str[0])); ++i) {
00565             if ((properties.*(prop_to_str[i].fn))()) {
00566                 printf(" %s", prop_to_str[i].str);
00567             }
00568         }
00569         printf(" ]");
00570     }
00571 
00572     GattClient *_client;
00573     Gap::Handle_t _connection_handle;
00574     DiscoveredCharacteristicNode *_characteristics;
00575     DiscoveredCharacteristicNode *_it;
00576     GattAttribute::Handle_t _descriptor_handle;
00577     BLE *_ble_interface;
00578     events::EventQueue *_event_queue;
00579 };
00580 
00581 
00582 int main() {
00583 
00584     BLE &ble_interface = BLE::Instance();
00585     events::EventQueue event_queue;
00586     BLEProcess ble_process(event_queue, ble_interface);
00587     GattClientProcess gatt_client_process;
00588 
00589     // Register GattClientProcess::init in the ble_process; this function will
00590     // be called once the ble_interface is initialized.
00591     ble_process.on_init(
00592         mbed::callback(&gatt_client_process, &GattClientProcess::init)
00593     );
00594 
00595     // bind the event queue to the ble interface, initialize the interface
00596     // and start advertising
00597     ble_process.start();
00598 
00599     // Process the event queue.
00600     event_queue.dispatch_forever();
00601 
00602     return 0;
00603 }