Eddystone test using modified DAL
Dependencies: BLE_API mbed-dev-bin nRF51822
Dependents: microbit-eddystone
Fork of microbit-dal by
Revision 74:a8f5674a0079, committed 2017-02-08
- Comitter:
- bluetooth_mdw
- Date:
- Wed Feb 08 07:49:17 2017 +0000
- Parent:
- 73:eb91bba49623
- Commit message:
- Eddystone URL test using modified DAL
Changed in this revision
--- a/inc/bluetooth/MicroBitBLEManager.h Wed Jul 13 14:32:54 2016 +0000 +++ b/inc/bluetooth/MicroBitBLEManager.h Wed Feb 08 07:49:17 2017 +0000 @@ -35,7 +35,7 @@ * The ARM cc compiler is more tolerant. We don't test __GNUC__ here to detect GCC as ARMCC also typically sets this * as a compatability option, but does not support the options used... */ -#if !defined (__arm) +#if !defined(__arm) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-parameter" #endif @@ -44,7 +44,7 @@ /* * Return to our predefined compiler settings. */ -#if !defined (__arm) +#if !defined(__arm) #pragma GCC diagnostic pop #endif @@ -61,21 +61,24 @@ #include "MicroBitButton.h" #include "MicroBitStorage.h" -#define MICROBIT_BLE_PAIR_REQUEST 0x01 -#define MICROBIT_BLE_PAIR_COMPLETE 0x02 -#define MICROBIT_BLE_PAIR_PASSCODE 0x04 -#define MICROBIT_BLE_PAIR_SUCCESSFUL 0x08 +#define MICROBIT_BLE_PAIR_REQUEST 0x01 +#define MICROBIT_BLE_PAIR_COMPLETE 0x02 +#define MICROBIT_BLE_PAIR_PASSCODE 0x04 +#define MICROBIT_BLE_PAIR_SUCCESSFUL 0x08 -#define MICROBIT_BLE_PAIRING_TIMEOUT 90 -#define MICROBIT_BLE_POWER_LEVELS 8 -#define MICROBIT_BLE_MAXIMUM_BONDS 4 -#define MICROBIT_BLE_ENABLE_BONDING true +#define MICROBIT_BLE_PAIRING_TIMEOUT 90 +#define MICROBIT_BLE_POWER_LEVELS 8 +#define MICROBIT_BLE_MAXIMUM_BONDS 4 +#define MICROBIT_BLE_ENABLE_BONDING true + +#define MICROBIT_BLE_EDDYSTONE_ADV_INTERVAL 400 +#define MICROBIT_BLE_EDDYSTONE_DEFAULT_POWER 0xF0 extern const int8_t MICROBIT_BLE_POWER_LEVEL[]; struct BLESysAttribute { - uint8_t sys_attr[8]; + uint8_t sys_attr[8]; }; struct BLESysAttributeStore @@ -89,13 +92,14 @@ */ class MicroBitBLEManager : MicroBitComponent { - public: + public: + static MicroBitBLEManager *manager; - // The mbed abstraction of the BlueTooth Low Energy (BLE) hardware - BLEDevice *ble; + // The mbed abstraction of the BlueTooth Low Energy (BLE) hardware + BLEDevice *ble; //an instance of MicroBitStorage used to store sysAttrs from softdevice - MicroBitStorage* storage; + MicroBitStorage *storage; /** * Constructor. @@ -107,7 +111,7 @@ * @note The BLE stack *cannot* be brought up in a static context (the software simply hangs or corrupts itself). * Hence, the init() member function should be used to initialise the BLE stack. */ - MicroBitBLEManager(MicroBitStorage& _storage); + MicroBitBLEManager(MicroBitStorage &_storage); /** * Constructor. @@ -120,6 +124,15 @@ MicroBitBLEManager(); /** + * getInstance + * + * Allows other objects to easily obtain a pointer to the single instance of this object. By rights the constructor should be made + * private to properly implement the singleton pattern. + * + */ + static MicroBitBLEManager *getInstance(); + + /** * Post constructor initialisation method as the BLE stack cannot be brought * up in a static context. * @@ -132,7 +145,7 @@ * bleManager.init(uBit.getName(), uBit.getSerial(), uBit.messageBus, true); * @endcode */ - void init(ManagedString deviceName, ManagedString serialNumber, EventModel& messageBus, bool enableBonding); + void init(ManagedString deviceName, ManagedString serialNumber, EventModel &messageBus, bool enableBonding); /** * Change the output power level of the transmitter to the given value. @@ -174,42 +187,102 @@ */ int getBondCount(); - /** + /** * A request to pair has been received from a BLE device. * If we're in pairing mode, display the passkey to the user. * Also, purge the bonding table if it has reached capacity. * * @note for internal use only. */ - void pairingRequested(ManagedString passKey); + void pairingRequested(ManagedString passKey); - /** + /** * A pairing request has been sucessfully completed. * If we're in pairing mode, display a success or failure message. * * @note for internal use only. */ - void pairingComplete(bool success); + void pairingComplete(bool success); - /** + /** * Periodic callback in thread context. * We use this here purely to safely issue a disconnect operation after a pairing operation is complete. */ - void idleTick(); + void idleTick(); + + /** + * Stops any currently running BLE advertisements + */ + void stopAdvertising(); +#if CONFIG_ENABLED(MICROBIT_BLE_EDDYSTONE_URL) - private: + /** + * Set the content of Eddystone URL frames + * + * @param url The url to broadcast + * + * @param calibratedPower the transmission range of the beacon (Defaults to: 0xF0 ~10m). + * + * @param connectable true to keep bluetooth connectable for other services, false otherwise. (Defaults to true) + * + * @param interval the rate at which the micro:bit will advertise url frames. (Defaults to MICROBIT_BLE_EDDYSTONE_ADV_INTERVAL) + * + * @note The calibratedPower value ranges from -100 to +20 to a resolution of 1. The calibrated power should be binary encoded. + * More information can be found at https://github.com/google/eddystone/tree/master/eddystone-uid#tx-power + */ + int advertiseEddystoneUrl(const char *url, int8_t calibratedPower = MICROBIT_BLE_EDDYSTONE_DEFAULT_POWER, bool connectable = true, uint16_t interval = MICROBIT_BLE_EDDYSTONE_ADV_INTERVAL); - /** - * Displays the device's ID code as a histogram on the provided MicroBitDisplay instance. - * - * @param display The display instance used for displaying the histogram. - */ - void showNameHistogram(MicroBitDisplay &display); + /** + * Set the content of Eddystone URL frames, but accepts a ManagedString as a url. + * + * @param url The url to broadcast + * + * @param calibratedPower the transmission range of the beacon (Defaults to: 0xF0 ~10m). + * + * @param connectable true to keep bluetooth connectable for other services, false otherwise. (Defaults to true) + * + * @param interval the rate at which the micro:bit will advertise url frames. (Defaults to MICROBIT_BLE_EDDYSTONE_ADV_INTERVAL) + * + * @note The calibratedPower value ranges from -100 to +20 to a resolution of 1. The calibrated power should be binary encoded. + * More information can be found at https://github.com/google/eddystone/tree/master/eddystone-uid#tx-power + */ + int advertiseEddystoneUrl(ManagedString url, int8_t calibratedPower = MICROBIT_BLE_EDDYSTONE_DEFAULT_POWER, bool connectable = true, uint16_t interval = MICROBIT_BLE_EDDYSTONE_ADV_INTERVAL); +#endif - int pairingStatus; - ManagedString passKey; - ManagedString deviceName; +#if CONFIG_ENABLED(MICROBIT_BLE_EDDYSTONE_UID) + /** + * Set the content of Eddystone UID frames + * + * @param uid_namespace: the uid namespace. Must 10 bytes long. + * + * @param uid_instance: the uid instance value. Must 6 bytes long. + * + * @param calibratedPower the transmission range of the beacon (Defaults to: 0xF0 ~10m). + * + * @param connectable true to keep bluetooth connectable for other services, false otherwise. (Defaults to true) + * + * @param interval the rate at which the micro:bit will advertise url frames. (Defaults to MICROBIT_BLE_EDDYSTONE_ADV_INTERVAL) + * + * @note The calibratedPower value ranges from -100 to +20 to a resolution of 1. The calibrated power should be binary encoded. + * More information can be found at https://github.com/google/eddystone/tree/master/eddystone-uid#tx-power + */ + int advertiseEddystoneUid(const char* uid_namespace, const char* uid_instance, int8_t calibratedPower = MICROBIT_BLE_EDDYSTONE_DEFAULT_POWER, bool connectable = true, uint16_t interval = MICROBIT_BLE_EDDYSTONE_ADV_INTERVAL); +#endif + private: + /** + * Displays the device's ID code as a histogram on the provided MicroBitDisplay instance. + * + * @param display The display instance used for displaying the histogram. + */ + void showNameHistogram(MicroBitDisplay &display); + + #define MICROBIT_BLE_DISCONNECT_AFTER_PAIRING_DELAY 500 + unsigned long pairing_completed_at_time; + + int pairingStatus; + ManagedString passKey; + ManagedString deviceName; }; -#endif \ No newline at end of file +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/inc/bluetooth/MicroBitEddystone.h Wed Feb 08 07:49:17 2017 +0000 @@ -0,0 +1,104 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#ifndef MICROBIT_EDDYSTONE_H +#define MICROBIT_EDDYSTONE_H + +#include "MicroBitConfig.h" + +/* + * Return to our predefined compiler settings. + */ +#if !defined(__arm) +#pragma GCC diagnostic pop +#endif + +#include "MicroBitBLEManager.h" + +/** + * Class definition for the MicroBitEddystone. + * + */ +class MicroBitEddystone +{ + public: + + static MicroBitEddystone* getInstance(); + +#if CONFIG_ENABLED(MICROBIT_BLE_EDDYSTONE_URL) + + /** + * Set the content of Eddystone URL frames + * + * @param url The url to broadcast + * + * @param calibratedPower the transmission range of the beacon (Defaults to: 0xF0 ~10m). + * + * @note The calibratedPower value ranges from -100 to +20 to a resolution of 1. The calibrated power should be binary encoded. + * More information can be found at https://github.com/google/eddystone/tree/master/eddystone-uid#tx-power + */ + int setURL(BLEDevice* ble, const char *url, int8_t calibratedPower = 0xF0); + + /** + * Set the content of Eddystone URL frames, but accepts a ManagedString as a url. + * + * @param url The url to broadcast + * + * @param calibratedPower the transmission range of the beacon (Defaults to: 0xF0 ~10m). + * + * @note The calibratedPower value ranges from -100 to +20 to a resolution of 1. The calibrated power should be binary encoded. + * More information can be found at https://github.com/google/eddystone/tree/master/eddystone-uid#tx-power + */ + int setURL(BLEDevice* ble, ManagedString url, int8_t calibratedPower = 0xF0); + +#endif + +#if CONFIG_ENABLED(MICROBIT_BLE_EDDYSTONE_UID) + /** + * Set the content of Eddystone UID frames + * + * @param uid_namespace the uid namespace. Must 10 bytes long. + * + * @param uid_instance the uid instance value. Must 6 bytes long. + * + * @param calibratedPower the transmission range of the beacon (Defaults to: 0xF0 ~10m). + * + * @note The calibratedPower value ranges from -100 to +20 to a resolution of 1. The calibrated power should be binary encoded. + * More information can be found at https://github.com/google/eddystone/tree/master/eddystone-uid#tx-power + */ + int setUID(BLEDevice* ble, const char* uid_namespace, const char* uid_instance, int8_t calibratedPower = 0xF0); +#endif + + private: + + /** + * Private constructor. + */ + MicroBitEddystone(); + + static MicroBitEddystone *_instance; +}; + +#endif
--- a/inc/core/MicroBitConfig.h Wed Jul 13 14:32:54 2016 +0000 +++ b/inc/core/MicroBitConfig.h Wed Feb 08 07:49:17 2017 +0000 @@ -72,6 +72,35 @@ #define MICROBIT_HEAP_END (CORTEX_M0_STACK_BASE - MICROBIT_STACK_SIZE) #endif +// Defines the size of a physical FLASH page in RAM. +#ifndef PAGE_SIZE +#define PAGE_SIZE 1024 +#endif + +// Defines where in memory persistent data is stored. +#ifndef KEY_VALUE_STORE_PAGE +#define KEY_VALUE_STORE_PAGE (PAGE_SIZE * (NRF_FICR->CODESIZE - 17)) +#endif + +#ifndef BLE_BOND_DATA_PAGE +#define BLE_BOND_DATA_PAGE (PAGE_SIZE * (NRF_FICR->CODESIZE - 18)) +#endif + +#ifndef DEFAULT_SCRATCH_PAGE +#define DEFAULT_SCRATCH_PAGE (PAGE_SIZE * (NRF_FICR->CODESIZE - 19)) +#endif + +// Address of the end of the current program in FLASH memory. +// This is recorded by the C/C++ linker, but the symbol name varies depending on which compiler is used. +#if defined(__arm) +extern uint32_t Image$$ER_IROM1$$RO$$Limit; +#define FLASH_PROGRAM_END (uint32_t) (&Image$$ER_IROM1$$RO$$Limit) +#else +extern uint32_t __etext; +#define FLASH_PROGRAM_END (uint32_t) (&__etext) +#endif + + // Enables or disables the MicroBitHeapllocator. Note that if disabled, no reuse of the SRAM normally // reserved for SoftDevice is possible, and out of memory condition will no longer be trapped... // i.e. panic() will no longer be triggered on memory full conditions. @@ -237,6 +266,18 @@ #define MICROBIT_BLE_DFU_SERVICE 1 #endif +// Enable/Disable availability of Eddystone URL APIs +// Set '1' to enable. +#ifndef MICROBIT_BLE_EDDYSTONE_URL +#define MICROBIT_BLE_EDDYSTONE_URL 1 +#endif + +// Enable/Disable availability of Eddystone UID APIs +// Set '1' to enable. +#ifndef MICROBIT_BLE_EDDYSTONE_UID +#define MICROBIT_BLE_EDDYSTONE_UID 0 +#endif + // Enable/Disable BLE Service: MicroBitEventService // This allows routing of events from the micro:bit message bus over BLE. // Set '1' to enable. @@ -318,6 +359,26 @@ #define MICROBIT_DEFAULT_SERIAL_MODE SYNC_SLEEP #endif +// +// File System configuration defaults +// + +// +// Defines the logical block size for the file system. +// Must be a factor of the physical PAGE_SIZE (ideally a power of two less). +// +#ifndef MBFS_BLOCK_SIZE +#define MBFS_BLOCK_SIZE 256 +#endif + +// +// FileSystem writeback cache size, in bytes. Defines how many bytes will be stored +// in RAM before being written back to FLASH. Set to zero to disable this feature. +// Should be <= MBFS_BLOCK_SIZE. +// +#ifndef MBFS_CACHE_SIZE +#define MBFS_CACHE_SIZE 16 +#endif // // I/O Options @@ -384,4 +445,4 @@ extern RawSerial* SERIAL_DEBUG; #endif -#endif \ No newline at end of file +#endif
--- a/source/bluetooth/MicroBitBLEManager.cpp Wed Jul 13 14:32:54 2016 +0000 +++ b/source/bluetooth/MicroBitBLEManager.cpp Wed Feb 08 07:49:17 2017 +0000 @@ -25,9 +25,10 @@ #include "MicroBitConfig.h" #include "MicroBitBLEManager.h" +#include "MicroBitEddystone.h" #include "MicroBitStorage.h" #include "MicroBitFiber.h" - +#include "MicroBitSystemTimer.h" /* The underlying Nordic libraries that support BLE do not compile cleanly with the stringent GCC settings we employ. * If we're compiling under GCC, then we suppress any warnings generated from this code (but not the rest of the DAL) @@ -42,8 +43,7 @@ #include "ble.h" -extern "C" -{ +extern "C" { #include "device_manager.h" uint32_t btle_set_gatt_table_size(uint32_t size); } @@ -55,13 +55,28 @@ #pragma GCC diagnostic pop #endif -#define MICROBIT_PAIRING_FADE_SPEED 4 +#define MICROBIT_PAIRING_FADE_SPEED 4 -const char* MICROBIT_BLE_MANUFACTURER = NULL; -const char* MICROBIT_BLE_MODEL = "BBC micro:bit"; -const char* MICROBIT_BLE_HARDWARE_VERSION = NULL; -const char* MICROBIT_BLE_FIRMWARE_VERSION = MICROBIT_DAL_VERSION; -const char* MICROBIT_BLE_SOFTWARE_VERSION = NULL; +// +// Local enumeration of valid security modes. Used only to optimise pre‐processor comparisons. +// +#define __SECURITY_MODE_ENCRYPTION_OPEN_LINK 0 +#define __SECURITY_MODE_ENCRYPTION_NO_MITM 1 +#define __SECURITY_MODE_ENCRYPTION_WITH_MITM 2 +// +// Some Black Magic to compare the definition of our security mode in MicroBitConfig with a given parameter. +// Required as the MicroBitConfig option is actually an mbed enum, that is not normally comparable at compile time. +// + +#define __CAT(a, ...) a##__VA_ARGS__ +#define SECURITY_MODE(x) __CAT(__, x) +#define SECURITY_MODE_IS(x) (SECURITY_MODE(MICROBIT_BLE_SECURITY_LEVEL) == SECURITY_MODE(x)) + +const char *MICROBIT_BLE_MANUFACTURER = NULL; +const char *MICROBIT_BLE_MODEL = "BBC micro:bit"; +const char *MICROBIT_BLE_HARDWARE_VERSION = NULL; +const char *MICROBIT_BLE_FIRMWARE_VERSION = MICROBIT_DAL_VERSION; +const char *MICROBIT_BLE_SOFTWARE_VERSION = NULL; const int8_t MICROBIT_BLE_POWER_LEVEL[] = {-30, -20, -16, -12, -8, -4, 0, 4}; /* @@ -69,17 +84,18 @@ * So, we maintain a pointer to the MicroBitBLEManager that's in use. Ths way, we can still access resources on the micro:bit * whilst keeping the code modular. */ -static MicroBitBLEManager *manager = NULL; // Singleton reference to the BLE manager. many mbed BLE API callbacks still do not support member funcions yet. :-( -static uint8_t deviceID = 255; // Unique ID for the peer that has connected to us. -static Gap::Handle_t pairingHandle = 0; // The connection handle used during a pairing process. Used to ensure that connections are dropped elegantly. +MicroBitBLEManager *MicroBitBLEManager::manager = NULL; // Singleton reference to the BLE manager. many mbed BLE API callbacks still do not support member funcions yet. :-( + +static uint8_t deviceID = 255; // Unique ID for the peer that has connected to us. +static Gap::Handle_t pairingHandle = 0; // The connection handle used during a pairing process. Used to ensure that connections are dropped elegantly. static void storeSystemAttributes(Gap::Handle_t handle) { - if(manager->storage != NULL && deviceID < MICROBIT_BLE_MAXIMUM_BONDS) + if (MicroBitBLEManager::manager->storage != NULL && deviceID < MICROBIT_BLE_MAXIMUM_BONDS) { ManagedString key("bleSysAttrs"); - KeyValuePair* bleSysAttrs = manager->storage->get(key); + KeyValuePair *bleSysAttrs = MicroBitBLEManager::manager->storage->get(key); BLESysAttribute attrib; BLESysAttributeStore attribStore; @@ -89,17 +105,17 @@ sd_ble_gatts_sys_attr_get(handle, attrib.sys_attr, &len, BLE_GATTS_SYS_ATTR_FLAG_SYS_SRVCS); //copy our stored sysAttrs - if(bleSysAttrs != NULL) + if (bleSysAttrs != NULL) { memcpy(&attribStore, bleSysAttrs->value, sizeof(BLESysAttributeStore)); delete bleSysAttrs; } //check if we need to update - if(memcmp(attribStore.sys_attrs[deviceID].sys_attr, attrib.sys_attr, len) != 0) + if (memcmp(attribStore.sys_attrs[deviceID].sys_attr, attrib.sys_attr, len) != 0) { attribStore.sys_attrs[deviceID] = attrib; - manager->storage->put(key, (uint8_t *)&attribStore, sizeof(attribStore)); + MicroBitBLEManager::manager->storage->put(key, (uint8_t *)&attribStore, sizeof(attribStore)); } } } @@ -109,20 +125,20 @@ */ static void bleDisconnectionCallback(const Gap::DisconnectionCallbackParams_t *reason) { - MicroBitEvent(MICROBIT_ID_BLE,MICROBIT_BLE_EVT_DISCONNECTED); + MicroBitEvent(MICROBIT_ID_BLE, MICROBIT_BLE_EVT_DISCONNECTED); storeSystemAttributes(reason->handle); - if (manager) - manager->advertise(); + if (MicroBitBLEManager::manager) + MicroBitBLEManager::manager->advertise(); } /** * Callback when a BLE connection is established. */ -static void bleConnectionCallback(const Gap::ConnectionCallbackParams_t*) +static void bleConnectionCallback(const Gap::ConnectionCallbackParams_t *) { - MicroBitEvent(MICROBIT_ID_BLE,MICROBIT_BLE_EVT_CONNECTED); + MicroBitEvent(MICROBIT_ID_BLE, MICROBIT_BLE_EVT_CONNECTED); } /** @@ -133,23 +149,23 @@ int complete = 0; deviceID = 255; - dm_handle_t dm_handle = {0,0,0,0}; + dm_handle_t dm_handle = {0, 0, 0, 0}; int ret = dm_handle_get(params->connHandle, &dm_handle); if (ret == 0) deviceID = dm_handle.device_id; - if(manager->storage != NULL && deviceID < MICROBIT_BLE_MAXIMUM_BONDS) + if (MicroBitBLEManager::manager->storage != NULL && deviceID < MICROBIT_BLE_MAXIMUM_BONDS) { ManagedString key("bleSysAttrs"); - KeyValuePair* bleSysAttrs = manager->storage->get(key); + KeyValuePair *bleSysAttrs = MicroBitBLEManager::manager->storage->get(key); BLESysAttributeStore attribStore; BLESysAttribute attrib; - if(bleSysAttrs != NULL) + if (bleSysAttrs != NULL) { //restore our sysAttrStore memcpy(&attribStore, bleSysAttrs->value, sizeof(BLESysAttributeStore)); @@ -161,40 +177,39 @@ complete = 1; - if(ret == 0) + if (ret == 0) ret = sd_ble_gatts_service_changed(params->connHandle, 0x000c, 0xffff); } } if (!complete) sd_ble_gatts_sys_attr_set(params->connHandle, NULL, 0, 0); - } static void passkeyDisplayCallback(Gap::Handle_t handle, const SecurityManager::Passkey_t passkey) { - (void) handle; /* -Wunused-param */ + (void)handle; /* -Wunused-param */ - ManagedString passKey((const char *)passkey, SecurityManager::PASSKEY_LEN); + ManagedString passKey((const char *)passkey, SecurityManager::PASSKEY_LEN); - if (manager) - manager->pairingRequested(passKey); + if (MicroBitBLEManager::manager) + MicroBitBLEManager::manager->pairingRequested(passKey); } static void securitySetupCompletedCallback(Gap::Handle_t handle, SecurityManager::SecurityCompletionStatus_t status) { - (void) handle; /* -Wunused-param */ + (void)handle; /* -Wunused-param */ - dm_handle_t dm_handle = {0,0,0,0}; + dm_handle_t dm_handle = {0, 0, 0, 0}; int ret = dm_handle_get(handle, &dm_handle); if (ret == 0) deviceID = dm_handle.device_id; - if (manager) + if (MicroBitBLEManager::manager) { pairingHandle = handle; - manager->pairingComplete(status == SecurityManager::SEC_STATUS_SUCCESS); + MicroBitBLEManager::manager->pairingComplete(status == SecurityManager::SEC_STATUS_SUCCESS); } } @@ -208,12 +223,11 @@ * @note The BLE stack *cannot* be brought up in a static context (the software simply hangs or corrupts itself). * Hence, the init() member function should be used to initialise the BLE stack. */ -MicroBitBLEManager::MicroBitBLEManager(MicroBitStorage& _storage) : - storage(&_storage) +MicroBitBLEManager::MicroBitBLEManager(MicroBitStorage &_storage) : storage(&_storage) { manager = this; - this->ble = NULL; - this->pairingStatus = 0; + this->ble = NULL; + this->pairingStatus = 0; } /** @@ -224,12 +238,24 @@ * @note The BLE stack *cannot* be brought up in a static context (the software simply hangs or corrupts itself). * Hence, the init() member function should be used to initialise the BLE stack. */ -MicroBitBLEManager::MicroBitBLEManager() : - storage(NULL) +MicroBitBLEManager::MicroBitBLEManager() : storage(NULL) { manager = this; - this->ble = NULL; - this->pairingStatus = 0; + this->ble = NULL; + this->pairingStatus = 0; +} + +/** + * When called, the micro:bit will begin advertising for a predefined period, + * MICROBIT_BLE_ADVERTISING_TIMEOUT seconds to bonded devices. + */ +MicroBitBLEManager *MicroBitBLEManager::getInstance() +{ + if (manager == 0) + { + manager = new MicroBitBLEManager; + } + return manager; } /** @@ -238,7 +264,7 @@ */ void MicroBitBLEManager::advertise() { - if(ble) + if (ble) ble->gap().startAdvertising(); } @@ -255,18 +281,18 @@ * bleManager.init(uBit.getName(), uBit.getSerial(), uBit.messageBus, true); * @endcode */ -void MicroBitBLEManager::init(ManagedString deviceName, ManagedString serialNumber, EventModel& messageBus, bool enableBonding) +void MicroBitBLEManager::init(ManagedString deviceName, ManagedString serialNumber, EventModel &messageBus, bool enableBonding) { - ManagedString BLEName("BBC micro:bit"); - this->deviceName = deviceName; + ManagedString BLEName("BBC micro:bit"); + this->deviceName = deviceName; #if !(CONFIG_ENABLED(MICROBIT_BLE_WHITELIST)) - ManagedString namePrefix(" ["); - ManagedString namePostfix("]"); - BLEName = BLEName + namePrefix + deviceName + namePostfix; + ManagedString namePrefix(" ["); + ManagedString namePostfix("]"); + BLEName = BLEName + namePrefix + deviceName + namePostfix; #endif - // Start the BLE stack. +// Start the BLE stack. #if CONFIG_ENABLED(MICROBIT_HEAP_REUSE_SD) btle_set_gatt_table_size(MICROBIT_SD_GATT_TABLE_SIZE); #endif @@ -289,14 +315,26 @@ sd_ble_opt_set(BLE_COMMON_OPT_RADIO_CPU_MUTEX, (const ble_opt_t *)&opt); #if CONFIG_ENABLED(MICROBIT_BLE_PRIVATE_ADDRESSES) - // Configure for private addresses, so kids' behaviour can't be easily tracked. - ble->gap().setAddress(BLEProtocol::AddressType::RANDOM_PRIVATE_RESOLVABLE, {0}); + // Configure for private addresses, so kids' behaviour can't be easily tracked. + ble->gap().setAddress(BLEProtocol::AddressType::RANDOM_PRIVATE_RESOLVABLE, {0}); #endif // Setup our security requirements. ble->securityManager().onPasskeyDisplay(passkeyDisplayCallback); ble->securityManager().onSecuritySetupCompleted(securitySetupCompletedCallback); - ble->securityManager().init(enableBonding, (SecurityManager::MICROBIT_BLE_SECURITY_LEVEL == SecurityManager::SECURITY_MODE_ENCRYPTION_WITH_MITM), SecurityManager::IO_CAPS_DISPLAY_ONLY); +// @bluetooth_mdw: select either passkey pairing (more secure), "just works" pairing (less secure but nice and simple for the user) +// or no security +// Default to passkey pairing with MITM protection +#if (SECURITY_MODE_IS(SECURITY_MODE_ENCRYPTION_NO_MITM)) + // Just Works + ble->securityManager().init(enableBonding, false, SecurityManager::IO_CAPS_NONE); +#elif (SECURITY_MODE_IS(SECURITY_MODE_ENCRYPTION_OPEN_LINK)) + // no security + ble->securityManager().init(false, false, SecurityManager::IO_CAPS_DISPLAY_ONLY); +#else + // passkey + ble->securityManager().init(enableBonding, true, SecurityManager::IO_CAPS_DISPLAY_ONLY); +#endif if (enableBonding) { @@ -329,13 +367,13 @@ // Configure the radio at our default power level setTransmitPower(MICROBIT_BLE_DEFAULT_TX_POWER); - // Bring up core BLE services. +// Bring up core BLE services. #if CONFIG_ENABLED(MICROBIT_BLE_DFU_SERVICE) new MicroBitDFUService(*ble); #endif #if CONFIG_ENABLED(MICROBIT_BLE_DEVICE_INFORMATION_SERVICE) - DeviceInformationService ble_device_information_service (*ble, MICROBIT_BLE_MANUFACTURER, MICROBIT_BLE_MODEL, serialNumber.toCharArray(), MICROBIT_BLE_HARDWARE_VERSION, MICROBIT_BLE_FIRMWARE_VERSION, MICROBIT_BLE_SOFTWARE_VERSION); + DeviceInformationService ble_device_information_service(*ble, MICROBIT_BLE_MANUFACTURER, MICROBIT_BLE_MODEL, serialNumber.toCharArray(), MICROBIT_BLE_HARDWARE_VERSION, MICROBIT_BLE_FIRMWARE_VERSION, MICROBIT_BLE_SOFTWARE_VERSION); #else (void)serialNumber; #endif @@ -346,16 +384,15 @@ (void)messageBus; #endif - // Configure for high speed mode where possible. Gap::ConnectionParams_t fast; ble->getPreferredConnectionParams(&fast); - fast.minConnectionInterval = 8; // 10 ms + fast.minConnectionInterval = 8; // 10 ms fast.maxConnectionInterval = 16; // 20 ms fast.slaveLatency = 0; ble->setPreferredConnectionParams(&fast); - // Setup advertising. +// Setup advertising. #if CONFIG_ENABLED(MICROBIT_BLE_WHITELIST) ble->accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED); #else @@ -370,9 +407,9 @@ ble->gap().setAdvertisingTimeout(MICROBIT_BLE_ADVERTISING_TIMEOUT); #endif - // If we have whitelisting enabled, then prevent only enable advertising of we have any binded devices... - // This is to further protect kids' privacy. If no-one initiates BLE, then the device is unreachable. - // If whiltelisting is disabled, then we always advertise. +// If we have whitelisting enabled, then prevent only enable advertising of we have any binded devices... +// This is to further protect kids' privacy. If no-one initiates BLE, then the device is unreachable. +// If whiltelisting is disabled, then we always advertise. #if CONFIG_ENABLED(MICROBIT_BLE_WHITELIST) if (whitelist.size > 0) #endif @@ -427,8 +464,8 @@ void MicroBitBLEManager::pairingRequested(ManagedString passKey) { // Update our mode to display the passkey. - this->passKey = passKey; - this->pairingStatus = MICROBIT_BLE_PAIR_REQUEST; + this->passKey = passKey; + this->pairingStatus = MICROBIT_BLE_PAIR_REQUEST; } /** @@ -439,11 +476,13 @@ */ void MicroBitBLEManager::pairingComplete(bool success) { - this->pairingStatus = MICROBIT_BLE_PAIR_COMPLETE; + this->pairingStatus = MICROBIT_BLE_PAIR_COMPLETE; + + pairing_completed_at_time = system_timer_current_time(); - if(success) + if (success) { - this->pairingStatus |= MICROBIT_BLE_PAIR_SUCCESSFUL; + this->pairingStatus |= MICROBIT_BLE_PAIR_SUCCESSFUL; fiber_add_idle_component(this); } } @@ -454,11 +493,115 @@ */ void MicroBitBLEManager::idleTick() { - if (ble) - ble->disconnect(pairingHandle, Gap::REMOTE_DEV_TERMINATION_DUE_TO_POWER_OFF); + if((system_timer_current_time() - pairing_completed_at_time) >= MICROBIT_BLE_DISCONNECT_AFTER_PAIRING_DELAY) { + if (ble) + ble->disconnect(pairingHandle, Gap::REMOTE_DEV_TERMINATION_DUE_TO_POWER_OFF); + fiber_remove_idle_component(this); + } + +} + + +/** +* Stops any currently running BLE advertisements +*/ +void MicroBitBLEManager::stopAdvertising() +{ + ble->gap().stopAdvertising(); +} + +#if CONFIG_ENABLED(MICROBIT_BLE_EDDYSTONE_URL) +/** + * Set the content of Eddystone URL frames + * + * @param url The url to broadcast + * + * @param calibratedPower the transmission range of the beacon (Defaults to: 0xF0 ~10m). + * + * @param connectable true to keep bluetooth connectable for other services, false otherwise. (Defaults to true) + * + * @param interval the rate at which the micro:bit will advertise url frames. (Defaults to MICROBIT_BLE_EDDYSTONE_ADV_INTERVAL) + * + * @note The calibratedPower value ranges from -100 to +20 to a resolution of 1. The calibrated power should be binary encoded. + * More information can be found at https://github.com/google/eddystone/tree/master/eddystone-uid#tx-power + */ +int MicroBitBLEManager::advertiseEddystoneUrl(const char* url, int8_t calibratedPower, bool connectable, uint16_t interval) +{ + ble->gap().stopAdvertising(); + ble->clearAdvertisingPayload(); + + ble->setAdvertisingType(connectable ? GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED : GapAdvertisingParams::ADV_NON_CONNECTABLE_UNDIRECTED); + ble->setAdvertisingInterval(interval); + + ble->accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE); + + int ret = MicroBitEddystone::getInstance()->setURL(ble, url, calibratedPower); + +#if (MICROBIT_BLE_ADVERTISING_TIMEOUT > 0) + ble->gap().setAdvertisingTimeout(MICROBIT_BLE_ADVERTISING_TIMEOUT); +#endif + ble->gap().startAdvertising(); + + return ret; +} - fiber_remove_idle_component(this); +/** + * Set the content of Eddystone URL frames, but accepts a ManagedString as a url. + * + * @param url The url to broadcast + * + * @param calibratedPower the transmission range of the beacon (Defaults to: 0xF0 ~10m). + * + * @param connectable true to keep bluetooth connectable for other services, false otherwise. (Defaults to true) + * + * @param interval the rate at which the micro:bit will advertise url frames. (Defaults to MICROBIT_BLE_EDDYSTONE_ADV_INTERVAL) + * + * @note The calibratedPower value ranges from -100 to +20 to a resolution of 1. The calibrated power should be binary encoded. + * More information can be found at https://github.com/google/eddystone/tree/master/eddystone-uid#tx-power + */ +int MicroBitBLEManager::advertiseEddystoneUrl(ManagedString url, int8_t calibratedPower, bool connectable, uint16_t interval) +{ + return advertiseEddystoneUrl((char *)url.toCharArray(), calibratedPower, connectable, interval); } +#endif + +#if CONFIG_ENABLED(MICROBIT_BLE_EDDYSTONE_UID) +/** + * Set the content of Eddystone UID frames + * + * @param uid_namespace: the uid namespace. Must 10 bytes long. + * + * @param uid_instance: the uid instance value. Must 6 bytes long. + * + * @param calibratedPower the transmission range of the beacon (Defaults to: 0xF0 ~10m). + * + * @param connectable true to keep bluetooth connectable for other services, false otherwise. (Defaults to true) + * + * @param interval the rate at which the micro:bit will advertise url frames. (Defaults to MICROBIT_BLE_EDDYSTONE_ADV_INTERVAL) + * + * @note The calibratedPower value ranges from -100 to +20 to a resolution of 1. The calibrated power should be binary encoded. + * More information can be found at https://github.com/google/eddystone/tree/master/eddystone-uid#tx-power + */ +int MicroBitBLEManager::advertiseEddystoneUid(const char* uid_namespace, const char* uid_instance, int8_t calibratedPower, bool connectable, uint16_t interval) +{ + ble->gap().stopAdvertising(); + ble->clearAdvertisingPayload(); + + ble->setAdvertisingType(connectable ? GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED : GapAdvertisingParams::ADV_NON_CONNECTABLE_UNDIRECTED); + ble->setAdvertisingInterval(interval); + + ble->accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE); + + int ret = MicroBitEddystone::getInstance()->setUID(ble, uid_namespace, uid_instance, calibratedPower); + +#if (MICROBIT_BLE_ADVERTISING_TIMEOUT > 0) + ble->gap().setAdvertisingTimeout(MICROBIT_BLE_ADVERTISING_TIMEOUT); +#endif + ble->gap().startAdvertising(); + + return ret; +} +#endif /** * Enter pairing mode. This is mode is called to initiate pairing, and to enable FOTA programming @@ -472,21 +615,21 @@ * bleManager.pairingMode(uBit.display, uBit.buttonA); * @endcode */ -void MicroBitBLEManager::pairingMode(MicroBitDisplay& display, MicroBitButton& authorisationButton) +void MicroBitBLEManager::pairingMode(MicroBitDisplay &display, MicroBitButton &authorisationButton) { - ManagedString namePrefix("BBC micro:bit ["); - ManagedString namePostfix("]"); - ManagedString BLEName = namePrefix + deviceName + namePostfix; + ManagedString namePrefix("BBC micro:bit ["); + ManagedString namePostfix("]"); + ManagedString BLEName = namePrefix + deviceName + namePostfix; - ManagedString msg("PAIRING MODE!"); + ManagedString msg("PAIRING MODE!"); - int timeInPairingMode = 0; - int brightness = 255; - int fadeDirection = 0; + int timeInPairingMode = 0; + int brightness = 255; + int fadeDirection = 0; ble->gap().stopAdvertising(); - // Clear the whitelist (if we have one), so that we're discoverable by all BLE devices. +// Clear the whitelist (if we have one), so that we're discoverable by all BLE devices. #if CONFIG_ENABLED(MICROBIT_BLE_WHITELIST) BLEProtocol::Address_t addresses[MICROBIT_BLE_MAXIMUM_BONDS]; Gap::Whitelist_t whitelist; @@ -497,7 +640,7 @@ ble->gap().setAdvertisingPolicyMode(Gap::ADV_POLICY_IGNORE_WHITELIST); #endif - // Update the advertised name of this micro:bit to include the device name + // Update the advertised name of this micro:bit to include the device name ble->clearAdvertisingPayload(); ble->accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE); @@ -508,68 +651,68 @@ ble->gap().setAdvertisingTimeout(0); ble->gap().startAdvertising(); - // Stop any running animations on the display - display.stopAnimation(); - display.scroll(msg); + // Stop any running animations on the display + display.stopAnimation(); + display.scroll(msg); - // Display our name, visualised as a histogram in the display to aid identification. - showNameHistogram(display); + // Display our name, visualised as a histogram in the display to aid identification. + showNameHistogram(display); - while(1) - { - if (pairingStatus & MICROBIT_BLE_PAIR_REQUEST) - { - timeInPairingMode = 0; - MicroBitImage arrow("0,0,255,0,0\n0,255,0,0,0\n255,255,255,255,255\n0,255,0,0,0\n0,0,255,0,0\n"); - display.print(arrow,0,0,0); + while (1) + { + if (pairingStatus & MICROBIT_BLE_PAIR_REQUEST) + { + timeInPairingMode = 0; + MicroBitImage arrow("0,0,255,0,0\n0,255,0,0,0\n255,255,255,255,255\n0,255,0,0,0\n0,0,255,0,0\n"); + display.print(arrow, 0, 0, 0); - if (fadeDirection == 0) - brightness -= MICROBIT_PAIRING_FADE_SPEED; - else - brightness += MICROBIT_PAIRING_FADE_SPEED; + if (fadeDirection == 0) + brightness -= MICROBIT_PAIRING_FADE_SPEED; + else + brightness += MICROBIT_PAIRING_FADE_SPEED; - if (brightness <= 40) - display.clear(); + if (brightness <= 40) + display.clear(); - if (brightness <= 0) - fadeDirection = 1; + if (brightness <= 0) + fadeDirection = 1; - if (brightness >= 255) - fadeDirection = 0; + if (brightness >= 255) + fadeDirection = 0; - if (authorisationButton.isPressed()) - { - pairingStatus &= ~MICROBIT_BLE_PAIR_REQUEST; - pairingStatus |= MICROBIT_BLE_PAIR_PASSCODE; - } - } + if (authorisationButton.isPressed()) + { + pairingStatus &= ~MICROBIT_BLE_PAIR_REQUEST; + pairingStatus |= MICROBIT_BLE_PAIR_PASSCODE; + } + } - if (pairingStatus & MICROBIT_BLE_PAIR_PASSCODE) - { - timeInPairingMode = 0; - display.setBrightness(255); - for (int i=0; i<passKey.length(); i++) - { - display.image.print(passKey.charAt(i),0,0); - fiber_sleep(800); - display.clear(); - fiber_sleep(200); + if (pairingStatus & MICROBIT_BLE_PAIR_PASSCODE) + { + timeInPairingMode = 0; + display.setBrightness(255); + for (int i = 0; i < passKey.length(); i++) + { + display.image.print(passKey.charAt(i), 0, 0); + fiber_sleep(800); + display.clear(); + fiber_sleep(200); - if (pairingStatus & MICROBIT_BLE_PAIR_COMPLETE) - break; - } + if (pairingStatus & MICROBIT_BLE_PAIR_COMPLETE) + break; + } - fiber_sleep(1000); - } + fiber_sleep(1000); + } - if (pairingStatus & MICROBIT_BLE_PAIR_COMPLETE) - { - if (pairingStatus & MICROBIT_BLE_PAIR_SUCCESSFUL) - { - MicroBitImage tick("0,0,0,0,0\n0,0,0,0,255\n0,0,0,255,0\n255,0,255,0,0\n0,255,0,0,0\n"); - display.print(tick,0,0,0); + if (pairingStatus & MICROBIT_BLE_PAIR_COMPLETE) + { + if (pairingStatus & MICROBIT_BLE_PAIR_SUCCESSFUL) + { + MicroBitImage tick("0,0,0,0,0\n0,0,0,0,255\n0,0,0,255,0\n255,0,255,0,0\n0,255,0,0,0\n"); + display.print(tick, 0, 0, 0); fiber_sleep(15000); - timeInPairingMode = MICROBIT_BLE_PAIRING_TIMEOUT * 30; + timeInPairingMode = MICROBIT_BLE_PAIRING_TIMEOUT * 30; /* * Disabled, as the API to return the number of active bonds is not reliable at present... @@ -584,20 +727,20 @@ * * */ - } - else - { - MicroBitImage cross("255,0,0,0,255\n0,255,0,255,0\n0,0,255,0,0\n0,255,0,255,0\n255,0,0,0,255\n"); - display.print(cross,0,0,0); - } - } + } + else + { + MicroBitImage cross("255,0,0,0,255\n0,255,0,255,0\n0,0,255,0,0\n0,255,0,255,0\n255,0,0,0,255\n"); + display.print(cross, 0, 0, 0); + } + } - fiber_sleep(100); - timeInPairingMode++; + fiber_sleep(100); + timeInPairingMode++; - if (timeInPairingMode >= MICROBIT_BLE_PAIRING_TIMEOUT * 30) - microbit_reset(); - } + if (timeInPairingMode >= MICROBIT_BLE_PAIRING_TIMEOUT * 30) + microbit_reset(); + } } /** @@ -613,7 +756,7 @@ int h; display.clear(); - for (int i=0; i<MICROBIT_DFU_HISTOGRAM_WIDTH;i++) + for (int i = 0; i < MICROBIT_DFU_HISTOGRAM_WIDTH; i++) { h = (n % d) / ld; @@ -621,7 +764,7 @@ d *= MICROBIT_DFU_HISTOGRAM_HEIGHT; ld *= MICROBIT_DFU_HISTOGRAM_HEIGHT; - for (int j=0; j<h+1; j++) - display.image.setPixelValue(MICROBIT_DFU_HISTOGRAM_WIDTH-i-1, MICROBIT_DFU_HISTOGRAM_HEIGHT-j-1, 255); + for (int j = 0; j < h + 1; j++) + display.image.setPixelValue(MICROBIT_DFU_HISTOGRAM_WIDTH - i - 1, MICROBIT_DFU_HISTOGRAM_HEIGHT - j - 1, 255); } -} \ No newline at end of file +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/source/bluetooth/MicroBitEddystone.cpp Wed Feb 08 07:49:17 2017 +0000 @@ -0,0 +1,215 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#include "MicroBitConfig.h" +#include "MicroBitEddystone.h" + +MicroBitEddystone *MicroBitEddystone::_instance = NULL; + +/* The underlying Nordic libraries that support BLE do not compile cleanly with the stringent GCC settings we employ. + * If we're compiling under GCC, then we suppress any warnings generated from this code (but not the rest of the DAL) + * The ARM cc compiler is more tolerant. We don't test __GNUC__ here to detect GCC as ARMCC also typically sets this + * as a compatability option, but does not support the options used... + */ +#if !defined(__arm) +#pragma GCC diagnostic ignored "-Wunused-function" +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" +#endif + +/* + * Return to our predefined compiler settings. + */ +#if !defined(__arm) +#pragma GCC diagnostic pop +#endif + +const uint8_t EDDYSTONE_UUID[] = {0xAA, 0xFE}; + +#if CONFIG_ENABLED(MICROBIT_BLE_EDDYSTONE_URL) +const char *EDDYSTONE_URL_PREFIXES[] = {"http://www.", "https://www.", "http://", "https://"}; +const size_t EDDYSTONE_URL_PREFIXES_LENGTH = sizeof(EDDYSTONE_URL_PREFIXES) / sizeof(char *); +const char *EDDYSTONE_URL_SUFFIXES[] = {".com/", ".org/", ".edu/", ".net/", ".info/", ".biz/", ".gov/", ".com", ".org", ".edu", ".net", ".info", ".biz", ".gov"}; +const size_t EDDYSTONE_URL_SUFFIXES_LENGTH = sizeof(EDDYSTONE_URL_SUFFIXES) / sizeof(char *); +const int EDDYSTONE_URL_MAX_LENGTH = 18; +const uint8_t EDDYSTONE_URL_FRAME_TYPE = 0x10; +#endif + +#if CONFIG_ENABLED(MICROBIT_BLE_EDDYSTONE_UID) +const int EDDYSTONE_UID_NAMESPACE_MAX_LENGTH = 10; +const int EDDYSTONE_UID_INSTANCE_MAX_LENGTH = 6; +const uint8_t EDDYSTONE_UID_FRAME_TYPE = 0x00; +#endif + +/** + * Constructor. + * + * Configure and manage the micro:bit's Bluetooth Low Energy (BLE) stack. + * + * @param _storage an instance of MicroBitStorage used to persist sys attribute information. (This is required for compatability with iOS). + * + * @note The BLE stack *cannot* be brought up in a static context (the software simply hangs or corrupts itself). + * Hence, the init() member function should be used to initialise the BLE stack. + */ +MicroBitEddystone::MicroBitEddystone() +{ +} + +MicroBitEddystone* MicroBitEddystone::getInstance() +{ + if (_instance == 0) + _instance = new MicroBitEddystone; + + return _instance; +} + +#if CONFIG_ENABLED(MICROBIT_BLE_EDDYSTONE_URL) + +/** + * Set the content of Eddystone URL frames + * + * @param url The url to broadcast + * + * @param calibratedPower the transmission range of the beacon (Defaults to: 0xF0 ~10m). + * + * @note The calibratedPower value ranges from -100 to +20 to a resolution of 1. The calibrated power should be binary encoded. + * More information can be found at https://github.com/google/eddystone/tree/master/eddystone-uid#tx-power + */ +int MicroBitEddystone::setURL(BLEDevice* ble, const char* url, int8_t calibratedPower) +{ + int urlDataLength = 0; + char urlData[EDDYSTONE_URL_MAX_LENGTH]; + memset(urlData, 0, EDDYSTONE_URL_MAX_LENGTH); + + if (url == NULL || strlen(url) == 0) + return MICROBIT_INVALID_PARAMETER; + + // Prefix + for (size_t i = 0; i < EDDYSTONE_URL_PREFIXES_LENGTH; i++) + { + size_t prefixLen = strlen(EDDYSTONE_URL_PREFIXES[i]); + if (strncmp(url, EDDYSTONE_URL_PREFIXES[i], prefixLen) == 0) + { + urlData[urlDataLength++] = i; + url += prefixLen; + break; + } + } + + // Suffix + while (*url && (urlDataLength < EDDYSTONE_URL_MAX_LENGTH)) + { + size_t i; + for (i = 0; i < EDDYSTONE_URL_SUFFIXES_LENGTH; i++) + { + size_t suffixLen = strlen(EDDYSTONE_URL_SUFFIXES[i]); + if (strncmp(url, EDDYSTONE_URL_SUFFIXES[i], suffixLen) == 0) + { + urlData[urlDataLength++] = i; + url += suffixLen; + break; + } + } + + // Catch the default case where the suffix doesn't match a preset ones + if (i == EDDYSTONE_URL_SUFFIXES_LENGTH) + { + urlData[urlDataLength++] = *url; + ++url; + } + } + + uint8_t rawFrame[EDDYSTONE_URL_MAX_LENGTH + 4]; + size_t index = 0; + rawFrame[index++] = EDDYSTONE_UUID[0]; + rawFrame[index++] = EDDYSTONE_UUID[1]; + rawFrame[index++] = EDDYSTONE_URL_FRAME_TYPE; + rawFrame[index++] = calibratedPower; + memcpy(rawFrame + index, urlData, urlDataLength); + + ble->accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_16BIT_SERVICE_IDS, EDDYSTONE_UUID, sizeof(EDDYSTONE_UUID)); + ble->accumulateAdvertisingPayload(GapAdvertisingData::SERVICE_DATA, rawFrame, index + urlDataLength); + + return MICROBIT_OK; +} + +/** + * Set the content of Eddystone URL frames, but accepts a ManagedString as a url. + * + * @param url The url to broadcast + * + * @param calibratedPower the transmission range of the beacon (Defaults to: 0xF0 ~10m). + * + * @note The calibratedPower value ranges from -100 to +20 to a resolution of 1. The calibrated power should be binary encoded. + * More information can be found at https://github.com/google/eddystone/tree/master/eddystone-uid#tx-power + */ +int MicroBitEddystone::setURL(BLEDevice* ble, ManagedString url, int8_t calibratedPower) +{ + return setURL(ble, (char *)url.toCharArray(), calibratedPower); +} +#endif + +#if CONFIG_ENABLED(MICROBIT_BLE_EDDYSTONE_UID) + +/** + * Set the content of Eddystone UID frames + * + * @param uid_namespace the uid namespace. Must 10 bytes long. + * + * @param uid_instance the uid instance value. Must 6 bytes long. + * + * @param calibratedPower the transmission range of the beacon (Defaults to: 0xF0 ~10m). + * + * @note The calibratedPower value ranges from -100 to +20 to a resolution of 1. The calibrated power should be binary encoded. + * More information can be found at https://github.com/google/eddystone/tree/master/eddystone-uid#tx-power + */ +int MicroBitEddystone::setUID(BLEDevice* ble, const char* uid_namespace, const char* uid_instance, int8_t calibratedPower) +{ + if (uid_namespace == NULL || uid_instance == NULL) + return MICROBIT_INVALID_PARAMETER; + + char uidData[EDDYSTONE_UID_NAMESPACE_MAX_LENGTH + EDDYSTONE_UID_INSTANCE_MAX_LENGTH]; + + // UID namespace + memcpy(uidData, uid_namespace, EDDYSTONE_UID_NAMESPACE_MAX_LENGTH); + + // UID instance + memcpy(uidData + EDDYSTONE_UID_NAMESPACE_MAX_LENGTH, uid_instance, EDDYSTONE_UID_INSTANCE_MAX_LENGTH); + + uint8_t rawFrame[EDDYSTONE_UID_NAMESPACE_MAX_LENGTH + EDDYSTONE_UID_INSTANCE_MAX_LENGTH + 4]; + size_t index = 0; + rawFrame[index++] = EDDYSTONE_UUID[0]; + rawFrame[index++] = EDDYSTONE_UUID[1]; + rawFrame[index++] = EDDYSTONE_UID_FRAME_TYPE; + rawFrame[index++] = calibratedPower; + memcpy(rawFrame + index, uidData, EDDYSTONE_UID_NAMESPACE_MAX_LENGTH + EDDYSTONE_UID_INSTANCE_MAX_LENGTH); + + ble->accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_16BIT_SERVICE_IDS, EDDYSTONE_UUID, sizeof(EDDYSTONE_UUID)); + ble->accumulateAdvertisingPayload(GapAdvertisingData::SERVICE_DATA, rawFrame, index + EDDYSTONE_UID_NAMESPACE_MAX_LENGTH + EDDYSTONE_UID_INSTANCE_MAX_LENGTH); + + return MICROBIT_OK; +} + +#endif