ESP-IDFとNimBLEとESP32 S3でBLE キーボードを作る

表題のままです。専用のBLEアプリケーションを作らなくても、簡単な文字列データならば送ることができます。

ESP32 S3をBLEキーボードとして動作させ、接続されると、5秒ごとに1234567890とEnterを押すプログラムです。

ペアリング後、交換した鍵を保持するための仕組みボンド(ボンディング)があると思いますが、ESP32の電源を切った後も保持させるために
NimBLE Optionsの「Persist the BLE Bonding keys in NVS」にチェックを入れること
RPA(Resolvable Private Address)に対応するために

ble_hs_util_ensure_addr(1);
  assert(rc == 0);

  // アドバタイジング中に使用しているアドレスを把握する
  rc = ble_hs_id_infer_auto(
          1,  // プライベートアドレスをを使うかどうか。0=使わない 1=使う
          &my_device.own_addr_type);
ble_hs_cfg.sm_our_key_dist = BLE_SM_PAIR_KEY_DIST_ENC | BLE_SM_PAIR_KEY_DIST_ID;
ble_hs_cfg.sm_their_key_dist = BLE_SM_PAIR_KEY_DIST_ENC | BLE_SM_PAIR_KEY_DIST_ID;

の設定を行っています。ble_hs_pvcy_rpa_config(NIMBLE_HOST_ENABLE_RPA)が必要だと思ったのですが、ESP32無印でないと使えず、そもそもS3は不要のようでした。
RPAの対応をしないと、iPhoneなどで使えないようです。(手持ちがWindwos10とUbuntuとAndroid端末だったので、これらESPを再起動しても自動的に接続したのは確認しました。)

ソースコード

ble_keyboard.h

#ifndef H_BLE_KEYBOARD_
#define H_BLE_KEYBOARD_

#include "nimble/ble.h"
#include "modlog/modlog.h"

#include "host/ble_gatt.h"
#include "host/ble_uuid.h"
#include "host/ble_hs_pvcy.h"
#include "host/util/util.h"
#include "services/gap/ble_svc_gap.h"
#include "services/gatt/ble_svc_gatt.h"
#include "services/bas/ble_svc_bas.h"
//#include "services/dis/ble_svc_dis.h"
#include "os/os_mbuf.h"

#include "nimble/nimble_port.h"
#include "nimble/nimble_port_freertos.h"
#include "host/ble_hs.h"

//////////////////////////////////////////////////
// デバイス情報サービス Device Infomation Service
//////////////////////////////////////////////////

// デバイス情報サービス用UUID
#define GATT_UUID_DIS_SERVICE             0x180A
#define GATT_UUID_DIS_SYSTEM_ID           0x2A23
#define GATT_UUID_DIS_MODEL_NUMBER        0x2A24
#define GATT_UUID_DIS_SERIAL_NUMBER       0x2A25
#define GATT_UUID_DIS_FIRMWARE_REVISION   0x2A26
#define GATT_UUID_DIS_HARDWARE_REVISION   0x2A27
#define GATT_UUID_DIS_SOFTWARE_REVISION   0x2A28
#define GATT_UUID_DIS_MANUFACTURER_NAME   0x2A29
#define GATT_UUID_DIS_PNP_INFO            0x2A50


//////////////////////////////////////////////////
// characteristic presentation format
//////////////////////////////////////////////////
// 名前空間定数(現状は下記の1種類だけ)
#define GATT_NAME_SPACE_BLUETOOTH_SIG     0x01

// キャラクタリスティックで読み取ったときの値のフォーマット
#define GATT_CPF_FORMAT_RFU               0x00
#define GATT_CPF_FORMAT_BOOLEAN           0x01
#define GATT_CPF_FORMAT_2BIT              0x02
#define GATT_CPF_FORMAT_NIBBLE            0x03
#define GATT_CPF_FORMAT_UINT8             0x04
#define GATT_CPF_FORMAT_UINT12            0x05
#define GATT_CPF_FORMAT_UINT16            0x06
#define GATT_CPF_FORMAT_UINT24            0x07
#define GATT_CPF_FORMAT_UINT32            0x08
#define GATT_CPF_FORMAT_UINT48            0x09
#define GATT_CPF_FORMAT_UINT64            0x0A
#define GATT_CPF_FORMAT_UINT128           0x0B
#define GATT_CPF_FORMAT_SINT8             0x0C
#define GATT_CPF_FORMAT_SINT12            0x0D
#define GATT_CPF_FORMAT_SINT16            0x0E
#define GATT_CPF_FORMAT_SINT24            0x0F
#define GATT_CPF_FORMAT_SINT32            0x10
#define GATT_CPF_FORMAT_SINT48            0x11
#define GATT_CPF_FORMAT_SINT64            0x12
#define GATT_CPF_FORMAT_SINT128           0x13
#define GATT_CPF_FORMAT_FLOAT32           0x14
#define GATT_CPF_FORMAT_FLOAT64           0x15
#define GATT_CPF_FORMAT_SFLOAT            0x16
#define GATT_CPF_FORMAT_FLOAT             0x17
#define GATT_CPF_FORMAT_DUINT16           0x18
#define GATT_CPF_FORMAT_UTF8S             0x19
#define GATT_CPF_FORMAT_UTF16S            0x1A
#define GATT_CPF_FORMAT_STRUCT            0x1B

// キャラクタリスティックの値を説明するための情報
typedef struct characteristic_presentation_format{
  uint16_t  unit;         // UUIDで単位を表します。 3.5 Units を参照
  uint16_t  description;  // name_space=1の時は 2.4.2.1 Bluetooth SIG GATT Characteristic Presentation Format Description を参照
  uint8_t   format;       // GATT_CPF_FORMAT_〜定数で選択。数値のフォーマットを表す
  uint8_t   exponent;     // 指数。キャラクタリスティックの値に10の何乗をかけるのか。
  uint8_t   name_space;   // descriptionの名前空間。 0:なし。 1:Bluetooth SIGによる定義 のどちらか。通常は1 
} gatt_cpf_struct;

// https://www.bluetooth.com/specifications/assigned-numbers/
// 2.4.2.1 Bluetooth SIG GATT Characteristic Presentation Format Description 抜粋
// 0x0000:不明
// 0x0001〜0x00FF:1〜255番目 という意味
// 0x0100:フロント
// 0x010F:内側
// のようになっていて、例えば温度計を設置したあとに、1番目や、内側などの意味を持たせるための説明に使用する
// 3.5 Units 抜粋
// 0x272F:温度℃
// 0x2704:電流A
// 0x2728:電圧V
// 0x27B0:電池容量Ah
// 0x2727:電荷量C
// 0x27AD:パーセント%
// 0x27AB:電力量kWh

//////////////////////////////////////////////////
// HID
//////////////////////////////////////////////////

// HIDの最新バージョン 1.11
#define HID_USB_VERSION                   0x0111

// HID protocol mode values
#define HID_PROTOCOL_MODE_BOOT            0x00      // ブートプロトコルモード
#define HID_PROTOCOL_MODE_REPORT          0x01      // レポートプロトコルモード(デフォルト。通常時はこちら)

// HIDコントロールポイントの値 下位4ビット 上位4ビットは1 = HID_CONTROL request
// Bluetooth SIGのHuman Interface Device Profile 1.0のSection 7.4.2を参照
#define HID_CTRL_POINT_NOP                0   // No Operation
#define HID_CTRL_POINT_HARD_RESET         1   // ハードウェアリセット
#define HID_CTRL_POINT_SOFT_RESET         2   // ソフトウェアリセット
#define HID_CTRL_POINT_SUSPEND            3   // 休止状態へ移行
#define HID_CTRL_POINT_EXIT_SUSPEND       4   // 休止状態から復帰
#define HID_CTRL_POINT_VIRTUAL_UNPLUG     5   // 仮想的にケーブルを引っこ抜く

