/** * 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(); } }
/** * 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(); }