iOSのUWB通信用フレームワーク「Nearby Interaction」の基礎

# はじめに
スマートフォンの近距離無線通信といえば、みなさん Bluetooth, Wi-Fi, NFC あたりを思い浮かべるでしょう。実はiPhoneでは、UWB（超広帯域無線)というもう一つの無線通信があることをご存知でしょうか。
iPhone11 から iPhone に標準搭載されるようになった Apple U1チップ。Apple製デバイスをUWBに対応させる専用チップで、一般向け製品に UWB が搭載された初のケースです。特に近距離の正確な位置測位に強く、他にはないポテンシャルを秘めています。
しかし、LiDARスキャナの話題性に比べ、こちらはあまり使われているアプリを見ることがありません（Apple公式の「探す」アプリくらい？）。今回はそんな UWB と、Apple の UWB 技術を簡単に使える開発者向けフレームワーク「NearbyInteraction」についてご紹介いたします。

# 目次
 - [UWBについて](#uwbについて)
     - [UWBとは？](#uwbとは)
     - [Bluetoothとの比較](#bluetoothとの比較)
 - [Nearby Interaction](#nearbyinteraction)
     - [これまでの Nearby Interaction](#これまでのnearbyinteraction)
     - [フレームワークの概要](#フレームワークの概要)
     - [使用例](#使用例)

# UWBについて
### UWBとは？
##### **UWB(Ultra Wide Band)**
- **距離と方向が正確に取得できる、近距離の屋内位置測位技術。**
通信距離は10m程度の近距離通信
距離と方向が正確に取得できる（誤差数十cm）

- 超広帯域の周波数幅にわたって、低電力密度の無線電波を用いて無線通信を行う技術 (500MHz ~ 数GHz)
 屋外利用は7.25-9.0 GHz, 24.25-29 GHz に限られる（2019年5月20日の総務省令）
- 標準規格 IEEE 802.15.4z（2019年）
- 消費電力が極めて少ない(2.4 mW)
- データ伝送の通信量 100 Mbps以上
- 三点測位によるリアルタイム位置測位システム（Real Time Location System：RTLS）に使われる
- AirTagにも対応。「探す」アプリで正確な位置を特定することができる。

### Bluetoothとの比較
同じく近距離無線通信で広く活躍している Bluetooth (BLE) との違いは以下の通りです。
| |UWB (Ultra Wide Band)|Bluetooth|Bluetooth (5.1~)|
|:----|:----|:----|:----|
|消費電力|少ない|少ない(特にBLE)|少ない(特にBLE)|
|通信距離|~10m程度|~10m程度|~10m程度|
|測距の誤差|10cm程度|1~5m程度|1~5m程度|
|方向の取得|可|不可|可(AoA)|
|測距・測位方法|ToF (Time of Flight)|RSSI (Received Signal Strength Indicator)|RSSI,AoA (Angle of Arrival)|
|対障害物|比較的強い|弱い|弱い|
|通信速度|100 Mbps以上|1 Mbps (4.0)|2 Mbps (5.0以降)|
|対応機種|iPhone 11~|全てのiPhone|iPhone 11~|
|周波数|500MHz ~ 数GHz。屋外利用は7.25-9.0 GHz, 24.25-29 GHz に限られる（2019年5月20日の総務省令）|2.4GHz帯|2.4GHz帯(日本の屋外利用)|

# Nearby Interaction
Apple で UWB 通信をするために iOS14で導入されたフレームワーク。

### これまでの Nearby Interaction
WWDC2020での登場以来、毎年新情報が更新されています。
[WWDC2020 Meet Nearby Interaction](https://developer.apple.com/videos/play/wwdc2020/10668/)
・iPhone to iPhone
[WWDC2021 Explore Nearby Interaction with third-party accessories](https://developer.apple.com/videos/play/wwdc2021/10165)
・iPhone to Apple Watch
・Third-party Accessories
[WWDC2022 What's new in Nearby Interaction](https://developer.apple.com/videos/play/wwdc2022/10008)
・Enhanced NI with ARKit
・Background Sessions, Third-party Hardware

実際の動作のイメージなども、ここから動画で確認できます。


### フレームワークの概要
今回は最も簡単なiPhone to iPhone をベースに説明します。

##### 接続のステップ
iPhone to iPhone のUWB接続は、大きく分けて以下の6ステップで実行されます。
1. NISession の生成
2. delegate のセット
3. Peerデバイスと NI discovery token を共有
＊tokenの共有方法はNearbyInteractionは提供していないため、bluetooth接続など他の方法で共有する必要がある
例：[Multipeer Connectivity](https://developer.apple.com/documentation/multipeerconnectivity), [Core Bluetooth](https://developer.apple.com/documentation/corebluetooth)
4. Peerデバイスから NI discovery token を受信
5. session configration の作成
6. 上記 configration でセッションを実行

```Summary.swift
// 1.NISession の生成
let session = NISession()

// 2.delegate のセット
session.delegate = self

// 3.Peerデバイスと NI discovery token を共有
let myDiscoveryToken = session.discoveryToken
sendDiscoveryTokenToMyPeer(peer, myDiscoveryToken) // YOUR app's method

// 4.Peerデバイスから NI discovery token を受信
let peerDiscoveryToken = ...

// 5.session configration の作成
let config = NINearbyPeerConfigration(peerToken: peerDiscoveryToken)

// 6.上記 configration でセッションを実行
session.run(config)
```
**NI discovery token**: ランダムに生成された一時的なセッションID。セッションが存続する間は有効。
NISessionクラスのインスタンスプロパティとして呼び出せる。

##### セッション実行の流れ
1. NISessionでデリゲートを取得
2. 実行すると、デリゲートが近傍オブジェクトの更新データを取得
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/652177/03317ee1-87ce-7f2a-1f8a-ecbb0caa7f49.png)
余談ですが、1vsN で multiple session をする場合は、NISession までの流れを複数用意して、同一のdelegateを取得します。

##### NISessionクラス のよく使うインスタンスメソッド
NISession: 2 つのpeer devicies 間の一意の接続を識別する。
以下に、よく使うインスタンスメソッドの名前と引数、使用例を記載する。
```swift
open class NISession : NSObject {
    // NI discovery token
    @NSCopying var discoveryToken: NIDiscoveryToken? { get }
    // セッションで実行される構成
    @NSCopying var configuration: NIConfiguration? { get }
    // セッションの操作
    func run(_ configuration: NIConfiguration)
    func pause()
    func invalidate()
}
```
```使用例.swift
let session = NISession()

// セッションの実行
session.run(config)
// セッションのポーズ
session.pause()
// セッションの再開
session.run(session.config)
// セッションの停止
session.invalidate()
```

##### NISessionDelegate のよく使うインスタンスメソッド
NISessionDelegate: セッションの更新を監視して対応するオブジェクト。
以下に、よく使うインスタンスメソッドの名前と引数を記載する。

```swift
// NearbyInteraction の session delegate
public protocol NISessionDelegate : NSObjectProtocol {
    //　セッションが近傍オブジェクトを更新したときに通知
    optional func session(
        _ session: NISession,
        didUpdate nearbyObjects: [NINearbyObject]
    )

    //　セッションが1つ以上の近傍オブジェクトを削除したときに通知
    optional func session(
        _ session: NISession,
        didRemove nearbyObjects: [NINearbyObject],
        reason: NINearbyObject.RemovalReason
    )

    //　中断されたセッションを通知
    optional func sessionWasSuspended(_ session: NISession)

    //　セッションの再開を通知
    optional func sessionSuspensionEnded(_ session: NISession)

    // 無効化されたセッションを通知
    optional func session(
        _ session: NISession,
        didInvalidateWith error: Error
    )
}
```

##### NINearbyObjectクラス から取得できる情報

```swift
open class NINearbyObject : NSObject, NSCopying, NSSecureCoding {
    // discovery Token
    @NSCopying var discoveryToken: NIDiscoveryToken { get }
    // 相対距離の取得
    public var distance: Float? { get }
    // 角度の取得
    public var direction: simd_float3? { get }
}
```

### 使用例
以下に、NearbyInteraction を開始するための簡単なメソッドの例を記載します。
```sample.swift
    func startup() {
        // ① NISessionの生成
        session = NISession()
        
        // ② delegate のセット
        session?.delegate = self
        
        // トークン共有フラグのリセット（新規セッション開始のため）
        sharedTokenWithPeer = false

        // `connectedPeer`が存在する場合（P2P接続できている場合）、必要に応じて discovery トークンを共有
        if connectedPeer != nil && mpc != nil {
            // 接続済みの場合
            // ③ Peerデバイスと discovery token を共有
            if let myToken = session?.discoveryToken {
                updateInformationLabel(description: "Initializing ...")
                if !sharedTokenWithPeer {
                    shareMyDiscoveryToken(token: myToken)
                }
                // ④ Peerデバイスから discovery token を受信
                guard let peerToken = peerDiscoveryToken else {
                    return
                }
                // ⑤ session configration の作成
                let config = NINearbyPeerConfiguration(peerToken: peerToken)
                // ⑥ 上記 configration でセッションを実行
                session?.run(config)
            } else {
                fatalError("Unable to get self discovery token, is this session invalidated?")
            }
        } else {
            // 接続できていない場合
            updateInformationLabel(description: "Discovering Peer ...") // UIに文字列を設定
            
            // P2P通信スタートアップ（P2P通信の開始トリガー）
            // * Multipeer Connectivity フレームワークを使用 *
            startupMPC()
            
            // 距離・方向ステータスにunknownをセット
            currentDistanceDirectionState = .unknown
        }
    }
```

# おわりに
実際にアプリを作って動かしてみて、以下のような感想を持ちました。
・距離は10m以内であれば、どのように持っていても非常に正確な値が取れる。
・角度はiPhoneの背面同士を向け合わないと、取得が難しい。

すぐに使えるサンプルコードもあるので、ぜひお手元のiPhoneにビルドして動かしてみてください。

# 参考
Apple公式 https://developer.apple.com/documentation/nearbyinteraction
[NearbyInteractionで周囲の端末の位置を測定する](https://www.toyship.org/2020/06/24/054655)