M5Atom に Bluetooth キーボードを接続

注意:BLE対応の Bluetooth キーボードでは接続ができない可能性が高いです。 古めの Bluetooth キーボードを使ってみてください。


M5Atom に HDMI モニタを接続する Atom Display が数日中に配達される、との連絡を受けて、お迎え前にやっておきたいことがありました。モニタが接続できるなら、キーボードから文字入力したい!そう、Bluetooth キーボードの接続です。

M5Atom「を」Bluetoothキーボード「に」するのではなくて、M5Atom「に」Bluetoothキーボード「を」接続するのです。

以前に遊んでみた「ESP_8_BIT」が、これを実現していました。この時は「カラーコンポジットビデオ出力」のインパクトが大きすぎたので、Bluetooth キーボード接続についてはあまり追っかけてませんでした…。

いま、改めて見直してみますと、入力部分のソースは src/hid_server/ 以下にまとまってて、そのままで流用できそうな感じです…。と、いうことで、実現できてしまいました!

すばらしい。


そうこうしているうちに、Atom Display 到着。


これまたすばらしい。

ということで、合体です!

やりました!


ソースは、こういう感じです。

M5AtomDisplay.h を使うために、M5GFX が必要です。

M5Stack/M5GFX
https://github.com/m5stack/M5GFX

それと、ESP_8_BIT のソースから /src/hid_server/ 以下のファイルをコピーしてください。

rossumur/esp_8_bit
https://github.com/rossumur/esp_8_bit

サブディレクトリ使った構成になってるので、PlatformIO でビルドする必要あります。


ビルド時に、2箇所でこういうエラー出ると思います。変数の定義順とコンストラクタでの初期化順が違ってるとエラーになるみたいですね。ここは、コンストラクタ初期化順に変数定義順を合わせるように hid_server\hci_server.cpp を修正してください。

src\hid_server\hci_server.cpp:456:13: error: 'BTDevice::_txid' will be initialized after [-Werror=reorder]

 


起動すると、5秒間、Bluetooth機器接続モードになりますので、その間に Bluetooth キーボードを接続待ち状態にしてください。
画面に「enter ‘0000’ on keyboard to pair」と表示されると思いますのでキーボードから「0000」入力してください。

うまく接続できない場合は、hci_server.cpp でTRACE を有効にして、シリアルログ見てみてください。Bluetooth 接続処理が走ってると、それっぽいログがドンドン出てると思います。出てなければキーボードからの接続要求が来てないってことですね。

ちなみに、Nitendo ポケモンタイピングDS付属のキーボードを使うと、機器名称が「Nintendo」から始まっているために「Wiiリモコン」と認識されてしまうワナがあります。hci_server.cpp の、このチェックです。

bool is_wii()
{
   if (_name.find("Nintendo") == 0)
       return true;
   if (_dev_class == 0x042500 || _dev_class == 0x080500)
        return true;
   return false;
}

ポケモンキーボード使いたい人は、ここの呼び出しチェック修正するなど対応してください。僕はワナにかかりました。


プログラムで必要な部分は、

#include "hid_server/hid_server.h"

hid_server.h ヘッダのインクルード。

hid_init("emu32");

setup() での hid 初期化。(引数は何でもよさそう)
これを呼び出すと、5秒間のペアリングタイムが始まります。開始ステータス立てるだけなので、ここで処理が止まるわけではないです。

hid_update();

loop() での hid 更新。ペアリングの実際のチェックはこちらで行われています。

uint8_t buf[64];
int n = hid_get(buf,sizeof(buf)); 
if (n > 0){
  gui_hid(buf,n);
}

データ読み出しとチェック。hid_get は hid_server が用意している関数です。何か入力があった場合、値が返ります。
値が返ってきていたら、中身のチェックを行います。 gui_hid(buf, n) は、こちらで用意した関数です。ESP_8_BIT ソースの入力チェック処理のところから流用してきました。

void gui_hid(const uint8_t* hid, int len) // Parse HID event
{
  if (hid[0] != 0xA1)
  return;
  switch (hid[1]) {
  case 0x01: keyboard(hid+1,len-1); break; // parse keyboard and maintain 1 key state
  // case 0x32: wii(); break; // parse wii stuff: generic?
  // case 0x42: ir(hid+2,len); break; // ir joy
  }
}

