xref: /btstack/port/arduino/examples/LECentral/LECentral.ino (revision 1b464e99afd70ddaf6b75be1ba7cc563a5f5dfd8)
1#include <BTstack.h>
2#include <SPI.h>
3
4/*
5 * EXAMPLE_START(LECentral): LE Central
6 *
7 * @text Compared with the other examples, the LE Central is
8 * a bit more complex. This is because it performs multiple
9 * steps in sequence as it is common with GATT Client APIs.
10 *
11 * It shows how to first scan for other
12 * devices and then connect to one. When connected, a series of
13 * GATT Client operations are performed: first the list of
14 * GATT Services is queried. If a particular service is found,
15 * the list of its GATT Characteristics is retrieved and a set
16 * of known Characteristics are cached for later access.
17 */
18
19/*
20 * @section Characteristic Summary
21 * @text As multiple Characteristics need to be found, a custom
22 * struct is used to collect all information about it. This allows
23 * to defined the list of neccessary characteristics in the
24 * characteristics[] array
25 */
26/* LISTING_START(LECentralSummary): Characteristic Summary */
27
28// BLE Shield Service V2 incl. used Characteristics
29UUID bleShieldServiceV2UUID("B8E06067-62AD-41BA-9231-206AE80AB550");
30
31typedef struct characteristic_summary {
32    UUID         uuid;
33    const char * name;
34    bool         found;
35    BLECharacteristic characteristic;
36} characteristic_summary_t;
37
38typedef enum characteristicIDs {
39    charRX = 0,
40    charTX,
41    charBaud,
42    charBdAddr,
43    numCharacteristics  /* last one */
44} characteristicIDs_t;
45
46characteristic_summary characteristics[] = {
47    { UUID("f897177b-aee8-4767-8ecc-cc694fd5fcee"), "RX"       },
48    { UUID("bf45e40a-de2a-4bc8-bba0-e5d6065f1b4b"), "TX"       },
49    { UUID("2fbc0f31-726a-4014-b9fe-c8be0652e982"), "Baudrate" },
50    { UUID("65c228da-bad1-4f41-b55f-3d177f4e2196"), "BD ADDR"  }
51};
52
53/* LISTING_END(LECentralSummary): Characteristic Summary */
54
55// Application state
56BLEDevice  myBLEDevice;
57BLEService myBLEService;
58bool serviceFound;
59bool sendCounter = false;
60
61int counter = 0;
62char counterString[20];
63
64static btstack_timer_source_t heartbeat;
65
66/*
67 * @section Setup
68 * @text In the setup, various callbacks are registered. After that
69 * we start scanning for other devices
70 */
71/* LISTING_START(LECentralSetup): LE Central Setup */
72 void setup(void){
73    Serial.begin(9600);
74    BTstack.setBLEAdvertisementCallback(advertisementCallback);
75    BTstack.setBLEDeviceConnectedCallback(deviceConnectedCallback);
76    BTstack.setBLEDeviceDisconnectedCallback(deviceDisconnectedCallback);
77    BTstack.setGATTServiceDiscoveredCallback(gattServiceDiscovered);
78    BTstack.setGATTCharacteristicDiscoveredCallback(gattCharacteristicDiscovered);
79    BTstack.setGATTCharacteristicNotificationCallback(gattCharacteristicNotification);
80    BTstack.setGATTCharacteristicReadCallback(gattReadCallback);
81    BTstack.setGATTCharacteristicWrittenCallback(gattWrittenCallback);
82    BTstack.setGATTCharacteristicSubscribedCallback(gattSubscribedCallback);
83    BTstack.setup();
84    BTstack.bleStartScanning();
85}
86/* LISTING_END(LECentralSetup): LE Central Setup */
87
88/*
89 * @section Loop
90 *
91 * @text In the standard Arduino loop() function, BTstack's loop() is called first
92 * If we're connected, we send the string "BTstack" plus a counter as fast as possible.
93 * As the Bluetooth module might be busy, it's important to check the result of the
94 * writeCharacteristicWithoutResponse() call. If it's not ok, we just try again in the
95 * next loop iteration.
96 */
97/* LISTING_START(LECentralLoop): Loop */
98void loop(void){
99    BTstack.loop();
100
101    // send counter as fast as possible
102    if (sendCounter){
103        sprintf(counterString, "BTstack %u\n", counter);
104        int result = myBLEDevice.writeCharacteristicWithoutResponse(&characteristics[charTX].characteristic, (uint8_t*) counterString, strlen(counterString) );
105        if (result == 0){
106            Serial.print("Wrote without response: ");
107            Serial.println(counterString);
108            counter++;
109        }
110    }
111}
112/* LISTING_END(LECentralLoop): Loop */
113
114/*
115 * @section Advertisement Callback
116 *
117 * @text When an Advertisement is received, we check if it contains
118 * the UUID of the service we're interested in. Only a single service
119 * with a 128-bit UUID can be contained in and Advertisement and not
120 * all BLE devices provides this. Other options are to match on the
121 * reported device name or the BD ADDR prefix.
122 *
123 * If we found an interesting device, we try to connect to it.
124 */
125/* LISTING_START(LECentralAdvertisementCallback): Advertisement Callback */
126void advertisementCallback(BLEAdvertisement *bleAdvertisement) {
127    Serial.print("Device discovered: ");
128    Serial.print(bleAdvertisement->getBdAddr()->getAddressString());
129    Serial.print(", RSSI: ");
130    Serial.println(bleAdvertisement->getRssi());
131    if (bleAdvertisement->containsService(&bleShieldServiceV2UUID)) {
132        Serial.println("\nBLE ShieldService V2 found!\n");
133        BTstack.bleStopScanning();
134        BTstack.bleConnect(bleAdvertisement, 10000);  // 10 s
135    }
136}
137/* LISTING_END(LECentralAdvertisementCallback): Advertisement Callback */
138
139/*
140 * @section Device Connected Callback
141 *
142 * @text At the end of bleConnect(), the device connected callback is callec.
143 * The status argument tells if the connection timed out, or if the connection
144 * was established successfully.
145 *
146 * On a successful connection, a GATT Service Discovery is started.
147 */
148/* LISTING_START(LECentralDeviceConnectedCallback): Device Connected Callback */
149void deviceConnectedCallback(BLEStatus status, BLEDevice *device) {
150    switch (status){
151        case BLE_STATUS_OK:
152            Serial.println("Device connected!");
153            myBLEDevice = *device;
154            counter = 0;
155            myBLEDevice.discoverGATTServices();
156            break;
157        case BLE_STATUS_CONNECTION_TIMEOUT:
158            Serial.println("Error while Connecting the Peripheral");
159            BTstack.bleStartScanning();
160            break;
161        default:
162            break;
163    }
164}
165/* LISTING_END(LECentralDeviceConnectedCallback): Device Connected Callback */
166
167/*
168 * @section Device Disconnected Callback
169 *
170 * @text If the connection to a device breaks, the device disconnected callback
171 * is called. Here, we start scanning for new devices again.
172 */
173/* LISTING_START(LECentralDeviceDisconnectedCallback): Device Disconnected Callback */
174void deviceDisconnectedCallback(BLEDevice * device){
175    Serial.println("Disconnected, starting over..");
176    sendCounter = false;
177    BTstack.bleStartScanning();
178}
179/* LISTING_END(LECentralDeviceDisconnectedCallback): Device Disconnected Callback */
180
181/*
182 * @section Service Discovered Callback
183 *
184 * @text The service discovered callback is called for each service and after the
185 * service discovery is complete. The status argument is provided for this.
186 *
187 * The main information about a discovered Service is its UUID.
188 * If we find our service, we store the reference to this service.
189 * This allows to discover the Characteristics for our service after
190 * the service discovery is complete.
191 */
192/* LISTING_START(LECentralServiceDiscoveredCallback): Service Discovered Callback */
193void gattServiceDiscovered(BLEStatus status, BLEDevice *device, BLEService *bleService) {
194    switch(status){
195        case BLE_STATUS_OK:
196            Serial.print("Service Discovered: :");
197            Serial.println(bleService->getUUID()->getUuidString());
198            if (bleService->matches(&bleShieldServiceV2UUID)) {
199                serviceFound = true;
200                Serial.println("Our service located!");
201                myBLEService = *bleService;
202            }
203            break;
204        case BLE_STATUS_DONE:
205            Serial.println("Service discovery finished");
206            if (serviceFound) {
207                device->discoverCharacteristicsForService(&myBLEService);
208            }
209            break;
210        default:
211            Serial.println("Service discovery error");
212            break;
213    }
214}
215/* LISTING_END(LECentralServiceDiscoveredCallback): Service Discovered Callback */
216
217/*
218 * @section Characteristic Discovered Callback
219 *
220 * @text Similar to the Service Discovered callback, the Characteristic Discovered
221 * callback is called for each Characteristic found and after the discovery is complete.
222 *
223 * The main information is again its UUID. If we find a Characteristic that we're
224 * interested in, it's name is printed and a reference stored for later.
225 *
226 * On discovery complete, we subscribe to a particular Characteristic to receive
227 * Characteristic Value updates in the Notificaation Callback.
228 */
229/* LISTING_START(LECentralCharacteristicDiscoveredCallback): Characteristic Discovered Callback */
230void gattCharacteristicDiscovered(BLEStatus status, BLEDevice *device, BLECharacteristic *characteristic) {
231    switch(status){
232        case BLE_STATUS_OK:
233            Serial.print("Characteristic Discovered: ");
234            Serial.print(characteristic->getUUID()->getUuidString());
235            Serial.print(", handle 0x");
236            Serial.println(characteristic->getCharacteristic()->value_handle, HEX);
237            int i;
238            for (i=0;i<numCharacteristics;i++){
239                if (characteristic->matches(&characteristics[i].uuid)){
240                    Serial.print("Characteristic found: ");
241                    Serial.println(characteristics[i].name);
242                    characteristics[i].found = 1;
243                    characteristics[i].characteristic = *characteristic;
244                    break;
245                }
246            }
247            break;
248        case BLE_STATUS_DONE:
249            Serial.print("Characteristic discovery finished, status ");
250            Serial.println(status, HEX);
251            if (characteristics[charRX].found) {
252                device->subscribeForNotifications(&characteristics[charRX].characteristic);
253            }
254            break;
255        default:
256            Serial.println("Characteristics discovery error");
257            break;
258    }
259}
260/* LISTING_END(LECentralCharacteristicDiscoveredCallback): Characteristic Discovered Callback */
261
262/*
263 * @section Subscribed Callback
264 *
265 * @text After the subcribe operation is complete, we get notified if it was
266 * successful. In this example, we read the Characteristic that contains the
267 * BD ADDR of the other device. This isn't strictly neccessary as we already
268 * know the device address from the Advertisement, but it's a common pattern
269 * with iOS as the device address is hidden from applications.
270 */
271/* LISTING_START(LECentralSubscribedCallback): Subscribed Callback */
272void gattSubscribedCallback(BLEStatus status, BLEDevice * device){
273    device->readCharacteristic(&characteristics[charBdAddr].characteristic);
274}
275/* LISTING_END(LECentralSubscribedCallback): Subscribed Callback */
276
277/*
278 * @section Read Callback
279 *
280 * @text The Read callback is called with the result from a read operation.
281 * Here, we write to the TX Characteristic next.
282 */
283/* LISTING_START(LECentralReadCallback): Read Callback */
284void gattReadCallback(BLEStatus status, BLEDevice *device, uint8_t *value, uint16_t length) {
285    Serial.print("Read callback: ");
286    Serial.println((const char *)value);
287    device->writeCharacteristic(&characteristics[charTX].characteristic, (uint8_t*) "Hello!", 6);
288}
289/* LISTING_END(LECentralReadCallback): Read Callback */
290
291/*
292 * @section Written Callback
293 *
294 * @text After the write operation is complete, the Written Callback is callbed with
295 * the result in the status argument. As we're done with the initial setup of the remote
296 * device, we set the flag to write the test string as fast as possible.
297 */
298/* LISTING_START(LECentralWrittenCallback): Written Callback */
299void gattWrittenCallback(BLEStatus status, BLEDevice *device){
300    sendCounter = true;
301}
302/* LISTING_END(LECentralWrittenCallback): Written Callback */
303
304/*
305 * @section Notification Callback
306 *
307 * @text Notifictions for Characteristic Value Updates are delivered via the
308 * Notification Callback. When more than one Characteristic is subscribed,
309 * the value handle can be used to distinguish between them. The
310 * BLECharacteristic.isValueHandle(int handle) allows to test if a value handle
311 * belongs to a particular Characteristic.
312 */
313/* LISTING_START(LECentralNotificationCallback): Notification Callback */
314void gattCharacteristicNotification(BLEDevice *device, uint16_t value_handle, uint8_t *value, uint16_t length) {
315    Serial.print("Notification: ");
316    Serial.println((const char *)value);
317}
318/* LISTING_END(LECentralNotificationCallback): Notification Callback */
319
320