Bluetooth Low Energy Simulator-A New Hope in IoT Development

私たちは21世紀に生きており、動物はまだ私たちと話をしないかもしれませんが、モノは確実に話をし始めています。 お金や通信、ニュース、スマートホームなど、私たちを取り巻くあらゆるスマートソリューションを扱うことができるミッションコントロールセンター(別名スマートフォン)を持って、私たちは歩き回っています。 スマートな筋肉酸素モニターHumonを使えば、それが可能です。 睡眠の質を高めたい? スマートバンド、スマートウォッチ、スリープトラッカー(いずれもBluetooth Low Energyを使用し、スマホと通信します)を入手しましょう。 自転車でどこかに行きたいですか? BLE対応のナビゲーションを使えば、通知などの雑念を最小限に抑えながら、目的地までの道のりを知ることができます。 オフィスのコーヒーができあがったことを知りたいですか?

BLE 対応アプリの開発

Bluetooth Low Energy デバイスに接続するモバイル アプリに取り組んだことがあるなら、それが最も簡単なタスクでないことはおわかりいただけるでしょう。 アプリには、周辺機器とその動作に関連する多くの画面と内部状態があります。 アプリは多くの場合、デバイスのファームウェア(時にはハードウェア(!!))と一緒に作成されており、接続は気まぐれで、外部インタフェースは不安定で、内部ロジックはバグだらけになっている可能性があります。 その結果、チームは機能をテストする際に既知の問題で多くの時間を浪費しています。

一般に、これにアプローチする方法は 2 つあります:

  1. 最初に、簡単で時間のかかる方法があります。
  2. 次に、大変な方法があります。デバイスを抽象化し、実際の BLE 処理を隠すことができる簡単なモックアップを作成します。 これにより、Android エミュレータ/iOS シミュレータで作業し、後で時間を節約し、CI で自動テストを実行できるようになります。 同時に、コードを実行するたびに実際の通信をテストしないことで、メンテナンスコストが増加し、新たなリスクが発生します。 結局のところ、その Bluetooth 周辺機器はおそらく私たちのアプリケーションの中心にあり、予期せず切断したり、奇妙な動作をしたりしてはいけません。 彼らは、誰もが恩恵を受けることができる、信頼性の高い BLE 対応アプリ開発のためのオープン ソース ソリューションを開発するよう私たちに依頼しました。 BLE 関連のすべての生産コードを処理し、実際の周辺機器やシステムの Bluetooth スタックの動作をシミュレートします。 シンプルで柔軟性があり、基本的なモックと BLE 対応デバイスの完全なシミュレーションの両方を作成することができます。 そして何より、オープン ソースです!

    これにより、ハードウェア プロトタイピングのコストをかけずに、デバイスの背後にあるコンセプトをテストすることができます。 モバイル チームは、ファームウェアやプロトタイプを待つことなく、仕様書だけで前進することができます。 AndroidエミュレータやiOSシミュレータを使用することで、モバイル性を高め、リモートワークを容易にし、物理的なスマートフォンの入手が制限されるのを避けることができます。

    Currently BLEmulator は Flutter で利用可能で、当社の FlutterBleLib とのみ動作します。

    How it works

    Flutter はマルチプラットフォーム フレームワークとして、両方のプラットフォームのネイティブ依存が必要です。 通常、それらはライブラリ自体の一部となりますが、ここでは異なるアプローチを使用しました。 Polideaにはreact-native-ble-plxというReact Native BLEライブラリがあり、これは我々の同僚の素晴らしい仕事です。 私たちは、そこからすべてのネイティブ・ロジックを抽出して、Multiplatform BLE Adapterという別のライブラリにすることにしました。 こうすることで、react-native-ble-plxとFlutterプラグインのFlutterBleLibの両方で使用される共通のコアを作成することができました。 副次的な効果として、ネイティブブリッジで使われる共通の抽象化機能であるBleAdapterを作成し、これはシミュレーションのための完璧なエントリポイントです

    FlutterBleLib のデータ フローはこのような感じです。

    1. FlutterBleLibオブジェクト(BleManager、Peripheral、Service.NET)の1つでメソッドを呼び出す。 Characteristic)
    2. Dart コードがメソッド名とそのパラメータをネイティブ ブリッジに送信する
    3. Native bridge はデータを受け取り、必要に応じてデシリアライズする
    4. Native bridge は Multiplatform BLE Adapter から BleAdapter インスタンスの該当メソッドを呼び出す
    5. BleAdapter が RxAndroidBle または RxBluetoothKit 上のメソッドを呼び出す
    6. My BLE Adapter は、Multiplatform BLE Adapter のメソッドを呼び出します。 プラットフォームに応じて
    7. RxAndroidBle/RxBluetoothKit はシステムメソッドを呼び出します
    8. System returns a response to the intermediary
    9. Intermediary returns the response to the BleAdapter
    10. BleAdapter responses to the native bridge
    11. Native ブリッジはレスポンスをマップし、Dart
    12. Dart はレスポンスを解析し、元の呼び出し元

    私たちの仕事は、4 から 9 点が一連の外部契約による理想的な入口であることを準備しました。 BleAdapter の異なる実装をインジェクトすることで、いつでもシミュレーションをオンにすることができます。

    次に、シミュレーションを行う場所を決定する必要がありました。

    1. 両方のプラットフォームで 1 つの定義

    間違いを犯す可能性のある場所の数と、シミュレーションされた周辺機器を作成しそれを維持するために必要な作業量の両方を最小限にすることが重要である。 まず、開発者は既知のツールでより効率的に作業できるため、追加の言語を導入することは避けなければなりません。 第二に、シミュレーションされたペリフェラルで可能なことを制限したくなかったのです。 もしあなたが、いくつかのHTTPサーバーから回答を要求したい場合(おそらく、ファームウェア自体を実行する、より高度なシミュレーションを使用して?

    シミュレーションされたコールルートは次のようになります。

    1. FlutterBleLibオブジェクトの1つ(BleManager、Peripheral、Service.NET)のメソッドを呼び出す。 Characteristic)
    2. Dart コードがメソッド名とそのパラメータをネイティブ ブリッジに送信する
    3. Native ブリッジはデータを受け取り、必要に応じてデシリアライズする
    4. Native ブリッジは SimulatedAdapter

      Changedの該当メソッドを呼び出す 部分が開始されます

    5. BleAdapter (この場合、シミュレータからのもの) は、BLEmulator のネイティブブリッジに呼び出しを転送します
    6. BLEmulator は、ロジック自体を実行するか (周辺機器を伴わない場合)、提供した周辺機器の適切なメソッドをコールします
  3. BLEmulator のネイティブブリッジにレスポンスが渡される
  4. BLEmulator のネイティブブリッジは SimulatedAdapter にレスポンスを渡す
  5. SimulatedAdapter はネイティブブリッジにレスポンスを返す

    Back to original flow

  6. ネイティブブリッジが、そのレスポンスを受け取る。 レスポンスをマッピングしてDartに送る
  7. Dartはレスポンスを解析して元の呼び出し元に返す

