iOS端末同士をbluetooth通信させて端末間の距離っぽいものを測る

なんかbluetoothってイケテル感じしない？なんとなく未来感あるし、できるようになりたいなー。でもbluetoothといえばIoTで、といってもハード面はあまり得意じゃないし、ちょっと難しそうだなー。。。

あ、とりあえずスマホ同士でやってみればいいんじゃね。

というわけで、iOS端末同士をbluetooth通信させてみます。
Core Bluetoothを使います。

## 初めに
用語の確認。
セントラルが接続をする方、ペリフェラルが接続される方。

まず初めにセントラル側は周囲の端末をスキャンして、ペリフェラル側は周囲に自分がいることを知らせるためにアドバタイズをする必要があります。

ちなみにこの内容、ほぼここに凝縮されてます。
http://www.amazon.co.jp/iOS×BLE-Core-Bluetoothプログラミング-堤-修一/dp/4883379736

結構分厚いですが、大変わかりやすく書いてあるので、bluetoothの開発に関わる人は必読ですね。

## 周囲に存在を通知（ペリフェラル側）

スキャンの対象となるためには、ペリフェラル側でアドバタイズを開始しないといけません。
ペリフェラル側でごにょごにょするには、基本**CBPeripheralManager**を使用します。

### サービス、キャラクタリスティックを作成

通常ペリフェラル側は1つ以上のサービスをセントラルに提供します。そのためまずはサービスを作成します。1つのサービスの中には1つ以上のキャラクタリスティックが含まれるのでサービスを作成時にキャラクタリスティックも用意します。

**プロパティの用意**

```
var peripheralManager: CBPeripheralManager!

//サービスのUUIDを決める
let serviceUUID = CBUUID(string: "0000")
let service: CBMutableService!

//キャラクタリスティックのUUIDを決める
let characteristicUUID = CBUUID(string: "0001")
//キャラクタリスティックに指定するプロパティとパーミッション
let properties: CBCharacteristicProperties!
let permissions: CBAttributePermissions!
```

**初期化**

```
properties = [CBCharacteristicProperties.Notify,
            CBCharacteristicProperties.Read,
            CBCharacteristicProperties.Write]
   
permissions = [CBAttributePermissions.Readable, CBAttributePermissions.Writeable]

self.characteristic = CBMutableCharacteristic(type: self.characteristicUUID, properties: properties, value: nil, permissions: permissions)
        
//サービスの作成
self.service = CBMutableService(type: self.serviceUUID, primary: true)
//サービスのキャラクタリスティックを追加
self.service.characteristics = [self.characteristic]

//CBPeripheralManagerを初期化
self.peripheralManager = CBPeripheralManager(delegate: self, queue: nil, options: nil)
```

パーミッションの設定によって読み込み用とか書き込み用とかできます。

### サービスをCBPeripheralManagerに追加

CBCentralManagerのスキャンと同じノリで、サービスを追加するタイミングはCBPeripheralManagerのステータスがOnの時に追加をするようにしないとサービス追加処理で失敗します。

そこでCBPeripheralManagerのステータス変更時に呼ばれるデリゲートメソッドであるperipheralManagerDidUpdateStateの中でサービス追加処理を実行します。

```
func peripheralManagerDidUpdateState(peripheral: CBPeripheralManager) {
    let enumName = "CBPeripheralManagerState"
    var valueName = ""
    switch self.peripheralManager.state {
    case .PoweredOff:
        valueName = enumName + "PoweredOff"
    case .PoweredOn:
        valueName = enumName + "PoweredOn"
        
        //このタイミングでペリフェラルにサービスを追加するようにしないと失敗する
        self.peripheralManager.addService(self.service)
        
        
    case .Resetting:
        valueName = enumName + "Resetting"
    case .Unauthorized:
        valueName = enumName + "Unauthorized"
    case .Unknown:
        valueName = enumName + "Unknown"
    case .Unsupported:
        valueName = enumName + "Unsupported"
    }
    print(valueName)
}
```

## アドバタイズの開始

アドバタイズするデータの用意

```
var advertisementData: Dictionary = [CBAdvertisementDataLocalNameKey: UIDevice.currentDevice().name]
```

**アドバタイズの開始**
```
if (self.peripheralManager.isAdvertising == false) {
    self.peripheralManager.startAdvertising(self.advertisementData)
}
```

アドバタイズをする時にデータを送ることができるのですが、nilでも特に問題ないです。


## 周囲の端末をスキャン（セントラル側）

**プロパティの用意**

```
var centralManager: CBCentralManager!
```

**初期化**

```
self.centralManager = CBCentralManager(delegate: self, queue: nil)
```

**スキャン開始**
スキャンを開始するタイミングはCentralManagerのステータスがOnになった状態で始めないと失敗することがあります。

そこでステータスに変更があった時にCentralManagerのデリゲートメソッドであるcentralManagerDidUpdateStateが呼ばれるので、その中でスキャン開始のメソッドを実行します。

```
func centralManagerDidUpdateState(central: CBCentralManager) {
    //CentralManagerの状態変化を取得
    switch (central.state) {
        case CBCentralManagerState.PoweredOn:
            self.centralManager = central
            self.centralManager.scanForPeripheralsWithServices(nil, options: nil)
        default:
        break
    }
}
```

## 見つかった端末に接続（セントラル側）

### スキャンの結果を受け取る

スキャンをして端末が見つかると、CBCentralManagerのデリゲートメソッドである

```
centralManager(central: CBCentralManager, didDiscoverPeripheral peripheral: CBPeripheral, advertisementData: [String : AnyObject], RSSI: NSNumber){...}
```

が呼ばれます。

見つかったperipheralはstrongプロパティに入れて解放されないようにしておきます。

```
let peripheral: CBPeripheral!
```

```
func centralManager(central: CBCentralManager, didDiscoverPeripheral peripheral: CBPeripheral, advertisementData: [String : AnyObject], RSSI: NSNumber) {
     //周辺デバイスが見つかると呼ばれる
     self.peripheral = peripheral
}
```

### 見つかった端末に接続する

```
self.centralManager.connectPeripheral(peripheral, options: nil)
```

接続に成功するとCBCentralManagerのデリゲートメソッドの

```
centralManager(central: CBCentralManager, didConnectPeripheral peripheral: CBPeripheral) {...}
```
が呼ばれます。


## 端末間の距離を取得する

bluetoothで端末間の距離を取得はRSSIという値に入ってきます。
これを取得するにはCBPeripheralのreadRSSIメソッドを呼び出します。

```
self.peripheral.readRSSI()
```

readRSSIでRSSIを呼び出すと例によってデリゲートメソッドが実行されます。
とりあえず僕は**CLLocationManager**のコンパス機能を使ってiphoneの向きを変えたときにreadRSSIが呼び出されるようにしました。

```
func locationManager(manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) {
        //コンパス機能を使って方位を変更すると同時にrssiを取得するようにする
        if self.peripheral != nil{
            self.peripheral.readRSSI()
        }
    }
```

```
func peripheral(peripheral: CBPeripheral, didReadRSSI RSSI: NSNumber, error: NSError?) {
     print("RSSI: " + String(RSSI))
     if Int(RSSI) < -50 {
       AudioServicesPlaySystemSound(SystemSoundID(kSystemSoundID_Vibrate))
     }
}
```


## 困っていること

### 接続がすぐきれる。
strongプロパティに入れればいい、みたいに書いたけどやっぱりすぐ切れてしまうのです・・・
macとかkonashiといったiOS端末以外のものと接続するとそんなことがないので、ペリフェラル側の実装に問題あるのかなと・・・