LoginSignup
5
2

More than 3 years have passed since last update.

ESP32とBluetooth GamepadをHIDで接続してラジコンを操作してみた

Last updated at Posted at 2018-10-06

この記事は古いので
https://qiita.com/coppercele/items/57354f3e5a50ec7e0573
をご覧ください

-

-

-

-

-

-

-

-

-

ESP32(BLE Client)とBluetooth Gamepad(BLE Server)を接続してみました。

今回接続したのはこのゲームパッドです。

VR CASE2という奴らしいですが現在は品切れしてます。

まずはESP32ライブラリのBLE_clientサンプルスケッチを試してみます。

まずUUIDをHID/Reportとして指定します。

// UUID HID
static BLEUUID serviceUUID("00001812-0000-1000-8000-00805f9b34fb");
// UUID Report Charcteristic
static BLEUUID charUUID("00002a4d-0000-1000-8000-00805f9b34fb");

そしてBLE_clientを実行してみるとGamepadに接続するところまでは行くのですが、
データのやり取りができません。
println()等でデバッグしても正常に処理が行われてるように見えます。
readValue()すると何かの値が返ってくることがありますがこれは関係ありません。

いったんここでGamepadの仕様を確認してみましょう。
BLE Tool - Google Play のアプリ
https://play.google.com/store/apps/details?id=com.cozyoz.bletool
このアプリを使って(同名の別アプリがあるので注意)HIDのReport Characteristicの
部分を抜き出すとこうなっています。

discriptors.gif

なぜかReportが複数存在しています?!
ここでGamepadを操作してみるとわかるのですが、
データをやり取りできるのは一番上のCharacteristicだけです。
(DiscriptorのValue fieldが01 00,04 00の物)

というわけでここでソースに戻ります。
BLE_clientの中には原因がないのでESP32_BLEのライブラリの中を見てみます。

ちなみにAruduino C++のソースはこれを使って編集してます。
http://eclipse.baeyens.it/stable.php?OS=Windows

pRemoteService->getCharacteristic(charUUID);
からソースを追っていくと BLERemoteService::retrieveCharacteristics()に
行きつくのですが、

m_characteristicMap.insert(std::pair<std::string, BLERemoteCharacteristic*>(pNewRemoteCharacteristic->getUUID().toString(), pNewRemoteCharacteristic));

ここでMapにCharacteristicを格納しているのですが、
「UUIDをキーにしているためCharacteristicが複数存在しても1つしか格納されない」
のです。

Characteristicが複数存在するとは想定されてないわけですね。
なので、たまたま正解のCharacteristicが当たればよいのですが、
はずれのCharacteristicが返ってきたら通信できないわけです。

ここで開発者さんに相談して解決策を模索しました。

まずBLERemoteService::retrieveCharacteristics()のMapのkeyをhandleに変更して
複数あるCharacteristicが登録されるようにします。

m_characteristicMapByHandle.insert(std::pair<uint16_t, BLERemoteCharacteristic*>(result.char_handle, pNewRemoteCharacteristic));

ただこのままだとどれが正解のCharacteristicだかわからないので、
BLE_clientにCharcteristicのMapをメソッドを渡してその中で処理します。

// BLERemoteServiceからマップを取得
pCharacteristicMap = pRemoteService->getCharacteristicsByHandle();
    // iteratorで処理
    for (auto itr = pCharacteristicMap->begin();
            itr != pCharacteristicMap->end(); ++itr) {

        if (charUUID.toString() == itr->second->getUUID().toString()) {
            // UUIDがReport(2a4d)なら
            Serial.print("CharUUID matched: ");
            Serial.println(itr->second->getUUID().toString().c_str());

            if (itr->second->canRead() && itr->second->canNotify()
                    && !itr->second->canWrite()) {
         // CharacteristicのPropertyがRead=1,Notify=1,Write=0
                // 添付した画像を参照
                // DiscriptorのValueFieldを取得する手段は無い
                Serial.print("Correct : ");
                // Notifyに登録
                pRemoteCharacteristic = itr->second;
                pRemoteCharacteristic->registerForNotify(notifyCallback);

            }
            else {
                Serial.println("Incorrect");
            }
        }
    }

本当はDiscriptorのValueFieldがnullなら排除したかったのですが、
Notifyに複数のCharacteristicを登録しても問題ありませんでした。

というわけで正しいCharacteristicをNotifyに登録すれば
Notify callbackが正常に動作するようになります。

Notify callback for characteristic 00002a4d-0000-1000-8000-00805f9b34fb of data length 2
Data : 0000

あらかじめ作ってあったESP32ラジコンに組み合わせるとこういう感じで動くようになります。

とりあえずノウハウを共有するために記事にしてみました。
ザックリと書いただけなので構成に難があったりよくわからない表現があったりすると思います。
疑問、ご意見があったらコメントやリプをください。
随時反映していこうと思います。

5
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2