このように、どのバックエンドを使っていても、BLE処理のコードをすべて使ってFlutterBleLibが提供する型を操作できるのです。 実システムのBTスタックでも、シミュレーションでも。 これはまた、CI 上の自動テストで周辺機器との相互作用をテストできることを意味します!

How to use it

We’ve covered how it works and what possibilities it provides, so now jump into how to use it.

  1. Add dependency to blemulator in your pubspec.The PUBLIC.PUBLIC.yml
  2. プラグイン提供のクラス SimulatedPeripheral を使用して、独自のシミュレーション周辺機器を作成する。 SimulatedServiceSimulatedCharacteristic(次のセクションで詳しく説明します)
  3. 周辺機器をBlemulator.addPeripheral(SimulatedPeripheral)を使ってBLEmulatorに追加する
  4. FlutterBleLibからBleManager.createClient()を呼ぶ前にBlemulator.simulate()を呼ぶ

以上、たった4ステップで起動することができますね。

Peripheral contract

以下の例は Texas Instruments の CC2541 SensorTag に基づいて、IR 温度センサーに焦点を当てます。

UUIDs we’re interested in the documentation:

  • IR temperature service.UUIDがどのようなものか知りたい。 F000AA00-0451-4000-B000-000000000000 このサービスは、温度センサーに関連するすべての特性を含んでいます。
  • IR 温度データ。 F000AA01-0451-4000-B000-000000000000

    読み出しまたは監視可能な温度データです。 データのフォーマットは、ObjectLSB:ObjectMSB:AmbientLSB:AmbientMSBです。

  • IR temperature config.を監視している間、設定可能な期間ごとに通知を出します。 F000AA02-0451-4000-B000-000000000000

    センサーのオン/オフスイッチ。 この特性には2つの有効な値があります:

    • 00-sensor put to sleep (IR temperature data will be four bytes of zeros)
    • 01-sensor enabled (IR temperature data will emit correct readings)
  • IR temperature period(IR温度周期): F000AA03-0451-4000-B000-000000000000

    通知間隔:下限は300ms、上限は1000msです。 特性の値は10倍されるので、サポートされる値は30~100です。

求めていたものは以上なので、実装に行きましょう!

Simplest peripheral

最も単純なシミュレーションは、どんな値でも受け入れてすべてのオペレーションで成功させます。

Dart では次のようになります。

短く、簡潔で、ほとんど JSON のように見えます。

ここで何が起こるか:ランタイム指定 ID(任意の文字列でよいですが、BLEmulator に知られている周辺機器の中でユニークでなければなりません)を持つ SensorTag という周辺機器を作成しました。 ペリフェラルスキャンがオンになっている間、それはデフォルトのスキャン情報を使って800ミリ秒ごとにアドバタイズします。 広告データに含まれるUUIDを持つサービスを1つ含みます。 このサービスには、実際のデバイスと同じように3つの特性があり、そのすべてが読み取り可能です。 最初の特性は書き込みができないが、通知をサポートし、残りの2つは監視できないが、書き込みは可能である。 特性には無効な値はない。

