スマートロックの自動開錠がうまくいかない理由

スマートロック使ってますか？
つい先日誘惑に負けて[Qrio Smart Lock](https://qrio.me/smartlock/)を買ってしまって以来
オートロックの便利さに驚きを隠せないでいます。
（速攻で[Ladybug](http://toshi-saito.github.io/Ladybug/)もオートロックに対応しました！）
しかし、自動開錠（Qrioだとハンズフリー開錠）の精度はお世辞にも良いとは言えません。
結局の所、玄関先でスマホいじって開けています。
どうして、こうも精度が悪いんでしょうか？

Ladybugでも自動開錠の実装をしたものの、
やはり、玄関に着く直前で開錠されて、100%待ち時間なく家に入れる。
というところまで持っていくことはできませんでした。
（Qrioよりはバッテリー食う分断然良いですが！）
その辺りの事情を少しだけ踏み込んでみたいと思います。

August Smart LockやAkerunは別売のWiFiモジュールと組み合わせることで、BLEの電波が届かなくても開錠出来てしまうので、それは横に置いておきます。
今回の話は、「通信はBLEのみ。」が前提のお話です。

ちなみに[Ladybug](http://toshi-saito.github.io/Ladybug/)では、Version2から搭載した赤外線開錠機能により、IRKit等と連携することができるので、これが他製品のWiFiモジュールと同じ役割を果たします。

## BLEのみを使う
さて、自動開錠の実装についてです。
まず、簡単なやり方から考えてみましょう。
BLEは2.5m〜50m程度の電波強度を持っています。
これを鵜呑みにすれば、家の中にいる限りずっと接続させておくことができそうです。
BLEには切断時にコールバックがあるので、そのタイミング再接続の要求を投げればよさそうです。

```swift
    func centralManager(central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: NSError?) {

        central.connectPeripheral(peripheral, options: [CBConnectPeripheralOptionNotifyOnDisconnectionKey: true])
    }
```

[CoreBluetoothプログラミングガイド](https://developer.apple.com/jp/documentation/CoreBluetoothPG.pdf) P.39にもそういった記述があります。

>アプリケーションによっては、Core Bluetoothフレームワークを利用して、長時間にわたるアクション
をバックグラウンドで実行することがあります。たとえばiOSデバイス用のホームセキュリティアプ
リケーションを考えてみましょう。これはBluetooth技術を搭載した施錠システムと通信します。アプ
リケーションと施錠システムが連携して、ユーザが家を出れば自動的に鍵をかけ、帰ってくれば鍵を
開けます。いずれもバックグラウンド状態での動作です。ユーザが家から離れると、iOSデバイスは
施錠システムと通信できる範囲外になるため、接続が失われることがあります。するとアプリケー
ションは、CBCentralManagerクラスのconnectPeripheral:options:メソッドを実行します。接続
要求はタイムアウトにならないので、家に帰れば再接続されることになります。

どうでしょう。私も最初はこの実装をしてみました。
・・・しかしそううまくはいきません。

iPhoneの場合、BLEの再接続要求を行えば、接続されるまで再接続試行をし続けてくれるのですが、再接続の試行間隔がだんだんと伸びていくようです。通常、家を出てから戻るのは数時間後とかなので、奇跡的に再接続試行と帰宅タイミングが合えば開錠されますが、通常はかなり長い間待たされることになります。
テストした感じだと、帰宅後数分経ってから（下手したら10分以上！）に開錠されるとか、ザラでした。

ついでに言うと
家の中でも通信状況によっては切断してしまうこともあるので、意図しないタイミングで開錠されてしまうこともあります。
それと、鍵のシェアなどで2台以上のスマホで開錠したい場合、他の端末と接続中の場合、別の接続を受け付けてくれないかもしれません。
できたとしても、同時接続を前提にファームウェアを書くのは面倒そうです。

## 大幅変更位置情報サービス
やはりここはBLEに頼らずGPSを使用して、家に近づいたかを判定するのがよさそうです。
こうすることで、BLEの電波強度は設置場所付近数メートルをカバーするだけで十分になるので、電波強度を極限まで抑えることでバッテリーを長持ちさせることもできます。

さて、GPSを使用する場合、バッテリーの持ちを考えて
大幅変更位置情報サービスもしくは、ジオフェンシングを使いたいところです。

残念ながら、大幅変更位置情報サービスのみを使用しての実装は現実的ではありません。
まず、精度が500m〜(らしい）とかなり悪いので、ちょっと近くのコンビニに出かけて帰ってくるケースが全くカバーできません。
さらに、先ほどのBLEの再接続問題があるので、500m移動中にかなり試行間隔が伸びます。
こちらは、「近所問題」を除けば、試行間隔は500mの移動時間程度に抑えられるので、BLEのみの場合よりはだいぶマシですが、やはり数分待たされるケースがありました。

## ジオフェンシング
では、ジオフェンシングはどうでしょうか？
ちなみにQrioはジオフェンシングで自動開錠を実装しているようです。

ジオフェンシングは指定した地点から指定した半径内から出た場合とその範囲に入った場合に通知を受けることができる仕組みです。
例えば、以下でcenterを中心とした、半径100mの範囲の監視ができます。

```swift

// ジオフェンシング開始
let region = CLCircularRegion(center: center, radius: 100, identifier: "key")
manager.startMonitoringForRegion(region)
```

```swift        
    func locationManager(manager: CLLocationManager, didEnterRegion region: CLRegion) {
        // 入った！
    }
    
    func locationManager(manager: CLLocationManager, didExitRegion region: CLRegion) {
        // 出た！
    }
```

しかし、ジオフェンシングの使用した場合も問題があります。
まず、領域内に入ったor出たの判定にかなりタイムラグがあるということです。
ドキュメントによると、20秒程度領域内にとどまっているor領域外に出ていることで、判定をしているようです。

[位置情報とマップ プログラミングガイド](https://developer.apple.com/jp/documentation/LocationAwarenessPG.pdf) P.35

>iOSシミュレータまたはデバイスで領域観測コードのテストを行う場合は、領域の境界線が横断され
てもすぐには領域イベントが発生しないことがあることを理解しておいてください。不確かな通知が
行われるのを避けるために、iOSでは一定のしきい値条件が満たされないと領域通知を行いません。
特に、ユーザの位置が領域の境界線を横断し、この境界線から最小距離だけ離れてから、この位置を
少なくとも20秒間保持しないと、通知は報告されません。

こちらも実装してみたんですが、didEnterRegionが呼び出されないことがあり（原因まで調べてない）、ちょっと精度的に実用的じゃないな。と感じてそれ以上踏み込んだことはしていないので、詳しいことはよくわかりません。

私がQrioを使用しての感想ですが、徒歩で帰宅した場合は、50％程度。車で帰宅した場合は、ほぼ100%自動開錠されません。
徒歩の場合も、自動開錠されるタイミングが遅い場合が多くので、開錠される前にアプリを起動して開錠しています。（アプリから開錠中に自動開錠が動くということがままあります。）

おそらく精度の問題でそうしているんだと思うのですが、Qrioの場合は半径を300mくらいにしているようで、そこそこ離れないと自動開錠が働きません。

つづく