From 69308c9e6064ac8401ea7d25b56c351a7f1e2f85 Mon Sep 17 00:00:00 2001 From: Matthias Ringwald Date: Wed, 3 Jun 2015 23:05:53 +0200 Subject: [PATCH] draft of LE Central example --- platforms/arduino/docs/docs/examples/intro.md | 5 + .../arduino/examples/LECentral/LECentral.ino | 151 ++++++++++++++++-- 2 files changed, 147 insertions(+), 9 deletions(-) diff --git a/platforms/arduino/docs/docs/examples/intro.md b/platforms/arduino/docs/docs/examples/intro.md index e69de29bb..63eec43fe 100644 --- a/platforms/arduino/docs/docs/examples/intro.md +++ b/platforms/arduino/docs/docs/examples/intro.md @@ -0,0 +1,5 @@ +# Examples + +In this section, we will describe a number of examples that are available +from inside the Arduino IDE via File->Examples->BTstack. + diff --git a/platforms/arduino/examples/LECentral/LECentral.ino b/platforms/arduino/examples/LECentral/LECentral.ino index 66e915457..bea058715 100644 --- a/platforms/arduino/examples/LECentral/LECentral.ino +++ b/platforms/arduino/examples/LECentral/LECentral.ino @@ -1,11 +1,33 @@ - #include #include /* * EXAMPLE_START(LECentral): LE Central + * + * @text Compared with the other examples, the LE Central is + * a bit more complex. This is because it performs multiple + * steps in sequence as it is common with GATT Client APIs. + * + * It shows how to first scan for other + * devices and then connect to one. When connected, a series of + * GATT Client operations are performed: first the list of + * GATT Services is queried. If a particular service is found, + * the list of its GATT Characteristics is retrieved and a set + * of known Characteristics are cached for later access. */ +/* + * @section Characteristic Summary + * @text As multiple Characteristics need to be found, a custom + * struct is used to collect all information about it. This allows + * to defined the list of neccessary characteristics in the + * characteristics[] array + */ +/* LISTING_START(LECentralSummary): Characteristic Summary */ + +// BLE Shield Service V2 incl. used Characteristics +UUID bleShieldServiceV2UUID("B8E06067-62AD-41BA-9231-206AE80AB550"); + typedef struct characteristic_summary { UUID uuid; const char * name; @@ -28,8 +50,7 @@ characteristic_summary characteristics[] = { { UUID("65c228da-bad1-4f41-b55f-3d177f4e2196"), "BD ADDR" } }; -// BLE Shield Service V2 incl. used Characteristics -UUID bleShieldServiceV2UUID("B8E06067-62AD-41BA-9231-206AE80AB550"); +/* LISTING_END(LECentralSummary): Characteristic Summary */ // Application state BLEDevice myBLEDevice; @@ -42,10 +63,14 @@ char counterString[20]; static timer_source_t heartbeat; -void setup(void){ - +/* + * @section Setup + * @text In the setup, various callbacks are registered. After that + * we start scanning for other devices + */ +/* LISTING_START(LECentralSetup): LE Central Setup */ + void setup(void){ Serial.begin(9600); - BTstack.setBLEAdvertisementCallback(advertisementCallback); BTstack.setBLEDeviceConnectedCallback(deviceConnectedCallback); BTstack.setBLEDeviceDisconnectedCallback(deviceDisconnectedCallback); @@ -55,13 +80,21 @@ void setup(void){ BTstack.setGATTCharacteristicReadCallback(gattReadCallback); BTstack.setGATTCharacteristicWrittenCallback(gattWrittenCallback); BTstack.setGATTCharacteristicSubscribedCallback(gattSubscribedCallback); - BTstack.setup(); - BTstack.bleStartScanning(); } +/* LISTING_END(LECentralSetup): LE Central Setup */ - +/* + * @section Loop + * + * @text In the standard Arduino loop() function, BTstack's loop() is called first + * If we're connected, we send the string "BTstack" plus a counter as fast as possible. + * As the Bluetooth module might be busy, it's important to check the result of the + * writeCharacteristicWithoutResponse() call. If it's not ok, we just try again in the + * next loop iteration. + */ +/* LISTING_START(LECentralLoop): Loop */ void loop(void){ BTstack.loop(); @@ -76,7 +109,20 @@ void loop(void){ } } } +/* LISTING_END(LECentralLoop): Loop */ +/* + * @section Advertisement Callback + * + * @text When an Advertisement is received, we check if it contains + * the UUID of the service we're interested in. Only a single service + * with a 128-bit UUID can be contained in and Advertisement and not + * all BLE devices provides this. Other options are to match on the + * reported device name or the BD ADDR prefix. + * + * If we found an interesting device, we try to connect to it. + */ +/* LISTING_START(LECentralAdvertisementCallback): Advertisement Callback */ void advertisementCallback(BLEAdvertisement *bleAdvertisement) { Serial.print("Device discovered: "); Serial.print(bleAdvertisement->getBdAddr()->getAddressString()); @@ -88,7 +134,18 @@ void advertisementCallback(BLEAdvertisement *bleAdvertisement) { BTstack.bleConnect(bleAdvertisement, 10000); // 10 s } } +/* LISTING_END(LECentralAdvertisementCallback): Advertisement Callback */ +/* + * @section Device Connected Callback + * + * @text At the end of bleConnect(), the device connected callback is callec. + * The status argument tells if the connection timed out, or if the connection + * was established successfully. + * + * On a successful connection, a GATT Service Discovery is started. + */ +/* LISTING_START(LECentralDeviceConnectedCallback): Device Connected Callback */ void deviceConnectedCallback(BLEStatus status, BLEDevice *device) { switch (status){ case BLE_STATUS_OK: @@ -105,13 +162,34 @@ void deviceConnectedCallback(BLEStatus status, BLEDevice *device) { break; } } +/* LISTING_END(LECentralDeviceConnectedCallback): Device Connected Callback */ +/* + * @section Device Disconnected Callback + * + * @text If the connection to a device breaks, the device disconnected callback + * is called. Here, we start scanning for new devices again. + */ +/* LISTING_START(LECentralDeviceDisconnectedCallback): Device Disconnected Callback */ void deviceDisconnectedCallback(BLEDevice * device){ Serial.println("Disconnected, starting over.."); sendCounter = false; BTstack.bleStartScanning(); } +/* LISTING_END(LECentralDeviceDisconnectedCallback): Device Disconnected Callback */ +/* + * @section Service Discovered Callback + * + * @text The service discovered callback is called for each service and after the + * service discovery is complete. The status argument is provided for this. + * + * The main information about a discovered Service is its UUID. + * If we find our service, we store the reference to this service. + * This allows to discover the Characteristics for our service after + * the service discovery is complete. + */ +/* LISTING_START(LECentralServiceDiscoveredCallback): Service Discovered Callback */ void gattServiceDiscovered(BLEStatus status, BLEDevice *device, BLEService *bleService) { switch(status){ case BLE_STATUS_OK: @@ -134,7 +212,21 @@ void gattServiceDiscovered(BLEStatus status, BLEDevice *device, BLEService *bleS break; } } +/* LISTING_END(LECentralServiceDiscoveredCallback): Service Discovered Callback */ +/* + * @section Characteristic Discovered Callback + * + * @text Similar to the Service Discovered callback, the Characteristic Discovered + * callback is called for each Characteristic found and after the discovery is complete. + * + * The main information is again its UUID. If we find a Characteristic that we're + * interested in, it's name is printed and a reference stored for later. + * + * On discovery complete, we subscribe to a particular Characteristic to receive + * Characteristic Value updates in the Notificaation Callback. + */ +/* LISTING_START(LECentralCharacteristicDiscoveredCallback): Characteristic Discovered Callback */ void gattCharacteristicDiscovered(BLEStatus status, BLEDevice *device, BLECharacteristic *characteristic) { switch(status){ case BLE_STATUS_OK: @@ -165,22 +257,63 @@ void gattCharacteristicDiscovered(BLEStatus status, BLEDevice *device, BLECharac break; } } +/* LISTING_END(LECentralCharacteristicDiscoveredCallback): Characteristic Discovered Callback */ +/* + * @section Subscribed Callback + * + * @text After the subcribe operation is complete, we get notified if it was + * successful. In this example, we read the Characteristic that contains the + * BD ADDR of the other device. This isn't strictly neccessary as we already + * know the device address from the Advertisement, but it's a common pattern + * with iOS as the device address is hidden from applications. + */ +/* LISTING_START(LECentralSubscribedCallback): Subscribed Callback */ void gattSubscribedCallback(BLEStatus status, BLEDevice * device){ device->readCharacteristic(&characteristics[charBdAddr].characteristic); } +/* LISTING_END(LECentralSubscribedCallback): Subscribed Callback */ +/* + * @section Read Callback + * + * @text The Read callback is called with the result from a read operation. + * Here, we write to the TX Characteristic next. + */ +/* LISTING_START(LECentralReadCallback): Read Callback */ void gattReadCallback(BLEStatus status, BLEDevice *device, uint8_t *value, uint16_t length) { Serial.print("Read callback: "); Serial.println((const char *)value); device->writeCharacteristic(&characteristics[charTX].characteristic, (uint8_t*) "Hello!", 6); } +/* LISTING_END(LECentralReadCallback): Read Callback */ +/* + * @section Written Callback + * + * @text After the write operation is complete, the Written Callback is callbed with + * the result in the status argument. As we're done with the initial setup of the remote + * device, we set the flag to write the test string as fast as possible. + */ +/* LISTING_START(LECentralWrittenCallback): Written Callback */ void gattWrittenCallback(BLEStatus status, BLEDevice *device){ sendCounter = true; } +/* LISTING_END(LECentralWrittenCallback): Written Callback */ +/* + * @section Notification Callback + * + * @text Notifictions for Characteristic Value Updates are delivered via the + * Notification Callback. When more than one Characteristic is subscribed, + * the value handle can be used to distinguish between them. The + * BLECharacteristic.isValueHandle(int handle) allows to test if a value handle + * belongs to a particular Characteristic. + */ +/* LISTING_START(LECentralNotificationCallback): Notification Callback */ void gattCharacteristicNotification(BLEDevice *device, uint16_t value_handle, uint8_t *value, uint16_t length) { Serial.print("Notification: "); Serial.println((const char *)value); } +/* LISTING_END(LECentralNotificationCallback): Notification Callback */ +