iPhoneにBeacon発信機能を実装してみた

## はじめに
先日アーキテクチャのデザインのレパートリーを増やしたいこともあり気になっていた「Amazon Web Services クラウドネイティブ・アプリケーション開発技法」という本を買いました。本書はAWSを活用したアプリケーションのサンプルがいくつか取り上げられているのですが、その中にある「iBeaconと連携する勤怠管理アプリケーション」の項目に惹かれたのが理由です。
**Beacon**と呼ばれるBluetooth4.0以降に搭載されている*BLE(Bluetooth Low-Energy)*の技術を使った位置情報を特定できるデバイスを使った実装例があるとのことで、IoT技術に関心を寄せている私は早速サンプルコードを動かしてみようと取り掛かりました。

## 問題：サンプルコードが動かない
まあ予想はしてました。なんせ出版されたのが2016年なんですもの。
近年のコンピュータ技術は発展がものすごく早いので、少し前に出版された本のサンプルが動くだなんて期待してはいけません。
ということで少し現状を整理しながら原因を探っていくことにしました。

### 仕組み
このiBeaconを使ったサンプルの仕組みですが、ざっくり言えばBeaconを受信する勤怠アプリがBeaconを受信したらAPI経由でDynamoDB上に作成された勤怠情報を格納するようになっています。肝心のBeaconの発信側ですが`node-bleacon`と呼ばれるライブラリを使うことでPCで代用できるようになっております。

### 調査
で、原因ですが、**node-bleaconはNode.jsの現行バージョン(ver 14.x)に非対応**だからでした。
`node-bleacon`は[bleno](https://github.com/noble/bleno#prerequisites)というライブラリをラップしたものなのですが、どうやらメンテがされておらずver9.xまでしか使えない様子でした。
代わりとなるライブラリを探してみたものの、`node-beacon-scanner`といった受信機能のあるものはありましたが、発信機能があるものはみつかりませんでした。
iBeaconを使ったサンプルなのにBeaconが発信できないとなると流石に困りました。
とは言え流石にサンプルを動かすためだけにラズパイやBeaconモジュールを買うわけにもいかず、すぐにでも試したかったので他の手段がないか探しました。

## スマホを発信器として使う
よく考えてみたら受信端末として使おうとしているこのiPhoneを発信器として利用できるのではと思い、調べてみたらちゃんとできるようです。

公式ドキュメント：https://developer.apple.com/documentation/corelocation/turning_an_ios_device_into_an_ibeacon_device

Beaconで使われてるBLEの通信にはそれぞれ役割があり、受信側を`Central`、発信側を`Peripheral`と呼ぶそうです。
というわけでサンプルコードを動かすためにまずはBeacon発信機能をiPhoneに実装することにしました。

## 実装
以下に作成したコードを載せておきます。
最小限の実装で済ませるため、ここではアプリを起動すると画面には何も表示されず、アプリを落とすまでBeaconを発信し続けるようにしております。

```appDelegate.swift
import UIKit
import CoreLocation
import CoreBluetooth

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, CLLocationManagerDelegate, CBPeripheralManagerDelegate {
    
    var localBeacon : CLBeaconRegion!;
    var beaconPeripheralData: NSDictionary!;
    var peripheralManager: CBPeripheralManager!
    
    let BEACON_ID: String = "BEACON"
    let uuid: UUID = UUID(uuidString: "01234567-89AB-CDEF-0123-456789ABCDEF")! // 事前にuuidを用意しておく
    let MAJOR: CLBeaconMajorValue = 10
    let MINOR: CLBeaconMinorValue = 20
    
    // アプリケーションの起動完了後に実行される処理
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
        self.initBeacon()
        return true
    }

    // Beaconを起動
    func initBeacon() {
        if localBeacon != nil {
            stopLocalBeacon()
        }
        localBeacon = createBeaconRegion()!

        beaconPeripheralData = localBeacon.peripheralData(withMeasuredPower: nil)
        peripheralManager = CBPeripheralManager(delegate: self, queue: nil, options: nil)
    }
    
    // Beaconを停止
    func stopLocalBeacon() {
        peripheralManager.stopAdvertising()
        peripheralManager = nil
        beaconPeripheralData = nil
        localBeacon = nil
    }
    
    // Bluetoothの電源が切り替わった際に実行される処理
    func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
        if peripheral.state == .poweredOn {
            peripheralManager.startAdvertising(beaconPeripheralData as? [String: Any])
        } else if peripheral.state == .poweredOff {
            peripheralManager.stopAdvertising()
        }
    }
    
    func createBeaconRegion() -> CLBeaconRegion? {
        return CLBeaconRegion(
            proximityUUID: self.uuid,
            major: self.MAJOR,
            minor: self.MINOR,
            identifier: self.BEACON_ID
        )
    }
    
```

## 動作確認
動作を確認するには先ほど作成したアプリ以外に、Beaconの受信機能とその端末が必要になります。最初の方に話した`node-beacon-scanner`などを使って開発することもできますが、そんな余裕はなかったのですでに出回っているもの、ここでは`Beacon Scan`というアプリを使って確認することにします。なお端末は自分のiPadを使用しました。

https://apps.apple.com/us/app/beacon-scan/id1011834079

アプリを起動するとこんな画面が出てきます。
<img width="768" alt="IMG_0942.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/412663/265af650-cd44-581e-d428-a5fb7bc53fb8.png">


右下の設定アイコンをタップして発信側で設定しているUUIDを入力します。
<img width="768" alt="IMG_0943.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/412663/fe142fb6-33dd-984a-ecd2-362f495747fb.png">

保存を押し、リスト画面に戻るとiPhoneから発信されているBeacon情報が確認できます。
<img width="768" alt="IMG_0944.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/412663/7173a660-a90f-5806-cb39-fec102402377.png">

## 補足
・BeaconRegionを作成する際にUUIDが必要になります。ターミナルで

```
$ uuidgen
```
を実行してUUIDを作成してください。
また、`major`と`minor`情報も必要となります。これは識別情報のひとつで`major`はグループ(例：フロアや階層)、minorは個々(例：置かれている部屋や場所)を識別するために使います。Beaconが複数あったときにどれが発信しているものかわかるようにするためですね。

・この機能はBluetoothを使用するので、`info.plist`に `Privacy - Bluetooth Always Usage Description`(xlmファイルで開いている場合は`<key>NSBluetoothAlwaysUsageDescription</key>`)の設定をするようにしてください。これがないとアプリを実行することができません。



## 感想
最初はサンプルコードが動かず、Beacon発信器もないためあきらめかけていましたが、iPhoneに発信機能を実装することで引き続きサンプルを試すことができそうです。
常に感じていることですが、まだプログラミング経験の浅い初学者はサンプルコードが動く書籍を買うことをお勧めします。そのためにも書籍の発刊日やアップデートに対応しているをチェックしましょう。
なお不満が多く批判的に聞こえているかもしれませんが、本書はクラウドネイティブな構成に必要なサービスの知識及び例が豊富に含まれているので、私を含めて事故解決できるひとでデザインパターンを知りたい方にはおすすめです。