NUCLEO BLEのアドバタイジングをM5STACKでスキャンしてみた話

![DSC02696.JPG](https://qiita-image-store.s3.amazonaws.com/0/246506/b9baa16c-e400-e61f-590c-9a0000745e76.jpeg)
# はじめに
`Nucleo`にBLE拡張ボードである`IDB05A1`を接続して、BLEが利用できる`M5STACK`と共に遊んでみました。
ネット上にある色々な参考情報を元に`Nucleo`や`他BLEデバイス`から送信されている情報を`M5STACK`側で取得し表示までは簡単に行えたのですが、表示される内容がデバイスによってまちまちでした。
その理由を探っていった内容を投稿してみます。
# BLE参考にした情報
このあたりを読んでおいていただけるとこれから先の内容は読みやすいかと思います。
https://qiita.com/moaible/items/111e2b637f3404a2de49/
https://ambidata.io/samples/m5stack/m5stack_ble_sensor/
https://sites.google.com/a/gclue.jp/ble-docs/advertising-1/advertising
https://blog.reinforce-lab.com/2013/08/13/blebook-ch2-ble-spec/
#登場するデバイスの役割
`Nucleo`を`ペリフェラル`として`アドバタイジングパケット`を送信します。
`M5STACK`を`セントラル`として、`アドバタイジングパケット`の`LocalName`と`UUID`を取得し表示します。
`LocalName` : `UUID` というように`:`を挟んで表示させます。
# アドバタイジング表示内容がペリフェラルよってまちまちなのだが…
下写真が表示内容結果です。表示パターンが様々あり？？？となりました。
`LocalName` : `UUID` どちらも表示される
`LocalName` : `UUID` どちらも表示されない
`LocalName`のみ表示される
`UUID`のみ表示される
![DSC02688.JPG](https://qiita-image-store.s3.amazonaws.com/0/246506/dc072f34-d64c-6965-7c2c-ff73def82f37.jpeg)
セントラルである`M5STACK`プログラムのバグなのか…
なお`M5STACK`側プログラムは`ESP32 BLE Arduino BLE_client`スケッチを参考に作成しています。
# セントラル(M5STACK)側プログラムを見直してみる
`M5STACK`プログラムを見直してみました。単純な表示プログラムなので問題はなさそうです。しかしあるコードに気づきました。下記がそのコードです。
3行目に`haveServiceUUID()`という関数をコールしています。どうやら`アドバタイジングパケット`中に`UUID`を所有している場合と所有していない場合がありそうです。

```c:M5Stack
    M5.Lcd.print(advertisedDevice.getName().c_str());
    M5.Lcd.print(":");
    if (advertisedDevice.haveServiceUUID() ) {
        M5.Lcd.println(advertisedDevice.getServiceUUID().toString().c_str());
    }
```
# BLEアドバダイジングに関する仕様を見てみる
この仕様を簡単にいうと、`アドバタイジングパケット`は`31バイト`を上限として`ペリフェラル名`とか`UUID`とか`送信電力`等々を`Generic Access Profile`仕様にそって格納できます。必要に応じて格納すればよい情報なので`アドバタイジングパケット`の中身は`ペリフェラル`によって異なります。
どうやら`Generic Access Profile`仕様による種別搭載有無が `ペリフェラル`によって表示内容をまちまちにする原因のようです。

+ [Generic Access Profile](https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile)

# ペリフェラル(NUCLEO)側プログラムを見直してみる
前述でアドバタイジング表示内容がまちまちになると思わしき原因はつかめたので、`Nucleo`プログラムを見直してみます。参考にしたプログラムは `stm32 STM32CubeExpansion_BLE1_4.1.0 SensorDemo`です。
`aci_gap_set_discoverable`という関数呼び出しがありますが、第７引数に`local_name`という変数名で今回表示する `LocalName`を定義してあり、既出の写真では`セントラル`側に`NucleoBLE`という名称で表示されていることが確認できます。

```c:Nucleo
void setConnectable(void)
{  
  tBleStatus ret;
  const char local_name[] = {AD_TYPE_COMPLETE_LOCAL_NAME,'N','u','c','l','e','o','B','L','E'};

  /* disable scan response */
  hci_le_set_scan_resp_data(0,NULL);
  /* disable scan response */
  ret = aci_gap_set_discoverable(ADV_IND, 0, 0, PUBLIC_ADDR, NO_WHITE_LIST_USE, sizeof(local_name), local_name, 0, NULL, 0, 0);
} 
```

# ペリフェラル(NUCLEO)側プログラムを修正してみる
さて`aci_gap_set_discoverable()`という関数仕様を見てみると第９引数は`ServiceUUIDList`となっています。どうやら第８引数に`UUID長`、第９引数に`UUID名`を設定すれば`セントラル`側に無事表示されそうです。

```c:Nucleo
void setConnectable(void)
{  
  tBleStatus ret;
  const char local_name[] = {AD_TYPE_COMPLETE_LOCAL_NAME,'N','u','c','l','e'};
  static uint8_t discoverable_uuid[17];
   
  /* disable scan response */
  hci_le_set_scan_resp_data(0,NULL);
  /* UUIDをここで設定 */
  discoverable_uuid[0] = AD_TYPE_128_BIT_SERV_UUID_CMPLT_LIST;
  uint8_t* uuid_start = &discoverable_uuid[1];
  COPY_ENV_SENS_SERVICE_UUID(uuid_start);
  /* disable scan response */
  ret = aci_gap_set_discoverable(ADV_IND, 0, 0, PUBLIC_ADDR, NO_WHITE_LIST_USE, sizeof(local_name), local_name, sizeof(discoverable_uuid), (uint8_t*)discoverable_uuid, 0, 0);
} 
```
思惑通り`Nucle:UUID`で表示されることが確認できました。
![DSC02694.JPG](https://qiita-image-store.s3.amazonaws.com/0/246506/1daec9c2-ee07-c6fc-ad35-2b56fbb7a8be.jpeg)

### 補足
第９引数が`ServiceUUIDList`という`リスト`を意味させている理由は、`短縮UUID`を複数乗せられると解釈しています。

# ペリフェラル(NUCLEO)側プログラム修正時の注意事項
前項にて思惑通り`Nucle:UUID`で表示されていることが確認できましたと書きましたが…
`LocalName`について以下の違いがあります。
修正前：`NucleoBLE`
修正後：`Nucle`
## 理由 その１
`aci_gap_set_discoverable()`関数内で`if ((LocalNameLen+ServiceUUIDLen+14) > sizeof(buffer))`と制限されています。またこの判定文は`アドバタイジングパケット`の`31バイト`制限とは異なることも注意してください。

```c:0バイト目にはデータ種別が入るため、各名称+1バイトのサイズとなることを理解しておくこと
local_name[10] = {AD_TYPE_COMPLETE_LOCAL_NAME,'N','u','c','l','e','o','B','L','E'};
discoverable_uuid[17] = {'AD_TYPE_128_BIT_SERV_UUID_CMPLT_LIST','U','U'/*略*/,'I','D'};
```
`10 + 17 + 14 = 41`となり`aci_gap_set_discoverable()`関数は失敗します。

```c:Nucleo
tBleStatus aci_gap_set_discoverable(uint8_t AdvType, uint16_t AdvIntervMin, uint16_t AdvIntervMax,uint8_t OwnAddrType, uint8_t AdvFilterPolicy, uint8_t LocalNameLen,const char *LocalName, uint8_t ServiceUUIDLen, uint8_t* ServiceUUIDList,uint16_t SlaveConnIntervMin, uint16_t SlaveConnIntervMax)
{
  struct hci_request rq;
  uint8_t status;    
  uint8_t buffer[40];
  uint8_t indx = 0;
  
  if ((LocalNameLen+ServiceUUIDLen+14) > sizeof(buffer))
    return BLE_STATUS_INVALID_PARAMS;
  // 以下略
```
## 理由 その２
`アドバタイジングパケット`の最大データ長は`31バイト`です。
`UUID`は128ビットUUIDを用いているため、データ長とデータ種別を含めた合計18バイトを必要とします。`1(データ長) + 1(データ種別) + 16(UUID) = 18`
そのため残り`アドバタイジングパケット`は13バイト使えることになりますが、`LocalName`も`UUID`同様にデータ長とデータ種別が必要になります。そのため`LocalName`として使用できるのは11バイトとなります。`31 - 18(UUID) - 1(データ長) - 1データ種別 = 11(LocalName)`
しかしながら、`Nucle`まで名称を短くしないと`セントラル`側で名称を表示できませんでした。`Nucle`は5バイトなので6バイト分`11 - 5 = 6` が何かに利用されていると推測しています。 
## 理由 その３
理由その２の続きになりますが、名称に5バイトしか利用できず残りの6バイトはなぜ使えないのかと疑問がわきます。理由についてはおそらく`送信電力`に関する情報が`アドバタイジングパケット`の一部として乗っかっていると推測しています。`セントラル`側で`RSSI`を取得可能なことがその根拠です。そのため`アドバタイジングパケット`としてバッファが足りなくなった場合には、`LocalName`などの後付けデータが無効になってしまうのではないかと。
現状ではどこの処理(プログラム)でその情報が付加されているか追い切れていません。また、これらは推測です。
# その他のペリフェラルに着目してみる
最後の写真にある`CASIOペリフェラル`データに着目してみると、`LocalName`が`14バイト`もあるため、`UUID`やデータ長、データ種別とあわせると`アドバタイジングパケット`最大値である`31バイト`をオーバします。それが可能なからくりは`16ビット短縮UUID`を用いて送出しているためと思っています。そしてわかりやすい`ペリフェラル名`を`アドバタイジングパケット`に乗せられているのではないでしょうか。
# 最後に
`NUCLEO`と`M5STACK`という人気で遊びやすいデバイスを用いて、`BLE`の一部に触れることができ`IoT`な領域に一歩踏み出せました。同様に遊んでみてなんだかおかしいなとか思っていた方の参考になれば幸いです。また、本文中で解釈が間違っていることが多少あるかとは思いますがご容赦ください。
これからも様々な`BLE`デバイスのパケットをのぞいてみたり、デバイス同士でお話ができるスキルを得ていきたいです。