Marko @ Mobile Coding Factory
Home of Mobile Coding Factory

NearbyInteractions hands-on

Intro

For the last couple of months, I have been working on a project that is leveraging Apple’s new NearbyInteractions framework. This app is using the UWB chip and NearbyInteraction to gather location data from a device.

## What is UWB and how does it work?

UWB (short for Ultra-Wideband) is a wireless communication protocol that is using high-frequency waves for high-precision locations on short distances.

Basically, the UWB chip is triggering a high number of pulses in a short time (it can go up to billions of pulses in a second) and receiving side (iPhone in our case) translates these pulses into location information.

This technology already has applications in wireless car keys, indoor navigation, etc.

NearbyInteraction Framework introduction

Although Apple introduced NearbyInteractions Framework in 2020 with the WWDC session Meet Nearby Interaction, support for Ultra-Wideband third-party accessories come a year later in 2021 (Explore Nearby Interaction with third-party accessories). In the first iteration of the NearbyInteractions Framework, developers had the option to communicate and locate another U1 (Apple’s version of UWB chip) capable iPhone (models from iPhone 11 and iOS 14 and newer), and later this support is extended to the UWB third-party accessories.

NearbyInteractions Framework usage

Implementation of the NearbyInteraction is basically the same for all devices (iPhone or accessories):
- we need to establish a connection with the device we want to locate - create NISession - get NISession updates from the device

Connecting devices

As for the step of connecting devices, Apple left it to developers to decide what technology they will use. Devices can be connected via CoreBluetooth, MultiPeer connectivity, Watch connectivity, or even a server. When using UWB capable chip, the most logical choice will be Bluetooth since it is present in most of the integrated boards. Bluetooth comes with a lot of advantages but also with some limitations. First, connecting two devices with Bluetooth is not always straightforward and it involves a couple of steps until it is successful:

- ask iOS to grant Bluetooth permission to the app - check if Bluetooth is turned on - start scanning nearby devices - try connecting to the device - discover Bluetooth Service in connected device - discover Bluetooth Characteristics for service - read the value of Bluetooth Characteristics

As you can see in the above steps, there is a lot of room for errors to happen in this process so we need to handle it all appropriately.

> One note here:
> Because scanning nearby devices will get us all devices that have Bluetooth in the range (and this is usually a lot more than we need), we can filter them out by asking CoreBluetooth to scan only for specific services. In our case, this service has ids 6E400001-B5A3-F393-E0A9-E50E24DCCA9E

`swift centralManager.scanForPeripherals(withServices: [CBUUID(string: “6E400001-B5A3-F393-E0A9-E50E24DCCA9E”)], options: [CBCentralManagerScanOptionAllowDuplicatesKey: true])


For the purpose of NearbyInteractions, we also need to get characteristics that are related to the UWB connection. This is done by passing the id’s of the write and read UWB characteristics in the “discover characteristics” phase:  

swift let writeCharacteristic = CBUUID(string: “6E400002-B5A3-F393-E0A9-E50E24DCCA9E”) let notifyCharacteristic = CBUUID(string: “6E400003-B5A3-F393-E0A9-E50E24DCCA9E”)

peripheral.discoverCharacteristics([writeCharacteristic, notifyCharacteristic], for: service)


### NISession

When we get our (Bluetooth) connection up and running, we can move on to the actual implementation of the NearbyInteractions Framework.
First, we need to send the signal to the device to initialize NISession by sending the **0xA** command and if everything is ok, the response from the device should be **0x1**. Alongside this response, the UWB device should provide us with configuration data that we use to create and run our NISession object:  

swift do { let configuration = try NINearbyAccessoryConfiguration(data: configData) let niSession = NISession() niSession.delegate = self niSession.run(configuration) } catch { print("Failed to create NINearbyAccessoryConfiguration with Error: (error)") return }


After this, NISession will generate its configuration data (inside *didGenerateShareableConfigurationData* delegate method) that we need to send back to the device. This set of data (for two iPhone devices this phase will be *share discovery tokens*) is necessary since it is unique to the session and identifies the device that created the session
After this exchange of the data, *didUpdate* delegate method should be called and it indicates that the NearbyInteractions session has started successfully
[Initiating and Maintaining a Session](https://developer.apple.com/documentation/nearbyinteraction/initiating_and_maintaining_a_session)

### Problems

Like with Bluetooth where we have several steps, connecting with NearbyInteractions Framework also can fail in multiple stages. 
The thing that we need to check first is whether the iPhone actually has support for this (as we said, only devices with a U1 chip can use this Framework). This is done by checking *isSupported* flag:  

swift guard NISession.isSupported else { print(“This device doesn’t support Nearby Interaction.”) return }


Besides this, using Nearby Interaction with third-party accessories is available from iOS 15 and later, so we need to check this also.

One “issue” that is noticeable is when there is no UWB information from the device although everything seems ok (Bluetooth connection is established and NISession is started). This is usually happening when the device is out of range or the phone is in a still position. Namely, the U1 chip on the iPhone has an approximately wide camera field of view (around 120 degrees) and if the UWB accessory is out, the direction could not be determined. Similarly, if the iPhone is not moving, then the distance will be nil.

In the cases when NISession is established and running but the phone is not moving for some time, then the UWB accessory could send *timeout* message since no major events happen for some time. This will trigger the delegate *didRemove* method with the reason. We can check if the reason is a timeout and we conclude that the device should be running, then we can use previously saved NINearbyAccessoryConfiguration to rerun the session again.

## Conclusion  

All in all, using the NearbyInteractions framework is fun and could open up a lot of potential use-cases. Setting up the connection and interactions between devices can be tricky, but I get impressed by the accuracy and speed when it is working.
  
___

Hi, I’m [Marko](http://codingfactory.xyz), an iOS developer (most of the time) and this is a somewhat weekly list of interesting articles, books, podcasts, music, and videos that I come across during the week that helps me grow or resonate with me in some way. I hope you enjoy it and find something useful here.

I would love to hear from you at [@MobileCoding](https://twitter.com/MobileCoding), [LinkedIn](https://linkedin.com/in/jomi86) or via [email](mailto:dev@codingfactory.xyz).


Tags: