2021年1月24日日曜日

ESP32とXboxのコントローラーをBLEで接続する方法


追加情報: ライブラリを作りました

この記事で試したこと(ESP32のArduino環境でxboxのコントローラーと連携)を呼び出すライブラリを作りました。
良かったらご利用ください。

XboxSeriesXControllerESP32_asukiaaa

背景

Xbox Series XまたはS向けのコントローラーはbluetooth通信可能ためPCやスマホと連携できます。
ESP32でも連携できたので、接続方法と情報の扱い方を備忘録として共有します。

注意: 2022.02.09時点ではESP32のBLE通信が不安定

記事に書いている内容で通信できることはあるのですが、通信内容の抜けが発生することがあるのか、なかなか繋がらない時があります。
BLEライブラリ管理者の意見では、ESP32の非公開のプログラムか物理的な回路に問題があると推測されています。

安定性を求めるなら、bluetooth3系で通信するPS3のコントローラーの利用をお勧めします。
jvpernis/esp32-ps3

参考issue:
BLE service discovery fails on low BLE connections : lld_pdu_get_tx_flush_nb HCI packet count mismatch (1, 2) (IDFGH-6671)
Resuest of some advice to solve lld_pdu_get_tx_flush_nb HCI packet count mismatch (1, 2)

使ったもの


ライブラリの準備: NimBLE-Arduino >= 1.1.0

NimBLE-Arduinoというライブラリを利用するので、それをインストールしてください。
Xboxのコントローラーと通信するためのアドレス保持形式に対応したバージョン1.1.0以上が必要です。

Arduino IDEならライブラリマネージャ(スケッチ -> ライブラリをインクルード -> ライブラリを管理)からインストールできます。


PlatformIOならplatformio.iniに下記の記述をすることで利用できます。

lib_deps = NimBLE-Arduino

サンプルコードを変更して作ったコードの説明

コード全体

全体のコードは下記のリンクを参照してください。

https://github.com/asukiaaa/esp32-client-for-xbox-controller-with-nim-ble/blob/master/src/main.cpp

BLEのclientとして動作するサンプルコードを元にXboxのコントローラーの情報を受け取るための変更を行っています。
主要な変更箇所を解説します。

初期化設定を変更

setupの初期化処理を変更して、ペアリングの有効化(setSecurityAuth(true, true, true);)とアドレス保持形式を静的なランダム(BLE_OWN_ADDR_RANDOM)にします。
この設定をしないと、接続できて部分的に値も読めるのに、コントローラーのLEDの点滅が止まらず、notificationを受信できない状態になります。
void setup() {
NimBLEDevice::init("");
NimBLEDevice::setOwnAddrType(BLE_OWN_ADDR_RANDOM); // 追加
NimBLEDevice::setSecurityAuth(true, true, true); // 変更
// ..
}

発見したBLE機器の選別処理を変更

保有しているXboxのBLEアドレスを調べて記述し、それに対して接続する処理にしています。
// 所有しているxboxのコントローラーのアドレス
static NimBLEAddress targetDeviceAddress("44:16:22:5e:b2:d4");

class AdvertisedDeviceCallbacks : public NimBLEAdvertisedDeviceCallbacks {
void onResult(NimBLEAdvertisedDevice* advertisedDevice) {
// ..
// if (advertisedDevice->isAdvertisingService(uuidServiceHid)) // 無効化
if (advertisedDevice->getAddress().equals(targetDeviceAddress)) // 追加
{
// ..
}
};
};

接続時の処理を変更

接続処理を記述している関数を変更し、HIDのサービスに対してreadとnotificationの登録処理を記述します。
notificationの登録だけではコントローラーが接続したと認識しないため、readも必要です。
void printValue(std::__cxx11::string str) {
Serial.printf("str: %s\n", str.c_str());
Serial.printf("hex:");
for (auto v : str) {
Serial.printf(" %02x", v);
}
Serial.println("");
}

void charaRead(NimBLERemoteCharacteristic* pChara) {
if (pChara->canRead()) {
charaPrintId(pChara);
Serial.println(" canRead");
auto str = pChara->readValue();
if (str.size() == 0) {
str = pChara->readValue();
}
printValue(str);
}
}

void charaSubscribeNotification(NimBLERemoteCharacteristic* pChara) {
if (pChara->canNotify()) {
charaPrintId(pChara);
Serial.println(" canNotify ");
if (pChara->subscribe(true, notifyCB, true)) {
Serial.println("set notifyCb");
// return true;
} else {
Serial.println("failed to subscribe");
}
}
}

bool afterConnect(NimBLEClient* pClient) {
for (auto pService : *pClient->getServices(true)) {
auto sUuid = pService->getUUID();
if (!sUuid.equals(uuidServiceHid)) {
continue; // skip
}
Serial.println(pService->toString().c_str());
for (auto pChara : *pService->getCharacteristics(true)) {
charaSubscribeNotification(pChara);
charaRead(pChara);
}
}
return true;
}

bool connectToServer(NimBLEAdvertisedDevice* advDevice) {
// ..
Serial.print("Connected to: ");
Serial.println(pClient->getPeerAddress().toString().c_str());
Serial.print("RSSI: ");
Serial.println(pClient->getRssi());
 // これ以後の行を下記の記述に変更

bool result = afterConnect(pClient);
if (!result) {
return result;
}

Serial.println("Done with this device!");
return true;
}

notification受信時に値を表示

参考にしたコードではnotificationの値を表示していなかったので、表示する処理を追加しました。
void notifyCB(NimBLERemoteCharacteristic* pRemoteCharacteristic, uint8_t* pData,
size_t length, bool isNotify) {
// ..
Serial.print("value: ");
for (int i = 0; i < length; ++i) {
Serial.printf(" %02x", pData[i]);
}
Serial.println("");
}

