最近流行っている?ワンキーの特定目的キーボードとして、ZoomやTeamsのマイクON/マイクOFFができるものを作ろうとWebを彷徨っていたら、たまたまこちらのページを見つけました。
今回は実質的にこちらの追実験として、ESP32 BLE Keyboard libraryを使ったキーボードが作れるかのテストしてみます。
ハードウェアの用意
ハードウェアは(ちょっと怪しい)ESP32 DevKit互換っぽいESP32が載ったボードを使います。写真ではユニバーサル基板に載っていますが、意味はありません。
ソースコードの用意
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)を送信するしかなさそうです。ただ、両方使っているので、どうしたもんでしょうかね。