// HID information flags
#define HID_FLAGS_REMOTE_WAKE             0x01      // RemoteWake
#define HID_FLAGS_NORMALLY_CONNECTABLE    0x02      // NormallyConnectable

// HID Report types
#define HID_REPORT_TYPE_INPUT             1
#define HID_REPORT_TYPE_OUTPUT            2
#define HID_REPORT_TYPE_FEATURE           3

// Bootモード時のレポートID
#define HID_BOOT_REPORT_ID_KEYBOARD       1
#define HID_BOOT_REPORT_ID_MOUSE          2

// Bootモード時のレポートサイズ
#define HID_BOOT_REPORT_SIZE_KEYBOARD     9 // 1-byte Report ID + standard 8-byte keyboard boot report
#define HID_BOOT_REPORT_SIZE_MOUSE        4 // 1-byte Report ID + standard 3-byte mouse boot report

// HIDサービス用UUID
#define GATT_UUID_HID_SERVICE             0x1812
#define GATT_UUID_HID_INFORMATION         0x2A4A
#define GATT_UUID_HID_REPORT_MAP          0x2A4B
#define GATT_UUID_HID_CONTROL_POINT       0x2A4C
#define GATT_UUID_HID_REPORT              0x2A4D
#define GATT_UUID_HID_PROTOCOL_MODE       0x2A4E

#define GATT_UUID_HID_BT_KB_INPUT         0x2A22
#define GATT_UUID_HID_BT_KB_OUTPUT        0x2A32
#define GATT_UUID_HID_BT_MOUSE_INPUT      0x2A33

// Keyboard レポートサイズ (レポートマップで自分で定義したサイズ)
#define HID_REPORT_SIZE_KEYBOARD_IN       8   // キーコードを送る時のサイズ
#define HID_REPORT_SIZE_KEYBOARD_OUT      1   // NumLockなどのLED情報を受け取る時のサイズ

// HID レポートモード用レポートID (レポートマップで自分で定義したID)
enum hid_report_id{
  HID_REPORT_ID_KB_OUT,    // LED output report ID from report map
  HID_REPORT_ID_KB_IN,     // Keyboard input report ID from report map
  HID_REPORT_ID_CC_IN,     // Consumer Control input report ID from report map
  HID_REPORT_ID_FEATURE    // Feature report ID from report map
};

// ディスクリプタ用UUID
#define GATT_UUID_BAT_PRESENT_DESCR       0x2904
#define GATT_UUID_EXT_RPT_REF_DESCR       0x2907
#define GATT_UUID_RPT_REF_DESCR           0x2908


enum attr_handles_list{
  HANDLE_BATTERY_LEVEL,

  HANDLE_DIS_MODEL_NUMBER,
  HANDLE_DIS_SERIAL_NUMBER,
  HANDLE_DIS_HARDWARE_REVISION,
  HANDLE_DIS_FIRMWARE_REVISION,
  HANDLE_DIS_SOFWARE_REVISION,
  HANDLE_DIS_MANUFACTURER_NAME,
  HANDLE_DIS_SYSTEM_ID,
  HANDLE_DIS_PNP_INFO,

  HANDLE_HID_INFORMATION,
  HANDLE_HID_CONTROL_POINT,
  HANDLE_HID_REPORT_MAP,
  HANDLE_HID_PROTOCOL_MODE,
  HANDLE_HID_KB_IN_REPORT,
  HANDLE_HID_KB_OUT_REPORT,
  HANDLE_HID_KB_CCC_DISC,
  HANDLE_HID_CC_REPORT,
  HANDLE_HID_BOOT_KB_IN_REPORT,
  HANDLE_HID_BOOT_KB_OUT_REPORT,
  HANDLE_HID_FEATURE_REPORT,

  HANDLE_NUM
};

#define MAC2STR_REV(a) (a)[5], (a)[4], (a)[3], (a)[2], (a)[1], (a)[0]

int ble_init_server();
bool ble_is_connected();
void send_text(const char *text);

void ble_store_config_init(void);

#endif

ble_keyboard.c

#include "ble_keyboard.h"

// NimBLE Optionsの下記にチェックを入れる
// Persist the BLE Bonding keys in NVS

#define BLE_SVC_GAP_APPEARANCE            0x03C1      // デバイスの見た目 : 0x03C1:キーボードの絵
const char BLE_DEVICE_NAME[]            = "BLE KB";   // アドバタイズで表示されるデバイス名。長い名前は不可。最大31バイト
const int  PASS_KEY                     = 1234;

// デバイス情報
const char GATT_DIS_MODEL_NUMBER[]      = "0001";
const char GATT_DIS_SERIAL_NUMBER[]     = "0000";
const char GATT_DIS_FIRMWARE_REVISION[] = "Ver.1.1";
const char GATT_DIS_HARDWARE_REVISION[] = "Ver.1.1";
const char GATT_DIS_SOFTWARE_REVISION[] = "Ver.1.1";
const char GATT_DIS_MANUFACTURER_NAME[] = "ESP";
const char GATT_DIS_SYSTEM_ID[]         = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
const char GATT_DIS_PNP_INFO[]          = {0x00, 0x47, 0x00, 0xff, 0xff, 0xff, 0xff};

struct device_status_strct {
  SemaphoreHandle_t semaphore;
  bool     connected;
  uint16_t handle;
  bool     suspended;
  uint8_t  own_addr_type;
  uint8_t  protocol_mode;
  uint8_t  control_point;
  bool     indicate[HANDLE_NUM];
  bool     notify[HANDLE_NUM];
  uint8_t  battery_level;
  uint8_t  keycode[8];
} my_device = {
  .semaphore     = 0,
  .handle        = 0,
  .connected     = false,
  .suspended     = false,
  .own_addr_type = 0,
  .protocol_mode = HID_PROTOCOL_MODE_REPORT,
  .control_point = HID_CTRL_POINT_NOP,
  .battery_level = 100 // バッテリ残量100%と表示
};

uint8_t  hid_info[] = {
  (HID_USB_VERSION & 0xFF),         // bcdHID:BCD表現のHIDバージョン(1.10なら0x0110)。USB HID version
  ((HID_USB_VERSION >> 8) & 0xFF),
  0x00,                             // bCountryCode:地域固有デバイスのための国識別番号。必要がなければ0
  HID_FLAGS_REMOTE_WAKE             // Flags
};
uint16_t hid_ext_report_ref_desc        = BLE_SVC_BAS_CHR_UUID16_BATTERY_LEVEL;

