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.
Diff: source/main.cpp
- Revision:
- 0:c736663c1bcc
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/source/main.cpp Wed Feb 13 18:35:09 2019 +0000 @@ -0,0 +1,533 @@ +/* mbed Microcontroller Library + * Copyright (c) 2006-2018 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <events/mbed_events.h> +#include <mbed.h> +#include "ble/BLE.h" +#include "gap/Gap.h" +#include "gap/AdvertisingDataParser.h" +#include "pretty_printer.h" +#include "BatteryService.h" + +/** This example demonstrates extended and periodic advertising + */ + +events::EventQueue event_queue; + +static const char DEVICE_NAME[] = "Periodic"; + +static const uint16_t MAX_ADVERTISING_PAYLOAD_SIZE = 50; +static const uint16_t SCAN_TIME = 5000; +static const uint8_t CONNECTION_DURATION = 2; + +/** Demonstrate periodic advertising and scanning and syncing with the advertising + */ +class PeriodicDemo : private mbed::NonCopyable<PeriodicDemo>, public ble::Gap::EventHandler +{ +public: + PeriodicDemo(BLE& ble, events::EventQueue& event_queue) : + _ble(ble), + _gap(ble.gap()), + _event_queue(event_queue), + _led1(LED1, 0), + _is_scanner(false), + _is_connecting_or_syncing(false), + _role_established(false), + _battery_uuid(GattService::UUID_BATTERY_SERVICE), + _battery_level(100), + _battery_service(ble, _battery_level), + _adv_data_builder(_adv_buffer), + _adv_handle(ble::INVALID_ADVERTISING_HANDLE), + _sync_handle(ble::INVALID_ADVERTISING_HANDLE) + { + } + + ~PeriodicDemo() + { + if (_ble.hasInitialized()) { + _ble.shutdown(); + } + } + + /** Start BLE interface initialisation */ + void run() + { + if (_ble.hasInitialized()) { + printf("Ble instance already initialised.\r\n"); + return; + } + + /* handle gap events */ + _gap.setEventHandler(this); + + ble_error_t error = _ble.init(this, &PeriodicDemo::on_init_complete); + if (error) { + print_error(error, "Error returned by BLE::init\r\n"); + return; + } + + /* to show we're running we'll blink every 500ms */ + _event_queue.call_every(500, this, &PeriodicDemo::blink); + + /* this will not return until shutdown */ + _event_queue.dispatch_forever(); + } + +private: + /** This is called when BLE interface is initialised and starts the first mode */ + void on_init_complete(BLE::InitializationCompleteCallbackContext *event) + { + if (event->error) { + print_error(event->error, "Error during the initialisation\r\n"); + return; + } + + if (!_gap.isFeatureSupported(ble::controller_supported_features_t::LE_EXTENDED_ADVERTISING) || + !_gap.isFeatureSupported(ble::controller_supported_features_t::LE_PERIODIC_ADVERTISING)) { + printf("Periodic advertising not supported, cannot run example.\r\n"); + return; + } + + print_mac_address(); + + /* all calls are serialised on the user thread through the event queue */ + _event_queue.call(this, &PeriodicDemo::start_role); + } + + void start_role() + { + /* This example is designed to be run on two boards at the same time, + * depending on our role we will either be the advertiser or scanner, + * until the roles are established we will cycle the roles until we find each other */ + if (_role_established) { + if (_is_scanner) { + _event_queue.call(this, &PeriodicDemo::scan_periodic); + } else { + _event_queue.call(this, &PeriodicDemo::advertise_periodic); + } + } else { + _is_scanner = !_is_scanner; + + if (_is_scanner) { + _event_queue.call(this, &PeriodicDemo::scan); + } else { + _event_queue.call(this, &PeriodicDemo::advertise); + } + } + } + + /** Set up and start advertising */ + void advertise() + { + ble_error_t error; + + ble::AdvertisingParameters adv_params; + adv_params.setUseLegacyPDU(false); + adv_params.setOwnAddressType(ble::own_address_type_t::RANDOM); + + /* create the advertising set with its parameter if we haven't yet */ + if (_adv_handle == ble::INVALID_ADVERTISING_HANDLE) { + error = _gap.createAdvertisingSet( + &_adv_handle, + adv_params + ); + + if (error) { + print_error(error, "Gap::createAdvertisingSet() failed\r\n"); + return; + } + } else { + _gap.setAdvertisingParameters(_adv_handle, adv_params); + } + + _adv_data_builder.clear(); + _adv_data_builder.setFlags(); + _adv_data_builder.setName(DEVICE_NAME); + + /* Set payload for the set */ + error = _gap.setAdvertisingPayload( + _adv_handle, + _adv_data_builder.getAdvertisingData() + ); + + if (error) { + print_error(error, "Gap::setAdvertisingPayload() failed\r\n"); + return; + } + + /* since we have two boards which might start running this example at the same time + * we randomise the interval of advertising to have them meet when one is advertising + * and the other one is scanning (we use their random address as source of randomness) */ + ble::millisecond_t random_duration_ms((3 + rand() % 5) * 1000); + ble::adv_duration_t random_duration(random_duration_ms); + + error = _ble.gap().startAdvertising( + _adv_handle, + random_duration + ); + + if (error) { + print_error(error, "Gap::startAdvertising() failed\r\n"); + return; + } + + printf("Advertising started for %dms\r\n", random_duration_ms); + } + + void advertise_periodic() + { + ble::AdvertisingParameters adv_params; + adv_params.setType(ble::advertising_type_t::NON_CONNECTABLE_UNDIRECTED); + adv_params.setUseLegacyPDU(false); + adv_params.setOwnAddressType(ble::own_address_type_t::RANDOM); + + ble_error_t error = _gap.setAdvertisingParameters(_adv_handle, adv_params); + + if (error) { + print_error(error, "Gap::setAdvertisingParameters() failed\r\n"); + return; + } + + /* Start advertising the set as the advertising needs to be active + * before we start periodic advertising */ + error = _gap.startAdvertising(_adv_handle); + + if (error) { + print_error(error, "Gap::startAdvertising() failed\r\n"); + return; + } + + error = _gap.setPeriodicAdvertisingParameters( + _adv_handle, + ble::periodic_interval_t(50), + ble::periodic_interval_t(500) + ); + + if (error) { + print_error(error, "Gap::setPeriodicAdvertisingParameters() failed\r\n"); + return; + } + + /* we will put the battery level data in there and update it every second */ + update_payload(); + + error = _gap.startPeriodicAdvertising(_adv_handle); + + if (error) { + print_error(error, "Gap::startPeriodicAdvertising() failed\r\n"); + return; + } + + printf("Periodic advertising started\r\n"); + + /* tick over our fake battery data, this will also update the advertising payload */ + _event_queue.call_every(1000, this, &PeriodicDemo::update_sensor_value); + } + + void update_payload() + { + /* advertising payload will have the battery level which we will update */ + ble_error_t error = _adv_data_builder.setServiceData( + _battery_uuid, + mbed::make_Span(&_battery_level, 1) + ); + + if (error) { + print_error(error, "AdvertisingDataBuilder::setServiceData() failed\r\n"); + return; + } + + /* the data in the local host buffer has been updated but now + * we have to update the data in the controller */ + error = _gap.setPeriodicAdvertisingPayload( + _adv_handle, + _adv_data_builder.getAdvertisingData() + ); + + if (error) { + print_error(error, "Gap::setPeriodicAdvertisingPayload() failed\r\n"); + return; + } + } + + /** Set up and start scanning */ + void scan() + { + _is_connecting_or_syncing = false; + + ble::ScanParameters scan_params; + scan_params.setOwnAddressType(ble::own_address_type_t::RANDOM); + + ble_error_t error = _gap.setScanParameters(scan_params); + + if (error) { + print_error(error, "Error caused by Gap::setScanParameters\r\n"); + return; + } + + error = _gap.startScan(ble::scan_duration_t(500)); + + if (error) { + print_error(error, "Error caused by Gap::startScan\r\n"); + return; + } + + printf("Scanning started\r\n"); + } + + void scan_periodic() + { + _is_connecting_or_syncing = false; + + ble_error_t error = _gap.startScan(); + + if (error) { + print_error(error, "Error caused by Gap::startScan\r\n"); + return; + } + + printf("Scanning for periodic advertising started\r\n"); + } + +private: + /* Gap::EventHandler */ + + /** Look at scan payload to find a peer device and connect to it */ + virtual void onAdvertisingReport( + const ble::AdvertisingReportEvent &event + ) { + /* don't bother with analysing scan result if we're already connecting */ + if (_is_connecting_or_syncing) { + return; + } + + /* if we're looking for periodic advertising don't bother unless it's present */ + if (_role_established && !event.isPeriodicIntervalPresent()) { + return; + } + + ble::AdvertisingDataParser adv_parser(event.getPayload()); + + /* parse the advertising payload, looking for a discoverable device */ + while (adv_parser.hasNext()) { + ble::AdvertisingDataParser::element_t field = adv_parser.next(); + + /* identify peer by name */ + if (field.type == ble::adv_data_type_t::COMPLETE_LOCAL_NAME && + field.value.size() == strlen(DEVICE_NAME) && + (memcmp(field.value.data(), DEVICE_NAME, field.value.size()) == 0)) { + /* if we haven't established our roles connect, otherwise sync with advertising */ + if (_role_established) { + printf("We found the peer, syncing with SID %d" + "and periodic interval %dms\r\n", + event.getSID(), event.getPeriodicInterval().valueInMs()); + + ble_error_t error = _gap.createSync( + event.getPeerAddressType(), + event.getPeerAddress(), + event.getSID(), + 2, + ble::sync_timeout_t(ble::millisecond_t(500)) + ); + + if (error) { + print_error(error, "Error caused by Gap::createSync\r\n"); + return; + } + } else { + printf("We found the peer, connecting\r\n"); + + ble_error_t error = _gap.connect( + event.getPeerAddressType(), + event.getPeerAddress(), + ble::ConnectionParameters() // use the default connection parameters + ); + + if (error) { + print_error(error, "Error caused by Gap::connect\r\n"); + return; + } + } + + /* we may have already scan events waiting to be processed + * so we need to remember that we are already connecting + * or syncing and ignore them */ + _is_connecting_or_syncing = true; + + return; + } + } + } + + virtual void onAdvertisingEnd( + const ble::AdvertisingEndEvent &event + ) { + if (event.isConnected()) { + printf("Stopped advertising due to connection\r\n"); + } else { + printf("Advertising ended\r\n"); + _event_queue.call(this, &PeriodicDemo::start_role); + } + } + + virtual void onScanTimeout( + const ble::ScanTimeoutEvent& + ) { + if (!_is_connecting_or_syncing) { + printf("Scanning ended, failed to find peer\r\n"); + _event_queue.call(this, &PeriodicDemo::start_role); + } + } + + /** This is called by Gap to notify the application we connected */ + virtual void onConnectionComplete( + const ble::ConnectionCompleteEvent &event + ) { + if (event.getStatus() == BLE_ERROR_NONE) { + printf("Connected to: "); + print_address(event.getPeerAddress().data()); + printf("Roles established\r\n"); + _role_established = true; + + /* we have to specify the disconnect call because of ambiguous overloads */ + typedef ble_error_t (Gap::*disconnect_call_t)(ble::connection_handle_t, ble::local_disconnection_reason_t); + const disconnect_call_t disconnect_call = &Gap::disconnect; + + if (_is_scanner) { + _event_queue.call_in( + 2000, + &_ble.gap(), + disconnect_call, + event.getConnectionHandle(), + ble::local_disconnection_reason_t(ble::local_disconnection_reason_t::USER_TERMINATION) + ); + } + } else { + printf("Failed to connect\r\n"); + _event_queue.call(this, &PeriodicDemo::start_role); + } + } + + /** This is called by Gap to notify the application we disconnected */ + virtual void onDisconnectionComplete( + const ble::DisconnectionCompleteEvent &event + ) { + printf("Disconnected\r\n"); + _event_queue.call(this, &PeriodicDemo::start_role); + } + + /** Called when first advertising packet in periodic advertising is received. */ + virtual void onPeriodicAdvertisingSyncEstablished( + const ble::PeriodicAdvertisingSyncEstablishedEvent &event + ) { + if (event.getStatus() == BLE_ERROR_NONE) { + printf("Synced with periodic advertising\r\n"); + _sync_handle = event.getSyncHandle(); + } else { + printf("Sync with periodic advertising failed\r\n"); + } + } + + /** Called when a periodic advertising packet is received. */ + virtual void onPeriodicAdvertisingReport( + const ble::PeriodicAdvertisingReportEvent &event + ) { + ble::AdvertisingDataParser adv_parser(event.getPayload()); + + /* parse the advertising payload, looking for a battery level */ + while (adv_parser.hasNext()) { + ble::AdvertisingDataParser::element_t field = adv_parser.next(); + + if (field.type == ble::adv_data_type_t::SERVICE_DATA) { + if (memcmp(field.value.data(), _battery_uuid.getBaseUUID(), _battery_uuid.getLen()) != 0) { + printf("Unexpected service data\r\n"); + } else { + /* battery level is right after the UUID */ + const uint8_t *battery_level = field.value.data() + _battery_uuid.getLen(); + printf("Peer battery level: %d\r\n", *battery_level); + } + } + } + } + + /** Called when a periodic advertising sync has been lost. */ + virtual void onPeriodicAdvertisingSyncLoss( + const ble::PeriodicAdvertisingSyncLoss &event + ) { + printf("Sync to periodic advertising lost\r\n"); + _sync_handle = ble::INVALID_ADVERTISING_HANDLE; + _event_queue.call(this, &PeriodicDemo::scan_periodic); + } + +private: + void update_sensor_value() { + _battery_level--; + if (_battery_level < 1) { + _battery_level = 100; + } + + _battery_service.updateBatteryLevel(_battery_level); + update_payload(); + } + + /** Blink LED to show we're running */ + void blink(void) + { + _led1 = !_led1; + } + +private: + BLE &_ble; + ble::Gap &_gap; + events::EventQueue &_event_queue; + + DigitalOut _led1; + + bool _is_scanner; + bool _is_connecting_or_syncing; + bool _role_established; + + UUID _battery_uuid; + uint8_t _battery_level; + BatteryService _battery_service; + + uint8_t _adv_buffer[MAX_ADVERTISING_PAYLOAD_SIZE]; + ble::AdvertisingDataBuilder _adv_data_builder; + + ble::advertising_handle_t _adv_handle; + ble::periodic_sync_handle_t _sync_handle; +}; + +/** Schedule processing of events from the BLE middleware in the event queue. */ +void schedule_ble_events(BLE::OnEventsToProcessCallbackContext *context) { + event_queue.call(Callback<void()>(&context->ble, &BLE::processEvents)); +} + +int main() +{ + BLE &ble = BLE::Instance(); + + /* this will inform us off all events so we can schedule their handling + * using our event queue */ + ble.onEventsToProcess(schedule_ble_events); + + /* look for other device and then settle on a role and sync periodic advertising */ + PeriodicDemo demo(ble, event_queue); + + demo.run(); + + return 0; +}