Help us understand the problem. What is going on with this article?

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

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

image.png

Bluetoothを選択します。

image.png

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

image.png

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

PIN
12345678

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

image.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

プロジェクトの準備

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

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

シリアルポートの番号は、接続されているM5Stick-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){
      // ペアリング完了
      uint16_t length;
      esp_ble_gap_get_whitelist_size(&length);
      Serial.println("auth success");
      print_screen();
    }else{
      // ペアリング失敗
      Serial.println("auth failed");
    }
  }
};

// BLEデバイスの起動
void taskServer(void*){
  M5.begin();
  M5.IMU.Init();

  BLEDevice::init("Password-Reminder");

  BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT);
  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_BOND); // PIN入力無し用
// AndroidではうまくPIN入力が機能しない場合有り
  pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_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まわりが不安定です。。。おしい。。。

以上

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした