近接接触確認アプリ FolkBears の BLE 通信構造を探る

前置きが長くなりそうですが、ざっとまとめておきます。もともとの発端は新型コロナウィルスの感染対策用アプリのである「COCOA」の前身の頃がスタートになります。いわゆる、近接接触確認アプリというジャンルです。既に COCOA だけでなく近接接触確認アプリ自体は各国で終了となっていますが、次期のため、といいますか、同様の技術を継承するために FolkBears というアプリを開発し続けて今に至っています。

元ネタは まもりあいJapan https://github.com/mamori-i-japan の mamori-i-japan-android と mamori-i-japan-ios をベースにして、北見工業大学 https://www.kitami-it.ac.jp/ が独自開発をして、FolkBears https://github.com/FolkBearsGroup/folkbears-android を開発するに至っています。現在のところ android 版のみコード公開してますが、ios 版もあり、公開のためにドキュメント等を整え中です。

開発経緯自体は、別のことろでまとめるとして、ここでは FolkBears の本質なところの BLE 相互通信の部分の解説をします。

COCOA の BLE 通信構造

COCOA の BLE 通信構造は、Google/Apple の Exposure Notification API (ENA) https://developer.apple.com/documentation/exposurenotification で提供される API を利用しています。ENA 自体は、国が開発することが前提となっているライブラリなので、現在一般的な開発を試みることはできません。しかし、ENA の BLE 通信構造自体は公開されているので、その構造を真似ることは可能です。

端的に言えば、ENA は Bluetooth Low Energy (BLE) を使って、コネクション無しの広告型(アドバタイジング)で ID を配布します。受信する端末は ID を保管しておき、サーバーで ID 同士を比較するという形になっています。問題は、このアドバタイジングの部分です。

  • ENA のアドバタイズはコネクションレスである
  • BLE Service UUID は 16 bit(0xFD6F)である

という仕様となっています。

FolkBears も同様の仕様にしたいところですが、これを真似ることはできません。BLE Service UUID は一般に使う場合には 128 bit を使うことが推奨されており、16 bit UUID は Bluetooth SIG に登録しなければいけません。お金がかかります。0xFD6F は Apple の UUID であり、ENA で使うようにしてありますが、これが使えません(OS レベルでガードが掛かっています)。また、ENA で使っているコネクションレスの接続方法は、iOS では使えません。ENA が使えるのは OS レベルで特別な仕様として使っているもので、iOS の API としては用意されていないわけです。

そのあたりの諸々の事情もあり、元ネタである「まもりあい Japan」バージョンは BLE によるコネクション版が使われています。FolkBears も同様にコネクション版を使うことができます。

ただし、、これも諸々の実験の事情もあり、コネクション版も不安定なところがあるため、ちょっとトリッキーな形ですが、

  • コネクションレス版の通信
  • iBeacon 形式を利用したコネクションレスの通信

の2種類を用意することができています。

COCOA / ENS の通信状況が検証されていない

このあたり、有耶無耶になっていると思うのですが、COCOA あるいは ENA の実機における通信状況はほとんど検証されていません。研究段階として、ドイツのアプリで距離を測ったデータなどがあるのですが、実際に 10 人集まった時のデータや、連続運用したときのデータや、人がすれ違ったときのデータなどの詳細なデータは公開されていません。すくなくとも私は目にしたことがありません。

というのも、ENA の仕様自体で、開発が国単位に制限されているのと状況が緊迫していた状態から、開発ログなどの研究データを取得する手段が完全にふさがれているためです。いちおう、デバッグモードの情報を取れるようになったおり、試験サーバーを用意して突き合わせをすることは可能なのですが、それをやっているところは見たことがありません。

そのあたり、緊急事態であったので仕方がないところではありますが、せっかくの技術がブラックボックス化してしまっているのは残念なところです。

できるだけ ENA に似せた状態で、研究データが取れる

FolkBears は、ENA に似せた状態で、かつ研究データが取れるように設計し直しています。本格的な運用(ENA が目指した個人の動向の隠蔽など)という点では現状では達していませんが、まずは、スマホ実機を使ったときの動作や通信状態を確認できるようにしています。

相互した結果は別途 aware サーバーに送ることで、どのような通信が行われたかを確認できるようになっています。これをもとに学部生や大学院生が研究ができることを目指しています。

通信構造とシーケンス

COCOA の場合

COCOA の場合、ENA の仕様に従って、以下のような通信構造とシーケンスになっています。

Exposure_Notification_-_Bluetooth_Specification_v1.2.2 より

BLE のデータ自体は 32 byte しか取れないので、Service UUID などの情報を除くと、ID を送信するため(Rolling Proximity Identifier: RPI)に 16 byte、付加情報(Associated Encrypted Metadata: AEM)に 4 byte しか使えません。
これは、先に書いた通り、お金を払って 16 bit の Service UUID を取得すれば使える問題なのですが、FolkBears ではそういうわけにはいきません。さらに言えば、お金を払って 16 bit UUID を取得したとしても、iOS ではコネクションレスのアドバイズができないので、ENA と同じ動作にはできない、という問題が残ります。

余談ですが、iOS を外してしまって、Android だけで実装するとか、M5Stack のようなマイコンで実装する場合には、16 bit UUID を取得して ENA と同じ構造にすることは可能です。これは実際、実験でできています。

FolkBears の場合(コネクション版)

もともと、まもりあい Japan ではコネクション版の通信を使ってあります。現コードでは、データの送受信状態がうまくいかなかったのと、コードの再構成のためにかなり書き直してはありますが、基本的な通信構造は同じです。

BLE のコネクション版では、Service UUID は 128 bit を使うことができます。これはフリーで使えます。
コネクション版のシーケンスは、次のような形です。

この操作を相互に行うことになります。一見すると、このコネクション版ではうまく動作するような気もしますが、実機で連続運用してみるといくつか問題が発生します。(実は、まもりあい Japan モードの場合は、データ送信(ID) の部分を Write/Read の両方でやっているので、相互にデータ送信が行うようになっていました。が、この部分が不安定だったので、いったん片方だけに変更しています)

  • コネクションを頻繁に行うため、BLE 接続で不安定になることが多い
  • Android の機種によって、BLE デバイスが止まることがある
  • Android のバージョンによってバックグランド動作の違いがある
  • Android の機種によって、BLE のスキャン頻度が異なる
  • Android と iOS の接続頻度が異なる
  • iOS からの接続頻度が多くて、Android 側がパンクすることが多い
  • 5,6 台集めたときに、接続されない端末た出ることが多い

接続頻度に関しては、再接続までの感覚を1分間隔にするなどの工夫をしていますが、まだ根本的な解決に至っていません。特に Android の機種(特に中華製)では、BLE が不安定になることが多く、BLE ドライバの ON/OFF を繰り返さないと復帰でない現象がでています。

2020 年頃の秋ごろに COCOA の Android 版で受信がされない現象が頻発していましたが、これが原因かもしれません。そのあたりも検証したいところです。

FolkBears のコードとしては、folkbears-android の以下に実装されています。

  • app/service/GattAdvertise.kt
  • app/service/GattServer.kt
  • app/service/GattClient.kt
  • app/service/GattTraceService.kt

FolkBears の場合(コネクションレス版)

コネクションの場合、コネクション確立と ID のデータ送信の2手順となるため、多くの端末が集結したときに問題がおきそうです。実際、5,6 台の Android を集めると、通信量が多くなって(単純に接続頻度が5倍になってしまいます)しまうので、満員電車やコンサート会場のように 100 台位の接近した状態になるとパンクしそうです。

ただし、実際のところ COCOA のときに試してみたのですが、受信自体は 20 台程度が限界のようです。これは当時、実際にツールを使って AEON で確認してみました。

そうなると、ENA のようのコネクションしない状態で、つまり、アドバイズする BLE データ自体に ID を乗せて配信するのがよさそうです。実際、Google/Apple の ENA もそうなっています。
ですが、先に書いた通り iOS ではコネクションレスのアドバイズができないので、ENA と同じ動作にはできない、という問題が残ります。実験機としては Android 限定でもよいのですが、実運用を考えるとそうもいきません。

そこで、iBeacon 形式を利用したコネクションレスのアドバタイズを使うことにしました。iBeacon 形式は Apple が提唱している BLE のアドバタイズ形式で、iOS でもサポートされています。iBeacon 形式では、Service UUID の代わりに Proximity UUID (128 bit)、Major (16 bit)、Minor (16 bit) を使ってデータを送信します。これを ID としてうことにしています。

これだと、コネクション版と違い台数が多くなっても大丈夫そうだし、通信形式が ENA に似ているので検証としてもよさそうです。

ただし、これも難点があって、ENA の ID/RPI は 16 byte ですが、iBeacon 形式では Major (16 bit) + Minor (16 bit) で合計 4 byte となってしまいかなり範囲が狭いです。衝突の問題がもありますが、ひとまず研究用として、4 byte の ID を使うことにしています。

これで良さそうなコネクションレスの iBeacon 版ですが、これも実装上いくつかの問題があります。

  • iOS 側のスキャンでは、移動状態しか判別できない
  • iOS ではバックグラウンドで iBeacon の発信ができない
  • iOS と Android で iBeacon 発信の頻度が大きく異なる

特に iOS 側のスキャンでは、相手が移動した(距離が変わった)ときにしかイベントが発生しないらしく、いまのところ常に取得できているように見えますが、実際にどうなるかは不明です。大抵の iBeacon 受信アプリでは、店内に入ったとときや、美術館や博物館で iBeacon 発信機の近くに寄ったときにイベントが発生するので、常に近接している状態ではイベントが発生しないという仕様になっています。

まあ、それであっても、ENA のようにコネクションレスで ID を配信する形にはなるので、比較対象の研究用としてはよいかと思います。

コード的には

  • app/service/BeaconScan.kt
  • app/service/BeaconTraceService.kt
  • app/service/BeaconTransmitter.kt

に実装されています。

長くなったので

長くなったので続きは、別途書きます。
今度はもうちょっと、BLE 通信部分をコードレベルで、実際の動きの詳細を解説します。

余談ですが

余談ですが、FolkBears のリファクタリングには Copilot を使っています。元コードが絡み合ってしまっていて不具合解消が上手くいかない状態が続いていたのですが、ここ1年位の AI コーディングの進歩がすさまじく、結構綺麗にリファクタリングができています。このあたりのノウハウも公開していく予定です。

参考資料

カテゴリー: 開発, FolkBears パーマリンク