Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
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 }
Generated on Wed Aug 3 2022 03:17:14 by
1.7.2