// HID Report Map characteristic value
const uint8_t hid_report_map[] = {
  // KEYBOARD REPORT
  0x05, 0x01,  // Usage Page (Generic Desktop)
  0x09, 0x06,  // Usage (Keyboard)
  0xA1, 0x01,  // Collection: (Application)

  0x85, HID_REPORT_ID_KB_IN,
               // Report Id (1)
  // Input Byte 0: 修飾キー Modifier
  0x75, 0x01,  //   Report Size (1)  1bit を 8 回
  0x95, 0x08,  //   Report Count (8)
  0x05, 0x07,  //   Usage Pg (Key Codes)
  0x15, 0x00,  //   Log Min (0)
  0x25, 0x01,  //   Log Max (1)
  0x19, 0xE0,  //   Usage Min (224)
  0x29, 0xE7,  //   Usage Max (231)
  0x81, 0x02,  //   Input: (Data, Variable, Absolute)

  //   Key arrays (7 bytes)
  0x75, 0x08,  //   Report Size (8)
  0x95, 0x07,  //   Report Count (7)
  0x15, 0x00,  //   Log Min (0)
  0x25, 0x65,  //   Log Max (101)
  0x05, 0x07,  //   Usage Pg (Key Codes)
  0x19, 0x00,  //   Usage Min (0)
  0x29, 0x65,  //   Usage Max (101)
  0x81, 0x00,  //   Input: (Data, Array)

  //   LED report
  0x95, 0x05,  //   Report Count (5)
  0x75, 0x01,  //   Report Size (1)
  0x05, 0x08,  //   Usage Pg (LEDs)
  0x19, 0x01,  //   Usage Min (1)
  0x29, 0x05,  //   Usage Max (5)
  0x91, 0x02,  //   Output: (Data, Variable, Absolute)

  //   LED report padding
  0x95, 0x01,  //   Report Count (1)
  0x75, 0x03,  //   Report Size (3)
  0x91, 0x03,  //   Output: (Constant)

  0xC0         // End Collection
};

// GATTサービスにある全てのキャラクタリスティックのハンドル
uint16_t handles_list[HANDLE_NUM];

int battery_access(uint16_t, uint16_t, struct ble_gatt_access_ctxt*, void*);
int dis_access(uint16_t, uint16_t, struct ble_gatt_access_ctxt*, void*);
int hid_chr_access(uint16_t, uint16_t, struct ble_gatt_access_ctxt*, void*);
int hid_report_access(uint16_t, uint16_t, struct ble_gatt_access_ctxt*, void*);

// セカンダリーサービス
const struct ble_gatt_svc_def gatt_secondary_services[] = {
  { // バッテリーサービス
    .type       = BLE_GATT_SVC_TYPE_PRIMARY,
    .uuid       = BLE_UUID16_DECLARE(BLE_SVC_BAS_UUID16),
    .includes   = NULL,
    .characteristics = (struct ble_gatt_chr_def[]){
      { // バッテリー残量 キャラクタリスティック
        .uuid         = BLE_UUID16_DECLARE(BLE_SVC_BAS_CHR_UUID16_BATTERY_LEVEL),
        .access_cb    = battery_access,
        .arg          = (void *) HANDLE_BATTERY_LEVEL,
        .val_handle   = &handles_list[HANDLE_BATTERY_LEVEL],
        .flags        = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_INDICATE,
        .min_key_size = 0,
        .descriptors  = (struct ble_gatt_dsc_def[]) {
          {
            .uuid         = BLE_UUID16_DECLARE(GATT_UUID_BAT_PRESENT_DESCR),
            .att_flags    = BLE_ATT_F_READ | BLE_ATT_F_READ_ENC,
            .access_cb    = battery_access,
            .arg          = NULL,
            .min_key_size = 0
          }, {0}  // ディスクリプタ終端
        }
      }, {0}      // キャラクタリスティック終端
    }
  },
  { // デバイス情報サービス(DIS)
    .type     = BLE_GATT_SVC_TYPE_PRIMARY,
    .uuid     = BLE_UUID16_DECLARE(GATT_UUID_DIS_SERVICE),
    .includes = NULL,
    .characteristics = (struct ble_gatt_chr_def[]) {
      // キャラクタリスティック
      { // 型番
        .uuid         = BLE_UUID16_DECLARE(GATT_UUID_DIS_MODEL_NUMBER),
        .access_cb    = dis_access,
        .val_handle   = &handles_list[HANDLE_DIS_MODEL_NUMBER],
        .flags        = BLE_GATT_CHR_F_READ,
        .descriptors  = NULL,
        .arg          = NULL,
        .min_key_size = 0,
      },
      { // シリアル番号
        .uuid         = BLE_UUID16_DECLARE(GATT_UUID_DIS_SERIAL_NUMBER),
        .access_cb    = dis_access,
        .val_handle   = &handles_list[HANDLE_DIS_SERIAL_NUMBER],
        .flags        = BLE_GATT_CHR_F_READ,
        .descriptors  = NULL,
        .arg          = NULL,
        .min_key_size = 0,
      },
      { // ハードウェアリビジョン
        .uuid         = BLE_UUID16_DECLARE(GATT_UUID_DIS_HARDWARE_REVISION),
        .access_cb    = dis_access,
        .val_handle   = &handles_list[HANDLE_DIS_HARDWARE_REVISION],
        .flags        = BLE_GATT_CHR_F_READ,
        .descriptors  = NULL,
        .arg          = NULL,
        .min_key_size = 0,
      },
      { // ファームウェアリビジョン
        .uuid         = BLE_UUID16_DECLARE(GATT_UUID_DIS_FIRMWARE_REVISION),
        .access_cb    = dis_access,
        .val_handle   = &handles_list[HANDLE_DIS_FIRMWARE_REVISION],
        .flags        = BLE_GATT_CHR_F_READ,
        .descriptors  = NULL,
        .arg          = NULL,
        .min_key_size = 0,
      },
      { // ソフトウェアリビジョン
        .uuid         = BLE_UUID16_DECLARE(GATT_UUID_DIS_SOFTWARE_REVISION),
        .access_cb    = dis_access,
        .val_handle   = &handles_list[HANDLE_DIS_SOFWARE_REVISION],
        .flags        = BLE_GATT_CHR_F_READ,
        .descriptors  = NULL,
        .arg          = NULL,
        .min_key_size = 0,
      },
      { // 製造者名
        .uuid         = BLE_UUID16_DECLARE(GATT_UUID_DIS_MANUFACTURER_NAME),
        .access_cb    = dis_access,
        .val_handle   = &handles_list[HANDLE_DIS_MANUFACTURER_NAME],
        .flags        = BLE_GATT_CHR_F_READ,
        .descriptors  = NULL,
        .arg          = NULL,
        .min_key_size = 0,
      },
      { // システムID
        .uuid         = BLE_UUID16_DECLARE(GATT_UUID_DIS_SYSTEM_ID),
        .access_cb    = dis_access,
        .val_handle   = &handles_list[HANDLE_DIS_SYSTEM_ID],
        .flags        = BLE_GATT_CHR_F_READ,
        .descriptors  = NULL,
        .arg          = NULL,
        .min_key_size = 0,
      },
      { // PNP
        .uuid         = BLE_UUID16_DECLARE(GATT_UUID_DIS_PNP_INFO),
        .access_cb    = dis_access,
        .val_handle   = &handles_list[HANDLE_DIS_PNP_INFO],
        .flags        = BLE_GATT_CHR_F_READ,
        .descriptors  = NULL,
        .arg          = NULL,
        .min_key_size = 0,
      }, {0}      // キャラクタリスティック終端
    }
  }, {0}      // サービス終端
};

const struct ble_gatt_svc_def *included_services_array[] = {
    &gatt_secondary_services[0],
    NULL,
};

