Kai Ren / Mbed OS BluetoothLEGap
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-2013 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 
00021 /** This example demonstrates all the basic setup required
00022  *  to advertise, scan and connect to other devices.
00023  *
00024  *  It contains a single class that performs both scans and advertisements.
00025  *
00026  *  The demonstrations happens in sequence, after each "mode" ends
00027  *  the demo jumps to the next mode to continue. There are several modes
00028  *  that show scanning and several showing advertising. These are configured
00029  *  according to the two arrays containing parameters. During scanning
00030  *  a connection will be made to a connectable device upon its discovery.
00031  */
00032 
00033 static const uint8_t DEVICE_NAME[]        = "GAP_device";
00034 
00035 /* Duration of each mode in milliseconds */
00036 static const size_t MODE_DURATION_MS      = 20000;
00037 
00038 /* Time between each mode in milliseconds */
00039 static const size_t TIME_BETWEEN_MODES_MS = 2000;
00040 
00041 /* how long to wait before disconnecting in milliseconds */
00042 static const size_t CONNECTION_DURATION = 10000;
00043 
00044 typedef struct {
00045     GapAdvertisingParams::AdvertisingType_t adv_type;
00046     uint16_t interval;
00047     uint16_t timeout;
00048 } AdvModeParam_t;
00049 
00050 typedef struct {
00051     uint16_t interval;
00052     uint16_t window;
00053     uint16_t timeout;
00054     bool active;
00055 } ScanModeParam_t;
00056 
00057 /** the entries in this array are used to configure our advertising
00058  *  parameters for each of the modes we use in our demo */
00059 static const AdvModeParam_t advertising_params[] = {
00060     /*            advertising type                        interval  timeout */
00061     { GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED,      40,/*ms*/ 6/*s*/},
00062     { GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED,      40,       6     },
00063     { GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED,      40,       6     }
00064 };
00065 
00066 /* when we cycle through all our advertising modes we will move to scanning modes */
00067 
00068 /** the entries in this array are used to configure our scanning
00069  *  parameters for each of the modes we use in our demo */
00070 static const ScanModeParam_t scanning_params[] = {
00071 /* interval      window    timeout       active */
00072     {   4,/*ms*/   4,/*ms*/   0,/*s*/    false },
00073     { 160,       100,         3,         false },
00074     { 160,        40,         0,         true  },
00075     { 500,        10,         0,         false }
00076 };
00077 
00078 /* parameters to use when attempting to connect to maximise speed of connection */
00079 static const GapScanningParams connection_scan_params(
00080     GapScanningParams::SCAN_INTERVAL_MAX,
00081     GapScanningParams::SCAN_WINDOW_MAX,
00082     3,
00083     false
00084 );
00085 
00086 /* get number of items in our arrays */
00087 static const size_t SCAN_PARAM_SET_MAX =
00088     sizeof(scanning_params) / sizeof(GapScanningParams);
00089 static const size_t ADV_PARAM_SET_MAX  =
00090     sizeof(advertising_params) / sizeof(GapAdvertisingParams);
00091 
00092 static const char* to_string(Gap::Phy_t phy) {
00093     switch(phy.value()) {
00094         case Gap::Phy_t::LE_1M:
00095             return "LE 1M";
00096         case Gap::Phy_t::LE_2M:
00097             return "LE 2M";
00098         case Gap::Phy_t::LE_CODED:
00099             return "LE coded";
00100         default:
00101             return "invalid PHY";
00102     }
00103 }
00104 
00105 /** Demonstrate advertising, scanning and connecting
00106  */
00107 class GAPDevice : private mbed::NonCopyable<GAPDevice>, public Gap::EventHandler
00108 {
00109 public:
00110     GAPDevice() :
00111         _ble(BLE::Instance()),
00112         _led1(LED1, 0),
00113         _set_index(0),
00114         _is_in_scanning_mode(false),
00115         _is_connecting(false),
00116         _on_duration_end_id(0),
00117         _scan_count(0) { };
00118 
00119     ~GAPDevice()
00120     {
00121         if (_ble.hasInitialized()) {
00122             _ble.shutdown();
00123         }
00124     };
00125 
00126     /** Start BLE interface initialisation */
00127     void run()
00128     {
00129         ble_error_t error;
00130 
00131         if (_ble.hasInitialized()) {
00132             printf("Ble instance already initialised.\r\n");
00133             return;
00134         }
00135 
00136         /* this will inform us off all events so we can schedule their handling
00137          * using our event queue */
00138         _ble.onEventsToProcess(
00139             makeFunctionPointer(this, &GAPDevice::schedule_ble_events)
00140         );
00141 
00142         /* handle timeouts, for example when connection attempts fail */
00143         _ble.gap().onTimeout(
00144             makeFunctionPointer(this, &GAPDevice::on_timeout)
00145         );
00146 
00147         /* handle gap events */
00148         _ble.gap().setEventHandler(this);
00149 
00150         error = _ble.init(this, &GAPDevice::on_init_complete);
00151 
00152         if (error) {
00153             printf("Error returned by BLE::init.\r\n");
00154             return;
00155         }
00156 
00157         /* to show we're running we'll blink every 500ms */
00158         _event_queue.call_every(500, this, &GAPDevice::blink);
00159 
00160         /* this will not return until shutdown */
00161         _event_queue.dispatch_forever();
00162     };
00163 
00164 private:
00165     /** This is called when BLE interface is initialised and starts the first mode */
00166     void on_init_complete(BLE::InitializationCompleteCallbackContext *event)
00167     {
00168         if (event->error) {
00169             printf("Error during the initialisation\r\n");
00170             return;
00171         }
00172 
00173         /* print device address */
00174         Gap::AddressType_t addr_type;
00175         Gap::Address_t addr;
00176         _ble.gap().getAddress(&addr_type, addr);
00177         printf("Device address: %02x:%02x:%02x:%02x:%02x:%02x\r\n",
00178                addr[5], addr[4], addr[3], addr[2], addr[1], addr[0]);
00179 
00180         /* setup the default phy used in connection to 2M to reduce power consumption */
00181         Gap::PhySet_t tx_phys(/* 1M */ false, /* 2M */ true, /* coded */ false);
00182         Gap::PhySet_t rx_phys(/* 1M */ false, /* 2M */ true, /* coded */ false);
00183         ble_error_t err = _ble.gap().setPreferredPhys(&tx_phys, &rx_phys);
00184         if (err) {
00185             printf("INFO: GAP::setPreferedPhys failed with error code %s", BLE::errorToString(err));
00186         }
00187 
00188         /* all calls are serialised on the user thread through the event queue */
00189         _event_queue.call(this, &GAPDevice::demo_mode_start);
00190     };
00191 
00192     /** queue up start of the current demo mode */
00193     void demo_mode_start()
00194     {
00195         if (_is_in_scanning_mode) {
00196             /* when scanning we want to connect to a peer device so we need to
00197              * attach callbacks that are used by Gap to notify us of events */
00198             _ble.gap().onConnection(this, &GAPDevice::on_connect);
00199             _ble.gap().onDisconnection(this, &GAPDevice::on_disconnect);
00200 
00201             _event_queue.call(this, &GAPDevice::scan);
00202         } else {
00203             _event_queue.call(this, &GAPDevice::advertise);
00204         }
00205 
00206         /* for performance measurement keep track of duration of the demo mode */
00207         //_demo_duration.start();
00208         /* keep track of our state */
00209         _is_connecting = false;
00210 
00211         /* queue up next demo mode */
00212         _on_duration_end_id = _event_queue.call_in(
00213             MODE_DURATION_MS, this, &GAPDevice::on_duration_end
00214         );
00215 
00216         printf("\r\n");
00217     }
00218 
00219     /** Set up and start advertising */
00220     void advertise()
00221     {
00222         ble_error_t error;
00223         GapAdvertisingData advertising_data;
00224 
00225         /* add advertising flags */
00226         advertising_data.addFlags(GapAdvertisingData::LE_GENERAL_DISCOVERABLE
00227                                   | GapAdvertisingData::BREDR_NOT_SUPPORTED);
00228 
00229         /* add device name */
00230         advertising_data.addData(
00231             GapAdvertisingData::COMPLETE_LOCAL_NAME,
00232             DEVICE_NAME,
00233             sizeof(DEVICE_NAME)
00234         );
00235 
00236         error = _ble.gap().setAdvertisingPayload(advertising_data);
00237 
00238         if (error) {
00239             printf("Error during Gap::setAdvertisingPayload\r\n");
00240             return;
00241         }
00242 
00243         /* set the advertising parameters according to currently selected set,
00244          * see @AdvertisingType_t for explanation of modes */
00245         GapAdvertisingParams::AdvertisingType_t adv_type =
00246             advertising_params[_set_index].adv_type;
00247 
00248         /* how many milliseconds between advertisements, lower interval
00249          * increases the chances of being seen at the cost of more power */
00250         uint16_t interval = advertising_params[_set_index].interval;
00251 
00252         /* advertising will continue for this many seconds or until connected */
00253         uint16_t timeout = advertising_params[_set_index].timeout;
00254 
00255         _ble.gap().setAdvertisingType(adv_type);
00256         _ble.gap().setAdvertisingInterval(interval);
00257         _ble.gap().setAdvertisingTimeout(timeout);
00258 
00259         error = _ble.gap().startAdvertising();
00260 
00261         if (error) {
00262             printf("Error during Gap::startAdvertising.\r\n");
00263             return;
00264         }
00265 
00266         printf("Advertising started (type: 0x%x, interval: %dms, timeout: %ds)\r\n",
00267                adv_type, interval, timeout);
00268     };
00269 
00270     /** Set up and start scanning */
00271     void scan()
00272     {
00273         ble_error_t error;
00274 
00275         /* scanning happens repeatedly, interval is the number of milliseconds
00276          * between each cycle of scanning */
00277         uint16_t interval = scanning_params[_set_index].interval;
00278 
00279         /* number of milliseconds we scan for each time we enter
00280          * the scanning cycle after the interval set above */
00281         uint16_t window = scanning_params[_set_index].window;
00282 
00283         /* how long to repeat the cycles of scanning in seconds */
00284         uint16_t timeout = scanning_params[_set_index].timeout;
00285 
00286         /* active scanning will send a scan request to any scanable devices that
00287          * we see advertising */
00288         bool active = scanning_params[_set_index].active;
00289 
00290         /* set the scanning parameters according to currently selected set */
00291         error = _ble.gap().setScanParams(interval, window, timeout, active);
00292 
00293         if (error) {
00294             printf("Error during Gap::setScanParams\r\n");
00295             return;
00296         }
00297 
00298         /* start scanning and attach a callback that will handle advertisements
00299          * and scan requests responses */
00300         error = _ble.gap().startScan(this, &GAPDevice::on_scan);
00301 
00302         if (error) {
00303             printf("Error during Gap::startScan\r\n");
00304             return;
00305         }
00306 
00307         printf("Scanning started (interval: %dms, window: %dms, timeout: %ds).\r\n",
00308                interval, window, timeout);
00309     };
00310 
00311     /** After a set duration this cycles to the next demo mode
00312      *  unless a connection happened first */
00313     void on_duration_end()
00314     {
00315         print_performance();
00316 
00317         /* alloted time has elapsed, move to next demo mode */
00318         _event_queue.call(this, &GAPDevice::demo_mode_end);
00319     };
00320 
00321     /** Look at scan payload to find a peer device and connect to it */
00322     void on_scan(const Gap::AdvertisementCallbackParams_t *params)
00323     {
00324         /* keep track of scan events for performance reporting */
00325         _scan_count++;
00326 
00327         /* don't bother with analysing scan result if we're already connecting */
00328         if (_is_connecting) {
00329             return;
00330         }
00331 
00332         /* parse the advertising payload, looking for a discoverable device */
00333         for (uint8_t i = 0; i < params->advertisingDataLen; ++i) {
00334             /* The advertising payload is a collection of key/value records where
00335              * byte 0: length of the record excluding this byte
00336              * byte 1: The key, it is the type of the data
00337              * byte [2..N] The value. N is equal to byte0 - 1 */
00338             const uint8_t record_length = params->advertisingData[i];
00339             if (record_length == 0) {
00340                 continue;
00341             }
00342             const uint8_t type = params->advertisingData[i + 1];
00343             const uint8_t *value = params->advertisingData + i + 2;
00344 
00345             /* connect to a discoverable device */
00346             if ((type == GapAdvertisingData::FLAGS)
00347                 && (*value & GapAdvertisingData::LE_GENERAL_DISCOVERABLE)) {
00348 
00349                 /* abort timeout as the mode will end on disconnection */
00350                 _event_queue.cancel(_on_duration_end_id);
00351 
00352                 printf("We found a connectable device\r\n");
00353 
00354                 ble_error_t error = _ble.gap().connect(
00355                     params->peerAddr, Gap::ADDR_TYPE_RANDOM_STATIC,
00356                     NULL, &connection_scan_params
00357                 );
00358 
00359                 if (error) {
00360                     printf("Error during Gap::connect\r\n");
00361                     /* since no connection will be attempted end the mode */
00362                     _event_queue.call(this, &GAPDevice::demo_mode_end);
00363                     return;
00364                 }
00365 
00366                 /* we may have already scan events waiting
00367                  * to be processed so we need to remember
00368                  * that we are already connecting and ignore them */
00369                 _is_connecting = true;
00370 
00371                 return;
00372             }
00373 
00374             i += record_length;
00375         }
00376     };
00377 
00378     /** This is called by Gap to notify the application we connected,
00379      *  in our case it immediately disconnects */
00380     void on_connect(const Gap::ConnectionCallbackParams_t *connection_event)
00381     {
00382         print_performance();
00383 
00384         printf("Connected in %dms\r\n", _demo_duration.read_ms());
00385 
00386         /* cancel the connect timeout since we connected */
00387         _event_queue.cancel(_on_duration_end_id);
00388 
00389         _event_queue.call_in(
00390             CONNECTION_DURATION, &_ble.gap(), &Gap::disconnect, Gap::REMOTE_USER_TERMINATED_CONNECTION
00391         );
00392     };
00393 
00394     /** This is called by Gap to notify the application we disconnected,
00395      *  in our case it calls demo_mode_end() to progress the demo */
00396     void on_disconnect(const Gap::DisconnectionCallbackParams_t *event)
00397     {
00398         printf("Disconnected\r\n");
00399 
00400         /* we have successfully disconnected ending the demo, move to next mode */
00401         //_event_queue.call(this, &GAPDevice::demo_mode_end);
00402     };
00403 
00404     /**
00405      * Implementation of Gap::EventHandler::onReadPhy
00406      */
00407     virtual void onReadPhy(
00408         ble_error_t error,
00409         Gap::Handle_t connectionHandle,
00410         Gap::Phy_t txPhy,
00411         Gap::Phy_t rxPhy
00412     ) {
00413         if (error) {
00414             printf(
00415                 "Phy read on connection %d failed with error code %s\r\n",
00416                 connectionHandle,
00417                 BLE::errorToString(error)
00418             );
00419         } else {
00420             printf(
00421                 "Phy read on connection %d - Tx Phy: %s, Rx Phy: %s\r\n",
00422                 connectionHandle,
00423                 to_string(txPhy),
00424                 to_string(rxPhy)
00425             );
00426         }
00427     }
00428 
00429     /**
00430      * Implementation of Gap::EventHandler::onPhyUpdateComplete
00431      */
00432     virtual void onPhyUpdateComplete(
00433         ble_error_t error,
00434         Gap::Handle_t connectionHandle,
00435         Gap::Phy_t txPhy,
00436         Gap::Phy_t rxPhy
00437     ) {
00438         if (error) {
00439             printf(
00440                 "Phy update on connection: %d failed with error code %s\r\n",
00441                 connectionHandle,
00442                 BLE::errorToString(error)
00443             );
00444         } else {
00445             printf(
00446                 "Phy update on connection %d - Tx Phy: %d, Rx Phy: %d\r\n",
00447                 connectionHandle,
00448                 txPhy.value(), /*to_string(txPhy),*/
00449                 rxPhy.value() /*to_string(rxPhy)*/
00450             );
00451         }
00452     }
00453 
00454     /** called if timeout is reached during advertising, scanning
00455      *  or connection initiation */
00456     void on_timeout(const Gap::TimeoutSource_t source)
00457     {
00458         _demo_duration.stop();
00459 
00460         switch (source) {
00461             case Gap::TIMEOUT_SRC_ADVERTISING:
00462                 printf("Stopped advertising early due to timeout parameter\r\n");
00463                 break;
00464             case Gap::TIMEOUT_SRC_SCAN:
00465                 printf("Stopped scanning early due to timeout parameter\r\n");
00466                 break;
00467             case Gap::TIMEOUT_SRC_CONN:
00468                 printf("Failed to connect after scanning %d advertisements\r\n", _scan_count);
00469                 _event_queue.call(this, &GAPDevice::print_performance);
00470                 _event_queue.call(this, &GAPDevice::demo_mode_end);
00471                 break;
00472             default:
00473                 printf("Unexpected timeout\r\n");
00474                 break;
00475         }
00476     };
00477 
00478     /** clean up after last run, cycle to the next mode and launch it */
00479     void demo_mode_end()
00480     {
00481         /* reset the demo ready for the next mode */
00482         _scan_count = 0;
00483         _demo_duration.stop();
00484         _demo_duration.reset();
00485 
00486         /* cycle through all demo modes */
00487         _set_index++;
00488 
00489         /* switch between advertising and scanning when we go
00490          * through all the params in the array */
00491         if (_set_index >= (_is_in_scanning_mode? SCAN_PARAM_SET_MAX : ADV_PARAM_SET_MAX)) {
00492             _set_index = 0;
00493             //_is_in_scanning_mode = !_is_in_scanning_mode;
00494         }
00495 
00496         _ble.shutdown();
00497         _event_queue.break_dispatch();
00498     };
00499 
00500     /** print some information about our radio activity */
00501     void print_performance()
00502     {
00503         /* measure time from mode start, may have been stopped by timeout */
00504         uint16_t duration = _demo_duration.read_ms();
00505 
00506         if (_is_in_scanning_mode) {
00507             /* convert ms into timeslots for accurate calculation as internally
00508              * all durations are in timeslots (0.625ms) */
00509             uint16_t interval_ts = GapScanningParams::MSEC_TO_SCAN_DURATION_UNITS(
00510                 scanning_params[_set_index].interval
00511             );
00512             uint16_t window_ts = GapScanningParams::MSEC_TO_SCAN_DURATION_UNITS(
00513                 scanning_params[_set_index].window
00514             );
00515             uint16_t duration_ts = GapScanningParams::MSEC_TO_SCAN_DURATION_UNITS(
00516                 duration
00517             );
00518             /* this is how long we scanned for in timeslots */
00519             uint16_t rx_ts = (duration_ts / interval_ts) * window_ts;
00520             /* convert to milliseconds */
00521             uint16_t rx_ms = (rx_ts * GapScanningParams::UNIT_0_625_MS) / 1000;
00522 
00523             printf("We have scanned for %dms with an interval of %d"
00524                     " timeslot and a window of %d timeslots\r\n",
00525                     duration, interval_ts, window_ts);
00526 
00527             printf("We have been listening on the radio for at least %dms\r\n", rx_ms);
00528 
00529         } else /* advertising */ {
00530 
00531             /* convert ms into timeslots for accurate calculation as internally
00532              * all durations are in timeslots (0.625ms) */
00533             uint16_t interval_ts = GapAdvertisingParams::MSEC_TO_ADVERTISEMENT_DURATION_UNITS(
00534                 advertising_params[_set_index].interval
00535             );
00536             uint16_t duration_ts = GapAdvertisingParams::MSEC_TO_ADVERTISEMENT_DURATION_UNITS(
00537                 duration
00538             );
00539             /* this is how many times we advertised */
00540             uint16_t events = duration_ts / interval_ts;
00541 
00542             printf("We have advertised for %dms"
00543                    " with an interval of %d timeslots\r\n",
00544                    duration, interval_ts);
00545 
00546             /* non-scannable and non-connectable advertising
00547              * skips rx events saving on power consumption */
00548             if (advertising_params[_set_index].adv_type
00549                 == GapAdvertisingParams::ADV_NON_CONNECTABLE_UNDIRECTED) {
00550                 printf("We created at least %d tx events\r\n", events);
00551             } else {
00552                 printf("We created at least %d tx and rx events\r\n", events);
00553             }
00554         }
00555     };
00556 
00557     /** Schedule processing of events from the BLE middleware in the event queue. */
00558     void schedule_ble_events(BLE::OnEventsToProcessCallbackContext *context)
00559     {
00560         _event_queue.call(mbed::callback(&context->ble, &BLE::processEvents));
00561     };
00562 
00563     /** Blink LED to show we're running */
00564     void blink(void)
00565     {
00566         _led1 = !_led1;
00567     };
00568 
00569 private:
00570     BLE                &_ble;
00571     events::EventQueue  _event_queue;
00572     DigitalOut          _led1;
00573 
00574     /* Keep track of our progress through demo modes */
00575     size_t              _set_index;
00576     bool                _is_in_scanning_mode;
00577     bool                _is_connecting;
00578 
00579     /* Remember the call id of the function on _event_queue
00580      * so we can cancel it if we need to end the mode early */
00581     int                 _on_duration_end_id;
00582 
00583     /* Measure performance of our advertising/scanning */
00584     Timer               _demo_duration;
00585     size_t              _scan_count;
00586 };
00587 
00588 int main()
00589 {
00590     GAPDevice gap_device;
00591 
00592     while (1) {
00593         gap_device.run();
00594         wait_ms(TIME_BETWEEN_MODES_MS);
00595         printf("\r\nStarting next GAP demo mode\r\n");
00596     };
00597 
00598     return 0;
00599 }