その他

コードが散ってたり細かったりするので具体例は示しませんが、それ以外にはこれらの変更を行っています。
詳しくはコード全体をご覧ください。
  • loopがbluetoothの処理で停止しないようconnectedとscanningフラグを追加
  • notificationが見やすいよう、100msおきにprint
  • notificationが即時反映されるよう、connection後に通信を遅くする設定変更を削除

動作確認

先ほど説明したコードをESP32に書き込み、PCへのシリアルプリントを行う状態にしてxboxのコントローラーと接続します。

接続のために、xboxのコントローラーのシェアボタンを長押ししてLEDが細かく点滅するペアリングモードにします。


成功すると下記のようなログが表示されると共に、xboxのコントローラーのLEDの点滅から常時点灯に変わります。
Connected
Connected to: 44:16:22:5e:b2:d4
RSSI: -51
E NimBLERemoteCharacteristic: "Error: Failed to get descriptors"
E NimBLERemoteCharacteristic: "Error: Failed to get descriptors"
E NimBLERemoteCharacteristic: "Error: Failed to get descriptors"
E NimBLERemoteCharacteristic: "Error: Failed to get descriptors"
E NimBLERemoteCharacteristic: "Error: Failed to get descriptors"
E NimBLERemoteCharacteristic: "Error: Failed to get descriptors"
E NimBLERemoteCharacteristic: "Error: Failed to get descriptors"
E NimBLERemoteCharacteristic: "Error: Failed to get descriptors"
E NimBLERemoteCharacteristic: "Error: Failed to get descriptors"
E NimBLERemoteCharacteristic: "Error: Failed to get descriptors"
E NimBLERemoteCharacteristic: "Error: Failed to get descriptors"
E NimBLERemoteCharacteristic: "Error: Failed to get descriptors"
E NimBLERemoteCharacteristic: "Error: Failed to get descriptors"
Service: uuid: 0x1812, start_handle: 22 0x0016, end_handle: 35 0x0023
s:0x1812 c:0x2a4a h:24 canRead
onAuthenticationComplete
str: ␁␁
hex: 01 01 00 03
s:0x1812 c:0x2a4b h:28 canRead
str: ␅␁ ␅�␁�␁ ␁�
hex: 05 01 09 05 a1 01 85 01 09 01 a1 00 09 30 09 31 15 00 27 ff ff 00 00 95 02 75 10 81 02 c0 09 01 a1 00 09 32 09 35 15 00 27 ff ff 00 00 95 02 75 10 81 02 c0 05 02 09 c5 15 00 26 ff 03 95 01 75 0a 81 02 15 00 25 00 75 06 95 01 81 03 05 02 09 c4 15 00 26 ff 03 95 01 75 0a 81 02 15 00 25 00 75 06 95 01 81 03 05 01 09 39 15 01 25 08 35 00 46 3b 01 66 14 00 75 04 95 01 81 42 75 04 95 01 15 00 25 00 35 00 45 00 65 00 81 03 05 09 19 01 29 0f 15 00 25 01 75 01 95 0f 81 02 15 00 25 00 75 01 95 01 81 03 05 0c 0a b2 00 15 00 25 01 95 01 75 01 81 02 15 00 25 00 75 07 95 01 81 03 05 0f 09 21 85 03 a1 02 09 97 15 00 25 01 75 04 95 01 91 02 15 00 25 00 75 04 95 01 91 03 09 70 15 00 25 64 75 08 95 04 91 02 09 50 66 01 10 55 0e 15 00 26 ff 00 75 08 95 01 91 02 09 a7 15 00 26 ff 00 75 08 95 01 91 02 65 00 55 00 09 7c 15 00 26 ff 00 75 08 95 01 91 02 c0 c0
s:0x1812 c:0x2a4d h:30 canRead
str:
hex: 00 80 00 80 00 82 00 82 00 00 00 00 00 00 00 00
s:0x1812 c:0x2a4d h:30 canNotify
Notification from 44:16:22:5e:b2:d4: Service = 0x1812, Characteristic = 0x2a4d
value: 97 85 cd 7e c1 7d 55 7f 00 00 00 00 00 00 00 00
set notifyCb
s:0x1812 c:0x2a4d h:34 canRead
str:
hex: 00 00 00 00 00 00 00 00
Done with this device!
Success! we should now be getting notifications

何も操作してない時はこのようなログが出ます。
Notification from 44:16:22:5e:b2:d4: Service = 0x1812, Characteristic = 0x2a4d
value: 81 82 b1 7e 15 7e ad 7d 00 00 00 00 00 00 00 00

Aボタンを押した時は、このようなログになります。
Notification from 44:16:22:5e:b2:d4: Service = 0x1812, Characteristic = 0x2a4d
value: 81 82 b1 7e 15 7e ad 7d 00 00 00 00 00 01 00 00
後ろから3番目(前から14番目)の1ビット目がAボタンの状態を示していると分かります。


notificationの値を解釈するライブラリを作ったので、よかったらこちらもご利用ください。
arduino-XboxControllerNotificationParser

まとめ

xboxのコントローラーのnotificationをESP32で取得できました。
時々ESP32をリセットしても繋がらなくなる不可解な挙動が発生しますが、やりたかったことが達成できたので一旦良しです。

bluetoothまわりの不具合が気になる方は、NimBLE-Arduinoarduino-esp32の不具合調査やPR作成をしていただけると、とても嬉しいです。

参考

ESP32にBLE HIDデバイスを接続する方法(複数デバイスに対応)

変更履歴

2022.02.09
通信が不安定である注意書きを追加しました。
2023.10.21
ESP32向けのライブラリの情報を記事冒頭に追加しました。

0 件のコメント :