// プライマーサービス
const struct ble_gatt_svc_def gatt_primary_services[] = {
  { // HID サービス
    .type     = BLE_GATT_SVC_TYPE_PRIMARY,
    .uuid     = BLE_UUID16_DECLARE(GATT_UUID_HID_SERVICE),
    .includes = included_services_array,
    .characteristics = (struct ble_gatt_chr_def[]){
      { // HID情報 キャラクタリスティック
        .uuid         = BLE_UUID16_DECLARE(GATT_UUID_HID_INFORMATION),
        .access_cb    = hid_chr_access,
        .val_handle   = &handles_list[HANDLE_HID_INFORMATION],
        .flags        = BLE_GATT_CHR_F_READ,
        .descriptors  = NULL,
        .arg          = NULL,
        .min_key_size = 0,
      },
      { // HID Control Point キャラクタリスティック
        .uuid         = BLE_UUID16_DECLARE(GATT_UUID_HID_CONTROL_POINT),
        .access_cb    = hid_chr_access,
        .val_handle   = &handles_list[HANDLE_HID_CONTROL_POINT],
        .flags        = BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_READ,
        .descriptors  = NULL,
        .arg          = NULL,
        .min_key_size = 0,
      },
      { // レポートマップ キャラクタリスティック
        .uuid         = BLE_UUID16_DECLARE(GATT_UUID_HID_REPORT_MAP),
        .access_cb    = hid_chr_access,
        .val_handle   = &handles_list[HANDLE_HID_REPORT_MAP],
        .flags        = BLE_GATT_CHR_F_READ,
        .arg          = NULL,
        .min_key_size = 0,
        .descriptors  = (struct ble_gatt_dsc_def[]) {
          { // 外部レポート参照 ディスクリプタ
            .uuid = BLE_UUID16_DECLARE(GATT_UUID_EXT_RPT_REF_DESCR),
            .att_flags = BLE_ATT_F_READ,
            .access_cb = hid_chr_access,
            .arg = NULL, .min_key_size = 0,
          }, {0}  // ディスクリプタ終端
        }
      },
      { // キーボードHID入力レポート キャラクタリスティック
        .uuid         = BLE_UUID16_DECLARE(GATT_UUID_HID_REPORT),
        .access_cb    = hid_report_access,
        .arg          = (void *)HANDLE_HID_KB_IN_REPORT,
        .val_handle   = &handles_list[HANDLE_HID_KB_IN_REPORT],
        .flags        = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY,
        .min_key_size = 0,
        .descriptors  = (struct ble_gatt_dsc_def[]) {
          { // レポート参照ディスクリプタ
            .uuid         = BLE_UUID16_DECLARE(GATT_UUID_RPT_REF_DESCR),
            .att_flags    = BLE_ATT_F_READ,
            .access_cb    = hid_report_access,
            .arg          = (void *)HANDLE_HID_KB_IN_REPORT,
            .min_key_size = 0,
          }, {0}  // ディスクリプタ終端
        },
      },
      { // キーボードHID出力レポート (LED IN/OUT) キャラクタリスティック
        .uuid         = BLE_UUID16_DECLARE(GATT_UUID_HID_REPORT),
        .access_cb    = hid_report_access,
        .arg          = (void *)HANDLE_HID_KB_OUT_REPORT,
        .val_handle   = &handles_list[HANDLE_HID_KB_OUT_REPORT],
        .flags        = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_WRITE_NO_RSP,
        .min_key_size = 0,
        .descriptors  = (struct ble_gatt_dsc_def[]) {
          { // レポート参照ディスクリプタ
            .uuid         = BLE_UUID16_DECLARE(GATT_UUID_RPT_REF_DESCR),
            .att_flags    = BLE_ATT_F_READ,
            .access_cb    = hid_report_access,
            .arg          = (void *)HANDLE_HID_KB_OUT_REPORT,
            .min_key_size = 0,
          }, {0}  // ディスクリプタ終端
        },
      },
      { // プロトコルモード キャラクタリスティック
        .uuid         = BLE_UUID16_DECLARE(GATT_UUID_HID_PROTOCOL_MODE),
        .access_cb    = hid_chr_access,
        .val_handle   = &handles_list[HANDLE_HID_PROTOCOL_MODE],
        .flags        = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE,
        .descriptors  = NULL,
        .arg          = NULL,
        .min_key_size = 0,
      },
      { // キーボード入力BOOTモード時HIDレポート キャラクタリスティック
        .uuid         = BLE_UUID16_DECLARE(GATT_UUID_HID_BT_KB_INPUT),
        .access_cb    = hid_report_access,
        .arg          = (void *)HANDLE_HID_BOOT_KB_IN_REPORT,
        .val_handle   = &handles_list[HANDLE_HID_BOOT_KB_IN_REPORT],
        .flags        = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_INDICATE,
        .descriptors  = NULL,
        .min_key_size = 0,
      },
      { // キーボード出力BOOTモード時HIDレポート キャラクタリスティック
        .uuid         = BLE_UUID16_DECLARE(GATT_UUID_HID_BT_KB_OUTPUT),
        .access_cb    = hid_report_access,
        .arg          = (void *)HANDLE_HID_BOOT_KB_OUT_REPORT,
        .val_handle   = &handles_list[HANDLE_HID_BOOT_KB_OUT_REPORT],
        .flags        = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_WRITE_NO_RSP,
        .descriptors  = NULL,
        .min_key_size = 0,
      },
      {0}   // キャラクタリスティック終端
    }
  },
  {0} // サービス終端
};

// バッテリ残量の単位情報
gatt_cpf_struct battery_level_units = {
  .format      = GATT_CPF_FORMAT_UINT8,
  .exponent    = 0,
  .unit        = 0x27AD, // 単位:パーセント
  .name_space  = GATT_NAME_SPACE_BLUETOOTH_SIG,
  .description = 0
};

int battery_access(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg){
  uint16_t uuid16 = ble_uuid_u16(ctxt->chr->uuid);
  int rc = 0;

  MODLOG_DFLT(INFO, "%s: UUID %04X attr %04X arg %d op %d", __FUNCTION__, uuid16, attr_handle, (int) arg, ctxt->op);

  switch (uuid16){
    case BLE_SVC_BAS_CHR_UUID16_BATTERY_LEVEL:
      if (ctxt->op != BLE_GATT_ACCESS_OP_READ_CHR){
        MODLOG_DFLT(ERROR, "Invalid operation %d", ctxt->op);
        rc = BLE_ATT_ERR_UNLIKELY;
        break;
      }
      rc = os_mbuf_append(ctxt->om, &my_device.battery_level, sizeof(my_device.battery_level));
      if(rc){
        MODLOG_DFLT(ERROR, "Failed to read battery buffer : %d", rc);
        rc = BLE_ATT_ERR_INSUFFICIENT_RES;
      }
      break;

    case GATT_UUID_BAT_PRESENT_DESCR:
      if(ctxt->op != BLE_GATT_ACCESS_OP_READ_DSC){
        MODLOG_DFLT(ERROR, "Invalid operation %d", ctxt->op);
        rc = BLE_ATT_ERR_UNLIKELY;
        break;
      }
      rc = os_mbuf_append(ctxt->om, &battery_level_units, sizeof battery_level_units);
      if(rc){
        rc = BLE_ATT_ERR_INSUFFICIENT_RES;
      }

      break;

    default:
      rc = BLE_ATT_ERR_UNLIKELY;
      break;
  }

  return rc;
}

