M5Stick-Cでパスワード記憶装置 兼 自動入力装置を作る

M5Stick-Cネタです。せっかく入手したので、活用しましょう。

M5Stick-Cに、複数のログインパスワードを記憶させて、パスワード地獄から解放されましょう。また、キーボードとして認識させて、パスワードをPCに自動入力させましょう。

なぜそれができるか。

* M5StickCにBLEがあるため、HOGP (HID-over-GATT Profile)、すなわちHIDのBLEデバイスにすれば、ワイヤレスキーボードとしてふるまうことができる。
* M5StickCで採用しているCPU ESP32の不揮発メモリに保存する機能があるので、電源を切っても、パスワードを記憶してくれる。
* M5StickCにLCDとボタンがあるので、複数のパスワードを切り替えることができる。
* M5StickCにはバッテリとボタンがついているので、有線ケーブルで給電することなく、無線でボタン一つでパスワード入力することができる。

そしてなにより、以下のような有用な情報を提供してくださる有志の方々がいる。

・ESP-WROOM-32のセットアップについて
　https://trac.switch-science.com/wiki/esp32_setup

・ESP32_HID.ino
　https://gist.github.com/sabas1080/93115fb66e09c9b40e5857a19f3e7787

・Arduino-ESP32 Preference ライブラリ <不揮発性メモリへの読み書き>
　https://qiita.com/T-YOSH/items/f388b4d7cbc829829aae

※ 念のためですが、パスワードが紛失したり漏洩したりしても、責任は負いません。

# 使い方編

実装内容に入る前に、気になる使い方から説明します。

## M5Stick-Cにパスワードを登録

パスワードの登録は、USBケーブルでM5Stick-Cと接続するとでてくる仮想COMポートから、TeraTermなどのコンソールで入力します。
入力すると以下のようなメニューが表示されます。

```
Input Command Number
  1: List
  2: New
  3: Update
  4: Remove
  5: Check
```

2を入力し、タイトルとパスワードの入力が促されますので入力すると、不揮発メモリに保存されます。
たとえば、タイトルとして「Test」、パスワードとして「1234」と入力します。

保存されたパスワードは、1を入力することで、パスワードと一緒に入力したタイトルが一覧で表示されます。
3のUpdateは、パスワードを更新します。
4のRemoveは、パスワードを削除します。
5のCheckは、保存されているパスワードが、入力するパスワードと合っているかを確認します。

## PCにM5Stick-CをBluetoothキーボードとして登録

PCにパスワードを入力させる前に、M5Stick-CをBluetoothキーボードとしてペアリングしておく必要があります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/37ea8d04-c105-5e12-03c2-a9c219e0a1db.png)

Bluetoothまたはその他のデバイスを追加するの「＋」ボタンを押下します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/5e67d9ae-4bcf-df6e-6f96-a1b3f9171a0f.png)

Bluetoothを選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/ce6e88c1-c6e2-af11-ad96-fc34e42f785f.png)

そうすると、「Password-Reminder」という名前のデバイスが見つかります。
それを選択すると、PIN入力画面になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/99f6f465-1f02-830e-66ed-a03e034b94f5.png)

一方の、M5Stick-CのLCDには以下のように表示されているかと思います。

```
PIN
12345678
```

このPINをPCに表示されたPIN入力画面に入力し接続ボタンを押下します。（PINの値は毎回違います）
これで、「マウス、キーボード、ペン」のところに、Password-Reminderが増え、接続済みになっているのがわかります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/efa0963b-cfcb-0e92-b325-e1ced74c57a0.png)

## PCにM5Stick-Cからパスワードを入力する。

LCDには以下のようになっていますでしょうか？
上段が複数パスワードを登録した場合のためのインデックス番号、下段がパスワード登録したときのタイトルです。

```
0
Test
```

もし以下のようになっていたら、まだパスワードを登録していないことを表しています。

```

Not Found
```

それでは、パスワードを入力する先として、例えば、適当にメモ帳を開きます。
メモ帳の適当な場所にカーソルを合わせた状態で、おもむろに、M5Stick-Cの表のボタン（M5と書いているところ）を押下します。
そうすると、「1234」と入力されましたでしょうか？！

