ESP32をBluetoothキーボードやマウスにする

Arduino Leonardo等は、USBキーボードやマウスとして振舞わせ、PC等を操作するのに使うことができます。
同じくArduino IDEをプログラミング環境として使えるESP32ではUSBキーボード機能は付いていません。しかし、代わりにBluetooth LEを利用してキーボードとして振舞わせることができるはず。ということで、やってみました。

セットアップ

今回はM5StickCを使いましたが、ESP32ならどのボードでも同様です。

M5StickC ESP32 PICOミニIoT開発ボードフィンガーコンピューターカラーLCD付き

Arduino IDEでESP32を使用するためのセットアップ手順は、下記サイトなどを参考にしてください。
esp32_setup – スイッチサイエンス

BLE HID (Human Interface Device)のためのクラスはESP32のツールキットにすでに含まれていますが、キーボードやマウスとして振る舞うのに必要なセットアップをクラスにまとめたライブラリを作ってくれている方がいるので、これを利用すると簡単です。

  • キーボード

github.com

  • マウス

github.com

このライブラリをダウンロードして、スケッチを保存している Arduino フォルダの下の libraries 内に入れて IDE を起動すれば利用できるようになります。


ライブラリの使い方は、ファイル>スケッチ例>(ライブラリ名) 以下のサンプルを見れば一目瞭然なほど簡単です。
例えば以下のスケッチは、5秒ごとに Hello world とタイプし、エンターキーを押すものです。

#include <BleKeyboard.h>

BleKeyboard bleKeyboard;

void setup() {
  Serial.begin(115200);
  bleKeyboard.begin();
}

void loop() {
  if (bleKeyboard.isConnected()) {
    bleKeyboard.print("Hello world");

    bleKeyboard.write(KEY_RETURN);
  }
  delay(5000);
}

これをESPに書き込んで起動して、BLEに対応したPCやスマートフォン等からBluetoothバイス一覧を開くと、 「ESP32 BLE Keyboard」というデバイスが見えます。これに接続してテキストエディタ等に切り替えると、5秒ごとに入力されるのが確認できます。
PCだけでなく、iPhoneでも動作を確認できました。

マウスでも同様です。ただし、こちらはiPhoneでは動作が確認できませんでした(Assosiative Touchを有効にしてみたりしたのですが、ボタンもカーソル移動も反応せず…)。

追記

コメントで教えていただきました。多少の修正で接続できるようになるとのことです。
参考:
IoS 13 compatibility? · Issue #3 · T-vK/ESP32-BLE-Mouse · GitHub
ESP32でiOSにも繋がるBLE Mouse - Qiita


このままだと勝手に動くだけですが、せっかくM5StickCを使っているので、カウントダウンを表示したりボタン操作で一時停止などができるようにしてみました。
下記サンプルではCtrlキーを5秒ごとに押しています。(これだけだとあまり意味はないですが、ゲーム等と組み合わせたり、使い方は工夫次第ということで。)

#include <M5StickC.h>
#include <BleKeyboard.h>

BleKeyboard bleKeyboard;

void setup() {
  M5.begin();
  M5.Axp.ScreenBreath(7);
  M5.Lcd.setRotation(3);
  M5.Lcd.fillScreen(TFT_BLACK);
  M5.Lcd.setTextSize(1);
  M5.Lcd.setTextColor(TFT_WHITE);
  M5.Lcd.setCursor(0, 0);
  M5.Lcd.println("Starting ...");

  Serial.begin(115200);
  Serial.println("Starting ...");
  bleKeyboard.begin();
}

bool paused = false;

bool setPaused() {
  M5.update();
  if (M5.BtnA.wasReleased()) {
    paused = !paused;
    Serial.print("paused = ");
    Serial.println(paused);
  
    if (paused) {
      M5.Lcd.fillScreen(TFT_BLACK);
      M5.Lcd.setTextColor(TFT_WHITE);
      M5.Lcd.setTextSize(6);
      M5.Lcd.setCursor(50, 20);
      M5.Lcd.println("||");
    }
  }
  return paused;
}

void loop() {
  if (setPaused()) {
    delay(50);
    return;
  }

  if (bleKeyboard.isConnected()) {
    M5.Lcd.fillScreen(TFT_BLUE);
    Serial.println("Sending Ctrl...");
    bleKeyboard.press(KEY_LEFT_CTRL);
    delay(30);
    bleKeyboard.releaseAll();
  } else {
    M5.Lcd.fillScreen(TFT_RED);
  }
  Serial.println("Waiting 5 seconds...");

  M5.Lcd.setTextColor(TFT_WHITE);
  M5.Lcd.setTextSize(6);
  for (int i = 5; i > 0; i--) {
    if (bleKeyboard.isConnected()) {
      M5.Lcd.fillScreen(TFT_BLUE);
    } else {
      M5.Lcd.fillScreen(TFT_RED);
    }
    M5.Lcd.setCursor(60, 20);
    M5.Lcd.println(i);
    for (int j = 0; j < 10; j++) {
      if (setPaused())
        return;
      delay(100);
    }
  }      
}