ESP32でBluetoothキーボード

最近流行っている?ワンキーの特定目的キーボードとして、ZoomやTeamsのマイクON/マイクOFFができるものを作ろうとWebを彷徨っていたら、たまたまこちらのページを見つけました。

WordPressでこういう形で他のサイトを引用できることを初めて知りました・・・・

今回は実質的にこちらの追実験として、ESP32 BLE Keyboard libraryを使ったキーボードが作れるかのテストしてみます。

ハードウェアの用意

ハードウェアは(ちょっと怪しい)ESP32 DevKit互換っぽいESP32が載ったボードを使います。写真ではユニバーサル基板に載っていますが、意味はありません。

ちゃんと技適マークは入ってるモジュールです。CMIITは中国の電波法、ICはIndustry Canada、FCCは米国連邦通信委員会のそれぞれの認証IDですね。KCマーク(韓国)は一番面積食ってますね。CEマーキングは番号なし。ふと思ったんだけど、日本の電波法の技適マークって検索性悪そうです。こんなマークは知らないという海外の人はどうやって調べたらいいのだろう。

ソースコードの用意

ESP32 DevKitのビルドができるArduino環境で以下のソースコードを新規に作成します。

#include <BleKeyboard.h>

BleKeyboard bleKeyboard("ESP32 KEYBOARD"); //デバイスの名前

void setup() {
  Serial.begin(115200);
  Serial.println("Starting BLE work!");
  bleKeyboard.begin();

  pinMode(0,INPUT_PULLUP);    //GPIO0(BOOT)をプルアップ付き入力設定
}

void loop() {
  if(bleKeyboard.isConnected()) {    //接続されているとき
    if(digitalRead(0) == LOW){       //BOOTスイッチが押されている時
      bleKeyboard.print("keyboard test!");    //文字を送信
      bleKeyboard.write(KEY_RETURN); //Enterを送信
      delay(200);
    }
  }
}

参考にさせていただいた記事ではGPIO32とGPIO33にタクトスイッチとプルダウン抵抗を付けていますが、今回は動作テストなので入力にはDevKit上のBOOTのボタンを使用するよう改めています。

ライブラリはGitHubのESP32 BLE Keyboard libraryのページで、右上の「Code」のところから「Download ZIP」でライブラリをZIP形式でダウンロードしてきます。ダウンロードしたら「スケッチ」⇒「ライブラリのインクルード」でダウンロードしたライブラリを取り込みます。

動作テスト

これでビルドして書き込んでみると、Bluetooth内蔵のPCからキーボードとして見えるようになりますので、ペアリングしてから適当にテキストエディタを開いてDevKit上のBOOTのボタンを押すと、文字列が入力されて改行されます。

特殊キーの入力テスト

ソースコードを以下のように修正します。

#include <BleKeyboard.h>

BleKeyboard bleKeyboard("ESP32 KEYBOARD"); //デバイスの名前

void setup() {
  Serial.begin(115200);
  Serial.println("Starting BLE work!");
  bleKeyboard.begin();

  pinMode(0,INPUT_PULLUP);    //GPIO0(BOOT)をプルアップ付き入力設定
}

void loop() {
  static int sn,sp=HIGH;
  if(bleKeyboard.isConnected()) {    //接続されているとき
    sn = digitalRead(0);
    if(sn == LOW && sp==HIGH){       //BOOTスイッチが押されている時
      bleKeyboard.write(KEY_MEDIA_MUTE); //メディアキーを送信
    }
    sp=sn;
    delay(20);
  }
}

このコードでは、スイッチ押下時のチャタリング防止処理を追加しています。一旦スイッチを離さないと次のキーが入力できないようにしています。
これで実行すると、キーを押すたびにスピーカーがミュート/ミュート解除されます。

マイクのミュート/ミュート解除は一筋縄ではいきそうにない

この特殊キーのシンボルはライブラリのBleKeyboard.h 内で、

typedef uint8_t MediaKeyReport[2];

