Giter VIP home page Giter VIP logo

c19x-ios-ble's Introduction

C19X-iOS-BLE

An investigation into options for resuming beacon tracking for iOS devices in background mode after going out of range for long periods.

Solution

A reliable Bluetooth beacon for proximity detection between iOS and Android devices that runs on 81% of all devices today without a software upgrade. It was important to pursue a beacon solution that does not require the user, especially on iOS devices, to upgrade their software to the latest version to use the Google+Apple API. In the UK in June 2020, 46.53% of smartphone users are on iPhones, 35.67% of iPhone users are on iOS 13.5 but only 2% are on iOS 13.5.1 which is the version released in May 2020 that is required to use the new Google+Apple API. It is unlikely for all or even the majority of iPhone users to upgrade their phone to the latest version in order to support running a contact tracing app, thus it is essential to create a solution that works on existing software to eliminate this major adoption barrier.

Android Central - Android Peripheral

Detection is relatively trivial in both foreground and background modes (see https://github.com/c19x/C19X-Android). The key issues in a practical implementation are:

  • Some Android devices (e.g. the popular Samsung J6) can only act as a central, not a peripheral, thus it can only detect the presence of other devices and not make its presence known (no advertising). This is mitigated by using a writeable characteristic such that the non-transmitting device can share the measured RSSI value and its identifier as data.
  • The bluetooth stack on some Android devices (e.g. Pixel 2) may silently fail after too many connects without a corresponding disconnect. The result is a peripheral that can be detected and connected to, but no further actions can be taken (e.g. to read/write characteristic). In some cases, it will silently fail even with a corresponding disconnect. This is mitigated by minimising connect/disconnect to Android devices by caching reusable data (e.g. device identifier) and simply relying on scan results for RSSI value measurements.

iOS Central - Android Peripheral

Detection is trivial in foreground, background modes and also suspended and terminated modes on iOS. The key considerations are:

  • Android devices can always detect iOS devices and adverts, but the service UUID will be hidden in the overflow area when iOS is in background mode, thus the Android scan call will need to search for a fixed service UUID (for Android devices and iOS devices in foreground mode), and also search by Apple manufacturer ID to detect iOS devices advertising in background mode, then call discover service and characteristic to distinguish between iOS devices with and without the beacon.
  • iOS devices can always detect Android adverts, given a fixed service UUID. A scanForPeripheral call will always return the Android device in a didDiscover callback on every call, unlike iOS devices where the call will only result in one single didDiscover callback (i.e. calling scanForPeripheral won't return the iOS device again when both devices are in background mode). This implies for iOS centrals detecting Android peripherals, all that is required is a repeating scanForPeripheral call to obtain updated RSSI measurements.

iOS Central - iOS Peripheral

Detection is non-trivial when both iOS devices are in background mode, which is expected to be the norm. Detection is trivial if either of the devices is in foreground mode, but that will be an unusual condition. A complete solution is available at (https://github.com/c19x/C19X-iOS). The key issues and solutions are:

  • When an iOS devices is in background mode, a scanForPeripheral call will only call the didDiscover callback once for each new iOS device. As such, the iOS central will need to connect to the iOS peripheral and use a repeating readRSSI call to obtain updated RSSI measurements. When the iOS peripheral goes out of range, the central will receive a didDisconnect call. Given it is now a known device, the didDiscover callback won't be called when it returns back in range, thus the central will need to issue a pending connect call to the peripheral in the didDisconnect callback. This method will enable reconnection for an iOS peripheral going out of range for up to about 20 minutes. While the Apple documentation states the connect callback is not expiring, reconnection will only be established again if the peripheral has a fixed identity, but this is not the case for iOS peripherals.
  • After about 20 minutes of being out of range, the iOS peripheral will take on a new identity (new peripheral UUID), thus the pending connect call will not reconnect the peripheral. Bringing the central back to foreground will immediately result in a didDiscover callback showing the peripheral as a new device with a new UUID, however it is impractical to expect the app to stay in foreground mode. There are two key mitigations for this problem.
    • The peripheral identifier may change in less than 20 minutes when it is out of range. The connection can be re-established by using setNotifyValue and writeValue on the central side and updateValue on the peripheral side to generate pending calls between the two devices. In most instances, when the devices are back in range, the callback for these pending calls will provide the new peripheral identifier which can be used in a retrievePeripherals:withIdentifiers and retrieveConnectedPeripherals call to identify and connect to the peripheral under a new identity.
    • For situations where the central and peripheral have been out of range for over 20 minutes and no Bluetooth activities have occurred, the scanForPeripheral call will not generate a didDiscover callback for the peripheral, even though it has taken on a new identity. Experimentations have shown that both the central and peripheral are still scanning and advertising as they are both detectable by Android, and furthermore, even when the iOS central and peripheral are actively interacting with a Android device, they won't detect each other in this state. This implies the iOS Bluetooth stack is fully functional but simply not detecting each other, because the service UUID in the iOS background advert is in the overflow area and the iOS central in background state is not reading the data. The mitigation to this problem is to use CLLocationManager to startRangingBeacons and also startUpdatingLocation. The former ensures data in the overflow area is read by iOS centrals, while the screen is ON. The latter ensures the iOS central remains in background mode (not suspended or terminated) to read the data. A local notification is then used to trigger screen ON at regular intervals. The result is that the didDiscover callback is called on discovery of new iOS peripherals and known peripherals under a new identity.
  • Given an iOS central and iOS peripheral that have just started scanning and advertising. If both devices are in solitude and out of range of each other and they both enter background mode and become suspended or terminated following a period of inactivity (e.g. over 20 minutes), they will not detect each other if brought into range. This will be a common situation, where an isolated user installs and starts the contact tracing app, then wait for some time before venturing out to a populated area where a detection may occur. The cause of and mitigation to this problem is the same as that for reconnection of peripherals under a new identifier. It relies on the use of CLLocationManager and background beacon ranging to ensure the iOS central remains in background mode (not suspended or terminated) and reads the service UUIDs in the overflow area of an iOS background advert, and also using an automated procedure (e.g. repeating local notification) to trigger screen ON at regular intervals while the device is idle.

This C19X-iOS-BLE code repository was created specifically to investigate and solve this iOS central - iOS peripheral reconnection problem, which is the final challenge in creating a Bluetooth beacon that runs indefinitely for iOS and Android devices in all states. Special thanks to @UsamaAbdulAziz who has given many days to support this investigation and help solve this complex problem by providing invaluable encouragement, feedback and observations from numerous systematic and practical tests.

Limitations

Current solution for iOS Central - iOS Peripheral detection relies on screen activation (display is lit) to trigger discovery while app is in background mode. When the app is in background mode, screen off, device locked, it will track a discovered and connected device indefinitely and reconnect following short periods of being out of range (roughly < 20 minutes), however it will not discover new devices in this condition. Discovery will resume whenever the display is lit, e.g. while phone is unlocked and being used normally, or phone is locked and a notification is being displayed.

In practice, that means a device with an Apple folio case or 3rd party folio/flip case that supports Lock/Unlock (has magnets) will not detect new devices when the case is closed and being carried in a pocket or bag. Discovery will resume when:

  1. Phone is taken out of pocket and case opened (can remain locked). Any pending notification will immediately trigger screen on and discovery.
  2. Phone is taken out of pocket, case opened and screen on triggered manually (e.g. tapping the display or pressing any button).
  3. Phone is unlocked and being used normally.

Alternatively, discovery can be triggered automatically at regular intervals if the phone is either not in a covered case or the cover is open and device left face up or at an angle on a surface (i.e. not face down). A repeating local notification will automatically trigger screen on for short periods at regular intervals to trigger discovery.

Problem definition

Abbreviations for app state

  • F = Foreground
  • B = Background
  • S = Suspended
  • T = Terminated

The current C19X-iOS app is able to detect and track Android devices in F/B and iOS devices in F/B/S/T. For iOS devices in B/S/T it has several limitations once two iOS devices (A and B) have discovered each other and established connection.

  • Airplane mode ON -> Wait over 1 minute -> OFF on Phone A in B/S/T will terminate connection and Phone B in B/S/T won't be able to detect and track A again until the app on either A or B enters F again. This is caused by Phone A UUID change, thus the pending connect from B based on an expired UUID will not be established. Furthermore, didDiscover is not called in B as A is still the same known device, thus B has no means of reconnecting.
  • Phone A in B/S/T going out of range of Phone B in B/S/T for over 20 minutes will terminuate connection and Phone B in B/S/T won't be able to detect and track A again until the app on either A or B enters F again. Like the airplane mode cycle, this is caused by Phone A UUID change after a period of bluetooth inactivity, thus the pending connect from B based on an expired UUID will not be established. Furthermore, didDiscover is not called in B as the A is still the same known device, thus B has no means of reconnecting.

Failed solutions

Experiments have been conducted to understand and address this issue, ideals that have been tested but failed or not fully resolved the problem include:

  1. Use pending "write withResponse" request from CBCentralManager to extract new UUID from CBPeripheralManager (didWrite). This works for short periods, implying CoreBluetooth is able to transfer the pending request from one UUID to the next but it does not notify CBCentralManager.
  2. Use pending "setNotifyValue" request from CBCentralManager to extract new UUID from CBCentralManager (didUpdateNotificationStateFor). This works for short periods, once again implying CoreBluetooth is capable of transferring the pending request but it only lasts about 1 minute and rarely longer.
  3. Use "rotating service UUID" (one-in-many) in advert by CBPeripheralManager and "rotating scan for UUIDs" (all-but-one) in CBCentralManager scan in the hope of triggering "didDiscover" as both peripheral advert and scan parameters have changed, but this did not work. It appears the data is cached and didDiscover is only called once in B/S/T for each physical device.
  4. Use "disposible instances" of CBCentralManager to conduct each scan in the hope that didDiscover is called again for the same peripheral when a new CBCentralManager is created to conduct a new scan, but this did not work. It appears that peripheral discovery is reported once for each app instance, rather than each CBCentralManager instance. Doesn't make logical sense as a design.
  5. Use BGAppRefresh, BGProcessing and CoreLocation as triggers for CBCentralManager scan call in B/S/T, but this did not work. Scan only triggers didDiscover once for each peripheral, repeated calls make no difference.

c19x-ios-ble's People

Contributors

c19x avatar

Watchers

James Cloos avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.