nRF52でBLEデバイスを開発する（５）GATT/Characteristicの追加

この記事は、[nRF52でBLEデバイスを開発する（４）サービスを追加する](https://qiita.com/jiro-aqua/items/7a23f0b760b35243c5b2)の続きです。

# はじめに
　今回はGATTとCharacteristicを追加します。

# GATTの追加
　以下の呼び出しで、UUIDを指定してサービスハンドルを取得します。

```
static uint16_t service_handle;
```

```
    ble_uuid_t            ble_uuid;

    ble_uuid.type = uuid_type;
    ble_uuid.uuid = AB_BLE_UUID_SERVICE;

    err_code = sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY, &ble_uuid, &service_handle);
    APP_ERROR_CHECK(err_code);
```
　nRF ConnectでCONNECTすると、指定のUUIDでGATTサービスが出ていることが分かります。Characteristicを追加していないので、This service is empty.となっています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89529/f2f14cf6-34d8-1369-b7ca-583d40515c3c.png)

# Characteristicの追加
　以下の呼び出しでCharacteristicを追加します。
　三つ分のUUIDとハンドルを用意します。

```
#define AB_BLE_UUID_COMMAND_CHAR  0xef70
#define AB_BLE_UUID_RESPONSE_CHAR 0xef71
#define AB_BLE_UUID_DATA_CHAR   0xef72

static ble_gatts_char_handles_t    command_char_handles;
static ble_gatts_char_handles_t    response_char_handles;
static ble_gatts_char_handles_t    data_char_handles;
```

```
    ble_add_char_params_t add_char_params;

    // Add Command characteristic.
    memset(&add_char_params, 0, sizeof(add_char_params));
    add_char_params.uuid              = AB_BLE_UUID_COMMAND_CHAR;
    add_char_params.uuid_type         = uuid_type;
    add_char_params.init_len          = sizeof(uint8_t);
    add_char_params.max_len           = AB_BLE_MAX_DATA_LEN;
    add_char_params.is_var_len        = true;
    add_char_params.char_props.write_wo_resp  = 1;

    add_char_params.write_access      = SEC_OPEN;

    err_code = characteristic_add(service_handle,
                                  &add_char_params,
                                  &command_char_handles);
    APP_ERROR_CHECK(err_code);
```
　これが下りのコマンド用。

　通知用は、パラメータが変わります。

```
    add_char_params.char_props.read   = 1;
    add_char_params.char_props.notify = 1;

    add_char_params.read_access       = SEC_OPEN;
    add_char_params.cccd_write_access = SEC_OPEN;
```
　
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89529/e346bbff-dda8-0ea4-4afe-3dbf0f6632e8.png)

　これでデータの送受信チャネルが開きました。

# データ受信のイベントハンドラ
　サンプルに入っているble_lbs.c/hを元に作り込んでいきます。
　まず受信ハンドラの割り込みレベル。

```
#define AB_BLE_OBSERVER_PRIO 2
```
　関連構造体とオブザーバー定義のためのマクロ。

```
#define AB_BLE_SERVICE_DEF(_name)                                                                 \
static ab_ble_t _name;                                                                            \
NRF_SDH_BLE_OBSERVER(_name ## _obs,                                                               \
                     AB_BLE_OBSERVER_PRIO,                                                    \
                     ab_ble_on_ble_evt, &_name)
```

 構造体。

```
typedef struct ab_ble_s
{
    uint8_t                     uuid_type;
    uint16_t                    service_handle;
    ble_gatts_char_handles_t    command_char_handles;
    ble_gatts_char_handles_t    response_char_handles;
    ble_gatts_char_handles_t    data_char_handles;
    ab_ble_command_write_handler_t command_write_handler;
    ab_ble_tx_complete_handler_t tx_complete_handler;
} ab_ble_t; 
```
　受信ハンドラと送信完了ハンドラ。