int dis_access(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg){
  uint16_t   uuid16 = ble_uuid_u16(ctxt->chr->uuid);
  int        rc     = 0;
  const char *info  = NULL;
  int        len    = 0;

  MODLOG_DFLT(INFO, "%s: UUID %04X attr %04X arg %d op %d", __FUNCTION__, uuid16, attr_handle, (int) arg, ctxt->op);

  if (ctxt->op != BLE_GATT_ACCESS_OP_READ_CHR){
    MODLOG_DFLT(ERROR, "Invalid operation %d", ctxt->op);
    return BLE_ATT_ERR_UNLIKELY;
  }

  switch(uuid16){
    case GATT_UUID_DIS_MODEL_NUMBER:
      info = GATT_DIS_MODEL_NUMBER;
      break;

    case GATT_UUID_DIS_SERIAL_NUMBER:
      info = GATT_DIS_SERIAL_NUMBER;
      break;

    case GATT_UUID_DIS_FIRMWARE_REVISION:
      info = GATT_DIS_FIRMWARE_REVISION;
      break;

    case GATT_UUID_DIS_HARDWARE_REVISION:
      info = GATT_DIS_HARDWARE_REVISION;
      break;

    case GATT_UUID_DIS_SOFTWARE_REVISION:
      info = GATT_DIS_SOFTWARE_REVISION;
      break;

    case GATT_UUID_DIS_MANUFACTURER_NAME:
      info = GATT_DIS_MANUFACTURER_NAME;
      break;

    case GATT_UUID_DIS_SYSTEM_ID:
      info = GATT_DIS_SYSTEM_ID;
      len  = sizeof(GATT_DIS_SYSTEM_ID);
      break;

    case GATT_UUID_DIS_PNP_INFO:
      info = GATT_DIS_PNP_INFO;
      len  = sizeof(GATT_DIS_PNP_INFO);
      break;

    default:
      MODLOG_DFLT(ERROR, "Invalid characteristic.");
      rc = BLE_ATT_ERR_UNLIKELY;
  }

  if(info != NULL){
    if(len == 0){
      len =  strlen(info);
    }
    rc = os_mbuf_append(ctxt->om, info, len);
    if(rc){
      rc = BLE_ATT_ERR_INSUFFICIENT_RES;
    }
  }

  return rc;
}

int hid_chr_access(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg){
  uint16_t  uuid16 = ble_uuid_u16(ctxt->chr->uuid);
  int       rc = 0;
  uint8_t   new_state;
  uint16_t  len;

  MODLOG_DFLT(INFO, "%s: UUID %04X attr %04X arg %d op %d", __FUNCTION__, uuid16, attr_handle, (int) arg, ctxt->op);

  switch(uuid16){
    case GATT_UUID_HID_INFORMATION:
      MODLOG_DFLT(INFO, "HID INFORMATION");
      if(ctxt->op != BLE_GATT_ACCESS_OP_READ_CHR){
        MODLOG_DFLT(ERROR, "Invalid operation %d", ctxt->op);
        rc = BLE_ATT_ERR_UNLIKELY;
        break;
      }
      rc = os_mbuf_append(ctxt->om, hid_info, sizeof(hid_info));
      if(rc){
        rc = BLE_ATT_ERR_INSUFFICIENT_RES;
      }
      break;

    case GATT_UUID_HID_CONTROL_POINT: 
      MODLOG_DFLT(INFO, "HID CONTROL POINT");
      if(ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR){
        rc = ble_hs_mbuf_to_flat(ctxt->om, &new_state, 1, &len);
        if(rc){
          rc = BLE_ATT_ERR_INSUFFICIENT_RES;
          break;
        }
        MODLOG_DFLT(INFO, "HID_CONTROL_POINT: new state:%d, old state:%d", (int)new_state, (int)my_device.control_point);
        my_device.control_point = new_state;
        if(new_state == HID_CTRL_POINT_SUSPEND){
          my_device.suspended = true;
        }else if(new_state == HID_CTRL_POINT_EXIT_SUSPEND){
          my_device.suspended = false;
        }
        // 値を変えるだけで特に何もする予定なし

      }else if(ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR){
        rc = os_mbuf_append(ctxt->om, &my_device.control_point, sizeof(my_device.control_point));
        if(rc){
          rc = BLE_ATT_ERR_INSUFFICIENT_RES;
        }
      }else{
        MODLOG_DFLT(ERROR, "Invalid operation %d", ctxt->op);
        rc = BLE_ATT_ERR_UNLIKELY;
        break;
      }
      break;

    case GATT_UUID_HID_REPORT_MAP:
      MODLOG_DFLT(INFO, "HID REPORT MAP");
      if(ctxt->op != BLE_GATT_ACCESS_OP_READ_CHR){
        MODLOG_DFLT(ERROR, "Invalid operation %d", ctxt->op);
        rc = BLE_ATT_ERR_UNLIKELY;
        break;
      }
      rc = os_mbuf_append(ctxt->om, hid_report_map, sizeof(hid_report_map));
      if(rc){
        rc = BLE_ATT_ERR_INSUFFICIENT_RES;
      }
      break;

    case GATT_UUID_HID_PROTOCOL_MODE:
      MODLOG_DFLT(INFO, "HID PROTOCOL MODE");
      if(ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR) {
        if(my_device.protocol_mode == HID_PROTOCOL_MODE_BOOT){
          MODLOG_DFLT(INFO, "Now protocol mode: BOOT");
        }else{
          MODLOG_DFLT(INFO, "Now protocol mode: REPORT");
        }
        rc = os_mbuf_append(ctxt->om, &my_device.protocol_mode, sizeof(my_device.protocol_mode));
        if(rc){
          rc = BLE_ATT_ERR_INSUFFICIENT_RES;
        }

      }else if(ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR){
        rc = ble_hs_mbuf_to_flat(ctxt->om, &my_device.protocol_mode, sizeof(my_device.protocol_mode), &len);
        if(rc){
          rc = BLE_ATT_ERR_INSUFFICIENT_RES;
          break;
        }
        if(my_device.protocol_mode == HID_PROTOCOL_MODE_BOOT){
          MODLOG_DFLT(INFO, "New protocol mode: BOOT");
        }else{
          MODLOG_DFLT(INFO, "New protocol mode: REPORT");
        }

      }else{
        MODLOG_DFLT(ERROR, "Invalid operation %d", ctxt->op);
        rc = BLE_ATT_ERR_UNLIKELY;
      }
      break;

    case GATT_UUID_EXT_RPT_REF_DESCR: 
      MODLOG_DFLT(INFO, "GATT_UUID_EXT_RPT_REF_DESCR");
      if(ctxt->op != BLE_GATT_ACCESS_OP_READ_DSC){
        MODLOG_DFLT(ERROR, "Invalid operation %d", ctxt->op);
        rc = BLE_ATT_ERR_UNLIKELY;
        break;
      }
      rc = os_mbuf_append(ctxt->om, (uint8_t *)&hid_ext_report_ref_desc, sizeof(hid_ext_report_ref_desc));
      if(rc){
        rc = BLE_ATT_ERR_INSUFFICIENT_RES;
      }
      break;

    default:
      MODLOG_DFLT(ERROR, "Invalid UUID %02X", uuid16);
      break;
  }

  return rc;
}