hid[0] が 0xA1 の時に「入力あり」。hid[1] が0x01 なら、Bluetoothキーボードからの入力と判断できます。その他、WiiリモコンとIRリモコンにも対応しています。(今回はそちらの判定は省略しました。)

その後、実際の入力をチェックして、入力に応じた動作を行っています。

String stringData[] = {
  " "," "," "," ",
  "a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z",
  "1","2","3","4","5","6","7","8","9","0"
};
String stringShiftData[] = {
  " "," "," "," ",
  "A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z",
  "!","\"","#","$","%","&","'","(",")"," "
};
static int _last_key = 0;
//https://wiki.onakasuita.org/pukiwiki/?HID%2F%E3%82%AD%E3%83%BC%E3%82%B3%E3%83%BC%E3%83%89
static void keyboard(const uint8_t* d, int len)
{
  int mods = d[1];
  int key_code = d[3]; // only care about first key
  if (key_code != _last_key) {
    if (key_code) {
      _last_key = key_code;
    } else {
      _last_key = 0;
    }
    Serial.printf("mods:%d keyCode:%d\n",mods,key_code);
    if(key_code > 0 && key_code <=39){
      if(mods == 0){
        display.print(stringData[key_code]);
      }else{
        display.print(stringShiftData[key_code]);
      }
    }else if(key_code==40){ //改行
      display.println();
    }else if(key_code==44){ //スペース
      display.println(" ");
    }else if(key_code==42){ //DELETEで全部消す
      display.clearDisplay();
      display.setCursor(0,0); 
    }
  }
}

d[1] に SHIFT / CTRL / ALT のキー押下状態が入り、d[3] に押されたキーコードが入ります。

HID のキーコードはこういう感じです。キーボードのキーそのものに直接紐づけられているのですね。

HID/キーコード
https://wiki.onakasuita.org/pukiwiki/?HID%2F%E3%82%AD%E3%83%BC%E3%82%B3%E3%83%BC%E3%83%89

SHIFT / CTRL / ALT 押下状態は、押されているキーのビットが立ちます。(GUIキーはちょっと何かわからないw)

0 LEFT CTRL
1 LEFT SHIFT
2 LEFT ALT
3 LEFT GUI
4 RIGHT CTRL
5 RIGHT SHIFT
6 RIGHT ALT
7 RIGHT GUI

SHIFT押下チェックするなら、if((mods & 0b00100010) > 0) {…}  という感じです。

void gui_msg(const char* msg) 
{
  Serial.println(msg);
}
void sys_msg(const char* msg) {
  Serial.println(msg);
  display.print(msg);
}

gui_msg、sys_msg は hid_server  から呼び出される関数です。sys_msg には「enter ‘0000’ on keyboard to pair」が入ってきますので、画面表示するなどしてください。(実際のところ、送られてくるメッセージはこれだけかも)


後は、画面に「Bluetoothキーボード接続完了!」とか表示したいのですが、Bluetooth キーボードが接続されてるかどうかをプログラムから確認する方法がよくわからなかったです。hid_get(buf,sizeof(buf)) の戻りが -1 なら、確実に繋がっていないということは判断できるのですが…。

あと、ペアリング情報がどこに保持されているかもわからなかったです。フラッシュメモリクリアしてプログラムを書き込みなおしても、ペアリングなしでキーボードがつながる…!どこかにペアリング情報を記録している…?


ArduinoIDE 使う場合は、ino と同じフォルダに hid_server 以下のファイルを置いて

#include "hid_server.h"

とするか、もしくは src/hid_server にファイルを置いて(src以下ならサブフォルダ認識してくれる)

#include "src/hid_server/hid_server.h"

で、ビルドまではできました。が、起動するとBluetooth認識に行く前にエラーで落ちてしまって、ArduinoIDEではまだうまく動かせてないです。なんかビルド状況が違うのかな…。


M5Stack でもやってみました。無事に動作です!画面付きなので M5Atom よりも M5Stack の方が Bluetooth キーボードを便利に使える状況は多そうですねー。