ねこ島

iOSのこととか、日常について雑に書きます

BLEビーコンを検知してネコチャンのいる部屋を判定してみた

モチベーション

最近おうちを買いました。戸建てで4LDKの平均的なサイズのやつです。
今まで2LDKのマンションっぽいアパートに住んでいたので自室がめちゃくちゃ広くなって割と快適な日々を過ごしているのですが、一つ問題がありました。
それは、「ネコチャンを探すのが大変!」です。

大体どの位置にいるのかはわかるのですが、如何せん部屋の数と階層、隠れ場所が増えたことによりネコチャンがどこで過ごしているのか分かりづらくなりました。 加えて我が家のネコチャンは毎回変なところに隠れてしまうので、こうなると妻と総出で探すハメになります。 猫の位置なんてわからなくてもいいじゃんと思うかもしれませんが、万が一何かのタイミングで脱走していたらやばいですし、出かける時には猫がいることを確認してから家を出るので、毎回1Fと2Fを往復して探していたりします。

そんなわけで猫のいる位置わからん問題というのは我が家ではそこそこのレベル感で抱えていたので、今回技術でどうにかしてみたという記事になります。

どうやるか

当初は部屋のドアにSwitchbotの開閉センサを設けて、ネコチャンがドアを開閉した時に位置を確定させるやり方(我が家のネコチャンはドアを開けられます)を考えていましたが、ドアは人間も利用するため精度が微妙です。

何か良い方法はないかと考えた結果、我が家のネコチャンが利用しているCatlogのデバイスが使えそうだと判断しました。CatlogはCatlog Pendantという首輪をネコチャンにつけることでネコチャンの行動を見守ることができるIoTデバイスです。 水を飲んだ、ご飯を食べたといった細かな挙動が時系列に記録されるので、常日頃の健康チェックに欠かせない存在となっています。

このCatlog Pendantですが、ネコチャンの動きをCatlog Homeという中継機に伝えるために定期的にビーコンを発信しています。これはBLEビーコンなので、Catlog Home以外からも検知が可能です。

この仕組みを使って、BLEスキャンするハードウェアを各部屋に置いて、ビーコンを検知し、電波強度を比較すればネコチャンのいる部屋がわかるのではないかと考えました。

ちなみに今回筆者が使っている関係上Catlogを使用していますが、Catlogに限らずBLEビーコンを発信かつデバイスのアドレスを特定できればなんでも良いです。例えばMAMORIOなど落とし物タグをネコチャンにつけていればそれでも可能です(AirTagは確かその辺情報をランダム化していたような気がしていて難しそうなので除外)

我が家のネコチャンは筆者の部屋(2F洋室A)、妻の部屋(2F洋室B)、1Fのリビングの3ヶ所によく寝っ転がってます。なので、これらの部屋にビーコンを検知できるハードウェアがあればよさそうです。

準備

まずはハードウェアの選定です。当初はRaspberry Pi Zero Wが家に転がってたのでこれを後2つ追加すれば良いかと考えていましたが、現在Zero Wはほとんど在庫がなく、あっても転売価格で販売されていました。
また世界的な半導体不足が影響していてRaspberry Piシリーズそのものも在庫がないようで他のハードウェアを使うのが手っ取り早そうでした。

色々調べた結果、ATOM LiteというWi-FiBluetoothも利用できてかつ安価で小型という至れり尽くせりなマイコンが最適と判断。3つ送料込みでも4000円くらいです。
安すぎて神〜と思っていたのですがこれでも値上げして高くなった方らしく、発売当初は1個900円台で買えていたそうです。マジかい

ちなみに筆者はマイコンを触ったことがありません。M5Stackを使った技術ブログ記事をちょっと読んだことがあるレベルなんですが、このハードウェアは値段と使いやすさの面もあってかなり流通しており、ググればたくさん情報があったので開発中困ることはほとんどなかったです。

ATOM Liteが配達されるまではCatlog Pendantのデバイスアドレスを調べました。 手軽に調べる方法として、iPhonenRF Connect というアプリを使う方法があります。これでデバイスの周り飛んでいるビーコンを可視化することができます。
Scannerでアドレス値を確認しメモしておきます。この値は後ほどクライアントの実装で利用します。

あとBLE周りを触るのも初めてなので、関連知識を得ておきたくこの本を買って読みました。Bluetoothの仕様書は公式のサイトに掲載されているのでそちらを読むのが確実なんですが、如何せん量が膨大すぎて全て読むのが大変です。

この本はざっくりと全体像とそれぞれのデバイスの役割、仕様について解説してくれるので、これだけでもかなり解像度が上がりました。iOS/Androidでの実装例も載っているのでモバイルエンジニアにもおすすめです。

受信機側の実装

そんなこんなで本を読み終わる頃にATOM Liteが届いたので早速コードを書いて動かしてみました。最低限必要な要件は以下の通り。

  • BLEスキャンを継続的に行い、ビーコンを見つけたらRSSI(受信信号強度)を記録する
  • 得られたRSSIを定期的にサーバーに送信する