```
typedef void (*ab_ble_command_write_handler_t) (const uint8_t *commands, uint8_t length);
typedef void (*ab_ble_tx_complete_handler_t)(void);

static void on_write(ab_ble_t * p_abs, ble_evt_t const * p_ble_evt)
{
    ble_gatts_evt_write_t const * p_evt_write = &p_ble_evt->evt.gatts_evt.params.write;

    if (p_evt_write->handle == p_abs->command_char_handles.value_handle)
    {
        p_abs->command_write_handler(p_evt_write->data , p_evt_write->len );
    }
}


void ab_ble_on_ble_evt(ble_evt_t const * p_ble_evt, void * p_context)
{
    ab_ble_t * p_abs = (ab_ble_t *)p_context;

    switch (p_ble_evt->header.evt_id)
    {
        case BLE_GATTS_EVT_WRITE:
            on_write(p_abs, p_ble_evt);
            break;

        case BLE_GATTS_EVT_HVN_TX_COMPLETE:
            p_abs->tx_complete_handler();
            break;

       default:
            // No implementation needed.
            break;
    }
}

```
　これらをこの一行でm_absという変数に定義します。

```
AB_BLE_SERVICE_DEF(m_abs);
```
　この後は、m_absという変数に関連するデータを代入します。

　NRF_SDH_BLE_OBSERVER()マクロでハンドラab_ble_on_ble_evt()が関連付けられます。ここから、データ受信時にonWrite()、送信完了イベントでtx_complete_handler()に分岐します。onWriteの中では、データ送信元を識別して、さらに適切なハンドラへと分岐を行います。

```
void ble_command_write_handler(const uint8_t *commands, uint8_t length)
{
  NRF_LOG_HEXDUMP_INFO(commands, length);
}


void ble_tx_complete_handler(void)
{
  NRF_LOG_INFO("TX COMPLETED.");
}

    m_abs.command_write_handler = ble_command_write_handler;
    m_abs.tx_complete_handler = ble_tx_complete_handler;
```
　それぞれ、上記の様なハンドラを用意して受信します。

　試してみましょう。nRF-ConnectでAuqaBlueデバイスに接続し、command用のCharacteristicを開きます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89529/c5aa08ee-0b78-84db-2b4a-e81748301bbf.png)
　ここの赤枠のアイコンを押すと、書き込み画面になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89529/9d96c67c-d875-406a-885b-147cc4d67ed0.png)
　ダイアログのデフォルトはバイト配列モードなので偶数個の数値を入れて、SENDボタンを押します。nRF-Connect上で書き込めたことが分かります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89529/91a4b448-550d-e0f3-68b7-3215ae4d6a2a.png)
　SESのログ画面に以下のログが出ます。

```
<info> app: Buttonless DFU Application started.
<info> app:  12 34 56               |.4V    
```

# データの送信
　データの送信には、sd_ble_gatts_hvx()APIを使用します。
　以下の様な関数を用意しておき、

```
uint32_t ble_service_send_response(uint16_t conn_handle, ab_ble_t * p_abs, const uint8_t* data , uint16_t len )
{
    ble_gatts_hvx_params_t params;

    memset(&params, 0, sizeof(params));
    params.type   = BLE_GATT_HVX_NOTIFICATION;
    params.handle = p_abs->response_char_handles.value_handle;
    params.p_data = data;
    params.p_len  = &len;

    return sd_ble_gatts_hvx(conn_handle, &params);
}
```
　以下の様に呼び出します。ここでは、受信したデータをそのままエコーバックします。

```
void ble_command_write_handler(const uint8_t *commands, uint8_t length)
{
  NRF_LOG_HEXDUMP_INFO(commands, length);

  ble_service_send_response(m_conn_handle, &m_abs , commands, length );
}
```

　先ほどと同様にAquaBlueに接続し、response用Characteristicの赤枠部分をクリックして通知を有効にしてから、0x123456を書き込みます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89529/ac32f9c5-51d0-1c9a-6677-966dfff8ee99.png)
　エコーバックされていることが確認できます。
　SESのログは以下の様になります。

```
<info> app: Buttonless DFU Application started.
<info> app:  12 34 56               |.4V     
<info> app: TX COMPLETED.
```
　送信完了イベントが上がっていることが分かります。




　今回は以上となります。

　ソース一式はいつものようにgithub(https://github.com/jiro-aqua/nrf52-ota-example )においてあります。