nuBorn Medical / Mbed 2 deprecated mbed-os-example-ble-master

Dependencies:   mbed

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      = 6000;
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 = 3000;
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*/ 3/*s*/},
00062     { GapAdvertisingParams::ADV_SCANNABLE_UNDIRECTED,       100,       4     },
00063     { GapAdvertisingParams::ADV_NON_CONNECTABLE_UNDIRECTED, 100,       0     }
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 
00093 /** Demonstrate advertising, scanning and connecting
00094  */
00095 class GAPDevice : private mbed::NonCopyable<GAPDevice>
00096 {
00097 public:
00098     GAPDevice() :
00099         _ble(BLE::Instance()),
00100         _led1(LED1, 0),
00101         _set_index(0),
00102         _is_in_scanning_mode(false),
00103         _is_connecting(false),
00104         _on_duration_end_id(0),
00105         _scan_count(0) { };
00106 
00107     ~GAPDevice()
00108     {
00109         if (_ble.hasInitialized()) {
00110             _ble.shutdown();
00111         }
00112     };
00113 
00114     /** Start BLE interface initialisation */
00115     void run()
00116     {
00117         ble_error_t error;
00118 
00119         if (_ble.hasInitialized()) {
00120             printf("Ble instance already initialised.\r\n");
00121             return;
00122         }
00123 
00124         /* this will inform us off all events so we can schedule their handling
00125          * using our event queue */
00126         _ble.onEventsToProcess(
00127             makeFunctionPointer(this, &GAPDevice::schedule_ble_events)
00128         );
00129 
00130         /* handle timeouts, for example when connection attempts fail */
00131         _ble.gap().onTimeout(
00132             makeFunctionPointer(this, &GAPDevice::on_timeout)
00133         );
00134 
00135         error = _ble.init(this, &GAPDevice::on_init_complete);
00136 
00137         if (error) {
00138             printf("Error returned by BLE::init.\r\n");
00139             return;
00140         }
00141 
00142         /* to show we're running we'll blink every 500ms */
00143         _event_queue.call_every(500, this, &GAPDevice::blink);
00144 
00145         /* this will not return until shutdown */
00146         _event_queue.dispatch_forever();
00147     };
00148 
00149 private:
00150     /** This is called when BLE interface is initialised and starts the first mode */
00151     void on_init_complete(BLE::InitializationCompleteCallbackContext *event)
00152     {
00153         if (event->error) {
00154             printf("Error during the initialisation\r\n");
00155             return;
00156         }
00157 
00158         /* print device address */
00159         Gap::AddressType_t addr_type;
00160         Gap::Address_t addr;
00161         _ble.gap().getAddress(&addr_type, addr);
00162         printf("Device address: %02x:%02x:%02x:%02x:%02x:%02x\r\n",
00163                addr[5], addr[4], addr[3], addr[2], addr[1], addr[0]);
00164 
00165         /* all calls are serialised on the user thread through the event queue */
00166         _event_queue.call(this, &GAPDevice::demo_mode_start);
00167     };
00168 
00169     /** queue up start of the current demo mode */
00170     void demo_mode_start()
00171     {
00172         if (_is_in_scanning_mode) {
00173             /* when scanning we want to connect to a peer device so we need to
00174              * attach callbacks that are used by Gap to notify us of events */
00175             _ble.gap().onConnection(this, &GAPDevice::on_connect);
00176             _ble.gap().onDisconnection(this, &GAPDevice::on_disconnect);
00177 
00178             _event_queue.call(this, &GAPDevice::scan);
00179         } else {
00180             _event_queue.call(this, &GAPDevice::advertise);
00181         }
00182 
00183         /* for performance measurement keep track of duration of the demo mode */
00184         _demo_duration.start();
00185         /* keep track of our state */
00186         _is_connecting = false;
00187 
00188         /* queue up next demo mode */
00189         _on_duration_end_id = _event_queue.call_in(
00190             MODE_DURATION_MS, this, &GAPDevice::on_duration_end
00191         );
00192 
00193         printf("\r\n");
00194     }
00195 
00196     /** Set up and start advertising */
00197     void advertise()
00198     {
00199         ble_error_t error;
00200         GapAdvertisingData advertising_data;
00201 
00202         /* add advertising flags */
00203         advertising_data.addFlags(GapAdvertisingData::LE_GENERAL_DISCOVERABLE
00204                                   | GapAdvertisingData::BREDR_NOT_SUPPORTED);
00205 
00206         /* add device name */
00207         advertising_data.addData(
00208             GapAdvertisingData::COMPLETE_LOCAL_NAME,
00209             DEVICE_NAME,
00210             sizeof(DEVICE_NAME)
00211         );
00212 
00213         error = _ble.gap().setAdvertisingPayload(advertising_data);
00214 
00215         if (error) {
00216             printf("Error during Gap::setAdvertisingPayload\r\n");
00217             return;
00218         }
00219 
00220         /* set the advertising parameters according to currently selected set,
00221          * see @AdvertisingType_t for explanation of modes */
00222         GapAdvertisingParams::AdvertisingType_t adv_type =
00223             advertising_params[_set_index].adv_type;
00224 
00225         /* how many milliseconds between advertisements, lower interval
00226          * increases the chances of being seen at the cost of more power */
00227         uint16_t interval = advertising_params[_set_index].interval;
00228 
00229         /* advertising will continue for this many seconds or until connected */
00230         uint16_t timeout = advertising_params[_set_index].timeout;
00231 
00232         _ble.gap().setAdvertisingType(adv_type);
00233         _ble.gap().setAdvertisingInterval(interval);
00234         _ble.gap().setAdvertisingTimeout(timeout);
00235 
00236         error = _ble.gap().startAdvertising();
00237 
00238         if (error) {
00239             printf("Error during Gap::startAdvertising.\r\n");
00240             return;
00241         }
00242 
00243         printf("Advertising started (type: 0x%x, interval: %dms, timeout: %ds)\r\n",
00244                adv_type, interval, timeout);
00245     };
00246 
00247     /** Set up and start scanning */
00248     void scan()
00249     {
00250         ble_error_t error;
00251 
00252         /* scanning happens repeatedly, interval is the number of milliseconds
00253          * between each cycle of scanning */
00254         uint16_t interval = scanning_params[_set_index].interval;
00255 
00256         /* number of milliseconds we scan for each time we enter
00257          * the scanning cycle after the interval set above */
00258         uint16_t window = scanning_params[_set_index].window;
00259 
00260         /* how long to repeat the cycles of scanning in seconds */
00261         uint16_t timeout = scanning_params[_set_index].timeout;
00262 
00263         /* active scanning will send a scan request to any scanable devices that
00264          * we see advertising */
00265         bool active = scanning_params[_set_index].active;
00266 
00267         /* set the scanning parameters according to currently selected set */
00268         error = _ble.gap().setScanParams(interval, window, timeout, active);
00269 
00270         if (error) {
00271             printf("Error during Gap::setScanParams\r\n");
00272             return;
00273         }
00274 
00275         /* start scanning and attach a callback that will handle advertisements
00276          * and scan requests responses */
00277         error = _ble.gap().startScan(this, &GAPDevice::on_scan);
00278 
00279         if (error) {
00280             printf("Error during Gap::startScan\r\n");
00281             return;
00282         }
00283 
00284         printf("Scanning started (interval: %dms, window: %dms, timeout: %ds).\r\n",
00285                interval, window, timeout);
00286     };
00287 
00288     /** After a set duration this cycles to the next demo mode
00289      *  unless a connection happened first */
00290     void on_duration_end()
00291     {
00292         print_performance();
00293 
00294         /* alloted time has elapsed, move to next demo mode */
00295         _event_queue.call(this, &GAPDevice::demo_mode_end);
00296     };
00297 
00298     /** Look at scan payload to find a peer device and connect to it */
00299     void on_scan(const Gap::AdvertisementCallbackParams_t *params)
00300     {
00301         /* keep track of scan events for performance reporting */
00302         _scan_count++;
00303 
00304         /* don't bother with analysing scan result if we're already connecting */
00305         if (_is_connecting) {
00306             return;
00307         }
00308 
00309         /* parse the advertising payload, looking for a discoverable device */
00310         for (uint8_t i = 0; i < params->advertisingDataLen; ++i) {
00311             /* The advertising payload is a collection of key/value records where
00312              * byte 0: length of the record excluding this byte
00313              * byte 1: The key, it is the type of the data
00314              * byte [2..N] The value. N is equal to byte0 - 1 */
00315             const uint8_t record_length = params->advertisingData[i];
00316             if (record_length == 0) {
00317                 continue;
00318             }
00319             const uint8_t type = params->advertisingData[i + 1];
00320             const uint8_t *value = params->advertisingData + i + 2;
00321 
00322             /* connect to a discoverable device */
00323             if ((type == GapAdvertisingData::FLAGS)
00324                 && (*value & GapAdvertisingData::LE_GENERAL_DISCOVERABLE)) {
00325 
00326                 /* abort timeout as the mode will end on disconnection */
00327                 _event_queue.cancel(_on_duration_end_id);
00328 
00329                 printf("We found a connectable device\r\n");
00330 
00331                 ble_error_t error = _ble.gap().connect(
00332                     params->peerAddr, Gap::ADDR_TYPE_RANDOM_STATIC,
00333                     NULL, &connection_scan_params
00334                 );
00335 
00336                 if (error) {
00337                     printf("Error during Gap::connect\r\n");
00338                     /* since no connection will be attempted end the mode */
00339                     _event_queue.call(this, &GAPDevice::demo_mode_end);
00340                     return;
00341                 }
00342 
00343                 /* we may have already scan events waiting
00344                  * to be processed so we need to remember
00345                  * that we are already connecting and ignore them */
00346                 _is_connecting = true;
00347 
00348                 return;
00349             }
00350 
00351             i += record_length;
00352         }
00353     };
00354 
00355     /** This is called by Gap to notify the application we connected,
00356      *  in our case it immediately disconnects */
00357     void on_connect(const Gap::ConnectionCallbackParams_t *connection_event)
00358     {
00359         print_performance();
00360 
00361         printf("Connected in %dms\r\n", _demo_duration.read_ms());
00362 
00363         /* cancel the connect timeout since we connected */
00364         _event_queue.cancel(_on_duration_end_id);
00365 
00366         _event_queue.call_in(
00367             CONNECTION_DURATION, &_ble.gap(), &Gap::disconnect, Gap::REMOTE_USER_TERMINATED_CONNECTION
00368         );
00369     };
00370 
00371     /** This is called by Gap to notify the application we disconnected,
00372      *  in our case it calls demo_mode_end() to progress the demo */
00373     void on_disconnect(const Gap::DisconnectionCallbackParams_t *event)
00374     {
00375         printf("Disconnected\r\n");
00376 
00377         /* we have successfully disconnected ending the demo, move to next mode */
00378         _event_queue.call(this, &GAPDevice::demo_mode_end);
00379     };
00380 
00381     /** called if timeout is reached during advertising, scanning
00382      *  or connection initiation */
00383     void on_timeout(const Gap::TimeoutSource_t source)
00384     {
00385         _demo_duration.stop();
00386 
00387         switch (source) {
00388             case Gap::TIMEOUT_SRC_ADVERTISING:
00389                 printf("Stopped advertising early due to timeout parameter\r\n");
00390                 break;
00391             case Gap::TIMEOUT_SRC_SCAN:
00392                 printf("Stopped scanning early due to timeout parameter\r\n");
00393                 break;
00394             case Gap::TIMEOUT_SRC_CONN:
00395                 printf("Failed to connect after scanning %d advertisements\r\n", _scan_count);
00396                 _event_queue.call(this, &GAPDevice::print_performance);
00397                 _event_queue.call(this, &GAPDevice::demo_mode_end);
00398                 break;
00399             default:
00400                 printf("Unexpected timeout\r\n");
00401                 break;
00402         }
00403     };
00404 
00405     /** clean up after last run, cycle to the next mode and launch it */
00406     void demo_mode_end()
00407     {
00408         /* reset the demo ready for the next mode */
00409         _scan_count = 0;
00410         _demo_duration.stop();
00411         _demo_duration.reset();
00412 
00413         /* cycle through all demo modes */
00414         _set_index++;
00415 
00416         /* switch between advertising and scanning when we go
00417          * through all the params in the array */
00418         if (_set_index >= (_is_in_scanning_mode? SCAN_PARAM_SET_MAX : ADV_PARAM_SET_MAX)) {
00419             _set_index = 0;
00420             _is_in_scanning_mode = !_is_in_scanning_mode;
00421         }
00422 
00423         _ble.shutdown();
00424         _event_queue.break_dispatch();
00425     };
00426 
00427     /** print some information about our radio activity */
00428     void print_performance()
00429     {
00430         /* measure time from mode start, may have been stopped by timeout */
00431         uint16_t duration = _demo_duration.read_ms();
00432 
00433         if (_is_in_scanning_mode) {
00434             /* convert ms into timeslots for accurate calculation as internally
00435              * all durations are in timeslots (0.625ms) */
00436             uint16_t interval_ts = GapScanningParams::MSEC_TO_SCAN_DURATION_UNITS(
00437                 scanning_params[_set_index].interval
00438             );
00439             uint16_t window_ts = GapScanningParams::MSEC_TO_SCAN_DURATION_UNITS(
00440                 scanning_params[_set_index].window
00441             );
00442             uint16_t duration_ts = GapScanningParams::MSEC_TO_SCAN_DURATION_UNITS(
00443                 duration
00444             );
00445             /* this is how long we scanned for in timeslots */
00446             uint16_t rx_ts = (duration_ts / interval_ts) * window_ts;
00447             /* convert to milliseconds */
00448             uint16_t rx_ms = (rx_ts * GapScanningParams::UNIT_0_625_MS) / 1000;
00449 
00450             printf("We have scanned for %dms with an interval of %d"
00451                     " timeslot and a window of %d timeslots\r\n",
00452                     duration, interval_ts, window_ts);
00453 
00454             printf("We have been listening on the radio for at least %dms\r\n", rx_ms);
00455 
00456         } else /* advertising */ {
00457 
00458             /* convert ms into timeslots for accurate calculation as internally
00459              * all durations are in timeslots (0.625ms) */
00460             uint16_t interval_ts = GapAdvertisingParams::MSEC_TO_ADVERTISEMENT_DURATION_UNITS(
00461                 advertising_params[_set_index].interval
00462             );
00463             uint16_t duration_ts = GapAdvertisingParams::MSEC_TO_ADVERTISEMENT_DURATION_UNITS(
00464                 duration
00465             );
00466             /* this is how many times we advertised */
00467             uint16_t events = duration_ts / interval_ts;
00468 
00469             printf("We have advertised for %dms"
00470                    " with an interval of %d timeslots\r\n",
00471                    duration, interval_ts);
00472 
00473             /* non-scannable and non-connectable advertising
00474              * skips rx events saving on power consumption */
00475             if (advertising_params[_set_index].adv_type
00476                 == GapAdvertisingParams::ADV_NON_CONNECTABLE_UNDIRECTED) {
00477                 printf("We created at least %d tx events\r\n", events);
00478             } else {
00479                 printf("We created at least %d tx and rx events\r\n", events);
00480             }
00481         }
00482     };
00483 
00484     /** Schedule processing of events from the BLE middleware in the event queue. */
00485     void schedule_ble_events(BLE::OnEventsToProcessCallbackContext *context)
00486     {
00487         _event_queue.call(mbed::callback(&context->ble, &BLE::processEvents));
00488     };
00489 
00490     /** Blink LED to show we're running */
00491     void blink(void)
00492     {
00493         _led1 = !_led1;
00494     };
00495 
00496 private:
00497     BLE                &_ble;
00498     events::EventQueue  _event_queue;
00499     DigitalOut          _led1;
00500 
00501     /* Keep track of our progress through demo modes */
00502     size_t              _set_index;
00503     bool                _is_in_scanning_mode;
00504     bool                _is_connecting;
00505 
00506     /* Remember the call id of the function on _event_queue
00507      * so we can cancel it if we need to end the mode early */
00508     int                 _on_duration_end_id;
00509 
00510     /* Measure performance of our advertising/scanning */
00511     Timer               _demo_duration;
00512     size_t              _scan_count;
00513 };
00514 
00515 int main()
00516 {
00517     GAPDevice gap_device;
00518 
00519     while (1) {
00520         gap_device.run();
00521         wait_ms(TIME_BETWEEN_MODES_MS);
00522         printf("\r\nStarting next GAP demo mode\r\n");
00523     };
00524 
00525     return 0;
00526 }