24
19

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

ESP32でキーボードショートカットを作ってしまおう

Last updated at Posted at 2020-04-18

Windowsには、キー配置を変更するためのツールがありますが、Altキーを押しながらF4キーや、Winキーを押しながらLキーなど、装飾キーとの組み合わせのキーボードショートカットが欲しい時があります。たいてい、ショートカットキーは、アプリをインストールして常駐させることが多いように思います。

それじゃあ、ESP32で作ってしまおうと。

・ESP32をBLEペリフェラルのHIDデバイスにする。
・ESP32に物理ボタンを接続して、押下を契機に、あらかじめ決めておいたキーの組み合わせを送信する。

今回は、ESP32としてM5StickCを採用し、物理ボタンとしてGroveで接続可能なデュアルボタンユニットを使います。

M5Stack用デュアルボタンユニット
 https://www.switch-science.com/catalog/4048/

キーの割り当て

以下の3つのボタンを使います。

IO 割り当てキー
G32 Win+Ctrl+→(仮想ディスクトップ切り替え+)
G33 Win+Ctrl+←(仮想ディスクトップ切り替えー)
ボタンA Win+TAB(タスクビュー表示)

要は、仮想ディスクトップの切り替えです。

#Arduinoコーディング
ソースコードはこんな感じです。あとで、カスタマイズ方法を示します。

//#include <Arduino.h>
#include <M5StickC.h>
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
#include "BLE2902.h"
#include "BLEHIDDevice.h"
#include "HIDTypes.h"
#include "HIDKeyboardTypes.h"

#define BLE_PASSKEY 123456
#define PIN_BTN1  32
#define PIN_BTN2  33

/*
 * BLEデバイス処理
 */
BLEHIDDevice* hid;
BLECharacteristic* input;
BLECharacteristic* output;

bool connected = false;

class MyCallbacks : public BLEServerCallbacks {
  void onConnect(BLEServer* pServer){
    connected = true;
    BLE2902* desc = (BLE2902*)input->getDescriptorByUUID(BLEUUID((uint16_t)0x2902));
    desc->setNotifications(true);

    M5.Lcd.fillScreen(BLACK);
    M5.Lcd.setCursor(0, 0);
    M5.Lcd.setTextSize(2);
    M5.Lcd.println("Connected");
  }

  void onDisconnect(BLEServer* pServer){
    connected = false;
    BLE2902* desc = (BLE2902*)input->getDescriptorByUUID(BLEUUID((uint16_t)0x2902));
    desc->setNotifications(false);

    M5.Lcd.fillScreen(BLACK);
    M5.Lcd.setCursor(0, 0);
    M5.Lcd.setTextSize(2);
    M5.Lcd.println("Disconnected");
  }
};

// ペアリング処理用
class MySecurity : public BLESecurityCallbacks {
  bool onConfirmPIN(uint32_t pin){
    return false;
  }

  uint32_t onPassKeyRequest(){
    Serial.println("ONPassKeyRequest");
    return BLE_PASSKEY;
  }

  void onPassKeyNotify(uint32_t pass_key){
    // ペアリング時のPINの表示
    Serial.println("onPassKeyNotify number");
    Serial.println(pass_key);

    M5.Lcd.fillScreen(BLACK);
    M5.Lcd.setCursor(0, 0);
    M5.Lcd.setTextSize(2);
    M5.Lcd.println("PIN");
    M5.Lcd.println(pass_key);
  }

  bool onSecurityRequest(){
    Serial.println("onSecurityRequest");
    return true;
  }

  void onAuthenticationComplete(esp_ble_auth_cmpl_t cmpl){
    Serial.println("onAuthenticationComplete");
    if(cmpl.success){
      // ペアリング完了
      Serial.println("auth success");
    }else{
      // ペアリング失敗
      Serial.println("auth failed");
    }
  }
};

