Gestione Bluetooth per il progetto Tarallo

Revision:
82:443323e7d4c9
Parent:
76:652c2be531c7
--- a/source/main.cpp	Thu Aug 15 17:00:47 2019 +0100
+++ b/source/main.cpp	Thu Jan 16 14:48:30 2020 +0000
@@ -1,5 +1,5 @@
 /* mbed Microcontroller Library
- * Copyright (c) 2006-2015 ARM Limited
+ * Copyright (c) 2006-2018 ARM Limited
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,221 +14,566 @@
  * limitations under the License.
  */
 
-#include <events/mbed_events.h>
-#include <mbed.h>
+#include <memory>
+#include <new>
+#include <stdio.h>
+
+#include "events/EventQueue.h"
+#include "platform/NonCopyable.h"
+
 #include "ble/BLE.h"
-#include "pretty_printer.h"
+#include "ble/Gap.h"
+#include "ble/GattClient.h"
+#include "ble/GapAdvertisingParams.h"
+#include "ble/GapAdvertisingData.h"
+#include "ble/GattClient.h"
+#include "ble/DiscoveredService.h"
+#include "ble/DiscoveredCharacteristic.h"
+#include "ble/CharacteristicDescriptorDiscovery.h"
 
-static events::EventQueue event_queue(/* event count */ 16 * EVENTS_EVENT_SIZE);
+#include "BLEProcess.h"
 
-/** @deprecated This demo is deprecated and no replacement is currently available.
+/**
+ * Handle discovery of the GATT server.
+ *
+ * First the GATT server is discovered in its entirety then each readable
+ * characteristic is read and the client register to characteristic
+ * notifications or indication when available. The client report server
+ * indications and notification until the connection end.
  */