int hid_report_access(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg){
  uint16_t  uuid16 = ble_uuid_u16(ctxt->chr->uuid);
  int       rc = 0;
  int       handle_num = (int)arg;
  uint8_t   buffer[16];
  uint16_t  len;

  MODLOG_DFLT(INFO, "%s: UUID %04X attr %04X arg %d op %d", __FUNCTION__, uuid16, attr_handle, (int) arg, ctxt->op);

  switch(uuid16){
    case GATT_UUID_HID_BT_KB_INPUT :
      //キーボード入力BOOTモード時HIDレポート
      MODLOG_DFLT(INFO, "Keyboard boot in report");
      if(ctxt->op != BLE_GATT_ACCESS_OP_READ_CHR){
        MODLOG_DFLT(ERROR, "Invalid operation %d", ctxt->op);
        rc = BLE_ATT_ERR_UNLIKELY;
        break;
      }
      memset(buffer, 0, HID_BOOT_REPORT_SIZE_KEYBOARD);
      buffer[0] = HID_BOOT_REPORT_ID_KEYBOARD;
      buffer[1] = my_device.keycode[0];  // 修飾キー
      buffer[3] = my_device.keycode[1];
      buffer[4] = my_device.keycode[2];
      buffer[5] = my_device.keycode[3];
      buffer[6] = my_device.keycode[4];
      buffer[7] = my_device.keycode[5];
      buffer[8] = my_device.keycode[6];
      rc = os_mbuf_append(ctxt->om, buffer, HID_BOOT_REPORT_SIZE_KEYBOARD);
      if(rc){
        rc = BLE_ATT_ERR_INSUFFICIENT_RES;
      }
      break;

    case GATT_UUID_HID_BT_KB_OUTPUT :
      MODLOG_DFLT(INFO, "Keyboard boot out report");
      // キーボード出力BOOTモード時HIDレポート (LED)
      if(ctxt->op != BLE_GATT_ACCESS_OP_WRITE_CHR){
        MODLOG_DFLT(ERROR, "Invalid operation %d", ctxt->op);
        rc = BLE_ATT_ERR_UNLIKELY;
        break;
      }
      rc = ble_hs_mbuf_to_flat(ctxt->om, buffer, 2, &len);
      if(rc){
        rc = BLE_ATT_ERR_INSUFFICIENT_RES;
        break;
      }
      break;

    case GATT_UUID_RPT_REF_DESCR :
      // キーボードのレポートIn/Out ディスクリプタ
      MODLOG_DFLT(INFO, "Keyboard report discriptor");
      if(ctxt->op != BLE_GATT_ACCESS_OP_READ_DSC){
        MODLOG_DFLT(ERROR, "Invalid operation %d", ctxt->op);
        rc = BLE_ATT_ERR_UNLIKELY;
        break;
      }
      if(handle_num == HANDLE_HID_KB_IN_REPORT){
        buffer[0] = HID_REPORT_ID_KB_IN;
        buffer[1] = HID_REPORT_TYPE_INPUT;
      }else if(handle_num == HANDLE_HID_KB_OUT_REPORT){
        buffer[0] = HID_REPORT_ID_KB_IN;
        buffer[1] = HID_REPORT_TYPE_OUTPUT;
      }else{
        rc = BLE_ATT_ERR_UNLIKELY;
        break;
      }
      rc = os_mbuf_append(ctxt->om, buffer, 2);
      if(rc){
        rc = BLE_ATT_ERR_INSUFFICIENT_RES;
      }
      break;

    case GATT_UUID_HID_REPORT :
      // キーボード レポートモード レポート
      MODLOG_DFLT(INFO, "Keyboard report");
      if(ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR){
        if(handle_num == HANDLE_HID_KB_IN_REPORT){
          // キーコードを送信
          rc = os_mbuf_append(ctxt->om, my_device.keycode, HID_REPORT_SIZE_KEYBOARD_IN);
          MODLOG_DFLT(INFO, "Send keycode %02X %02X %02X %02X %02X %02X %02X %02X",
            my_device.keycode[0], my_device.keycode[1], my_device.keycode[2], my_device.keycode[3],
            my_device.keycode[4], my_device.keycode[5], my_device.keycode[6], my_device.keycode[7]);

        }else if(handle_num == HANDLE_HID_KB_OUT_REPORT){
          buffer[0] = 0;
          rc = os_mbuf_append(ctxt->om, buffer, HID_REPORT_SIZE_KEYBOARD_OUT);
          MODLOG_DFLT(INFO, "Send LED %02X",  buffer[0]);

        }else{
          rc = BLE_ATT_ERR_UNLIKELY;
          break;
        }

      }else if(ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR){
        // NumLockなどのLED情報を取得
        rc = ble_hs_mbuf_to_flat(ctxt->om, buffer, HID_REPORT_SIZE_KEYBOARD_OUT, &len);
        MODLOG_DFLT(INFO, "recv %02X", buffer[0]);

      }else{
        MODLOG_DFLT(ERROR, "Invalid operation %d", ctxt->op);
        rc = BLE_ATT_ERR_UNLIKELY;
        break;
      }
      break;

    default:
      rc = BLE_ATT_ERR_UNLIKELY;
      break;
  }

  return rc;
}

void ble_advertise();