ざっくり書くと BLEDevice::getScan() で取得したインスタンスから setAdvertisedDeviceCallbacks を呼び出し、デバイスを見つけた時の処理を実装します。この中で事前に調べておいたBLEビーコンのデバイスアドレス(MACアドレス)と照らし合わせ、合致したらRSSIを記録します。

準備ができたらstart メソッドを呼び出しBLEスキャンを定期的に実行するだけです。しばらく待つとRSSIが配列に追加されていくので、これを別のコアで定期的にRSSIを送信するようにしています。

加えて、起動した時にSlackに起動通知を送ったり、Wi-Fi接続が切れた時に再接続する処理を追加しています。コードに関してはこちらのリポジトリにあるので興味ある方は見てみてください。

github.com

工夫した部分として、全てのデバイスからほぼ同じ時間にデータを送信するようにしました。
これはネコチャンのいる場所を判定する際に同じ条件(タイミング)で取得された最新のRSSI値を元に比較する必要があるためです。

実装としてはシンプルで、デバイス起動時にNTPサーバから最新の現在時刻を取得し、その値を元に5分毎にデータを送るようにしています。
時刻は定期的にsyncが行われていてずれることもないので、ほぼ同じタイミングでデータの送信が可能になります。

ちなみに当初はRSSI毎に取得した時間を含めてサーバーに送ることで非同期的に判定することも考えていましたが、クライアント・サーバーともに実装が複雑になりそうだったのでシンプルにできる方法として採用しています。

サーバー側の実装

お次はサーバー側の実装が必要です。今回はセットアップコストも鑑みてHerokuとPostgresを利用して構築しました。中身はGolang + Ginで動かしています。

github.com

起動時やネコチャンの判定位置の通知にIFTTT + Slackを利用しています。IFTTTを使っているのには理由があり、実は当初クライアント側から直接送るようにしてたのですが、文言を変更するときに全部のデバイスを書き換えるのが面倒でIFTTTで制御できるようにしたという経緯があります。

その後サーバーサイドにリクエストを集めることにしたのでIFTTTを使わなくても良くはなったのですが、めんどくさいのでそのままにしています。

またDB上でのビーコンの扱いとして、更新毎に古いデータを削除しています。
これは1デバイス = 1部屋なのが前提にあるのと、HerokuのPostgresは無料枠の場合10000Rowまでという制約があるためこのような仕組みにしました。ただこれだとスケールしづらいので、余裕があるならn件以上になったら消す、みたいな仕組みにしてもいいかもなと思っています。

さて、諸々準備ができたらATOM Liteにプログラムを書き込みます。起動時に受信機のWi-Fi MACアドレスをコンソールに出力するようにしているのでメモしておきます。このアドレスは受信機と受信機を置いている部屋との紐付けに利用しています。DBで管理しているので、今後デバイスを追加した時に柔軟に対応が可能な仕組みになっています。

書き込みが完了してDBの準備もできたら部屋に配置して起動します。受信機から5分おきに収集されたRSSI値がサーバーに送られてSlackに通知が飛ぶようになります。

これでネコチャンがどこにいるかわかるようになりました!めでたしめでたし

困ったこと

ここからは開発中困ったことや悩んだことを書いていきます。

[BLE] ビーコンが見つからない

これは家の構造上仕方がないのですが、ネコチャンがクローゼットや押し入れの中にいると電波が届かないらしくデータ上行方不明になることがありました。

そのため受信機をなるべく部屋の真ん中に寄せたり、ちょっとクローゼット寄りに置いてみたりしてできる限り電波強度を維持できるようにして解決しています。

それでも受信できない時はたまにあるので、苦肉の策としてSlack通知時に最後にいた場所を載せることで探す際のヒントとなるように工夫しています

[BLE] 部屋の判定を間違える

これもある程度仕方がないことなのですが、受信機と受信機の間(廊下など)にネコチャンがいると稀に部屋の判定を間違えることがあります。 これはお互いの受信機から見たRSSI値がほぼ同一かつ受信機の配置場所によって容易に変動するためです。

なるべく誤差を減らすためにサーバー側で集めたRSSIの平均値を用いたり(これは正直気休めにもなるか微妙です)デバイス毎の距離を離したりしています。

より厳密にやるのであれば、以下の記事のような方式でやるのがいいかと思いますが、実際利用してみて間違えているケースがあまりなかったため今回はそこまで踏み込んだ実装はしていません。課題感まで浮上してきたらやってみようと思います。

blog.dcs.co.jp

[ATOM Lite] 書き込みに失敗する

これはマイコンあるあるらしいのですが、シリアル通信の速度が早すぎると書き込みに失敗してしまうことがあるようです。 ボーレートを11520bpsに変更することで解決しました。