Upgrade to Pro — share decks privately, control downloads, hide ads and more …

When a Wearable Meets an iPhone

When a Wearable Meets an iPhone

Developing an iOS app that needs to interact with a custom wearable device via a Bluetooth Low Energy connection seems like an unusual experience, that couldn’t possibly have anything to do with you. But the Internet of Things is here and it’s yummy, so who knows when this might happen to you?

The talk was given at Appdevcon 2017 and includes some pointers on BLE development (especially on iOS), as well as some lessons learned about working with wearable devices.

Alberto Guarino

March 17, 2017
Tweet

More Decks by Alberto Guarino

Other Decks in Programming

Transcript

  1. TODAY'S ADVENTURE » Wearables » BLE basics » Coding on

    iOS » The importance of protocols » Connection issues » Testing » Remote maintenance
  2. Today a wearable will be » A piece of HW

    that sits somewhere on your body » Easy to carry with you » Measures and provides useful information » Uses BLE for data exchange with the outside world » In need of an app to communicate with » Custom: you know / have power on the internals
  3. Core Bluetooth “The framework is an abstraction of the Bluetooth

    4.0 specification for use with low energy devices. That said, it hides many of the low-level details of the specification from you, the developer, making it much easier for you to develop apps that interact with Bluetooth low energy devices.” - Apple Docs
  4. Peripheral » Has data » Advertises its presence to the

    world » It's our wearable » CBPeripheral
  5. Peripheral » It exposes services » Services contain characteristics »

    Characteristics allow data exchange » You can define your own
  6. Central » Wants data » Scans for advertising peripherals »

    Connects to peripherals » Explores services and characteristics » Reads, writes, is notified by characteristics » It's (part of) our app » CBCentralManager
  7. Scanning and connecting let central = CBCentralManager(delegate: self, queue: nil)

    let serviceUUID = CBUUID(string: "ABC00001-A0B1-FFFF-1234-1A2B3DD11001") // Scan central.scanForPeripherals(withServices: [serviceUUID], options: nil) // Discovery handler func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) { if thisIsMy(peripheral) { // Check before connecting central.stopScan() // Please do! self.peripheral = peripheral // Keep a strong ref! central.connect(peripheral, options: nil) } }
  8. Unencrypted Characteristics No BLE encryption on the device side »

    You can interface with the device immediately » It will go back to advertising mode when disconnected from the app » It will need a new scan to be found (or a direct connection via UUID)
  9. Encrypted Characteristics » The device reconnects automatically to iOS »

    Scan won't find it. You'll need to retrieve it from known peripherals: let peripherals = central .retrieveConnectedPeripherals(withServices: [serviceUUID])
  10. Encrypted Characteristics The pairing can be "delicate": » You can't

    retrieve the device anymore » You can't connect to the retrieved device » You can't exchange data with the device You can't solve this with code: » Soft solution: kill your app, forget device » Hard solution: restart your phone
  11. After connecting The "discovery dance" peripheral.discoverServices([serviceUUID]) peripheral.discoverCharacteristics([charUUID], for: service) peripheral.setNotifyValue(true,

    for: characteristic) // In your CBPeripheralDelegate func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) { if let data = characteristic.value { // data: Data // Enjoy your data! ! } }
  12. Thou shalt have a shared protocol » Available services and

    characteristics » Commands and command format » Response format (how to parse bytes)
  13. Protocol Example // Command Characteristic let charUUID = CBUUID(string: "ABC00001-A0B1-FFFF-1234-1A2B3DD11002")

    let char: CBCharacteristic // We have already discovered this // Available Commands enum CommandID: UInt8 { case test // 0 case reset // 1 case readTemp // 2 case writeTemp // 3 } // Issuing the Read Temperature command let readTempData = Data(bytes: [CommandID.readTemp.rawValue]) peripheral.writeValue(readTempData, for: char, type: .withResponse)
  14. Parsing the response - (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error

    { const uint8_t *data = (uint8_t *)[characteristic.value bytes]; uint8_t packetID = data[0]; if (packetID == TEMP_RESPONSE_ID) { // ID for the temperature response packet uint32_t timestamp; memcpy(&timestamp, &data[1], 4); uint8_t temperature; memcpy(&temperature, &data[5], 1); // Work with temp value & timestamp } }
  15. Messing with the protocol? // V1 enum CommandID: UInt8 {

    case test // 0 case reset // 1 case readTemp // 2 case writeTemp // 3 } // V2 enum CommandID: UInt8 { case test // 0 case shutdown // 1 <=== NEW COMMAND HERE! case reset // 2 case readTemp // 3 case writeTemp // 4 }
  16. Recap » We know how to discover our custom wearable

    » We know how to connect and pair it with iOS » We know how to subscribe to its characteristics » We know how to interpret bytes » GO HOME! !
  17. Connection Stability » Your wearable device measures critical data »

    It needs to be always connected » The background is a completely different story: “For iOS apps, it is crucial to know whether your app is running in the foreground or the background. An app must behave differently in the background than in the foreground, because system resources are more limited on iOS devices”
  18. Add state preservation and restoration let queue = DispatchQueue(label: "mySerialDispatchQueue")

    let options = [CBCentralManagerOptionRestoreIdentifierKey: "myCentralManagerIdentifier"] let centralManager = CBCentralManager(delegate: self, queue: queue, options: options)
  19. AS A RESULT... » Your app will continue working in

    the background » If your app is suspended, the system will track: » the services it was scanning for » the peripherals it was connected / connecting to » the characteristics it was subscribed to » Related BLE events will make the system relaunch your app in the background
  20. ERROR #1 Assuming app relaunch will always work If the

    users kills your app via the fast app switcher, there's no coming back from it. iOS won't relaunch the app. The user will need to tap on its icon. SOLUTION: ✅ Let your users know about "good" and "bad" behaviors
  21. ERROR #2 Assuming the background will just work If the

    BLE connection is dropped in the background and the app gets suspended, it won't be relaunched, as it's not doing what the system tracks for state restoration SOLUTION: ✅ Try to reconnect immediately ✅ If you need to do some processing first, start a background task: UIApplication.shared.beginBackgroundTask(expirationHandler: (() -> Void)?)
  22. ERROR #3 Assuming you have infinite time "10 second" rule:

    each BLE event renews your background time for about 10 seconds. If you stop receiving events, your app gets suspended. SOLUTION: ✅ Have your wearable ping your app more often than every 10 seconds ✅ Start a background task if you miss a packet
  23. ERROR #4 Trusting iOS Changelogs iOS updates break the Bluetooth

    in subtle ways SOLUTION: ✅ Never ever assume that, if changelogs don't mention the Bluetooth, then nothing has changed and your app will go on working with the same stability
  24. Basic Testing » Write your unit tests » Do your

    TDD, if possible » Automate your tests » Maybe add some UI testing » Use continuous integration » Involve your QA engineer colleagues, if you're lucky to have any
  25. But... with wearables? » Move your focus to full system

    integration testing » Involve all the components, see how they interact » Track exactly what you're testing: what HW revision, what FW it is running, the protocol it supports, the app running on the phone, the phone model, the phone OS... » Have clearly defined test scenarios and store test reports » Sadly, the simulator won't work here, you need to test in real life
  26. You're not alone in this Make sure these people are

    your best friends: » FW developer » Custom FW / Insight about the device... » QA engineer » Test scenarios / Collecting reports from the real world... » Your colleagues » Will run around with you ;)
  27. Sample test scenario » Custom FW that simulates disconnections »

    Move around the room, app in foreground, see what happens » Send the app to the background, see if it dies on you or it will recover the connection even from the BG » Check what happened both on the HW side and on the SW side » Repeat this on every FW / App / OS update!
  28. Logging and monitoring » NEVER forget about logging » Remote

    logging (in the cloud, e.g., ELK stack, Logentries, your custom solution ...) » Via the app, as your device doesn't have direct internet access » Session reconstruction from start to finish
  29. SILENT NOTIFICATIONS { "aps" : { "content-available" : 1 },

    "command" : 1, // Check Status "delay" : 0 // Now, "parameters": [ // ... ] }
  30. RECEIVING AND PARSING func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable

    : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { // Check if this is a remote command let (isCommand, command) = RemoteCommand.parseRemoteCommand(userInfo: userInfo) if isCommand { let executor = RemoteExecutor() executor.run(command: command) } completionHandler(.noData) }
  31. Remote command // Note: this is only a partial implementation

    enum RemoteCommandId: Int { case ping = 0 case checkStatus = 1 case reset = 2 } struct RemoteCommand { let id: RemoteCommandId let delay: Int let params: [String: Any]? }
  32. Executing // Note: this is only a partial implementation class

    RemoteExecutor { let peripheral: CBPeripheral let char: CBCharacteristic func run(command: RemoteCommand) { switch command.id { case .checkStatus: let cmdData = // Turn command into Data peripheral.writeValue(cmdData, for: char, type: .withResponse) default: break } } }
  33. RECAP » Know your BLE basics » Use encrypted chars,

    but know that the iOS BLE stack can get stuck » Don't be afraid to use ObjC (but know why you do it!) » Keep your connection stable, especially in the background » Always test at the system-integration level » Don't trust iOS updates » Flood the cloud with logs » Implement remote maintenance