mbed-OSでBLEのHIDキーボードを実装してみた 

#はじめに
mbedを用いて無線通信をしたい。もっというとキーボードやマウスとして接続したい。
そう思いたってmbedをHIDとして実装する方法を調べてみました。mbed×BLEの前例記事がそもそも少ないので、HIDの実装となると文献がほぼなく苦労しましたが、なんとか動くモノを実装できたのでまとめてみます。

今回の実装に関しては佐藤氏のブログ記事 [氾濫原｜ErgoDoxではないナニか。オープンソースかつBluetooth接続のキーボード](https://lowreal.net/2016/08/30/2) を大変参考にさせていただきました。


##実装環境
- mbed [Switch Science mbed TY51822r3](https://www.switch-science.com/catalog/2574/), mbed-os 5.5

##動作確認済み
- macOS High Sierra 10.13.3
- iOS 11.2.2

#作ったキーボード(?)
![IMG_5526.JPG](https://qiita-image-store.s3.amazonaws.com/0/67153/ca2b7e1e-f32a-10e6-26a9-29251aed8b6c.jpeg)

赤いキーでAが入力でき、黒いキーで削除ができます。

#回路
![mbed_hid.png](https://qiita-image-store.s3.amazonaws.com/0/67153/8a669ce9-4eb8-6849-d5a0-349dc5cb77a3.png)

キーとなるスイッチをp0とp1にそれぞれつなぎ、BLEの接続確認をするためのLEDをLED2ピンに繋ぎました。なおLEDは抵抗入りのものを利用したので省略しています。

動作確認だけのため電源は有線で得ています。本当に無線化したい場合はバッテリを使ってください。

#実装
##1.mbed-OSのプロジェクト立ち上げ
[ここ](https://os.mbed.com/docs/v5.7/tutorials/blinky-on-the-arm-mbed-online-compiler.html)から`Import into Mbed IDE`でMbed Compilerにインポートすると楽です。

最新のmbed-OSでの動作は未確認なので、状況に応じて古いバージョンにリビジョンしてください。

##2.有志のライブラリを拝借
[jpbrucker氏のgithubレポジトリ](https://github.com/jpbrucker/BLE_HID)のBLE_HIDディレクトリから
- HIDServiceBase.h
- HIDServiceBase.cpp
- KeyboardService.h
- Keyboard_types.h
- USBHID_Types.h
をダウンロードしてプロジェクトのルートディレクトリにインポートします。

##3.main.cppを実装
###ソースコード
```cpp:main.cpp
#include "mbed.h"
#include "BLE.h"
#include "KeyboardService.h"
#include "BatteryService.h"
#include "DeviceInformationService.h"

#if 0
Serial pc(USBTX, USBRX);
#define DEBUG(...) { pc.printf(__VA_ARGS__); }
#else
#define DEBUG(...)
#endif

InterruptIn btnA(p1);
InterruptIn btnD(p0);
DigitalOut led(LED2);

BLE ble;
KeyboardService *kbdService;

static const char DEVICE_NAME[] = "MyKeyboard";
static const char SHORT_DEVICE_NAME[] = "MK";

static const uint16_t UUID_SERVICES[] = {
        GattService::UUID_HUMAN_INTERFACE_DEVICE_SERVICE,
        GattService::UUID_DEVICE_INFORMATION_SERVICE,
        GattService::UUID_BATTERY_SERVICE};

static Gap::ConnectionParams_t fast;

static void connectionCallback(const Gap::ConnectionCallbackParams_t *params) {
    DEBUG("Connected!\r\n");
    led = 1;
    ble.gap().stopAdvertising();
    ble.updateConnectionParams(params->handle, &fast);
}

static void disconnectionCallback(const Gap::DisconnectionCallbackParams_t *params) {
    DEBUG("Disconnected!\r\n");
    led = 0;
    ble.gap().startAdvertising();
}

static void securitySetupInitiatedCallback(Gap::Handle_t, bool allowBonding, bool requireMITM, SecurityManager::SecurityIOCapabilities_t iocaps) {
    DEBUG("Security setup initiated\r\n");    
}

static void passkeyDisplayCallback(Gap::Handle_t handle, const SecurityManager::Passkey_t passkey) {
    DEBUG("Input passKey: ");
    for (int i=0; i<Gap::ADDR_LEN; i++) {
        DEBUG("%c", passkey[i]);
    }
    DEBUG("\r\n");
}

static void securitySetupCompletedCallback(Gap::Handle_t handle, SecurityManager::SecurityCompletionStatus_t status) {
    if (status == SecurityManager::SEC_STATUS_SUCCESS) {
        DEBUG("Security success %d\r\n", status);
    } else {
        DEBUG("Security failed %d\r\n", status);
    }
}

void send_A() {
    if (!kbdService)
        return;
    if (!kbdService->isConnected()) {
        DEBUG("Error: not connected");
    } else {
        DEBUG("Send");
        kbdService->_putc(65);
    }
}

void send_D() {
    if (!kbdService)
        return;
    if (!kbdService->isConnected()) {
        DEBUG("Error: not connected");
    } else {
        DEBUG("Send");
        kbdService->_putc(8);
    }
}

int main(void) {
    btnA.mode(PullUp);
    btnD.mode(PullUp);
    btnA.rise(send_A);
    btnD.rise(send_D);
    DEBUG("Start\r\n");
    
    DEBUG("Initialise\r\n");
    ble.init();

    DEBUG("Setup the event handlers\r\n");
    ble.gap().onConnection(connectionCallback);
    ble.gap().onDisconnection(disconnectionCallback);
    
    DEBUG("Setup latency\r\n");
    ble.getPreferredConnectionParams(&fast);
    fast.minConnectionInterval = 7.5;
    fast.maxConnectionInterval = 10;
    fast.slaveLatency = 0;
    ble.setPreferredConnectionParams(&fast);
    
    DEBUG("Initialize security\r\n");
    ble.securityManager().onSecuritySetupInitiated(securitySetupInitiatedCallback);
    ble.securityManager().onPasskeyDisplay(passkeyDisplayCallback);
    ble.securityManager().onSecuritySetupCompleted(securitySetupCompletedCallback);    
    ble.securityManager().init(true, false, SecurityManager::IO_CAPS_NONE);
    
    DEBUG("Adding device info and battery service\r\n");
    KeyboardService kbdService_(ble);
    kbdService = &kbdService_;
    DeviceInformationService deviceInfo(ble, "ARM", "m1", "abc", "def", "ghi", "jkl");
    BatteryService batteryInfo(ble, 80);
    
    DEBUG("Advertising payload\r\n");
    ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::KEYBOARD);
    ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LOCAL_NAME, (const uint8_t *)DEVICE_NAME, strlen(DEVICE_NAME));
    ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::SHORTENED_LOCAL_NAME, (const uint8_t *)SHORT_DEVICE_NAME, strlen(SHORT_DEVICE_NAME));
    ble.gap().setDeviceName((const uint8_t *)DEVICE_NAME);
    
    ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE);  
    ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_16BIT_SERVICE_IDS, (uint8_t *)UUID_SERVICES, sizeof(UUID_SERVICES)); 
    ble.gap().setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);
    ble.gap().setAdvertisingInterval(160); //100ms; in multiples of 0.625ms.
    ble.gap().startAdvertising();

    DEBUG("Add service\r\n");
    ble.gattServer().addService(customService);

    while (true) {
        ble.waitForEvent();
    }
}
```

###カスタマイズする時編集するところ
- DEVICE_NAME
- SHORT_DEVICE_NAME
- deviceInfo
- batteryInfo
- ボタン（スイッチ）関連

###ちょっと解説
・`kbdService->_putc(数字);`のところで入力するキーを指定しています。
　数字は[ASCIIコード](http://www3.nit.ac.jp/~tamura/ex2/ascii.html)の10進数番号です（たぶん）。
・スイッチはDigitalInに抵抗を挟まず繋いでますが、mbedのプルアップモードを使うとちゃんと動きます。
　また、`rise`でキーが押された時の関数を指定してあげれば割り込み処理ができます。

#Macと接続したい時
[ここ](https://lowreal.net/2016/07/18/2)にある通り、`BatteryService.h`と`DeviceInformationService.h`をコピーして中身を書きかえて、`requireSecurity(SecurityManager::SECURITY_MODE_ENCRYPTION_NO_MITM)`にすればOKです。
具体的には[佐藤氏のmbedレポジトリ](https://os.mbed.com/users/cho45/code/keyboard/)を見てください。

#注意
文字入力がうまくいかない時は、一度完全に端末との接続を解除してもう一度繋いでみてください。

#おわりに
先駆者様がいたおかげで短時間にてHIDキーボードの実装ができました。
この分野はマニアックなのか、BLEが難しいからか非常に前例文献が少ないので、なるべく分かったことは記事に残していきたいですね。