お久しぶりの赤西真論です。最近はBlueskyの民になっております。
前置き
今年に入ってから蓮ノ空のこと好き好きクラブのみなさんになりまして、明日兵庫公演に参戦することになりました。
基本ライブはミリオンライブしか行っていなかったので、ボタン電池式のペンライトしか持っておらず、ラブライブで一般的な単四電池のペンライト(ラ!的に言えばブレード)を持っていません。
最近は何も振らず動かずただただ音を浴びるだけの人間となっていたのですが、念のため買っておくかということでKING BLADE X10 Vを今更ながら購入しました。
正直公式のブレードでも良かったのですが、あれは103期版で1年生組がすでに加入しているのにメンカラが6色しか入っていないのはいかがなものかと思い、買わないことにしました。
そういうわけで購入したキンブレですが、こいつはスマホアプリで色をカスタマイズする機能が付いています。まあ、発売から5年ぐらい経っている商品なので皆さんご存知のことでしょう。
で、まあこのアプリがすごく使いにくいです。UIがOS標準のパーツを使わずに組まれているので、ボタンの画像が並んでるみたいな感じなんですよね。てか、未だに発売されていないone1Bシリーズの画像があるのがめちゃくちゃ面白い。
ここでアプリの名前に注目します。「KingBladeBLE」ですね。そうです、こいつはBLEでブレードとの通信を行います。
ということで、ブラウザのWeb Bluetooth APIで通信してみようというのが今回のお話になります。
解析に入る前に
今回必要なものは
になります。AndroidとMacが必要という意味が分からない組み合わせなのですが、通信の解析にAndroidが必要で、Web Bluetooth APIがまともに動くのがMacしかないという理由です。
Web Bluetooth APIの仕様はMDNで確認しておきます。
流れとしては
- navigator.bluetooth.requestDevice() でデバイスとペアリングする
- BluetoothDevice.gatt.connect() でデバイスに接続する
- BluetoothRemoteGATTServer.getPrimaryService() でサービスを取得する
- BluetoothRemoteGATTService.getCharacteristic() でキャラクタリスティックを取得する
- BluetoothRemoteGATTCharacteristic.writeValue() でデータを送信する
- BluetoothDevice.gatt.disconnect() でデバイスを切断する
といったことを行います。
サービスとキャラクタリスティックってなんやという方はこちらのサイトが分かりやすいです。
実際に通信を解析する
では、通信の解析を行っていきます。まず、Androidスマートフォンの開発者向けオプションの中から「Bluetooth HCI スヌープログ」を有効にします。今回はGalaxy S22を例で行っています。
ここで「有効」を選択します。フィルタ済みを選択すると全パケットがキャプチャされないのでご注意ください。この設定を変更後は画面上の指示通りBluetoothの設定を一度切る必要があります。
設定有効後にKing Blade BLEを開き、ペアリングから色登録までの操作を一通り行います。
設定が出来たら、無駄なパケットが記録されないようにBluetoothを切っておくとよいでしょう。
操作が出来たら、スマートフォンとMacを接続して、以下のコマンドを実行します。
> adb bugreport
これによりバグレポートが作成され、カレントディレクトリにzipファイルが保存されます。
zipファイルがダウンロードされたら、解凍し、以下の箇所にあるlogファイルを取り出しておきます。
dumpstate-*/FS/data/log/bt/btsnoop_hci.log
取り出したら、Wiresharkでそのログファイルを開きます。(Macでやるって書いているのにスクショがWindowsなのは許して)
このままだと要らないパケットが多すぎるのでフィルターをかけます。スクロールして、GreenPeakTec(KBX5のこと)とスマホが通信しているパケットを探します。今回の場合、スマホはSamsungElectになっていました。
パケットを見つけたら、選択して、右下のペインに表示されている「Bluetooth」を選択してSourceとDestinationのMACアドレスを確認します。
確認出来たら、一覧の上にある「表示フィルタ」に以下の内容を入力します。
bluetooth.src === {スマホのMACアドレス} || bluetooth.src === {KBX5のMACアドレス}
これでフィルターをかけると実際にスマホとKBX5が通信している内容だけが取り出せます。(MACアドレス丸わかりだけど、まあええやろ)
これらのパケットの中で重要になるのが
- Read By Group Type Request, PrimaryService
- Read By Group Type Request, Characteristic
- Sent Write Request
になります。それぞれ「サービスの取得」「キャラクタリスティックの取得」「データの書き込み」を行うリクエストです。
パケットを全部説明していると永遠に終わらないので、必要なパケットだけ抜粋して説明します。
サービスの取得
まず、BluetoothRemoteGATTServer.getPrimaryService()を実行する際に必要となるPrimaryServiceのUUIDを探します。
これはRead By Group Type Request, PrimaryServiceを実行した際に1つだけUnknownで返ってきているのが該当します。
このパケットからPrimaryServiceのUUIDが「00000000-0000-1000-8000-00805f9b34fb」であることが分かりました。
キャラクタリスティックの取得
次にBluetoothRemoteGATTService.getCharacteristic()を実行する際に必要となるUUIDを探します。
こちらもRead By Group Type Request, Characteristicを実行した際に1つだけUnknownで返ってきています。
Service UUIDも先ほど確認したものと同じものが表示されているので正解でしょう。サービスと同じ「00000000-0000-1000-8000-00805f9b34fb」になっています。
送信データの確認
では、実際に設定を行う際にどのようなパケットが送信されているのかを確認します。
まず一番最初に登場するSent Write Requstを見てみます。
Valueが「41000000000000000000」となっています。16進数の文字列なので「41 00 00 00 00 00 00 00 00 00」という10バイトのデータになります。
正直これは毎回接続時に送信されるのですが、よく分かりません。もしかすると接続が正常に出来たことを通知しているのかもしれないです(これを送信せずに接続状態で放置していると勝手にKBX5側の電源が切れる)。
次に色操作した時のパケットを見てみます。今回はRED:255で送信した内容です。
Valueが「00ff0000000000000000」となりました。色々試したところ、バイト文字列の構造としては、2バイト目がRed、3バイト目がGreen、4バイト目がBlue、5バイト目がWhiteを示しています。
ちなみに今後出てくる設定用のパケットも含めてすべて10バイトでデータは送信されていますが、実際に利用されているのは先頭の5バイトのみです。
一つ面白いのが、例えば白を表すときにWhiteのみを255にする方法とRGBすべてを255にする方法があります。
なのですが、RGBすべてを255にするとパケットの中身が「00555555000000000000」になります。色々試したところ、RGBで値を調整すると合計値がff(255)になるよう内部で調整されるようです。
アプリ内で色を調整する際に右側にバッテリー消費の目安値が表示されますが、あれはどうやら255を目標に作成されているようです。そのため、Whiteのみ255とRGBすべてで255を指定した際の消費量が同じ表示になっているようです。
最後にメモリーに保存するときのパケットを確認しておきます。今回は赤→青→緑をメモリーAに登録するときのパケットです。複数のパケットで構成されているので、一覧で値を書き出します。
- 00ff0000000000000000
- 010000ff000000000000
- 0200ff00000000000000
- 1e030000000000000000
- 1f010000000000000000
内容の説明です。
まず、先頭の3パケットで色を順番に送信しています。1バイト目は連番になっているようです。登録中にKBX5側の色が変わるのはこのパケットの影響です。
次に4パケット目で何色登録するかを送信します。2バイト目に個数が指定されていますが、ここの値がそれより前に送ったパケットより大きいと色がループするようです。ちなみにこのパケットの1バイト目が1eから始まっているように色の指定は仕様通り30色までしか登録できません(色の連番が1dまでしか利用できない)。
最後のパケットでおそらく保存することを通知しています。
メモリーBでは同じ形状で色連番の開始位置が20となっています。そのため、個数指定のパケットは3e、保存用パケットは3fになります。
Web Bluetooth APIに書き起こしてみる
さて、通信内容が分かったのでWeb Bluetooth API用のJSに書き起こしてみます。
ということで、書き起こしたのがこのGistです。
おまけでQRコードの中身も付いていますが、無視してください。
このJSはMacのChromeでしか動作しません。Windowsで実行するとなぜかgetPrimaryService()の値が返ってこないため、何も出来ないです。
ということで、今回は以上になります。正直Windowsでも接続出来たらWebアプリ化しようかと思ったのですが、Macでしか動かないのであまり実用性はありません。また、SafariがWeb Bluetooth APIに対応していないので、iOSでも動作しません。AndroidのChromeで動くかは確認できていないので、誰かやってください。