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-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 }
Generated on Sat Jul 16 2022 06:00:05 by
1.7.2