const MediaKeyReport KEY_MEDIA_NEXT_TRACK = {1, 0};
const MediaKeyReport KEY_MEDIA_PREVIOUS_TRACK = {2, 0};
const MediaKeyReport KEY_MEDIA_STOP = {4, 0};
const MediaKeyReport KEY_MEDIA_PLAY_PAUSE = {8, 0};
const MediaKeyReport KEY_MEDIA_MUTE = {16, 0};
const MediaKeyReport KEY_MEDIA_VOLUME_UP = {32, 0};
const MediaKeyReport KEY_MEDIA_VOLUME_DOWN = {64, 0};
const MediaKeyReport KEY_MEDIA_WWW_HOME = {128, 0};
const MediaKeyReport KEY_MEDIA_LOCAL_MACHINE_BROWSER = {0, 1}; // Opens "My Computer" on Windows
const MediaKeyReport KEY_MEDIA_CALCULATOR = {0, 2};
const MediaKeyReport KEY_MEDIA_WWW_BOOKMARKS = {0, 4};
const MediaKeyReport KEY_MEDIA_WWW_SEARCH = {0, 8};
const MediaKeyReport KEY_MEDIA_WWW_STOP = {0, 16};
const MediaKeyReport KEY_MEDIA_WWW_BACK = {0, 32};
const MediaKeyReport KEY_MEDIA_CONSUMER_CONTROL_CONFIGURATION = {0, 64}; // Media Selection
const MediaKeyReport KEY_MEDIA_EMAIL_READER = {0, 128};

として定義されています。要は16個の特殊キーの同時押しまで対応するようにビット割付されているようです。

そして、これらのキーは、BleKeyboard.c内で、

static const uint8_t _hidReportDescriptor[] = {
  USAGE_PAGE(1),      0x01,          // USAGE_PAGE (Generic Desktop Ctrls)
  USAGE(1),           0x06,          // USAGE (Keyboard)
  COLLECTION(1),      0x01,          // COLLECTION (Application)
  // ------------------------------------------------- Keyboard
  REPORT_ID(1),       KEYBOARD_ID,   //   REPORT_ID (1)
  USAGE_PAGE(1),      0x07,          //   USAGE_PAGE (Kbrd/Keypad)
  USAGE_MINIMUM(1),   0xE0,          //   USAGE_MINIMUM (0xE0)
  USAGE_MAXIMUM(1),   0xE7,          //   USAGE_MAXIMUM (0xE7)
  LOGICAL_MINIMUM(1), 0x00,          //   LOGICAL_MINIMUM (0)
  LOGICAL_MAXIMUM(1), 0x01,          //   Logical Maximum (1)
  REPORT_SIZE(1),     0x01,          //   REPORT_SIZE (1)
  REPORT_COUNT(1),    0x08,          //   REPORT_COUNT (8)
  HIDINPUT(1),        0x02,          //   INPUT (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
  REPORT_COUNT(1),    0x01,          //   REPORT_COUNT (1) ; 1 byte (Reserved)
  REPORT_SIZE(1),     0x08,          //   REPORT_SIZE (8)
  HIDINPUT(1),        0x01,          //   INPUT (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
  REPORT_COUNT(1),    0x05,          //   REPORT_COUNT (5) ; 5 bits (Num lock, Caps lock, Scroll lock, Compose, Kana)
  REPORT_SIZE(1),     0x01,          //   REPORT_SIZE (1)
  USAGE_PAGE(1),      0x08,          //   USAGE_PAGE (LEDs)
  USAGE_MINIMUM(1),   0x01,          //   USAGE_MINIMUM (0x01) ; Num Lock
  USAGE_MAXIMUM(1),   0x05,          //   USAGE_MAXIMUM (0x05) ; Kana
  HIDOUTPUT(1),       0x02,          //   OUTPUT (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
  REPORT_COUNT(1),    0x01,          //   REPORT_COUNT (1) ; 3 bits (Padding)
  REPORT_SIZE(1),     0x03,          //   REPORT_SIZE (3)
  HIDOUTPUT(1),       0x01,          //   OUTPUT (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
  REPORT_COUNT(1),    0x06,          //   REPORT_COUNT (6) ; 6 bytes (Keys)
  REPORT_SIZE(1),     0x08,          //   REPORT_SIZE(8)
  LOGICAL_MINIMUM(1), 0x00,          //   LOGICAL_MINIMUM(0)
  LOGICAL_MAXIMUM(1), 0x65,          //   LOGICAL_MAXIMUM(0x65) ; 101 keys
  USAGE_PAGE(1),      0x07,          //   USAGE_PAGE (Kbrd/Keypad)
  USAGE_MINIMUM(1),   0x00,          //   USAGE_MINIMUM (0)
  USAGE_MAXIMUM(1),   0x65,          //   USAGE_MAXIMUM (0x65)
  HIDINPUT(1),        0x00,          //   INPUT (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
  END_COLLECTION(0),                 // END_COLLECTION
  // ------------------------------------------------- Media Keys
  USAGE_PAGE(1),      0x0C,          // USAGE_PAGE (Consumer)
  USAGE(1),           0x01,          // USAGE (Consumer Control)
  COLLECTION(1),      0x01,          // COLLECTION (Application)
  REPORT_ID(1),       MEDIA_KEYS_ID, //   REPORT_ID (3)
  USAGE_PAGE(1),      0x0C,          //   USAGE_PAGE (Consumer)
  LOGICAL_MINIMUM(1), 0x00,          //   LOGICAL_MINIMUM (0)
  LOGICAL_MAXIMUM(1), 0x01,          //   LOGICAL_MAXIMUM (1)
  REPORT_SIZE(1),     0x01,          //   REPORT_SIZE (1)
  REPORT_COUNT(1),    0x10,          //   REPORT_COUNT (16)
  USAGE(1),           0xB5,          //   USAGE (Scan Next Track)     ; bit 0: 1
  USAGE(1),           0xB6,          //   USAGE (Scan Previous Track) ; bit 1: 2
  USAGE(1),           0xB7,          //   USAGE (Stop)                ; bit 2: 4
  USAGE(1),           0xCD,          //   USAGE (Play/Pause)          ; bit 3: 8
  USAGE(1),           0xE2,          //   USAGE (Mute)                ; bit 4: 16
  USAGE(1),           0xE9,          //   USAGE (Volume Increment)    ; bit 5: 32
  USAGE(1),           0xEA,          //   USAGE (Volume Decrement)    ; bit 6: 64
  USAGE(2),           0x23, 0x02,    //   Usage (WWW Home)            ; bit 7: 128
  USAGE(2),           0x94, 0x01,    //   Usage (My Computer) ; bit 0: 1
  USAGE(2),           0x92, 0x01,    //   Usage (Calculator)  ; bit 1: 2
  USAGE(2),           0x2A, 0x02,    //   Usage (WWW fav)     ; bit 2: 4
  USAGE(2),           0x21, 0x02,    //   Usage (WWW search)  ; bit 3: 8
  USAGE(2),           0x26, 0x02,    //   Usage (WWW stop)    ; bit 4: 16
  USAGE(2),           0x24, 0x02,    //   Usage (WWW back)    ; bit 5: 32
  USAGE(2),           0x83, 0x01,    //   Usage (Media sel)   ; bit 6: 64
  USAGE(2),           0x8A, 0x01,    //   Usage (Mail)        ; bit 7: 128
  HIDINPUT(1),        0x02,          //   INPUT (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
  END_COLLECTION(0)                  // END_COLLECTION
};