-MBED_DEPRECATED_SINCE(
-   "mbed-os-5.11",
-   "This demo is deprecated and no replacement is currently available."
-)
-class BeaconDemo : ble::Gap::EventHandler {
+class GattClientProcess : private mbed::NonCopyable<GattClientProcess>,
+                          public ble::Gap::EventHandler {
+
+    // Internal typedef to this class type.
+    // It is used as a shorthand to pass member function as callbacks.
+    typedef GattClientProcess Self;
+
+    typedef CharacteristicDescriptorDiscovery::DiscoveryCallbackParams_t
+        DiscoveryCallbackParams_t;
+
+    typedef CharacteristicDescriptorDiscovery::TerminationCallbackParams_t
+        TerminationCallbackParams_t;
+
+    typedef DiscoveredCharacteristic::Properties_t Properties_t;
+
 public:
-    BeaconDemo(BLE &ble, events::EventQueue &event_queue) :
-        _ble(ble),
-        _event_queue(event_queue),
-        _adv_data_builder(_adv_buffer) { }
+
+    /**
+     * Construct an empty client process.
+     *
+     * The function start() shall be called to initiate the discovery process.
+     */
+    GattClientProcess() :
+        _client(NULL),
+        _connection_handle(),
+        _characteristics(NULL),
+        _it(NULL),
+        _descriptor_handle(0),
+        _ble_interface(NULL),
+        _event_queue(NULL) {
+    }
+
+    ~GattClientProcess()
+    {
+        stop();
+    }
+
+    void init(BLE &ble_interface, events::EventQueue &event_queue)
+    {
+        _ble_interface = &ble_interface;
+        _event_queue = &event_queue;
+        _client = &_ble_interface->gattClient();
+
+        _ble_interface->gap().setEventHandler(this);
+    }
+
+    /**
+     * Start the discovery process.
+     *
+     * @param[in] client The GattClient instance which will discover the distant
+     * GATT server.
+     * @param[in] connection_handle Reference of the connection to the GATT
+     * server which will be discovered.
+     */
+    void start()
+    {
+        // setup the event handlers called during the process
+        _client->onDataWritten().add(as_cb(&Self::when_descriptor_written));
+        _client->onHVX().add(as_cb(&Self::when_characteristic_changed));
 
-    void start() {
-        _ble.gap().setEventHandler(this);
+        // The discovery process will invoke when_service_discovered when a
+        // service is discovered, when_characteristic_discovered when a
+        // characteristic is discovered and when_service_discovery_ends once the
+        // discovery process has ended.
+        _client->onServiceDiscoveryTermination(as_cb(&Self::when_service_discovery_ends));
+        ble_error_t error = _client->launchServiceDiscovery(
+            _connection_handle,
+            as_cb(&Self::when_service_discovered),
+            as_cb(&Self::when_characteristic_discovered)
+        );
+
+        if (error) {
+            printf("Error %u returned by _client->launchServiceDiscovery.\r\n", error);
+            return;
+        }
+
+        printf("Client process started: initiate service discovery.\r\n");
+    }
 
-        _ble.init(this, &BeaconDemo::on_init_complete);
+    /**
+     * Stop the discovery process and clean the instance.
+     */
+    void stop()
+    {
+        if (!_client) {
+            return;
+        }
 
-        _event_queue.dispatch_forever();
+        // unregister event handlers
+        _client->onDataWritten().detach(as_cb(&Self::when_descriptor_written));
+        _client->onHVX().detach(as_cb(&Self::when_characteristic_changed));
+        _client->onServiceDiscoveryTermination(NULL);
+
+        // remove discovered characteristics
+        clear_characteristics();
+
+        // clean up the instance
+        _connection_handle = 0;
+        _characteristics = NULL;
+        _it = NULL;
+        _descriptor_handle = 0;
+
+        printf("Client process stopped.\r\n");
     }
 
 private:
     /**
-     * iBeacon payload builder.
+     * Event handler invoked when a connection is established.
      *
-     * This data structure contains the payload of an iBeacon. The payload is
-     * built at construction time and application code can set up an iBeacon by
-     * injecting the raw field into the GAP advertising payload as a
-     * GapAdvertisingData::MANUFACTURER_SPECIFIC_DATA.
+     * This function setup the connection handle to operate on then start the
+     * discovery process.
      */
-    union Payload {
-        /**
-         * Raw data of the payload.
-         */
-        uint8_t raw[25];
-        struct {
-            /**
-             * Beacon manufacturer identifier.
-             */
-            uint16_t companyID;
-
-            /**
-             * Packet ID; Equal to 2 for an iBeacon.
-             */
-            uint8_t ID;
-
-            /**
-             * Length of the remaining data presents in the payload.
-             */
-            uint8_t len;
-
-            /**
-             * Beacon UUID.
-             */
-            uint8_t proximityUUID[16];
-
-            /**
-             * Beacon Major group ID.
-             */
-            uint16_t majorNumber;
-
-            /**
-             * Beacon minor ID.
-             */
-            uint16_t minorNumber;
+    virtual void onConnectionComplete(const ble::ConnectionCompleteEvent &event)
+    {
+        _connection_handle = event.getConnectionHandle();
+        _event_queue->call(mbed::callback(this, &Self::start));
+    }
 
-            /**
-             * Tx power received at 1 meter; in dBm.
-             */
-            uint8_t txPower;
-        };
-
-        /**
-         * Assemble an iBeacon payload.
-         *
-         * @param[in] uuid Beacon network ID. iBeacon operators use this value
-         * to group their iBeacons into a single network, a single region and
-         * identify their organization among others.
-         *
-         * @param[in] majNum Beacon major group ID. iBeacon exploitants may use
-         * this field to divide the region into subregions, their network into
-         * subnetworks.
-         *
-         * @param[in] minNum Identifier of the Beacon in its subregion.
-         *
-         * @param[in] transmitPower Measured transmit power of the beacon at 1
-         * meter. Scanners use this parameter to approximate the distance
-         * to the beacon.
-         *
-         * @param[in] companyIDIn ID of the beacon manufacturer.
-         */
-        Payload(
-            const uint8_t *uuid,
-            uint16_t majNum,
-            uint16_t minNum,
-            uint8_t transmitPower,
-            uint16_t companyIDIn
-        ) : companyID(companyIDIn),
-            ID(0x02),
-            len(0x15),
-            majorNumber(__REV16(majNum)),
-            minorNumber(__REV16(minNum)),
-            txPower(transmitPower)
-        {
-            memcpy(proximityUUID, uuid, 16);
+    /**
+     * Stop the discovery process and clean the instance.
+     */
+    virtual void onDisconnectionComplete(const ble::DisconnectionCompleteEvent &event)
+    {
+        if (_client && event.getConnectionHandle() == _connection_handle) {
+            stop();
         }
-    };
-
-    /** Callback triggered when the ble initialization process has finished */
-    void on_init_complete(BLE::InitializationCompleteCallbackContext *params) {
-        if (params->error != BLE_ERROR_NONE) {
-            printf("Ble initialization failed.");
-            return;
-        }
-
-        print_mac_address();
-
-        start_advertising();
     }
 
-    void start_advertising() {
-        /* Create advertising parameters and payload */
+private:
+////////////////////////////////////////////////////////////////////////////////
+// Service and characteristic discovery process.
 
-        ble::AdvertisingParameters adv_parameters(
-            ble::advertising_type_t::CONNECTABLE_UNDIRECTED,
-            ble::adv_interval_t(ble::millisecond_t(1000))
+    /**
+     * Handle services discovered.
+     *
+     * The GattClient invokes this function when a service has been discovered.
+     *
+     * @see GattClient::launchServiceDiscovery
+     */
+    void when_service_discovered(const DiscoveredService *discovered_service)
+    {
+        // print information of the service discovered
+        printf("Service discovered: value = ");
+        print_uuid(discovered_service->getUUID());
+        printf(", start = %u, end = %u.\r\n",
+            discovered_service->getStartHandle(),
+            discovered_service->getEndHandle()
+        );
+    }
+
+    /**
+     * Handle characteristics discovered.
+     *
+     * The GattClient invoke this function when a characteristic has been
+     * discovered.
+     *
+     * @see GattClient::launchServiceDiscovery
+     */
+    void when_characteristic_discovered(const DiscoveredCharacteristic *discovered_characteristic)
+    {
+        // print characteristics properties
+        printf("\tCharacteristic discovered: uuid = ");
+        print_uuid(discovered_characteristic->getUUID());
+        printf(", properties = ");
+        print_properties(discovered_characteristic->getProperties());
+        printf(
+            ", decl handle = %u, value handle = %u, last handle = %u.\r\n",
+            discovered_characteristic->getDeclHandle(),
+            discovered_characteristic->getValueHandle(),
+            discovered_characteristic->getLastHandle()
         );
 
-        _adv_data_builder.setFlags();
-
-        /**
-         * The Beacon payload has the following composition:
-         * 128-Bit / 16byte UUID = E2 0A 39 F4 73 F5 4B C4 A1 2F 17 D1 AD 07 A9 61
-         * Major/Minor  = 0x1122 / 0x3344
-         * Tx Power     = 0xC8 = 200, 2's compliment is 256-200 = (-56dB)
-         *
-         * Note: please remember to calibrate your beacons TX Power for more accurate results.
-         */
-        static const uint8_t uuid[] = { 0xE2, 0x0A, 0x39, 0xF4, 0x73, 0xF5, 0x4B, 0xC4,
-                                        0xA1, 0x2F, 0x17, 0xD1, 0xAD, 0x07, 0xA9, 0x61 };
-        uint16_t major_number = 1122;
-        uint16_t minor_number = 3344;
-        uint16_t tx_power     = 0xC8;
-        uint16_t comp_id      = 0x004C;
-
-        Payload ibeacon(uuid, major_number, minor_number, tx_power, comp_id);
-
-        _adv_data_builder.setManufacturerSpecificData(ibeacon.raw);
-
-        /* Setup advertising */
-
-        ble_error_t error = _ble.gap().setAdvertisingParameters(
-            ble::LEGACY_ADVERTISING_HANDLE,
-            adv_parameters
-        );
-
-        if (error) {
-            print_error(error, "_ble.gap().setAdvertisingParameters() failed");
-            return;
-        }
-
-        error = _ble.gap().setAdvertisingPayload(
-            ble::LEGACY_ADVERTISING_HANDLE,
-            _adv_data_builder.getAdvertisingData()
-        );
-
-        if (error) {
-            print_error(error, "_ble.gap().setAdvertisingPayload() failed");
-            return;
-        }
-
-        /* Start advertising */
-
-        error = _ble.gap().startAdvertising(ble::LEGACY_ADVERTISING_HANDLE);
-
-        if (error) {
-            print_error(error, "_ble.gap().startAdvertising() failed");
+        // add the characteristic into the list of discovered characteristics
+        bool success = add_characteristic(discovered_characteristic);
+        if (!success) {
+            printf("Error: memory allocation failure while adding the discovered characteristic.\r\n");
+            _client->terminateServiceDiscovery();
+            stop();
             return;
         }
     }
 
-private:
-    /* Event handler */
+    /**
+     * Handle termination of the service and characteristic discovery process.
+     *
+     * The GattClient invokes this function when the service and characteristic
+     * discovery process ends.
+     *
+     * @see GattClient::onServiceDiscoveryTermination
+     */
+    void when_service_discovery_ends(Gap::Handle_t connection_handle)
+    {
+        if (!_characteristics) {
+            printf("No characteristics discovered, end of the process.\r\n");
+            return;
+        }
+
+        printf("All services and characteristics discovered, process them.\r\n");
+
+        // reset iterator and start processing characteristics in order
+        _it = NULL;
+        _event_queue->call(mbed::callback(this, &Self::process_next_characteristic));
+    }
+
+////////////////////////////////////////////////////////////////////////////////
+// Processing of characteristics based on their properties.
+
+    /**
+     * Process the characteristics discovered.
+     *
+     * - If the characteristic is readable then read its value and print it. Then
+     * - If the characteristic can emit notification or indication then discover
+     * the characteristic CCCD and subscribe to the server initiated event.
+     * - Otherwise skip the characteristic processing.
+     */
+    void process_next_characteristic(void)
+    {
+        if (!_it) {
+            _it = _characteristics;
+        } else {
+            _it = _it->next;
+        }
+
+        while (_it) {
+            Properties_t properties = _it->value.getProperties();
+
+            if (properties.read()) {
+                read_characteristic(_it->value);
+                return;
+            } else if(properties.notify() || properties.indicate()) {
+                discover_descriptors(_it->value);
+                return;
+            } else {
+                printf(
+                    "Skip processing of characteristic %u\r\n",
+                    _it->value.getValueHandle()
+                );
+                _it = _it->next;
+            }
+        }
+
+        printf("All characteristics discovered have been processed.\r\n");
+    }
+
+    /**
+     * Initate the read of the characteristic in input.
+     *
+     * The completion of the operation will happens in when_characteristic_read()
+     */
+    void read_characteristic(const DiscoveredCharacteristic &characteristic)
+    {
+        printf("Initiating read at %u.\r\n", characteristic.getValueHandle());
+        ble_error_t error = characteristic.read(
+            0, as_cb(&Self::when_characteristic_read)
+        );
 
-    void onDisconnectionComplete(const ble::DisconnectionCompleteEvent&) {
-        _ble.gap().startAdvertising(ble::LEGACY_ADVERTISING_HANDLE);
+        if (error) {
+            printf(
+                "Error: cannot initiate read at %u due to %u\r\n",
+                characteristic.getValueHandle(), error
+            );
+            stop();
+        }
+    }
+
+    /**
+     * Handle the reception of a read response.
+     *
+     * If the characteristic can emit notification or indication then start the
+     * discovery of the the characteristic descriptors then subscribe to the
+     * server initiated event by writing the CCCD discovered. Otherwise start
+     * the processing of the next characteristic discovered in the server.
+     */
+    void when_characteristic_read(const GattReadCallbackParams *read_event)
+    {
+        printf("\tCharacteristic value at %u equal to: ", read_event->handle);
+        for (size_t i = 0; i <  read_event->len; ++i) {
+            printf("0x%02X ", read_event->data[i]);
+        }
+        printf(".\r\n");
+
+        Properties_t properties = _it->value.getProperties();
+
+        if(properties.notify() || properties.indicate()) {
+            discover_descriptors(_it->value);
+        } else {
+            process_next_characteristic();
+        }
+    }
+
+    /**
+     * Initiate the discovery of the descriptors of the characteristic in input.
+     *
+     * When a descriptor is discovered, the function when_descriptor_discovered
+     * is invoked.
+     */
+    void discover_descriptors(const DiscoveredCharacteristic &characteristic)
+    {
+        printf("Initiating descriptor discovery of %u.\r\n", characteristic.getValueHandle());
+
+        _descriptor_handle = 0;
+        ble_error_t error = characteristic.discoverDescriptors(
+            as_cb(&Self::when_descriptor_discovered),
+            as_cb(&Self::when_descriptor_discovery_ends)
+        );
+
+        if (error) {
+            printf(
+                "Error: cannot initiate discovery of %04X due to %u.\r\n",
+                characteristic.getValueHandle(), error
+            );
+            stop();
+        }
+    }
+
+    /**
+     * Handle the discovery of the characteristic descriptors.
+     *
+     * If the descriptor found is a CCCD then stop the discovery. Once the
+     * process has ended subscribe to server initiated events by writing the
+     * value of the CCCD.
+     */
+    void when_descriptor_discovered(const DiscoveryCallbackParams_t* event)
+    {
+        printf("\tDescriptor discovered at %u, UUID: ", event->descriptor.getAttributeHandle());
+        print_uuid(event->descriptor.getUUID());
+        printf(".\r\n");
+
+        if (event->descriptor.getUUID() == BLE_UUID_DESCRIPTOR_CLIENT_CHAR_CONFIG) {
+            _descriptor_handle = event->descriptor.getAttributeHandle();
+            _client->terminateCharacteristicDescriptorDiscovery(
+                event->characteristic
+            );
+        }
     }
 
-private:
-    BLE &_ble;
-    events::EventQueue &_event_queue;
+    /**
+     * If a CCCD has been found subscribe to server initiated events by writing
+     * its value.
+     */
+    void when_descriptor_discovery_ends(const TerminationCallbackParams_t *event) {
+        // shall never happen but happen with android devices ...
+        // process the next charateristic
+        if (!_descriptor_handle) {
+            printf("\tWarning: characteristic with notify or indicate attribute without CCCD.\r\n");
+            process_next_characteristic();
+            return;
+        }
+
+        Properties_t properties = _it->value.getProperties();
+
+        uint16_t cccd_value =
+            (properties.notify() << 0) | (properties.indicate() << 1);
+
+        ble_error_t error = _client->write(
+            GattClient::GATT_OP_WRITE_REQ,
+            _connection_handle,
+            _descriptor_handle,
+            sizeof(cccd_value),
+            reinterpret_cast<uint8_t*>(&cccd_value)
+        );
+
+        if (error) {
+            printf(
+                "Error: cannot initiate write of CCCD %u due to %u.\r\n",
+                _descriptor_handle, error
+            );
+            stop();
+        }
+    }
+
+    /**
+     * Called when the CCCD has been written.
+     */
+    void when_descriptor_written(const GattWriteCallbackParams* event)
+    {
+        // should never happen
+        if (!_descriptor_handle) {
+            printf("\tError: received write response to unsolicited request.\r\n");
+            stop();
+            return;
+        }
+
+        printf("\tCCCD at %u written.\r\n", _descriptor_handle);
+        _descriptor_handle = 0;
+        process_next_characteristic();
+    }
+
+    /**
+     * Print the updated value of the characteristic.
+     *
+     * This function is called when the server emits a notification or an
+     * indication of a characteristic value the client has subscribed to.
+     *
+     * @see GattClient::onHVX()
+     */
+    void when_characteristic_changed(const GattHVXCallbackParams* event)
+    {
+        printf("Change on attribute %u: new value = ", event->handle);
+        for (size_t i = 0; i < event->len; ++i) {
+            printf("0x%02X ", event->data[i]);
+        }
+        printf(".\r\n");
+    }
+
+    struct DiscoveredCharacteristicNode {
+        DiscoveredCharacteristicNode(const DiscoveredCharacteristic &c) :
+            value(c), next(NULL) { }
+
+        DiscoveredCharacteristic value;
+        DiscoveredCharacteristicNode *next;
+    };
+
+    /**
+     * Add a discovered characteristic into the list of discovered characteristics.
+     */
+    bool add_characteristic(const DiscoveredCharacteristic *characteristic)
+    {
+        DiscoveredCharacteristicNode* new_node =
+            new(std::nothrow) DiscoveredCharacteristicNode(*characteristic);
 
-    uint8_t _adv_buffer[ble::LEGACY_ADVERTISING_MAX_SIZE];
-    ble::AdvertisingDataBuilder _adv_data_builder;
+        if (new_node == NULL) {
+            printf("Error while allocating a new characteristic.\r\n");
+            return false;
+        }
+
+        if (_characteristics == NULL) {
+            _characteristics = new_node;
+        } else {
+            DiscoveredCharacteristicNode* c = _characteristics;
+            while(c->next) {
+                c = c->next;
+            }
+            c->next = new_node;
+        }
+
+        return true;
+    }
+
+    /**
+     * Clear the list of discovered characteristics.
+     */
+    void clear_characteristics(void)
+    {
+        DiscoveredCharacteristicNode *c= _characteristics;
+
+        while (c) {
+            DiscoveredCharacteristicNode *n = c->next;
+            delete c;
+            c = n;
+        }
+    }
+
+    /**
+     * Helper to construct an event handler from a member function of this
+     * instance.
+     */
+    template<typename ContextType>
+    FunctionPointerWithContext<ContextType> as_cb(
+        void (Self::*member)(ContextType context)
+    ) {
+        return makeFunctionPointer(this, member);
+    }
+
+    /**
+     * Print the value of a UUID.
+     */
+    static void print_uuid(const UUID &uuid)
+    {
+        const uint8_t *uuid_value = uuid.getBaseUUID();
+
+        // UUIDs are in little endian, print them in big endian
+        for (size_t i = 0; i < uuid.getLen(); ++i) {
+            printf("%02X", uuid_value[(uuid.getLen() - 1) - i]);
+        }
+    }
+
+    /**
+     * Print the value of a characteristic properties.
+     */
+    static void print_properties(const Properties_t &properties)
+    {
+        const struct {
+            bool (Properties_t::*fn)() const;
+            const char* str;
+        } prop_to_str[] = {
+            { &Properties_t::broadcast, "broadcast" },
+            { &Properties_t::read, "read" },
+            { &Properties_t::writeWoResp, "writeWoResp" },
+            { &Properties_t::write, "write" },
+            { &Properties_t::notify, "notify" },
+            { &Properties_t::indicate, "indicate" },
+            { &Properties_t::authSignedWrite, "authSignedWrite" }
+        };
+
+        printf("[");
+        for (size_t i = 0; i < (sizeof(prop_to_str) / sizeof(prop_to_str[0])); ++i) {
+            if ((properties.*(prop_to_str[i].fn))()) {
+                printf(" %s", prop_to_str[i].str);
+            }
+        }
+        printf(" ]");
+    }
+
+    GattClient *_client;
+    Gap::Handle_t _connection_handle;
+    DiscoveredCharacteristicNode *_characteristics;
+    DiscoveredCharacteristicNode *_it;
+    GattAttribute::Handle_t _descriptor_handle;
+    BLE *_ble_interface;
+    events::EventQueue *_event_queue;
 };
 
-/** Schedule processing of events from the BLE middleware in the event queue. */
-void schedule_ble_events(BLE::OnEventsToProcessCallbackContext *context) {
-    event_queue.call(Callback<void()>(&context->ble, &BLE::processEvents));
-}
+
+int main() {
+
+    BLE &ble_interface = BLE::Instance();
+    events::EventQueue event_queue;
+    BLEProcess ble_process(event_queue, ble_interface);
+    GattClientProcess gatt_client_process;
 
-int main()
-{
-    BLE &ble = BLE::Instance();
-    ble.onEventsToProcess(schedule_ble_events);
+    // Register GattClientProcess::init in the ble_process; this function will
+    // be called once the ble_interface is initialized.
+    ble_process.on_init(
+        mbed::callback(&gatt_client_process, &GattClientProcess::init)
+    );
 
-    BeaconDemo demo(ble, event_queue);
-    demo.start();
+    // bind the event queue to the ble interface, initialize the interface
+    // and start advertising
+    ble_process.start();
+
+    // Process the event queue.
+    event_queue.dispatch_forever();
 
     return 0;
-}
+}
\ No newline at end of file