mbed-os-examples / Mbed OS mbed-os-example-ble-PeriodicAdvertising
Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers main.cpp Source File

main.cpp

00001 /* mbed Microcontroller Library
00002  * Copyright (c) 2006-2018 ARM Limited
00003  *
00004  * Licensed under the Apache License, Version 2.0 (the "License");
00005  * you may not use this file except in compliance with the License.
00006  * You may obtain a copy of the License at
00007  *
00008  *     http://www.apache.org/licenses/LICENSE-2.0
00009  *
00010  * Unless required by applicable law or agreed to in writing, software
00011  * distributed under the License is distributed on an "AS IS" BASIS,
00012  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
00013  * See the License for the specific language governing permissions and
00014  * limitations under the License.
00015  */
00016 
00017 #include <events/mbed_events.h>
00018 #include <mbed.h>
00019 #include "ble/BLE.h"
00020 #include "gap/Gap.h"
00021 #include "gap/AdvertisingDataParser.h"
00022 #include "pretty_printer.h"
00023 #include "BatteryService.h"
00024 
00025 /** This example demonstrates extended and periodic advertising
00026  */
00027 
00028 events::EventQueue event_queue;
00029 
00030 static const char DEVICE_NAME[] = "Periodic";
00031 
00032 static const uint16_t MAX_ADVERTISING_PAYLOAD_SIZE = 50;
00033 static const uint16_t SCAN_TIME = 5000;
00034 static const uint8_t CONNECTION_DURATION = 2;
00035 
00036 /** Demonstrate periodic advertising and scanning and syncing with the advertising
00037  */
00038 class PeriodicDemo : private mbed::NonCopyable<PeriodicDemo>, public ble::Gap::EventHandler
00039 {
00040 public:
00041     PeriodicDemo(BLE& ble, events::EventQueue& event_queue) :
00042         _ble(ble),
00043         _gap(ble.gap()),
00044         _event_queue(event_queue),
00045         _led1(LED1, 0),
00046         _is_scanner(false),
00047         _is_connecting_or_syncing(false),
00048         _role_established(false),
00049         _battery_uuid(GattService::UUID_BATTERY_SERVICE),
00050         _battery_level(100),
00051         _battery_service(ble, _battery_level),
00052         _adv_data_builder(_adv_buffer),
00053         _adv_handle(ble::INVALID_ADVERTISING_HANDLE),
00054         _sync_handle(ble::INVALID_ADVERTISING_HANDLE)
00055     {
00056     }
00057 
00058     ~PeriodicDemo()
00059     {
00060         if (_ble.hasInitialized()) {
00061             _ble.shutdown();
00062         }
00063     }
00064 
00065     /** Start BLE interface initialisation */
00066     void run()
00067     {
00068         if (_ble.hasInitialized()) {
00069             printf("Ble instance already initialised.\r\n");
00070             return;
00071         }
00072 
00073         /* handle gap events */
00074         _gap.setEventHandler(this);
00075 
00076         ble_error_t error = _ble.init(this, &PeriodicDemo::on_init_complete);
00077         if (error) {
00078             print_error(error, "Error returned by BLE::init\r\n");
00079             return;
00080         }
00081 
00082         /* to show we're running we'll blink every 500ms */
00083         _event_queue.call_every(500, this, &PeriodicDemo::blink);
00084 
00085         /* this will not return until shutdown */
00086         _event_queue.dispatch_forever();
00087     }
00088 
00089 private:
00090     /** This is called when BLE interface is initialised and starts the first mode */
00091     void on_init_complete(BLE::InitializationCompleteCallbackContext *event)
00092     {
00093         if (event->error) {
00094             print_error(event->error, "Error during the initialisation\r\n");
00095             return;
00096         }
00097 
00098         if (!_gap.isFeatureSupported(ble::controller_supported_features_t::LE_EXTENDED_ADVERTISING) ||
00099             !_gap.isFeatureSupported(ble::controller_supported_features_t::LE_PERIODIC_ADVERTISING)) {
00100             printf("Periodic advertising not supported, cannot run example.\r\n");
00101             return;
00102         }
00103 
00104         print_mac_address();
00105 
00106         /* all calls are serialised on the user thread through the event queue */
00107         _event_queue.call(this, &PeriodicDemo::start_role);
00108     }
00109 
00110     void start_role()
00111     {
00112         /* This example is designed to be run on two boards at the same time,
00113          * depending on our role we will either be the advertiser or scanner,
00114          * until the roles are established we will cycle the roles until we find each other */
00115         if (_role_established) {
00116             if (_is_scanner) {
00117                 _event_queue.call(this, &PeriodicDemo::scan_periodic);
00118             } else {
00119                 _event_queue.call(this, &PeriodicDemo::advertise_periodic);
00120             }
00121         } else {
00122             _is_scanner = !_is_scanner;
00123 
00124             if (_is_scanner) {
00125                 _event_queue.call(this, &PeriodicDemo::scan);
00126             } else {
00127                 _event_queue.call(this, &PeriodicDemo::advertise);
00128             }
00129         }
00130     }
00131 
00132     /** Set up and start advertising */
00133     void advertise()
00134     {
00135         ble_error_t error;
00136 
00137         ble::AdvertisingParameters adv_params;
00138         adv_params.setUseLegacyPDU(false);
00139         adv_params.setOwnAddressType(ble::own_address_type_t::RANDOM);
00140 
00141         /* create the advertising set with its parameter if we haven't yet */
00142         if (_adv_handle == ble::INVALID_ADVERTISING_HANDLE) {
00143             error = _gap.createAdvertisingSet(
00144                 &_adv_handle,
00145                 adv_params
00146             );
00147 
00148             if (error) {
00149                 print_error(error, "Gap::createAdvertisingSet() failed\r\n");
00150                 return;
00151             }
00152         } else {
00153             _gap.setAdvertisingParameters(_adv_handle, adv_params);
00154         }
00155 
00156         _adv_data_builder.clear();
00157         _adv_data_builder.setFlags();
00158         _adv_data_builder.setName(DEVICE_NAME);
00159 
00160         /* Set payload for the set */
00161         error = _gap.setAdvertisingPayload(
00162             _adv_handle,
00163             _adv_data_builder.getAdvertisingData()
00164         );
00165 
00166         if (error) {
00167             print_error(error, "Gap::setAdvertisingPayload() failed\r\n");
00168             return;
00169         }
00170 
00171         /* since we have two boards which might start running this example at the same time
00172          * we randomise the interval of advertising to have them meet when one is advertising
00173          * and the other one is scanning (we use their random address as source of randomness) */
00174         ble::millisecond_t random_duration_ms((3 + rand() % 5) * 1000);
00175         ble::adv_duration_t random_duration(random_duration_ms);
00176 
00177         error = _ble.gap().startAdvertising(
00178             _adv_handle,
00179             random_duration
00180         );
00181 
00182         if (error) {
00183             print_error(error, "Gap::startAdvertising() failed\r\n");
00184             return;
00185         }
00186 
00187         printf("Advertising started for %dms\r\n", random_duration_ms);
00188     }
00189 
00190     void advertise_periodic()
00191     {
00192         ble::AdvertisingParameters adv_params;
00193         adv_params.setType(ble::advertising_type_t::NON_CONNECTABLE_UNDIRECTED);
00194         adv_params.setUseLegacyPDU(false);
00195         adv_params.setOwnAddressType(ble::own_address_type_t::RANDOM);
00196 
00197         ble_error_t error = _gap.setAdvertisingParameters(_adv_handle, adv_params);
00198 
00199         if (error) {
00200             print_error(error, "Gap::setAdvertisingParameters() failed\r\n");
00201             return;
00202         }
00203 
00204         /* Start advertising the set as the advertising needs to be active
00205          * before we start periodic advertising */
00206         error = _gap.startAdvertising(_adv_handle);
00207 
00208         if (error) {
00209             print_error(error, "Gap::startAdvertising() failed\r\n");
00210             return;
00211         }
00212 
00213         error = _gap.setPeriodicAdvertisingParameters(
00214             _adv_handle,
00215             ble::periodic_interval_t(50),
00216             ble::periodic_interval_t(500)
00217         );
00218 
00219         if (error) {
00220             print_error(error, "Gap::setPeriodicAdvertisingParameters() failed\r\n");
00221             return;
00222         }
00223 
00224         /* we will put the battery level data in there and update it every second */
00225         update_payload();
00226 
00227         error = _gap.startPeriodicAdvertising(_adv_handle);
00228 
00229         if (error) {
00230             print_error(error, "Gap::startPeriodicAdvertising() failed\r\n");
00231             return;
00232         }
00233 
00234         printf("Periodic advertising started\r\n");
00235 
00236         /* tick over our fake battery data, this will also update the advertising payload */
00237         _event_queue.call_every(1000, this, &PeriodicDemo::update_sensor_value);
00238     }
00239 
00240     void update_payload()
00241     {
00242         /* advertising payload will have the battery level which we will update */
00243         ble_error_t error = _adv_data_builder.setServiceData(
00244             _battery_uuid,
00245             mbed::make_Span(&_battery_level, 1)
00246         );
00247 
00248         if (error) {
00249             print_error(error, "AdvertisingDataBuilder::setServiceData() failed\r\n");
00250             return;
00251         }
00252 
00253         /* the data in the local host buffer has been updated but now
00254          * we have to update the data in the controller */
00255         error = _gap.setPeriodicAdvertisingPayload(
00256             _adv_handle,
00257             _adv_data_builder.getAdvertisingData()
00258         );
00259 
00260         if (error) {
00261             print_error(error, "Gap::setPeriodicAdvertisingPayload() failed\r\n");
00262             return;
00263         }
00264     }
00265 
00266     /** Set up and start scanning */
00267     void scan()
00268     {
00269         _is_connecting_or_syncing = false;
00270 
00271         ble::ScanParameters scan_params;
00272         scan_params.setOwnAddressType(ble::own_address_type_t::RANDOM);
00273 
00274         ble_error_t error = _gap.setScanParameters(scan_params);
00275 
00276         if (error) {
00277             print_error(error, "Error caused by Gap::setScanParameters\r\n");
00278             return;
00279         }
00280 
00281         error = _gap.startScan(ble::scan_duration_t(500));
00282 
00283         if (error) {
00284             print_error(error, "Error caused by Gap::startScan\r\n");
00285             return;
00286         }
00287 
00288         printf("Scanning started\r\n");
00289     }
00290 
00291     void scan_periodic()
00292     {
00293         _is_connecting_or_syncing = false;
00294 
00295         ble_error_t error = _gap.startScan();
00296 
00297         if (error) {
00298             print_error(error, "Error caused by Gap::startScan\r\n");
00299             return;
00300         }
00301 
00302         printf("Scanning for periodic advertising started\r\n");
00303     }
00304 
00305 private:
00306     /* Gap::EventHandler */
00307 
00308     /** Look at scan payload to find a peer device and connect to it */
00309     virtual void onAdvertisingReport(
00310         const ble::AdvertisingReportEvent &event
00311     ) {
00312         /* don't bother with analysing scan result if we're already connecting */
00313         if (_is_connecting_or_syncing) {
00314             return;
00315         }
00316 
00317         /* if we're looking for periodic advertising don't bother unless it's present */
00318         if (_role_established && !event.isPeriodicIntervalPresent()) {
00319             return;
00320         }
00321 
00322         ble::AdvertisingDataParser adv_parser(event.getPayload());
00323 
00324         /* parse the advertising payload, looking for a discoverable device */
00325         while (adv_parser.hasNext()) {
00326             ble::AdvertisingDataParser::element_t field = adv_parser.next();
00327 
00328             /* identify peer by name */
00329             if (field.type == ble::adv_data_type_t::COMPLETE_LOCAL_NAME &&
00330                 field.value.size() == strlen(DEVICE_NAME) &&
00331                 (memcmp(field.value.data(), DEVICE_NAME, field.value.size()) == 0)) {
00332                 /* if we haven't established our roles connect, otherwise sync with advertising */
00333                 if (_role_established) {
00334                     printf("We found the peer, syncing with SID %d"
00335                            "and periodic interval %dms\r\n",
00336                            event.getSID(), event.getPeriodicInterval().valueInMs());
00337 
00338                     ble_error_t error = _gap.createSync(
00339                         event.getPeerAddressType(),
00340                         event.getPeerAddress(),
00341                         event.getSID(),
00342                         2,
00343                         ble::sync_timeout_t(ble::millisecond_t(500))
00344                     );
00345 
00346                     if (error) {
00347                         print_error(error, "Error caused by Gap::createSync\r\n");
00348                         return;
00349                     }
00350                 } else {
00351                     printf("We found the peer, connecting\r\n");
00352 
00353                     ble_error_t error = _gap.connect(
00354                         event.getPeerAddressType(),
00355                         event.getPeerAddress(),
00356                         ble::ConnectionParameters() // use the default connection parameters
00357                     );
00358 
00359                     if (error) {
00360                         print_error(error, "Error caused by Gap::connect\r\n");
00361                         return;
00362                     }
00363                 }
00364 
00365                 /* we may have already scan events waiting to be processed
00366                  * so we need to remember that we are already connecting
00367                  * or syncing and ignore them */
00368                 _is_connecting_or_syncing = true;
00369 
00370                 return;
00371             }
00372         }
00373     }
00374 
00375     virtual void onAdvertisingEnd(
00376         const ble::AdvertisingEndEvent &event
00377     ) {
00378         if (event.isConnected()) {
00379             printf("Stopped advertising due to connection\r\n");
00380         } else {
00381             printf("Advertising ended\r\n");
00382             _event_queue.call(this, &PeriodicDemo::start_role);
00383         }
00384     }
00385 
00386     virtual void onScanTimeout(
00387         const ble::ScanTimeoutEvent&
00388     ) {
00389         if (!_is_connecting_or_syncing) {
00390             printf("Scanning ended, failed to find peer\r\n");
00391             _event_queue.call(this, &PeriodicDemo::start_role);
00392         }
00393     }
00394 
00395     /** This is called by Gap to notify the application we connected */
00396     virtual void onConnectionComplete(
00397         const ble::ConnectionCompleteEvent &event
00398     ) {
00399         if (event.getStatus() == BLE_ERROR_NONE) {
00400             printf("Connected to: ");
00401             print_address(event.getPeerAddress().data());
00402             printf("Roles established\r\n");
00403             _role_established = true;
00404 
00405             /* we have to specify the disconnect call because of ambiguous overloads */
00406             typedef ble_error_t (Gap::*disconnect_call_t)(ble::connection_handle_t, ble::local_disconnection_reason_t);
00407             const disconnect_call_t disconnect_call = &Gap::disconnect;
00408 
00409             if (_is_scanner) {
00410                 _event_queue.call_in(
00411                     2000,
00412                     &_ble.gap(),
00413                     disconnect_call,
00414                     event.getConnectionHandle(),
00415                     ble::local_disconnection_reason_t(ble::local_disconnection_reason_t::USER_TERMINATION)
00416                 );
00417             }
00418         } else {
00419             printf("Failed to connect\r\n");
00420             _event_queue.call(this, &PeriodicDemo::start_role);
00421         }
00422     }
00423 
00424     /** This is called by Gap to notify the application we disconnected */
00425     virtual void onDisconnectionComplete(
00426         const ble::DisconnectionCompleteEvent &event
00427     ) {
00428         printf("Disconnected\r\n");
00429         _event_queue.call(this, &PeriodicDemo::start_role);
00430     }
00431 
00432     /** Called when first advertising packet in periodic advertising is received. */
00433     virtual void onPeriodicAdvertisingSyncEstablished(
00434         const ble::PeriodicAdvertisingSyncEstablishedEvent &event
00435     ) {
00436         if (event.getStatus() == BLE_ERROR_NONE) {
00437             printf("Synced with periodic advertising\r\n");
00438             _sync_handle = event.getSyncHandle();
00439         } else {
00440             printf("Sync with periodic advertising failed\r\n");
00441         }
00442     }
00443 
00444     /** Called when a periodic advertising packet is received. */
00445     virtual void onPeriodicAdvertisingReport(
00446        const ble::PeriodicAdvertisingReportEvent &event
00447     ) {
00448         ble::AdvertisingDataParser adv_parser(event.getPayload());
00449 
00450         /* parse the advertising payload, looking for a battery level */
00451         while (adv_parser.hasNext()) {
00452             ble::AdvertisingDataParser::element_t field = adv_parser.next();
00453 
00454             if (field.type == ble::adv_data_type_t::SERVICE_DATA) {
00455                 if (memcmp(field.value.data(), _battery_uuid.getBaseUUID(), _battery_uuid.getLen()) != 0) {
00456                     printf("Unexpected service data\r\n");
00457                 } else {
00458                     /* battery level is right after the UUID */
00459                     const uint8_t *battery_level = field.value.data() + _battery_uuid.getLen();
00460                     printf("Peer battery level: %d\r\n", *battery_level);
00461                 }
00462             }
00463         }
00464     }
00465 
00466     /** Called when a periodic advertising sync has been lost. */
00467     virtual void onPeriodicAdvertisingSyncLoss(
00468        const ble::PeriodicAdvertisingSyncLoss &event
00469     ) {
00470         printf("Sync to periodic advertising lost\r\n");
00471         _sync_handle = ble::INVALID_ADVERTISING_HANDLE;
00472         _event_queue.call(this, &PeriodicDemo::scan_periodic);
00473     }
00474 
00475 private:
00476     void update_sensor_value() {
00477         _battery_level--;
00478         if (_battery_level < 1) {
00479             _battery_level = 100;
00480         }
00481 
00482         _battery_service.updateBatteryLevel(_battery_level);
00483         update_payload();
00484     }
00485 
00486     /** Blink LED to show we're running */
00487     void blink(void)
00488     {
00489         _led1 = !_led1;
00490     }
00491 
00492 private:
00493     BLE                &_ble;
00494     ble::Gap           &_gap;
00495     events::EventQueue &_event_queue;
00496 
00497     DigitalOut _led1;
00498 
00499     bool _is_scanner;
00500     bool _is_connecting_or_syncing;
00501     bool _role_established;
00502 
00503     UUID            _battery_uuid;
00504     uint8_t         _battery_level;
00505     BatteryService  _battery_service;
00506 
00507     uint8_t _adv_buffer[MAX_ADVERTISING_PAYLOAD_SIZE];
00508     ble::AdvertisingDataBuilder _adv_data_builder;
00509 
00510     ble::advertising_handle_t _adv_handle;
00511     ble::periodic_sync_handle_t _sync_handle;
00512 };
00513 
00514 /** Schedule processing of events from the BLE middleware in the event queue. */
00515 void schedule_ble_events(BLE::OnEventsToProcessCallbackContext *context) {
00516     event_queue.call(Callback<void()>(&context->ble, &BLE::processEvents));
00517 }
00518 
00519 int main()
00520 {
00521     BLE &ble = BLE::Instance();
00522 
00523     /* this will inform us off all events so we can schedule their handling
00524      * using our event queue */
00525     ble.onEventsToProcess(schedule_ble_events);
00526 
00527     /* look for other device and then settle on a role and sync periodic advertising */
00528     PeriodicDemo demo(ble, event_queue);
00529 
00530     demo.run();
00531 
00532     return 0;
00533 }