// BLEデバイスの起動
void taskServer(void*){
  BLEDevice::init("KeyShortcut");

  BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT_MITM);
  BLEDevice::setSecurityCallbacks(new MySecurity());  

  BLEServer *pServer = BLEDevice::createServer();
  pServer->setCallbacks(new MyCallbacks());

  hid = new BLEHIDDevice(pServer);
  input = hid->inputReport(1); // <-- input REPORTID from report map
  output = hid->outputReport(1); // <-- output REPORTID from report map

  std::string name = "Poruruba";
  hid->manufacturer()->setValue(name);

  hid->pnp(0x02, 0xe502, 0xa111, 0x0210);
  hid->hidInfo(0x00,0x02);

  BLESecurity *pSecurity = new BLESecurity();
  pSecurity->setKeySize(16);

  pSecurity->setAuthenticationMode(ESP_LE_AUTH_BOND);
  pSecurity->setCapability(ESP_IO_CAP_OUT);
  pSecurity->setInitEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK);

  uint32_t passkey = BLE_PASSKEY;
  esp_ble_gap_set_security_param(ESP_BLE_SM_SET_STATIC_PASSKEY, &passkey, sizeof(uint32_t));

    const uint8_t report[] = {
      USAGE_PAGE(1),      0x01,       // Generic Desktop Ctrls
      USAGE(1),           0x06,       // Keyboard
      COLLECTION(1),      0x01,       // Application
      REPORT_ID(1),       0x01,        //   Report ID (1)
      USAGE_PAGE(1),      0x07,       //   Kbrd/Keypad
      USAGE_MINIMUM(1),   0xE0,
      USAGE_MAXIMUM(1),   0xE7,
      LOGICAL_MINIMUM(1), 0x00,
      LOGICAL_MAXIMUM(1), 0x01,
      REPORT_SIZE(1),     0x01,       //   1 byte (Modifier)
      REPORT_COUNT(1),    0x08,
      HIDINPUT(1),           0x02,       //   Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position
      REPORT_COUNT(1),    0x01,       //   1 byte (Reserved)
      REPORT_SIZE(1),     0x08,
      HIDINPUT(1),           0x01,       //   Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position
      REPORT_COUNT(1),    0x06,       //   6 bytes (Keys)
      REPORT_SIZE(1),     0x08,
      LOGICAL_MINIMUM(1), 0x00,
      LOGICAL_MAXIMUM(1), 0x65,       //   101 keys
      USAGE_MINIMUM(1),   0x00,
      USAGE_MAXIMUM(1),   0x65,
      HIDINPUT(1),           0x00,       //   Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position
      REPORT_COUNT(1),    0x05,       //   5 bits (Num lock, Caps lock, Scroll lock, Compose, Kana)
      REPORT_SIZE(1),     0x01,
      USAGE_PAGE(1),      0x08,       //   LEDs
      USAGE_MINIMUM(1),   0x01,       //   Num Lock
      USAGE_MAXIMUM(1),   0x05,       //   Kana
      HIDOUTPUT(1),          0x02,       //   Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile
      REPORT_COUNT(1),    0x01,       //   3 bits (Padding)
      REPORT_SIZE(1),     0x03,
      HIDOUTPUT(1),          0x01,       //   Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile
      END_COLLECTION(0)
    };

  hid->reportMap((uint8_t*)report, sizeof(report));
  hid->startServices();

  BLEAdvertising *pAdvertising = pServer->getAdvertising();
  pAdvertising->setAppearance(HID_KEYBOARD);
  pAdvertising->addServiceUUID(hid->hidService()->getUUID());
  pAdvertising->start();
  hid->setBatteryLevel(7);

//  Serial.println("Advertising started!");
  delay(portMAX_DELAY);
};

bool Btn1_released(void){
  static int prev = 0;

  int val = digitalRead(PIN_BTN1);
  if( prev != val ){
    prev = val;
    return (val == 0);
  }
  return false;
}

bool Btn2_released(void){
  static int prev = 0;

  int val = digitalRead(PIN_BTN2);
  if( prev != val ){
    prev = val;
    return (val == 0);
  }
  return false;
}

enum KEY_MODIFIER_MASK {
  KEY_MASK_CTRL = 0x01,
  KEY_MASK_SHIFT = 0x02,
  KEY_MASK_ALT = 0x04,
  KEY_MASK_WIN = 0x08
};

