Creating a mobile app that communicates with a custom wearable device via a Bluetooth Low Energy connection seems like an unusual experience, that wouldn't possibly happen to you, would it? Well, let me tell you that the Internet of Things is here and it's full of interesting opportunities, so we, as a community, should keep learning and get ready to seize them!
During this talk, I'd like to tell you a few stories and lessons learned about my experience creating iOS apps and SDKs to interface with several smartwatches made for monitoring physiological signals. We'll go through code samples, tools, unexpected bugs, clever ideas and bad mistakes, hoping we'll learn something together in the process.
Introduction to Multilingual Retrieval Augmented Generation (RAG)
Alberto Guarino "When iPhones and Wearables Dance the Bluetooth Dance: Lessons Learned"
1.
2.
3. TODAY'S ADVENTURE
> Wearables & My Story
> Coding with BLE on iOS
> The importance of protocols
> Connection stability
> Testing with wearables
> Logging & monitoring
> Remote maintenance
> The End
9. TODAY A WEARABLE WILL BE
> A piece of HW that sits somewhere on your body
> Easy to carry with you
> Provides useful data via sensors
> 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
16. 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
19. PERIPHERAL
> It exposes services
> Services contain characteristics
> Characteristics allow data exchange
> You can define your own
20.
21.
22. 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
34. 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)
37. ENCRYPTED CHARACTERISTICS
> The device reconnects automatically to iOS
> You'll need to retrieve it from known peripherals:
let peripherals = central
.retrieveConnectedPeripherals(withServices: [serviceUUID])
38. 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
41. THOU SHALT HAVE A SHARED PROTOCOL
> Available services and characteristics
> Commands and command format
> Response format (how to parse bytes)
42. 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 readTemp // 1
case writeTemp // 2
}
// Issuing the Read Temperature command
let readTempData = Data(bytes: [CommandId.readTemp.rawValue])
peripheral.writeValue(readTempData, for: char, type: .withResponse)
46. MESSING WITH THE PROTOCOL?
// V1
enum CommandId: UInt8 {
case test // 0
case readTemp // 1
case writeTemp // 2
}
// V2
enum CommandId: UInt8 {
case test // 0
case reset // 1 <=== NEW COMMAND HERE! !
case readTemp // 2
case writeTemp // 3
}
48. 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 parse device data
> WE KNOW HOW TO DANCE! !"
50. 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
52. STATE PRESERVATION AND RESTORATION
let queue = DispatchQueue(label: "mySerialDispatchQueue")
let options = [CBCentralManagerOptionRestoreIdentifierKey:
"myCentralManagerIdentifier"]
let centralManager = CBCentralManager(delegate: self,
queue: queue,
options: options)
53. 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
54. ERROR #1
ASSUMING APP RELAUNCH WILL ALWAYS WORK
If the users kills your app, there's no coming back from it
SOLUTION
✅ Let your users know
55. 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
SOLUTION
✅ Try to reconnect immediately
✅ If you need to do some processing, start a background task
57. ERROR #3
ASSUMING YOU HAVE INFINITE TIME
"10 second" rule: each BLE event renews your background time for about 10
seconds.
SOLUTION
✅ Have your wearable ping your app more often than every 10 seconds
✅ Start a background task if you miss a packet
58. ERROR #4
TRUSTING IOS CHANGELOGS
iOS updates break the Bluetooth in subtle ways
SOLUTION
✅ TEST, TEST, TEST each iOS release
60. BASIC TESTING
> Write your unit tests
> Do your TDD
> Automate your tests
> Maybe add some UI testing
> Use continuous integration
> Involve your QA engineer colleagues
61. ...WITH WEARABLES?
> Focus on full system integration
> Involve all the components
> Track exactly what you're testing: HW revision, FW
version, OS, ...
> Have clearly defined test scenarios and store test
reports
> Sadly, the simulator won't work here
63. 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...
> Your colleagues: will run around with you !
64. 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
if it recovers the connection
> Check what happened (HW + SW)
> Repeat this on every FW / App / OS update!
65. LOGGING AND MONITORING
> NEVER forget about logging
> Remote logging (in the cloud)
> Via the app (no internet access on device)
> Session reconstruction from start to finish
68. REMOTE COMMANDS
// Note: this is only a partial implementation
enum RemoteCommandId: Int {
case ping = 0
case checkStatus = 1
case resetDevice = 2
// ...
}
72. RECEIVING AND PARSING
let executor = RemoteExecutor()
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 {
executor.run(command: command)
}
completionHandler(.noData)
}
73. 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:
// Turn command into Data
let cmdData = Data(bytes: [CommandId.checkStatus.rawValue])
peripheral.writeValue(cmdData, for: char, type: .withResponse)
default:
break
}
}
}
74. RECAP
> Know your BLE basics
> Use encrypted chars, but know that the iOS BLE stack can get stuck
> Don't be afraid to resort to ObjC
> 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