Bluetooth
Overview
Readings from validated Bluetooth Low Energy devices can be captured and uploaded to Validic with VLDBluetoothPeripheralController
. A list of supported devices can be obtained calling the static function +supportedPeripherals
on VLDBluetoothPeripheral
. You must implement the VLDBluetoothPeripheralControllerDelegate
protocol to receive callbacks so that the user may be prompted to take the measurement at the right time, display the measurement value, or get notified of any errors that occurred.
Setup
Add NSBluetoothAlwaysUsageDescription
to your app’s Information Property List file.
If viewing the raw source add the NSBluetoothAlwaysUsageDescription
key as displayed below.
<key>NSBluetoothAlwaysUsageDescription</key>
<string>Get Bluetooth readings</string>
If these values are not provided, the app will crash at runtime with an error reported to the console log.
Peripherals
VLDBluetoothPeripheral
represents Bluetooth peripheral models that can be discovered or read from by VLDBluetoothPeripheralController
A peripheral object contains various information to be displayed to the user:
name
- Name of the peripheral comprised of the the manufacturer name and model number.pairingInstructions
- Text telling the user how to pair the peripheral, if the peripheral requires pairing.instructions
- Text telling the user how to initialize the reading process with the peripheral.readingInstructions
- Text telling the user what to do during the reading process.imageURL
- URL for an image of the peripheral.
Supported Peripherals
To retrieve a List of supported peripherals call:
guard let supportedPeripherals = VLDBluetoothPeripheral.supportedPeripherals() as? [VLDBluetoothPeripheral] else { return }
for peripheral in supportedPeripherals {
print("Peripheral: \(peripheral.name())")
}
Other methods are available on VLDBluetoothPeripheral
to allow retrieval of a specific peripheral, or to retrieve a list of peripherals of a specific peripheral type. See the VLDBluetoothPeripheral
documentation for additional information.
Filtering
To get a subset of devices you can use the built in filter method on Arrays in Swift. Check out the Supported Peripherals list to find the ID of the devices you want to display. For example, if you only want to display the Pyle Health Therometer you would:
guard let supportedPeripherals = VLDBluetoothPeripheral.supportedPeripherals() as? [VLDBluetoothPeripheral]
let devices = peripherals.filter { $0.peripheralID == 1 }
Pairing
Certain peripherals require pairing with the mobile device before a reading can be taken. You can check the requiresPairing
property on the VLDBluetoothPeripheral
object to know if it must be paired.
To pair a peripheral with the mobile device, call the pairPeripheral:
method on VLDBluetoothPeripheralController
.
let peripheral: VLDBluetoothPeripheral
let controller = VLDBluetoothPeripheralController()
controller.delegate = self
controller.pairPeripheral(peripheral)
To know if a peripheral was successfully paired you will need to implement the pairing methods of the VLDBluetoothPeripheralControllerDelegate
protocol.
func bluetoothPeripheralController(_ controller: VLDBluetoothPeripheralController!, didPairPeripheral peripheral: VLDBluetoothPeripheral!) {
// Peripheral paired successfully
}
func bluetoothPeripheralController(_ controller: VLDBluetoothPeripheralController!, didNotPairPeripheral peripheral: VLDBluetoothPeripheral!, error: Error!) {
// Peripheral did not pair
}
Reading
Once you are ready to read from a peripheral, the process is fairly similar to the pairing process. You should initially show the peripheral’s instructions and then show the reading instructions once bluetoothPeripheralController:isReadyToReadFromPeripheral:
is called.
let peripheral: VLDBluetoothPeripheral = // A peripheral from the supportedPeripherals list
let controller = VLDBluetoothPeripheralController()
controller.delegate = self
controller.read(from: peripheral)
You must also implement the reading methods of the VLDBluetoothPeripheralControllerDelegate
protocol.
func bluetoothPeripheralController(_ controller: VLDBluetoothPeripheralController!, isReadyToReadFrom peripheral: VLDBluetoothPeripheral!) {
// Time to present the readingInstructions to the user
}
func bluetoothPeripheralController(_ controller: VLDBluetoothPeripheralController!, shouldSubmitReadings records: [VLDRecord]!, from peripheral: VLDBluetoothPeripheral!) -> Bool {
// To have the reading automatically added to the session and uploaded
// return YES from this method. To first have the reading validated by the user
// you can return NO from this method. Once the user has validated the reading
// you must manually submit the record by calling VLDSession.sharedInstance().submitRecord(record)
return true
}
func bluetoothPeripheralController(_ controller: VLDBluetoothPeripheralController!, didCancelReadingFor peripheral: VLDBluetoothPeripheral!) {
// Reading was cancelled
}
func bluetoothPeripheralController(_ controller: VLDBluetoothPeripheralController!, readingFailedFor peripheral: VLDBluetoothPeripheral!, error: Error!) {
// Reading failed
}
Passive Bluetooth
Overview
The ValidicBluetooth library supports passively reading from a given set of Bluetooth peripherals. Passive Bluetooth reading is a long-lived capability. Once enabled, the library will listen for and read from the specified peripherals. Reading will automatically occur once the peripheral is discovered regardless of app state. This includes if the application is currently in the foreground, in the background, or has been terminated due to memory pressure. Apps using this feature must have been started by the user at least once since the iOS device has booted otherwise the feature will not be active even if enabled.
Setup
Passive Bluetooth reading relies upon iOS Bluetooth state restoration and background processing support. Apps using passive reading need to set Bluetooth as a required background mode in the Info.plist
. In Xcode, in the Info.plist
add a new key, Required background modes
, add an item with value App communicates using CoreBluetooth
.
If editing the XML source file, add Bluetooth-central
background mode as follows:
<key>UIBackgroundModes</key>
<array>
<string>Bluetooth-central</string>
</array>
Passive Readings
VLDBluetoothPassiveManager
is a singleton which coordinates the passive reading process. Passive reading is enabled by passing it an array of peripheral IDs:
VLDBluetoothPassiveManager.sharedInstance().peripheralIDs = [1, 2]
Or by passing an array of peripherals:
if let peripheral = VLDBluetoothPeripheral(forID: 1) {
VLDBluetoothPassiveManager.sharedInstance().setPeripherals([peripheral])
}
To disable passive reading, set the peripheralIDs
property to an empty array or nil.
VLDBluetoothPassiveManager.sharedInstance().peripheralIDs = nil
To support state restoration, a method on the passive manager must be called on app launch. This should be done in the application:didFinishLaunchingWithOptions:
method of the AppDelegate as follows:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
VLDBluetoothPassiveManager.sharedInstance().restoreState()
return true
}
State restoration will automatically start the app in the background when a requested peripheral is discovered. Within application:didFinishLaunchingWithOptions:
, apps can determine if they were launched in background and avoid expensive operations.
Passive Readings Notifications
Passive readings are automatically sent to the Validic server. Session notifications are sent on upload success or failure. Passive Bluetooth specific notifications are sent when a device is discovered and becomes ready to read. Notifications are also sent for reading success and failure. These notifications specify the associated peripheral ID in the userInfo dictionary with a key, peripheralID
.
kVLDBluetoothPassiveDidReadNotification
is sent when records are read. The notification object is an array ofVLDRecord
read from the peripheral.kVLDBluetoothPassiveDidFailNotification
is sent when failing to read from a discovered peripheral.kVLDBluetoothPassiveIsReadyToReadNotification
is sent when the peripheral becomes ready to read. This notification is only sent out for devices that are discoverable before a measurement is taken. It is important to handle this notification in an app and provide feedback to the user that the device has been discovered and is ready to take a measurement. Since Passive Bluetooth reading is usually performed in the background, a standard UI is not available. A local notification can be sent with sound to provide feedback to the user that the app is ready to read from the peripheral. If the user doesn’t hear the sound, it indicates that the device has not been discovered.
The following code fragment registers an observer for the kVLDBluetoothPassiveIsReadyToReadNotification
notification and sends a local notification including the name of the peripheral. A similar process could be done for the kVLDBluetoothPassiveDidReadNotification
to confirm to the user that a reading was successfully taken.
NotificationCenter.default.addObserver(forName: NSNotification.Name.vldBluetoothPassiveIsReadyToRead, object: nil, queue: nil) { (notification) in
if UIApplication.shared.applicationState == .background {
if let peripheralID = notification.userInfo?["peripheralID"] as? UInt,
let peripheralName = VLDBluetoothPeripheral.init(forID: peripheralID)?.name()
{
let localNotification = UILocalNotification()
localNotification.alertBody = "Ready to read \(peripheralName)"
UIApplication.shared.presentLocalNotificationNow(localNotification)
}
}
}
Considerations
Agamatrix
The Agamatrix Jazz Wireless 2 glucose meter, supported via a custom Bluetooth integration (peripheralID = 32
), requires an authentication key, which is provided by Agamatrix, in order to communicate with the meter. To configure your Agamatrix authentication key for use with the Agamatrix meter in the Validic Mobile SDK:
- Create a file named
agamatrix-authkey.json
- File contents should be your Agamatrix key without the hex prefix notation
0x
. For example, if your key is0x011234567890987654321
then your file contains{"key": "011234567890987654321"}
. - Add the
agamatrix-authkey.json
file to the root of your mobile project.
CVS Health
The CVS Health Advanced Bluetooth Glucose Meter, supported via a custom Bluetooth integration (peripheralID = 33
), requires an authentication key, which is provided by Agamatrix, in order to communicate with the meter. To configure your Agamatrix authentication key for use with the CVS Health meter in the Validic Mobile SDK:
- Create a file named
cvshealth-authkey.json
- File contents should be your Agamatrix key without the hex prefix notation
0x
. For example, if your key is0x055234567890987654987
then your file contains{"key": "055234567890987654987"}
. - Add the
cvshealth-authkey.json
file to the root of your mobile project.
The CVS Health Digital Glass Body Analysis Scale is supported in an alpha state in the Validic Mobile SDKs. Body weight is the only metric that can be retrieved from the scale and a new user profile will be created on the scale during pairing. Users cannot pair to an existing user profile and historical readings (readings captured prior to pairing) will not be synced.
Roche CoaguChek INR Meters
❗️ Approval Required
Interacting with Roche CoaguChek devices requires pre-approval from Roche. Please reach out to Validic Support at support@validic.com if you are interested in integrating with Roche CoaguChek devices.
Overview
Interaction with Roche CoaguChek INR meters, supported via a custom Bluetooth integration (peripheralID = 51
), requires a Validic License file, provided by Validic Support. The license file (named validic.license
) must be included in the root of the main app bundle; to do so, add it to the Xcode project and make sure validic.license
is listed in the “Copy Bundle Resources” section of the app target’s “Build Phases”.
Roche CoaguChek INR meters have an uncommon reading workflow, which requires that the end user provide their meter’s unique encryption key. That encryption key is required to decrypt the reading values captured from the user’s meter. Without a valid encryption key, foreground and passive reads with the INR meter will fail. Because this workflow is specific to the Roche INR meter, any Validic clients implementing the INR meter Bluetooth integration must build a user experience (modal or screen) to capture the meter’s encryption key from the user. This key must then be passed by the client to the Validic SDK where it will be stored and used to decrypt readings. Note: the key is a long string of hexadecimal characters, so it is recommended to implement a QR code reading to capture the code displayed on the meter.
One way to do this would be to request it during the pairing process:
- Initiate pairing with peripheral ID 51
- Upon successful pair, the
VLDBluetoothPeripheralDelegate
callback (bluetoothPeripheralController:didPairPeripheral:metadata:
) will be fired. Store themetadata
object’speripheralUUID
string to a variable - it will be used to store the encryption key into the Validic SDK. - Prompt the user for the encryption key, found in the device’s menu at “Other” > “About” (possibly by using a QR code reader).
- Store the encryption key into the SDK. To do this, pass the user’s encryption key, the constant
kVLDBluetoothPeripheralDataKeyEncryptionKey
, and the peripheral’s UUID (frommetadata.peripheralUUID
in the pairing success delegate callback) to the CoaguChekVLDPeripheral
‘sstoreData:forKey:ofPeripheralUUID:
method.
Example code:
// This example requests the encryption key from inside the pairing success callback for simplicity. Note that the only requirement is that the `metadata.peripheralUUID` value is captured from this callback so it can be used in the `storeData:forKey:ofPeripheralUUID:` call.
// This example uses a UIAlertController text field to collect the encryption key from the user. It is recommended to use a QR code reader to reduce the likelihood of errors.
- (void)bluetoothPeripheralController:(VLDBluetoothPeripheralController *)controller didPairPeripheral:(VLDBluetoothPeripheral *)peripheral metadata:(VLDBluetoothOperationMetadata *)metadata {
if (peripheral.peripheralID == 51) {
// Peripheral is CoaguChek, ask for encryption key
UIAlertController *coaguChekEncryptionKeyAlertController = [UIAlertController alertControllerWithTitle:@"Enter Encryption Key" message:@"Please enter the CoaguChek's encryption key." preferredStyle:UIAlertControllerStyleAlert];
[coaguChekEncryptionKeyAlertController addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
textField.placeholder = @"Encryption Key";
}];
[coaguChekEncryptionKeyAlertController addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
NSString *encryptionKey = [[coaguChekEncryptionKeyAlertController textFields][0] text];
// Note: it is recommended to perform validation on the length and contents of the provided encryption key (if not scanned via QR code) to reduce the possibility of uncaught input error. The entered encryption key should be a 32-character hexadecimal string.
// Store the encryption key into the Validic SDK
[peripheral storeData:encryptionKey forKey:kVLDBluetoothPeripheralDataKeyEncryptionKey ofPeripheralUUID:metadata.peripheralUUID];
}]];
[coaguChekEncryptionKeyAlertController addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
NSLog(@"Encryption key entry canceled.");
}]];
[self presentViewController:coaguChekEncryptionKeyAlertController animated:YES completion:nil];
}
}
Passive Read
There are situations where passive Bluetooth reading is stopped and requires the app to be relaunched. These situations include:
- When the user explicitly kills the app by swiping up on the app switcher view, the app will not be restarted by iOS when a peripheral is discovered.
- If the iOS device is rebooted, state restoration is no longer in effect and iOS will not automatically restart the app when a peripheral is discovered. The app needs to be run at least once after reboot. When the app is launched the passive manager remembers what peripherals if any where being passively read and restarts the capability.
Passive Bluetooth reading has some additional considerations due to the behavior of the iOS Bluetooth stack and background support.
- Interactive, ie non-passive, Bluetooth operations will suspend passive Bluetooth processing until the interative operation completes.
- When scanning for peripherals in the background, iOS reduces the frequency and duration of time the phone listens for devices. This may cause some peripherals to not be discovered in the background. This varies by phone model and peripheral. Generally older phone models are more likely to miss discovery of a peripheral.
- Nonin pulse oximeters, ChoiceMMed pulse oximeter and scale are not usually detectable when in the background.
- The Pyle thermometer is sometimes not discovered on old phone when the app is in the background.
- iOS has additional heuristics to determine scanning frequency which may not be documented. If multiple apps on the phone are performing background scanning, scanning may become more infrequent.
- To prevent repeated attempted reads from a device for one actual reading, the passive manager waits after a successful read until the device stops broadcasting, then waits an additional few seconds before acknowledging the device again. If attempting multiple readings in quick succession, the second reading may not be read. Instead the user should wait until the device powers off and then wait an additional 15 seconds or longer before attempting another reading.