/** * Post constructor initialisation method. * After *MUCH* pain, it's noted that the BLE stack can't be brought up in a * static context, so we bring it up here rather than in the constructor. * n.b. This method *must* be called in main() or later, not before. * * Example: * @code * uBit.init(); * @endcode */ void MicroBitBLEManager::init(ManagedString deviceName, ManagedString serialNumber, bool enableBonding) { ManagedString BLEName("BBC micro:bit"); this->deviceName = deviceName; // Start the BLE stack. ble = new BLEDevice(); ble->init(); // automatically restart advertising after a device disconnects. ble->onDisconnection(bleDisconnectionCallback); ble->onConnection(bleConnectionCallback); #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}); #endif // Setup our security requirements. ble->securityManager().onPasskeyDisplay(passkeyDisplayCallback); ble->securityManager().onSecuritySetupCompleted(securitySetupCompletedCallback); ble->securityManager().init(enableBonding, MICROBIT_BLE_REQUIRE_MITM, SecurityManager::IO_CAPS_DISPLAY_ONLY); // If we're in pairing mode, review the size of the bond table. if (enableBonding) { // TODO: It would be much better to implement some sort of LRU/NFU policy here, // but this isn't currently supported in mbed, so we'd need to layer break... int bonds = getBondCount(); // If we're full, empty the bond table. if (bonds >= MICROBIT_BLE_MAXIMUM_BONDS) ble->securityManager().purgeAllBondingState(); } #if CONFIG_ENABLED(MICROBIT_BLE_WHITELIST) // Configure a whitelist to filter all connection requetss from unbonded devices. // Most BLE stacks only permit one connection at a time, so this prevents denial of service attacks. BLEProtocol::Address_t bondedAddresses[MICROBIT_BLE_MAXIMUM_BONDS]; Gap::Whitelist_t whitelist; whitelist.addresses = bondedAddresses; whitelist.capacity = MICROBIT_BLE_MAXIMUM_BONDS; ble->securityManager().getAddressesFromBondTable(whitelist); ble->gap().setWhitelist(whitelist); ble->gap().setScanningPolicyMode(Gap::SCAN_POLICY_IGNORE_WHITELIST); ble->gap().setAdvertisingPolicyMode(Gap::ADV_POLICY_FILTER_CONN_REQS); #endif // Configure the radio at our default power level setTransmitPower(MICROBIT_BLE_DEFAULT_TX_POWER); // Bring up any configured auxiliary 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); #endif #if CONFIG_ENABLED(MICROBIT_BLE_EVENT_SERVICE) new MicroBitEventService(*ble); #endif #if CONFIG_ENABLED(MICROBIT_BLE_LED_SERVICE) new MicroBitLEDService(*ble); #endif #if CONFIG_ENABLED(MICROBIT_BLE_ACCELEROMETER_SERVICE) new MicroBitAccelerometerService(*ble); #endif #if CONFIG_ENABLED(MICROBIT_BLE_MAGNETOMETER_SERVICE) new MicroBitMagnetometerService(*ble); #endif #if CONFIG_ENABLED(MICROBIT_BLE_BUTTON_SERVICE) new MicroBitButtonService(*ble); #endif #if CONFIG_ENABLED(MICROBIT_BLE_IO_PIN_SERVICE) new MicroBitIOPinService(*ble); #endif #if CONFIG_ENABLED(MICROBIT_BLE_TEMPERATURE_SERVICE) new MicroBitTemperatureService(*ble); #endif // Configure for high speed mode where possible. Gap::ConnectionParams_t fast; ble->getPreferredConnectionParams(&fast); fast.minConnectionInterval = 8; // 10 ms fast.maxConnectionInterval = 16; // 20 ms fast.slaveLatency = 0; ble->setPreferredConnectionParams(&fast); // Setup advertising. #if CONFIG_ENABLED(MICROBIT_BLE_WHITELIST) ble->accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED); #else ble->accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE); #endif ble->accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LOCAL_NAME, (uint8_t *)BLEName.toCharArray(), BLEName.length()); ble->setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED); ble->setAdvertisingInterval(200); #if (MICROBIT_BLE_ADVERTISING_TIMEOUT > 0) 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 CONFIG_ENABLED(MICROBIT_BLE_WHITELIST) if (whitelist.size > 0) #endif ble->startAdvertising(); }
/** * Enter pairing mode. This is mode is called to initiate pairing, and to enable FOTA programming * of the micro:bit in cases where BLE is disabled during normal operation. */ void MicroBitBLEManager::pairingMode(MicroBitDisplay &display) { ManagedString namePrefix("BBC micro:bit ["); ManagedString namePostfix("]"); ManagedString BLEName = namePrefix + deviceName + namePostfix; ManagedString msg("PAIRING MODE!"); 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. #if CONFIG_ENABLED(MICROBIT_BLE_WHITELIST) BLEProtocol::Address_t addresses[MICROBIT_BLE_MAXIMUM_BONDS]; Gap::Whitelist_t whitelist; whitelist.addresses = addresses; whitelist.capacity = MICROBIT_BLE_MAXIMUM_BONDS; whitelist.size = 0; ble->gap().setWhitelist(whitelist); ble->gap().setAdvertisingPolicyMode(Gap::ADV_POLICY_IGNORE_WHITELIST); #endif // 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); ble->accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LOCAL_NAME, (uint8_t *)BLEName.toCharArray(), BLEName.length()); ble->setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED); ble->setAdvertisingInterval(200); ble->gap().setAdvertisingTimeout(0); ble->gap().startAdvertising(); // 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); 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 (brightness <= 40) display.clear(); if (brightness <= 0) fadeDirection = 1; if (brightness >= 255) fadeDirection = 0; if (uBit.buttonA.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); uBit.sleep(800); display.clear(); uBit.sleep(200); if (pairingStatus & MICROBIT_BLE_PAIR_COMPLETE) break; } uBit.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); uBit.sleep(5000); /* * Disabled, as the API to return the number of active bonds is not reliable at present... * display.clear(); ManagedString c(getBondCount()); ManagedString c2("/"); ManagedString c3(MICROBIT_BLE_MAXIMUM_BONDS); ManagedString c4("USED"); display.scroll(c+c2+c3+c4); * * */ } 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); } } uBit.sleep(30); timeInPairingMode++; if (timeInPairingMode >= MICROBIT_BLE_PAIRING_TIMEOUT * 30) microbit_reset(); } }
/** * Copies characters into the buffer used for Transmitting to the central device. * * @param s the string to transmit * @param mode the selected mode, one of: ASYNC, SYNC_SPINWAIT, SYNC_SLEEP. Each mode * gives a different behaviour: * * ASYNC - Will copy as many characters as it can into the buffer for transmission, * and return control to the user. * * SYNC_SPINWAIT - will return MICROBIT_INVALID_PARAMETER * * SYNC_SLEEP - Will perform a cooperative blocking wait until all * given characters have been received by the connected * device. * * @return the number of characters written, or MICROBIT_NOT_SUPPORTED if there is * no connected device, or the connected device has not enabled indications. */ int MicroBitUARTService::send(ManagedString s, MicroBitSerialMode mode) { return send((uint8_t *)s.toCharArray(), s.length(), mode); }
/** * Transmits the given string onto the broadcast radio. * * This is a synchronous call that will wait until the transmission of the packet * has completed before returning. * * @param data The packet contents to transmit. * * @return MICROBIT_OK on success, or MICROBIT_INVALID_PARAMETER if the buffer is invalid, * or the number of bytes to transmit is greater than `MICROBIT_RADIO_MAX_PACKET_SIZE + MICROBIT_RADIO_HEADER_SIZE`. */ int MicroBitRadioDatagram::send(ManagedString data) { return send((uint8_t *)data.toCharArray(), data.length()); }
/** * Post constructor initialisation method as the BLE stack cannot be brought * up in a static context. * * @param deviceName The name used when advertising * @param serialNumber The serial number exposed by the device information service * @param messageBus An instance of an EventModel, used during pairing. * @param enableBonding If true, the security manager enabled bonding. * * @code * bleManager.init(uBit.getName(), uBit.getSerial(), uBit.messageBus, true); * @endcode */ void MicroBitBLEManager::init(ManagedString deviceName, ManagedString serialNumber, EventModel& messageBus, bool enableBonding) { ManagedString BLEName("BBC micro:bit"); this->deviceName = deviceName; #if !(CONFIG_ENABLED(MICROBIT_BLE_WHITELIST)) ManagedString namePrefix(" ["); ManagedString namePostfix("]"); BLEName = BLEName + namePrefix + deviceName + namePostfix; #endif // Start the BLE stack. #if CONFIG_ENABLED(MICROBIT_HEAP_REUSE_SD) btle_set_gatt_table_size(MICROBIT_SD_GATT_TABLE_SIZE); #endif ble = new BLEDevice(); ble->init(); // automatically restart advertising after a device disconnects. ble->gap().onDisconnection(bleDisconnectionCallback); ble->gattServer().onSysAttrMissing(bleSysAttrMissingCallback); // generate an event when a Bluetooth connection is established ble->gap().onConnection(bleConnectionCallback); // Configure the stack to hold onto the CPU during critical timing events. // mbed-classic performs __disable_irq() calls in its timers that can cause // MIC failures on secure BLE channels... ble_common_opt_radio_cpu_mutex_t opt; opt.enable = 1; 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}); #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); if (enableBonding) { // If we're in pairing mode, review the size of the bond table. int bonds = getBondCount(); // TODO: It would be much better to implement some sort of LRU/NFU policy here, // but this isn't currently supported in mbed, so we'd need to layer break... // If we're full, empty the bond table. if (bonds >= MICROBIT_BLE_MAXIMUM_BONDS) ble->securityManager().purgeAllBondingState(); } #if CONFIG_ENABLED(MICROBIT_BLE_WHITELIST) // Configure a whitelist to filter all connection requetss from unbonded devices. // Most BLE stacks only permit one connection at a time, so this prevents denial of service attacks. BLEProtocol::Address_t bondedAddresses[MICROBIT_BLE_MAXIMUM_BONDS]; Gap::Whitelist_t whitelist; whitelist.addresses = bondedAddresses; whitelist.capacity = MICROBIT_BLE_MAXIMUM_BONDS; ble->securityManager().getAddressesFromBondTable(whitelist); ble->gap().setWhitelist(whitelist); ble->gap().setScanningPolicyMode(Gap::SCAN_POLICY_IGNORE_WHITELIST); ble->gap().setAdvertisingPolicyMode(Gap::ADV_POLICY_FILTER_CONN_REQS); #endif // Configure the radio at our default power level setTransmitPower(MICROBIT_BLE_DEFAULT_TX_POWER); // 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); #endif #if CONFIG_ENABLED(MICROBIT_BLE_EVENT_SERVICE) new MicroBitEventService(*ble, messageBus); #else (void)messageBus; #endif // Configure for high speed mode where possible. Gap::ConnectionParams_t fast; ble->getPreferredConnectionParams(&fast); fast.minConnectionInterval = 8; // 10 ms fast.maxConnectionInterval = 16; // 20 ms fast.slaveLatency = 0; ble->setPreferredConnectionParams(&fast); // Setup advertising. #if CONFIG_ENABLED(MICROBIT_BLE_WHITELIST) ble->accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED); #else ble->accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE); #endif ble->accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LOCAL_NAME, (uint8_t *)BLEName.toCharArray(), BLEName.length()); ble->setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED); ble->setAdvertisingInterval(200); #if (MICROBIT_BLE_ADVERTISING_TIMEOUT > 0) 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 CONFIG_ENABLED(MICROBIT_BLE_WHITELIST) if (whitelist.size > 0) #endif ble->startAdvertising(); }
/** * Removes a KeyValuePair identified by a given key. * * @param key the unique name used to identify a KeyValuePair in flash. * * @return MICROBIT_OK on success, or MICROBIT_NO_DATA if the given key * was not found in flash. */ int MicroBitStorage::remove(ManagedString key) { return remove((char *)key.toCharArray()); }
/** * Places a given key, and it's corresponding value into flash at the earliest * available point. * * @param key the unique name that should be used as an identifier for the given data. * * @param data a pointer to the beginning of the data to be persisted. * * @return MICROBIT_OK on success, or MICROBIT_NO_RESOURCES if the storage page is full */ int MicroBitStorage::put(ManagedString key, uint8_t* data) { return put((char *)key.toCharArray(), data); }