でHIDのレポートディスクリプタとして定義されている中のハイライト部分に対応しているようで、この部分で16個のビットに対してキーコードを割り当てています。キーコードはUSBのHIDコードのようで、USBのHIDコードは

https://www.usb.org/document-library/hid-usage-tables-121

にあるPDFファイル(この1.21は2020/11/17のタイムスタンプがついている)に記載されているもののようです。試しに、このBleKeyboard.cppに書いてあるキーコードを書き換えると、動作が変わります。ただし、(Linuxの場合)一旦ペアリングを解除しないと、新しいディスクリプタを読み込んでくれませんでした。

そして、ZoomやTeamsといったオンラインミーティングの際にマイクのミュート/ミュート解除をしたくて、P.120にあるConsumer Pageの

を試してみるためにD5を設定してみたが、これは効きませんでした。(P.157にゲームの録画と配信の際に切り替えるようなことを書いてあります)

ところで、LenovoのPCではFn+F4でマイクをミュートすることができます。つまり、なんらかのキーコードがありそうなので探しまくりました。そして、Linuxのソースコードの中の /usr/include/linux/input-event-codes.h の中にも、

#define KEY_MICMUTE 248 /* Mute / unmute the microphone */

といういかにもマイクをミュート/ミュート解除するキーイベントを定義する行があり、いかにもマイクをミュート/ミュート解除するキーコードがありそうにも見えます。しかし、これに読み替えている箇所が見つかりません。さらには、キーの組み合わせでこのイベントを発生されているレノボ用のドライバっぽいソースコードも(インターネット上では)見つかるので、このキーコードは(少なくとも標準には)ないのかもしれないと思っています。

となると、スケッチ例にあるようにホットキーの組み合わせ(Zoomの場合は Alt + A、Teamsの場合は Ctrl + Shift + M)を送信するしかなさそうです。ただ、両方使っているので、どうしたもんでしょうかね。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)