int ble_gap_event_callback(struct ble_gap_event *event, void *arg){
  struct ble_gap_conn_desc  desc;
  struct ble_sm_io          pkey = {0};
  int   rc;
  int   i;

  switch (event->type) {
    case BLE_GAP_EVENT_CONNECT:
      // 新規接続が確立したか、失敗したか
      if (event->connect.status == 0) {
        MODLOG_DFLT(INFO, "Connection is established.");
        rc = ble_gap_conn_find(event->connect.conn_handle, &desc);
        assert(rc == 0);
        MODLOG_DFLT(INFO,"Info\n"
          " handle              : %d\n"
          " our_ota_addr_type   : %d\n"
          " our_ota_addr        : %02x:%02x:%02x:%02x:%02x:%02x\n"
          " our_id_addr_type    : %d\n"
          " our_id_addr         : %02x:%02x:%02x:%02x:%02x:%02x\n"
          " peer_ota_addr_type  : %d\n"
          " peer_ota_addr       : %02x:%02x:%02x:%02x:%02x:%02x\n"
          " peer_id_addr_type   : %d\n"
          " peer_id_addr        : %02x:%02x:%02x:%02x:%02x:%02x\n"
          " conn_itvl           : %d\n"
          " conn_latency        : %d\n"
          " supervision_timeout : %d\n"
          " encrypted           : %d\n"
          " authenticated       : %d\n"
          " bonded              : %d\n",
          desc.conn_handle,
          desc.our_ota_addr.type,
          MAC2STR_REV(desc.our_ota_addr.val),
          desc.our_id_addr.type,
          MAC2STR_REV(desc.our_id_addr.val),
          desc.peer_ota_addr.type,
          MAC2STR_REV(desc.peer_ota_addr.val),
          desc.peer_id_addr.type,
          MAC2STR_REV(desc.peer_id_addr.val),
          desc.conn_itvl, desc.conn_latency,
          desc.supervision_timeout,
          desc.sec_state.encrypted,
          desc.sec_state.authenticated,
          desc.sec_state.bonded);

          my_device.handle    = desc.conn_handle;
          my_device.connected = true;

          memset(my_device.notify, 0, sizeof(my_device.notify)); 
          memset(my_device.indicate, 0, sizeof(my_device.indicate)); 
          memset(my_device.keycode, 0, HID_REPORT_SIZE_KEYBOARD_IN);
          my_device.protocol_mode  = HID_PROTOCOL_MODE_REPORT;
          my_device.control_point  = HID_CTRL_POINT_NOP;

      }else{
        MODLOG_DFLT(INFO, "connection is failed; status=%d ", event->connect.status);
        ble_advertise();
      }
      break;

    case BLE_GAP_EVENT_DISCONNECT:
      // 切断時
      MODLOG_DFLT(INFO, "Disconnect : 0x%04x ", event->disconnect.reason);
      //https://mynewt.apache.org/latest/network/ble_hs/ble_hs_return_codes.html
      my_device.connected = false;
      ble_advertise();
      break;


    case BLE_GAP_EVENT_CONN_UPDATE_REQ:
      // セントラルから接続パラメタの更新を要求された
      MODLOG_DFLT(INFO, "Connection update request");
      break;

    case BLE_GAP_EVENT_CONN_UPDATE:
      // セントラルが接続パラメタを更新した
      MODLOG_DFLT(INFO, "Connection updated : %d ", event->conn_update.status);
      break;

    case BLE_GAP_EVENT_ADV_COMPLETE:
      // アドバタイジングが完了
      MODLOG_DFLT(INFO, "Advertise complete : %d", event->adv_complete.reason);
      ble_advertise();
      break;

    case BLE_GAP_EVENT_ENC_CHANGE:
      // 今回の接続で暗号化が有効/無効された。
      MODLOG_DFLT(INFO, "Encryption is changed : %d ",event->enc_change.status);
      break;

    case BLE_GAP_EVENT_SUBSCRIBE:
      MODLOG_DFLT(INFO,
        "Subscribe event\n"
        " conn_handle   : %d\n"
        " attr_handle   : %04X\n"
        " reason        : %d\n"
        " prev_notify   : %d\n"
        " cur_notify    : %d\n"
        " prev_indicate : %d\n"
        " cur_indicate  : %d\n",
        event->subscribe.conn_handle,
        event->subscribe.attr_handle,
        event->subscribe.reason,
        event->subscribe.prev_notify,
        event->subscribe.cur_notify,
        event->subscribe.prev_indicate,
        event->subscribe.cur_indicate);

      for(i=0;i<HANDLE_NUM;i++){
        if(handles_list[i] == event->subscribe.attr_handle){
          my_device.notify[i] = true;
          MODLOG_DFLT(INFO,"notify index : %d", i);
          break;
        }
      }
      break;

    case BLE_GAP_EVENT_NOTIFY_TX:
      MODLOG_DFLT(INFO,
        "Notify event\n"
        " status      : %d\n"
        " conn_handle : %d\n"
        " attr_handle : %04X\n"
        " type        : %s\n",
        event->notify_tx.status,
        event->notify_tx.conn_handle,
        event->notify_tx.attr_handle,
        event->notify_tx.indication?"indicate":"notify");
      break;

    case BLE_GAP_EVENT_MTU:
      // Maximum Transmission Unit 送信パケットの上限サイズの変更
      MODLOG_DFLT(INFO,
        "MTU update event.\n"
        " conn_handle : %d\n"
        " cid         : %d\n"
        " mtu         : %d\n",
        event->mtu.conn_handle,
        event->mtu.channel_id,
        event->mtu.value);
      break;

    case BLE_GAP_EVENT_REPEAT_PAIRING:
        MODLOG_DFLT(INFO, "BLE_GAP_EVENT_REPEAT_PAIRING");
      // すでにボンド済み(ペアリングで交換した鍵を保存済み)であったが、新しくセキュアリンクの確立が試みられた。
      // 古いボンドは削除し、新しいリンクを受け入れる

      // 古いボンドを削除
      rc = ble_gap_conn_find(event->repeat_pairing.conn_handle, &desc);
      assert(rc == 0);
      ble_store_util_delete_peer(&desc.peer_id_addr);

      // ホストにペアリングを続行するように通知
      return BLE_GAP_REPEAT_PAIRING_RETRY;

    case BLE_GAP_EVENT_PASSKEY_ACTION:
        MODLOG_DFLT(INFO, "PASSKEY_ACTION_EVENT started");

        if(event->passkey.params.action == BLE_SM_IOACT_DISP){
            MODLOG_DFLT(INFO, "Enter passkey on the peer side");
            pkey.action   = event->passkey.params.action;
            pkey.passkey  = PASS_KEY;  // ホスト側に入力してもらうパスキー
            rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey);
            MODLOG_DFLT(INFO, "ble_sm_inject_io result: %d", rc);

        }else if (event->passkey.params.action == BLE_SM_IOACT_INPUT ||
                  event->passkey.params.action == BLE_SM_IOACT_NUMCMP ||
                  event->passkey.params.action == BLE_SM_IOACT_OOB ) {
          MODLOG_DFLT(ERROR, "BLE_SM_IOACT_INPUT, BLE_SM_IOACT_NUMCMP, BLE_SM_IOACT_OOB"
            " bonding not supported!");
        }
        break;

    default:
      MODLOG_DFLT(INFO, "Unknown GAP event: %d", event->type);
      break;
  }

  return 0;
}

int user_parse(const struct ble_hs_adv_field *data, void *arg){
    MODLOG_DFLT(INFO, "Parse data: field len %d, type %x, total %d bytes",
        data->length, data->type, data->length + 2); /* first byte type and second byte length */
    return 1;
}

void ble_advertise(){
  struct ble_gap_adv_params   adv_params;
  struct ble_hs_adv_fields    fields;
  const char                  *name;
  int rc;

    /**
     *  Set the advertisement data included in our advertisements:
     *     o Flags (indicates advertisement type and other general info).
     *     o Advertising tx power.
     *     o Device name.
     *     o 16-bit service UUIDs (alert notifications).
     */

    memset(&fields, 0, sizeof fields);

    /* Advertise two flags:
     *     o Discoverability in forthcoming advertisement (general)
     *     o BLE-only (BR/EDR unsupported).
     */
    fields.flags = BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP;

    /* Indicate that the TX power level field should be included; have the
     * stack fill this value automatically.  This is done by assigning the
     * special value BLE_HS_ADV_TX_PWR_LVL_AUTO.
     */
    fields.tx_pwr_lvl_is_present = 1;
    fields.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO;

    //fields.adv_itvl_is_present = 1;
    //fields.adv_itvl = 40;

    name = ble_svc_gap_device_name();
    fields.name = (uint8_t *)name;
    fields.name_len = strlen(name);
    fields.name_is_complete = 1;

    fields.appearance = BLE_SVC_GAP_APPEARANCE;
    fields.appearance_is_present = 1;

    fields.uuids16 = (ble_uuid16_t[]) {
        BLE_UUID16_INIT(GATT_UUID_HID_SERVICE)
    };
    fields.num_uuids16 = 1;
    fields.uuids16_is_complete = 1;

    uint8_t buf[50], buf_sz;
    rc = ble_hs_adv_set_fields(&fields, buf, &buf_sz, 50);
    if (rc != 0) {
        MODLOG_DFLT(ERROR, "error setting advertisement data to buf; rc=%d", rc);
        return;
    }
    if (buf_sz > BLE_HS_ADV_MAX_SZ) {
        MODLOG_DFLT(ERROR, 
          "Too long advertising data.\n"
          " name       : %s\n"
          " appearance : %x\n"
          " uuid16     : %x\n"
          " advsize    : %d\n",
          name, 
          fields.appearance,
          GATT_UUID_HID_SERVICE,
          buf_sz);
        ble_hs_adv_parse(buf, buf_sz, user_parse, NULL);
        return;
    }

    rc = ble_gap_adv_set_fields(&fields);
    if (rc != 0) {
        MODLOG_DFLT(ERROR, "Failed to set advertisement data : %d", rc);
        return;
    }

    /* Begin advertising. */
    memset(&adv_params, 0, sizeof adv_params);
    adv_params.conn_mode = BLE_GAP_CONN_MODE_UND;
    adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN;
    rc = ble_gap_adv_start(my_device.own_addr_type, NULL, BLE_HS_FOREVER, &adv_params, ble_gap_event_callback, NULL);
    if (rc != 0) {
        MODLOG_DFLT(ERROR, "Failed to enable advertisement : %d", rc);
        return;
    }
}