IR Temperature ConfigとIR Temperature Periodは、渡された値をすべて受け取り、設定します。 IR Temperature Data 特性は通知をサポートしますが、どのような方法でも定義していないため、実際には何も送信しません。

BLEmulator はボックスから基本動作を提供し、すべての構成要素のハッピーパスの世話をし、必要な作業量を最小限に抑えます。 いくつかのテストや基本的な仕様順守のチェックには十分かもしれませんが、実際のデバイスのように動作しようとはしません。

カスタム動作を実装する必要があります。 開発者に、作成されたペリフェラルのあらゆる側面に対する必要なコントロールを提供する一方で、動作する定義を作成するために開発者が行う作業はできる限り少なくしたいと考えました。 この目標を達成するために、カスタム動作のエントリ ポイントとなる可能性のあるすべてのメソッドのデフォルト実装を作成し、何をオーバーライドするかを決定してもらうことにしました。

では、いくつかのロジックを追加してみましょう。 これを実現するには、1つのメソッドをオーバーライドするだけです:

以上です。 これで、接続に200ミリ秒かかるようになりました!

接続の拒否

同様のケースです。 遅延はそのままに、接続が確立できなかったというエラーを返したい。 メソッドをオーバーライドして自分でSimulatedBleErrorを投げることもできますが、次のようにすることもできます:

 @override Future<bool> onConnectRequest() async { await Future.delayed(Duration(milliseconds: 200)); return false; }

Disconnection initialized by the peripheral

再接続処理を確認したい、または圏外になるシミュレーションをしたいとしましょう。 同僚に本物の周辺機器を持ってオフィスの反対側まで走ってもらうか、デバッグ ボタンを追加して、その onPress で次のことを行います:

yourPeripheralInstance.onDisconnect();

(最初のオプションがより満足できるようですが)

スキャン情報内の RSSI を変更する

さて、感知した信号強度によって周辺機器を分類したいとし、テストを行う必要があるとします。 いくつかのシミュレーション周辺機器を作成し、1 つは静的な RSSI で残し、もう 1 つは次のようにします:

@overrideScanResult scanResult() { scanInfo.rssi = -20 - Random.nextInt(50); return super.scanResult();}

このように、可変 RSSI のデバイスをいくつか用意して、機能をテストすることができます。

Negotiating MTU

BLEmulator はほとんどのロジックを自分で行い、MTU を 23 から 512 のサポート範囲に制限しますが、さらに制限する必要がある場合は、requestMtu()メソッドをオーバーライドします:

BLEmulator は iOS で最も高いサポート MTU を自動的に交渉します。

Force values to supported range

特性によって受け入れられる値を制限するには、SimulatedCharacteristicを拡張した新しいクラスを作成する必要があります。

この特性は、最初のバイトで 0 または 1 に入力を制限し、追加のバイトは無視し、値がサポート範囲を超えるとエラーを返します。

センサーをオンにする

温度センサーがいつオンまたはオフになるかを知りたいのですが、どうすればいいですか。

これを実現するために、ハードコードされたUUIDを持つ新しいサービスを作成します。

定義した特性はisNotifiable: trueがないため、FlutterBleLibを通して監視できませんが、都合上、BLEmulatorレベルで監視することは可能です。

Emitting notifications

IR Temperature Data特性からのemitting notificationsがまだ足りませんね。 それを処理しましょう。

_emitTemperature() はTemperature Serviceのコンストラクタで呼び出され、無限ループで実行される。 IR Temperature Period特性の値で指定された各インターバルで、リスナーがいるかどうかチェックする(isNotifying)。 もしあれば、データ(センサーがオンかオフかに応じて、ゼロまたはランダムな値)をIR Temperature Data特性に書き込む。 SimulatedCharacteristic.write() はアクティブなリスナーに新しい値を通知します。

Advanced peripheral in action

BLEmulator のリポジトリで、より高度なペリフェラルの完全な例を見ることができます。 もし試してみたいなら、リポジトリをクローンしてそのサンプルを実行してください。

自動テストでの使用

ここで、自動テストでの BLEmulator の使用に関する私の仲間の Polidean Paweł Byszewski の研究に大いに感謝します。 できることは、enableFlutterDriverExtension(handler: DataHandler) を使用してテスト ドライバにデータ ハンドラを追加し、シミュレーションされたペリフェラルをアプリの main() に渡し、アプリの実行コンテキスト内でハンドラに文字列メッセージを渡すことです。

Wrapper for app

Your peripheral

Your test

このメカニズムのおかげで、周辺機器を好きなだけ初期化でき、シミュレーションしたデバイス内で定義済みの任意の動作を呼び出すことができます。 GitHub でご自身でチェックしてみてください。 使ってみて、楽しんでみて、壊してみて、それについてすべて教えてください!

他のプラットフォームでも見てみたいですか? 私たちにご連絡いただければ、実現させます! 私たちの他のライブラリもぜひご覧ください。また、あなたのプロジェクトで私たちが作業することを望むなら、遠慮なくご連絡ください。