これが、BluetoothキーボードとしてつながっているM5Stick-Cから入力されたものになります。
もし複数のパスワードを記録していた場合には、右わきのボタンを押すと、インデックス番号とタイトルとともに表示が切り替わります。
同じように、表のボタンを押下すると、選択されたインデックス番号のパスワードがメモ帳に入力されます。

使い勝手はいかがでしょうか。

# 実装編

環境設定と実装をしていきます。

## Arduinoのセットアップ

それでは、ソースをコンパイルするための環境をセットアップしていきます。
ESP32の機能をフルに使うので、Arduinoにarduino-esp32をセットアップします。

Arduino IDEから、「追加のボードマネージャーのURL」に https://dl.espressif.com/dl/package_esp32_index.json を追加します。

それから、ボードマネージャから「esp32」と入力すると表示される「esp32 by Espressif Systems」をインストールします。(以下の画像はインストール済みの状態)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/596fcb37-e554-b69b-daf1-2e15a09eeef7.png)

## プロジェクトの準備

PCにM5Stick-Cを接続しておきます。

Arduino IDEから新規ファイルを作成します。
「ツール」→「ボード」から「M5Stick-C」を選択します。ここらへんは、ボードマネージャとライブラリマネージャよりM5StickCがインストール済みの前提です。
まだの場合は以下もご参考にしてください。
　[M5Stick-CでJsonをPOSTする](https://qiita.com/poruruba/items/4bf6a52520e431a8f4a5)

シリアルポートの番号は、接続されているM5Stick-Cのものを選択しておきます。

## 実装

以下がソースコードです。むちゃくちゃ長いです！

```c
#include <M5StickC.h>
#include <Preferences.h>
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
#include "BLE2902.h"
#include "BLEHIDDevice.h"
#include "HIDTypes.h"
#include "HIDKeyboardTypes.h"

// 不揮発メモリアクセス用
Preferences pref;

/*
 * パスワード管理
 */
enum COMMAND_STATE {
  TOP, /* トップメニュー */
  NEW_TITLE, /* 新規(タイトル入力) */
  NEW_VALUE, /* 新規(パスワード入力) */
  UPDATE_SELECT, /* 更新(選択) */
  UPDATE_VALUE, /* 更新(新パスワード入力) */
  REMOVE_SELECT, /* 削除(選択) */
  CHECK_SELECT, /* 確認(選択) */
  CHECK_VALUE /* 確認(パスワード入力) */
};

enum COMMAND_STATE state = TOP; /* メニューの状態 */

#define MAX_TITLE_LENGTH  (16 + 1) /* タイトルの最大長 */
#define NUM_OF_TITLE  10 /* 保存可能なタイトル数 */

char title_list[MAX_TITLE_LENGTH * NUM_OF_TITLE] = { 0 }; /* タイトルリスト(オンメモリ) */
char input_buf[255]; /* シリアル入力バッファ */
unsigned char buf_index = 0; /* シリアル入力バッファのポインタ */
char backup_buf[255]; /* シリアル入力バッファのバックアップ */
unsigned char backup_number; /* シリアル入力値(1文字)のバックアップ */

// タイトルの削除
void remove_title(unsigned char index){
  memmove(&title_list[index * MAX_TITLE_LENGTH], &title_list[(index + 1) * MAX_TITLE_LENGTH], (NUM_OF_TITLE - (index + 1)) * MAX_TITLE_LENGTH);
  memset(&title_list[(NUM_OF_TITLE - 1) * MAX_TITLE_LENGTH], '\0', MAX_TITLE_LENGTH);
}

// タイトルの追加
long add_title(const char *title){
  unsigned char len = strlen(title);
  if( len == 0 || len > (MAX_TITLE_LENGTH - 1) )
    return -1;

  for( unsigned char i = 0 ; i < NUM_OF_TITLE ; i++ ){
    if( title_list[i * MAX_TITLE_LENGTH] == '\0' ){
      strcpy( &title_list[i * MAX_TITLE_LENGTH], title );
      return i;
    }
  }

  return -1;
}

// タイトルリストの表示
void print_title_list(void){
  for( unsigned char i = 0 ; i < NUM_OF_TITLE ; i++ ){
    if( title_list[i * MAX_TITLE_LENGTH] == '\0' )
      return;
    Serial.print("[");
    Serial.print(i);
    Serial.print("] ");
    Serial.println(&title_list[i * MAX_TITLE_LENGTH]);
  }
}

// タイトルの取得
char* get_title(unsigned char index){
  if( title_list[index * MAX_TITLE_LENGTH] == '\0' )
    return NULL;

  return &title_list[index * MAX_TITLE_LENGTH];
}

/*
 * シリアル入力処理
 */
enum SERIAL_MODE {
  CHAR, /* 1文字入力待ち */
  CHARED, /* 1文字入力完了 */
  BUFFERING, /* 文字列入力待ち */
  BUFFERED, /* 文字列入力完了 */
  ABORT /* 中断(Ctrl-C) */
};

enum SERIAL_MODE mode = CHAR; /* シリアル入力処理の状態 */
enum SERIAL_MODE process_serial(void);

// シリアル受信の処理
enum SERIAL_MODE process_serial(void){
  if( mode == CHAR ){
    if( Serial.available() > 0 ){
      unsigned char c = Serial.read(); 
      if( c == 0x03 ){
        mode = ABORT;
        return mode;
      }
      input_buf[0] = c;
      input_buf[1] = '\0';
      mode = CHARED;
      return mode;
    }
  }else{
    if( mode == BUFFERING ){
      while( Serial.available() > 0 ){
        unsigned char c = Serial.read();
        if( c == 0x03 ){
          mode = ABORT;
          return mode;
        }
        input_buf[buf_index] = c;
        if( c == '\r' ||  buf_index >= (sizeof(input_buf) - 1) ){
          input_buf[buf_index] = '\0';
          mode = BUFFERED;
          return mode;
        }
        buf_index++;
      }
    }
  }
  return mode;
}

/*
 * シリアル表示処理
 */

// 次への状態遷移とコンソール表示
void prompt(void){
  if( state == TOP ){
    Serial.println("");
    Serial.println("Input Command Number");
    Serial.println("  1: List");
    Serial.println("  2: New");
    Serial.println("  3: Update");
    Serial.println("  4: Remove");
    Serial.println("  5: Check");
    mode = CHAR;
  }else if( state == NEW_TITLE ){
    Serial.println("Input Title");
    buf_index = 0;
    mode = BUFFERING;
  }else if( state == NEW_VALUE ){
    Serial.println("Input Password");
    buf_index = 0;
    mode = BUFFERING;
  }else if( state == UPDATE_SELECT ){
    Serial.println("Input Index Number");
    mode = CHAR;
  }else if( state == UPDATE_VALUE ){
    Serial.println("Input Password");
    buf_index = 0;
    mode = BUFFERING;
  }else if( state == REMOVE_SELECT ){
    Serial.println("Input Index Number");
    mode = CHAR;
  }else if( state == CHECK_SELECT ){
    Serial.println("Input Index Number");
    mode = CHAR;
  }else if( state == CHECK_VALUE ){
    Serial.println("Input Password");
    buf_index = 0;
    mode = BUFFERING;
  }
}

/*
 * 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);
  }

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

// ペアリング処理用
class MySecurity : public BLESecurityCallbacks {
  bool onConfirmPIN(uint32_t pin){
    return false;
  }
  
  uint32_t onPassKeyRequest(){
    Serial.println("ONPassKeyRequest");
    return 123456;
  }
  
  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");
      print_screen();
    }else{
      // ペアリング失敗
      Serial.println("auth failed");
    }
  }
};

// BLEデバイスの起動
void taskServer(void*){
  BLEDevice::init("Password-Reminder");
  
  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();

//  pSecurity->setAuthenticationMode(ESP_LE_AUTH_NO_BOND); // NO Bond
// AndroidではうまくPIN入力が機能しない場合有り
  pSecurity->setAuthenticationMode(ESP_LE_AUTH_BOND);
  pSecurity->setCapability(ESP_IO_CAP_OUT);
  pSecurity->setInitEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK);

    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);
};

/*
 * LCD表示処理
 */
unsigned char current_index = 0xff; // 現在選択中のタイトルの番号。初期は未選択状態

// M5StickCのLCD表示
//  現在選択中の番号とタイトルの表示
void print_screen(void){
  M5.Lcd.fillScreen(BLACK);
  M5.Lcd.setCursor(0, 0);
  M5.Lcd.setTextSize(3);
  char *title;
  if( current_index == 0xff ){
    M5.Lcd.println("");
    title = "not found";
  }else{
    M5.Lcd.println(current_index);
    title = get_title(current_index);
  }
  
  M5.Lcd.setTextSize(2);
  M5.Lcd.println(title);  
}

/*
 * Arduinoメイン処理
 */
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 Password-Reminder!");

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

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

  // 不揮発メモリライブラリの初期化
  pref.begin("password_list", false);

  // 不揮発メモリからタイトルリストの読み出し
  pref.getBytes("title_list", title_list, sizeof(title_list));

  // デフォルト(インデックス=0)のタイトルの存在確認
  if( get_title(0) != NULL )
    current_index = 0;

  // LCDの表示
  print_screen();
}

void loop() {
  M5.update();

  // ButtonBが押されたとき
  if( M5.BtnB.wasReleased() ){
    char* title = NULL;
    if( current_index != 0xff ){
      // いずれかのタイトルが選択されている状態の場合
      current_index++; // 次のタイトルへ
      title = get_title(current_index);
    }

    if( title == NULL ){
      title = get_title(0);
      if( title == NULL )
        current_index = 0xff;
      else
        current_index = 0;
    }

    // LCD表示の更新
    print_screen();
  }

  // ButtonAが押されたとき
  if( M5.BtnA.wasReleased() ){
    if(connected){
      // BLEキーボードとしてPCに接続されている状態の場合
      if( current_index != 0xff ){
        // いずれかのタイトルが選択されている状態の場合
        char* title = get_title(current_index);
        if( title != NULL ){
          char value_buffer[255];
          // 不揮発メモリからパスワードを読み出し
          pref.getString( title, value_buffer, sizeof(value_buffer) );

          // 1文字ずつHID(BLE)で送信
          char *ptr = value_buffer;
          while(*ptr){
            KEYMAP map = keymap[(uint8_t)*ptr];
            uint8_t msg[] = {map.modifier, 0x0, map.usage, 0x0,0x0,0x0,0x0,0x0};
            input->setValue(msg, sizeof(msg));
            input->notify();
            ptr++;
            
            uint8_t msg1[] = {0x0, 0x0, 0x0, 0x0,0x0,0x0,0x0,0x0};
            input->setValue(msg1, sizeof(msg1));
            input->notify();
            
            delay(20);
          }
        }
      }
    }
  }

  // シリアル入力処理と受信完了後の処理
  switch(process_serial()){
    // 処理中断(Ctrl-C)
    case ABORT:{
      state = TOP;
      prompt();
      break;
    }
    // 1文字入力の完了時
    case CHARED:{
      Serial.print("->");
      Serial.println(input_buf);
      if( state == TOP ){
        if( input_buf[0] == '1' ){
          print_title_list();
          state = TOP;
        }else if( input_buf[0] == '2' ){
          state = NEW_TITLE;
        }else if( input_buf[0] == '3'){
          state = UPDATE_SELECT;
        }else if( input_buf[0] == '4'){
          state = REMOVE_SELECT;
        }else if( input_buf[0] == '5'){
          state = CHECK_SELECT;
        }else{
          Serial.println("Invalid Input");
          state = TOP;
        }
      }else if( state == UPDATE_SELECT ){
        if( !(input_buf[0] >= '0' && input_buf[0] <= '9') ){
          Serial.println("Invalid Input");
          state = TOP;
        }else{
          backup_number = input_buf[0] - '0';
          if( get_title(backup_number) == NULL  ){
            Serial.println("Not Found");
            state = TOP;
          }else{
            state = UPDATE_VALUE;
          }
        }
      }else if( state == REMOVE_SELECT ){
        if( !(input_buf[0] >= '0' && input_buf[0] <= '9') ){
          Serial.println("Invalid Input");
          state = TOP;
        }else{
          backup_number = input_buf[0] - '0';
          char *title = get_title(backup_number);
          if( title == NULL  ){
            Serial.println("Not Found");
          }else{
            // タイトル・パスワードの削除、不揮発メモリからも削除
            Serial.println(title);
            pref.remove(title);
            remove_title(backup_number);
            pref.putBytes("title_list", title_list, sizeof(title_list));
            Serial.println(" Removed");
          }
          state = TOP;
        }
      }else if( state == CHECK_SELECT ){
        if( !(input_buf[0] >= '0' && input_buf[0] <= '9') ){
          Serial.println("Invalid Input");
          state = TOP;
        }else{
          backup_number = input_buf[0] - '0';
          if( get_title(backup_number) == NULL  ){
            Serial.println("Not Found");
            state = TOP;
          }else{
            state = CHECK_VALUE;
          }
        }
      }else{
        state = TOP;
      }
      
      prompt();
      break;
    }
    // 文字列入力の完了時
    case BUFFERED:{
      if( state == NEW_TITLE ){
        unsigned char len = strlen(input_buf);
        if( len == 0 || len >= (MAX_TITLE_LENGTH - 1)){
          Serial.println("Invalid Input");
          state = TOP;
        }else{
          strcpy(backup_buf, input_buf);
          state = NEW_VALUE;
        }
      }else if( state == NEW_VALUE ){
        unsigned char len = strlen(input_buf);
        if( len == 0 ){
          Serial.println("Invalid Input");
          state = TOP;
        }else{
          long ret = add_title(backup_buf);
          if( ret < 0 ){
            Serial.println("Not Enough");
          }else{
            // タイトル・パスワードの追加、不揮発メモリへも追加
            pref.putString(backup_buf, input_buf);
            pref.putBytes("title_list", title_list, sizeof(title_list));
            Serial.println(backup_buf);
            Serial.println(" Added");
          }
          state = TOP;
        }
      }else if( state == UPDATE_VALUE ){
        // パスワードの更新、不揮発メモリを更新
        unsigned char len = strlen(input_buf);
        if( len == 0 ){
          Serial.println("Invalid Input");
          state = TOP;
        }else{
          char *title = get_title(backup_number);
          pref.putString(title, input_buf);
          Serial.println(title);
          Serial.println(" Updated");
          state = TOP;
        }
      }else if( state == CHECK_VALUE ){
        unsigned char len = strlen(input_buf);
        if( len == 0 ){
          Serial.println("Invalid Input");
          state = TOP;
        }else{
          // パスワードの不揮発メモリからの読み出しと、値の確認
          char* title = get_title(backup_number);
          pref.getString(title , backup_buf, sizeof(backup_buf) );
          Serial.println(title);
          if( strcmp(input_buf, backup_buf) != 0 ){
            Serial.println(" Mismatch!");
          }else{
            Serial.println(" Correct!");
          }
        
          state = TOP;
        }
      }else{
        state = TOP;
      }
  
      prompt();
      break;
    }
  }
}
```

## 補足

大きく分けて5の処理に分かれます。

* パスワード管理
　　不揮発メモリを操作して、パスワードを登録したり削除したりします。

* シリアル入力処理
　　シリアル入出力処理を行います。1文字入力や文字列入力のためのバッファリングをします。

* シリアル表示処理
　　状態に合わせて、シリアルにメニューを出力します。

* BLEデバイス処理
　　BLEデバイスとしてふるまうための処理です。HIDのクラスもあるので、ありがたく使わせていただきました。

* LCD表示処理
　　状態に合わせて、LCDの表示を切り替えます。

* Arduinoメイン処理
　　arduinoでお決まりの、setupとloopがあります。

　setup()
　　M5StickCの初期、LCDの初期化、シリアルの初期化、BLEデバイスの起動、不揮発メモリライブラリの初期化を行います。

　loop
　　ボタンの押下と、シリアル入力を監視します。
　　ボタン押下を検知すると、HIDでパスワードを送信します。
　　シリアル入力を検知すると、メニューの状態遷移とパスワード処理を行います。

# 制限事項

* Androidでは、HIDキーボードとして認識するときのPIN入力がうまくいかず、ペアリングできない場合があります。
その時にはあきらめて、PIN入力無しとなるようにソースコードを修正しかないのか。。。

* パスワード登録などで、不揮発メモリを更新すると、なぜかBLEのペアリングの鍵が壊れるようで、次回ペアリングに失敗してしまいます。Bondingはあきらめないといけないのか。。。（素直に、EEPROM.hを使うべきだったか？？）

* BLEまわりが不安定です。。。おしい。。。

以上