void sendKeys(uint8_t mod, uint8_t *keys, uint8_t num_key = 1){
  uint8_t msg[] = {mod, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
  for( int i = 0 ; i < num_key ; i++ )
    msg[2 + i] = keys[i];

  input->setValue(msg, sizeof(msg));
  input->notify();

  uint8_t msg1[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
  input->setValue(msg1, sizeof(msg1));
  input->notify();
}

void setup() {
  M5.begin();
  M5.IMU.Init();

  M5.Axp.ScreenBreath(9);
  M5.Lcd.setRotation(3);
  M5.Lcd.fillScreen(BLACK);
  M5.Lcd.setTextSize(2);

  M5.Lcd.println("[M5StickC]");
  delay(1000);

  M5.Lcd.println("start Serial");

  Serial.begin(9600);
  Serial.println("Starting KeyShortcut!");

  pinMode(PIN_BTN1, INPUT);
  pinMode(PIN_BTN2, INPUT);
  
  M5.Lcd.println("start BLE");

  // BLEデバイスの起動処理の開始
  xTaskCreate(taskServer, "server", 20000, NULL, 5, NULL);
}

void loop() {
  M5.update();

  if( M5.BtnA.wasReleased() ){
    if(connected){
        Serial.println("BtnA released");
        uint8_t keys[] = { 0x2B /* TAB */};
        sendKeys(KEY_MASK_WIN, keys, 1);

        delay(20);
    }
  }
  if( Btn1_released() ){
    if(connected){
        Serial.println("Btn1 released");
        uint8_t keys[] = { 0x50 /* LEFT Arrow */};
        sendKeys(KEY_MASK_CTRL | KEY_MASK_WIN, keys, 1);

        delay(20);
    }
  }  
  if( Btn2_released() ){
    if(connected){
        Serial.println("Btn2 released");
        uint8_t keys[] = { 0x4F /* RIGHT Arrow */,};
        sendKeys(KEY_MASK_CTRL | KEY_MASK_WIN, keys, 1);

        delay(20);
    }
  }
}

カスタマイズ

WindowsからESP32を検索するときに表示される名前を変更する場合には、以下を変更してください。

  BLEDevice::init("KeyShortcut");

Windowsに本ESP32をHIDとして登録する際に、PIN入力が必要なようにしています。
その時のPINを「123456」固定にしています。もし変える場合には、以下の部分を変更してください。

#define BLE_PASSKEY 123456

また、固定ではなく、毎回ランダムに変える場合には、以下の部分をコメントアウトしてください。ランダムPINは、M5StickCのLCDに表示されるようにしています。

  uint32_t passkey = BLE_PASSKEY;
  esp_ble_gap_set_security_param(ESP_BLE_SM_SET_STATIC_PASSKEY, &passkey, sizeof(uint32_t));

物理ボタンのスイッチが接続されたポート番号を変える場合には、以下を変更してください。

#define PIN_BTN1  32
#define PIN_BTN2  33

2つのボタンを想定していますが、増やすのはたやすいかと思います。

BLEでショートカットキーを送る処理は関数 void sendKeys(uint8_t mod, uint8_t *keys, uint8_t num_key = 1) で実装しています。

modに装飾キーとして enum KEY_MODIFIER_MASK をORでつなげて指定します。実際のキーは、keysに指定します。複数のキーを指定可能です。以下のページを参考に、USB HID Usageの列のID になります。

Japanese Keyboard (layout and scancode)
 http://hp.vector.co.jp/authors/VA003720/lpproj/others/kbdjpn.htm

使い方

最初に、WindowsPCに、BLEキーボードとして登録する必要があります。
通常通り、「Bluetoothまたはその他のデバイスを追加する」 から、Bluetoothを検索します。「KeyShortcut」が発見されると思いますので、それを選択し、途中PIN入力を求められますが、「123456」と入力すれば、登録が完了します。

image.png

次に、仮想ディスクトップを作成します。おそらく、まだ仮想ディスクトップを使っていない方が多いと思いますので、まだ作成されていない場合は、M5StickCのボタンAを押下します。
そうすると、ウィンドウ切り替えの画面になるのですが、左上に「+新しいディスクトップ」というのがあると思いますのでそれをクリックします。

image.png

これで準備完了です。

それでは、2つの物理ボタンを押してみましょう。
仮想ディスクトップが切り替わりましたでしょうか?

#参考情報

以下を参考にさせていただきました。
 T-vK/ESP32-BLE-Keyboard
 M5Stick-Cでパスワード記憶装置 兼 自動入力装置を作る

こちらが続編です。
 ESP32でキーボーショートカットを作ってしまおう:改良版

以上

24
19
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
24
19

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?