void ble_on_sync(void){
  uint8_t addr[6] = {0};
  int     rc;

  //ble_hs_pvcy_rpa_config(NIMBLE_HOST_ENABLE_RPA);
  rc = ble_hs_util_ensure_addr(1);
  assert(rc == 0);

  // アドバタイジング中に使用しているアドレスを把握する
  rc = ble_hs_id_infer_auto(
          1,  // プライベートアドレスをを使うかどうか。0=使わない 1=使う
          &my_device.own_addr_type);
  if (rc != 0) {
    MODLOG_DFLT(ERROR, "Failed to determine address type : %d", rc);
    return;
  }

  // 自身のアドレスを取得表示
  rc = ble_hs_id_copy_addr(my_device.own_addr_type, addr, NULL);
  MODLOG_DFLT(INFO, "Device Address: %02x:%02x:%02x:%02x:%02x:%02x",MAC2STR_REV(addr));

  // アドバタイズ開始
  ble_advertise();
}

void ble_on_reset(int reason){
  MODLOG_DFLT(ERROR, "Resetting state : %d", reason);
}

void ble_hid_host_task(void *param){
  MODLOG_DFLT(INFO, "BLE Host Task Started.");
  nimble_port_run();
  nimble_port_freertos_deinit();
}

void gatt_register_callback(struct ble_gatt_register_ctxt *ctxt, void *arg){
  char buff[BLE_UUID_STR_LEN];

  switch (ctxt->op) {
    case BLE_GATT_REGISTER_OP_SVC:
      MODLOG_DFLT(INFO, "register service:%s handle:%d",
        ble_uuid_to_str(ctxt->svc.svc_def->uuid, buff),
        ctxt->svc.handle);
      break;

    case BLE_GATT_REGISTER_OP_CHR:
      MODLOG_DFLT(INFO, "register characteristic %s  def:%d val:%d",
        ble_uuid_to_str(ctxt->chr.chr_def->uuid, buff),
        ctxt->chr.def_handle,
        ctxt->chr.val_handle);
      break;

    case BLE_GATT_REGISTER_OP_DSC:
      MODLOG_DFLT(INFO, "register descriptor %s handle:%d",
        ble_uuid_to_str(ctxt->dsc.dsc_def->uuid, buff),
        ctxt->dsc.handle);
      break;

    default:
      assert(0);
      break;
  }
}

int ble_init_server(){
  esp_err_t   ret;
  int         rc;

  // ホストとコントローラーのスタックを初期化
  ret = nimble_port_init();
  if (ret != ESP_OK) {
    MODLOG_DFLT(ERROR, "Failed to init nimble %d", ret);
    return ret;
  }

  // NimBLEホスト設定
  ble_hs_cfg.sync_cb    = ble_on_sync;    // ホストが同期したときに呼ばれる関数(スタート時やリセット時)
  ble_hs_cfg.reset_cb   = ble_on_reset;   // 深刻なエラーが発生でリセットがかかったときに呼ばれる関数

  ble_hs_cfg.gatts_register_cb  = gatt_register_callback;     // GATTサーバーのイベントコールバック
  ble_hs_cfg.store_status_cb    = ble_store_util_status_rr;   // 不揮発性の情報を保存する際のコールバック関数

  ble_hs_cfg.sm_io_cap  = BLE_SM_IO_CAP_NO_IO; // ペアリングする際の入力や確認手段

  // セキュアボンディング
  ble_hs_cfg.sm_bonding = 1;    // ボンディング(ペアリングで交換した鍵を保存する)
  ble_hs_cfg.sm_mitm    = 0;    // 中間者攻撃保護
  ble_hs_cfg.sm_sc      = 1;

  ble_hs_cfg.sm_our_key_dist    = BLE_SM_PAIR_KEY_DIST_ENC | BLE_SM_PAIR_KEY_DIST_ID;
  ble_hs_cfg.sm_their_key_dist  = BLE_SM_PAIR_KEY_DIST_ENC | BLE_SM_PAIR_KEY_DIST_ID;

  // デバイス名を設定
  rc = ble_svc_gap_device_name_set(BLE_DEVICE_NAME);
  if(rc){
    MODLOG_DFLT(ERROR, "Failed to set device name. %d", rc);
    return rc;
  }

  // 外観
  rc = ble_svc_gap_device_appearance_set(BLE_SVC_GAP_APPEARANCE);
  if(rc){
    MODLOG_DFLT(ERROR, "Failed to set device appearance. %d", rc);
    return rc;
  }

  ble_store_config_init();

  // 
  MODLOG_DFLT(INFO, "Initialize GAT & GATT");
  ble_svc_gap_init();
  ble_svc_gatt_init();

  memset(handles_list, 0, sizeof(handles_list));

  // セカンダリーサービスを登録
  rc = ble_gatts_count_cfg(gatt_secondary_services);
  if(rc){
    return rc;
  }
  rc = ble_gatts_add_svcs(gatt_secondary_services);
  if(rc){
    return rc;
  }
  MODLOG_DFLT(INFO, "GATT secondary_services added");

  // プライマリーサービスを登録
  rc = ble_gatts_count_cfg(gatt_primary_services);
  if(rc){
    return rc;
  }

  rc = ble_gatts_add_svcs(gatt_primary_services);
  if(rc){
    return rc;
  }
  MODLOG_DFLT(INFO, "GATT primary services added");

  nimble_port_freertos_init(ble_hid_host_task);

  return 0;
}


bool ble_is_connected(){
  return my_device.connected; 
}

void send_text(const char *text){
  uint8_t  numpad[] = {0x27, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26};
  uint8_t i;

  MODLOG_DFLT(INFO, "Send key.");
  if(my_device.notify[HANDLE_HID_KB_IN_REPORT] == false){
    MODLOG_DFLT(ERROR, "Not subscribed.");
    return;
  }

  memset(my_device.keycode, 0, HID_REPORT_SIZE_KEYBOARD_IN);

  i = 0;
  while(text[i]){
    switch(text[i]){
      case '0':
        my_device.keycode[1] = numpad[0];
        break;
      case '1':
        my_device.keycode[1] = numpad[1];
        break;
      case '2':
        my_device.keycode[1] = numpad[2];
        break;
      case '3':
        my_device.keycode[1] = numpad[3];
        break;
      case '4':
        my_device.keycode[1] = numpad[4];
        break;
      case '5':
        my_device.keycode[1] = numpad[5];
        break;
      case '6':
        my_device.keycode[1] = numpad[6];
        break;
      case '7':
        my_device.keycode[1] = numpad[7];
        break;
      case '8':
        my_device.keycode[1] = numpad[8];
        break;
      case '9':
        my_device.keycode[1] = numpad[9];
        break;
      case '\n':
        my_device.keycode[1] = 0x28;
        break;
      default:
        continue;
    }
    ble_gatts_notify(my_device.handle, handles_list[HANDLE_HID_KB_IN_REPORT]);
    vTaskDelay(10/portTICK_PERIOD_MS);
    i++;
  }
  my_device.keycode[1] = 0;
  ble_gatts_notify(my_device.handle, handles_list[HANDLE_HID_KB_IN_REPORT]);

  return;
}

main.c

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "freertos/semphr.h"

#include <nvs_flash.h>

#include <esp_system.h>
#include <esp_event.h>

#include "ble_keyboard.h"

void app_main(void){
  esp_err_t   ret;

  // NVS(不揮発性メモリ)を初期化
  MODLOG_DFLT(INFO, "Initialize NVS");
  ret = nvs_flash_init();
  if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
    ESP_ERROR_CHECK(nvs_flash_erase());
    ret = nvs_flash_init();
  }
  ESP_ERROR_CHECK(ret);

  // BLEサーバーを初期化
  ble_init_server();

  while(1){
    vTaskDelay(5000/portTICK_PERIOD_MS);
    if(ble_is_connected()){
      vTaskDelay(1000/portTICK_PERIOD_MS);
      send_text("1234567890\n");
    }
  }
}

ダウンロード

コメント

タイトルとURLをコピーしました