はじめに
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
のみ表示される
セントラルであるM5STACK
プログラムのバグなのか…
なおM5STACK
側プログラムはESP32 BLE Arduino BLE_client
スケッチを参考に作成しています。
セントラル(M5STACK)側プログラムを見直してみる
M5STACK
プログラムを見直してみました。単純な表示プログラムなので問題はなさそうです。しかしあるコードに気づきました。下記がそのコードです。
3行目にhaveServiceUUID()
という関数をコールしています。どうやらアドバタイジングパケット
中にUUID
を所有している場合と所有していない場合がありそうです。
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
仕様による種別搭載有無が ペリフェラル
によって表示内容をまちまちにする原因のようです。
ペリフェラル(NUCLEO)側プログラムを見直してみる
前述でアドバタイジング表示内容がまちまちになると思わしき原因はつかめたので、Nucleo
プログラムを見直してみます。参考にしたプログラムは stm32 STM32CubeExpansion_BLE1_4.1.0 SensorDemo
です。
aci_gap_set_discoverable
という関数呼び出しがありますが、第7引数にlocal_name
という変数名で今回表示する LocalName
を定義してあり、既出の写真ではセントラル
側にNucleoBLE
という名称で表示されていることが確認できます。
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()
という関数仕様を見てみると第9引数はServiceUUIDList
となっています。どうやら第8引数にUUID長
、第9引数にUUID名
を設定すればセントラル
側に無事表示されそうです。
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
で表示されることが確認できました。
補足
第9引数がServiceUUIDList
というリスト
を意味させている理由は、短縮UUID
を複数乗せられると解釈しています。
ペリフェラル(NUCLEO)側プログラム修正時の注意事項
前項にて思惑通りNucle:UUID
で表示されていることが確認できましたと書きましたが…
LocalName
について以下の違いがあります。
修正前:NucleoBLE
修正後:Nucle
理由 その1
aci_gap_set_discoverable()
関数内でif ((LocalNameLen+ServiceUUIDLen+14) > sizeof(buffer))
と制限されています。またこの判定文はアドバタイジングパケット
の31バイト
制限とは異なることも注意してください。
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()
関数は失敗します。
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;
// 以下略
理由 その2
アドバタイジングパケット
の最大データ長は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
が何かに利用されていると推測しています。
理由 その3
理由その2の続きになりますが、名称に5バイトしか利用できず残りの6バイトはなぜ使えないのかと疑問がわきます。理由についてはおそらく送信電力
に関する情報がアドバタイジングパケット
の一部として乗っかっていると推測しています。セントラル
側でRSSI
を取得可能なことがその根拠です。そのためアドバタイジングパケット
としてバッファが足りなくなった場合には、LocalName
などの後付けデータが無効になってしまうのではないかと。
現状ではどこの処理(プログラム)でその情報が付加されているか追い切れていません。また、これらは推測です。
その他のペリフェラルに着目してみる
最後の写真にあるCASIOペリフェラル
データに着目してみると、LocalName
が14バイト
もあるため、UUID
やデータ長、データ種別とあわせるとアドバタイジングパケット
最大値である31バイト
をオーバします。それが可能なからくりは16ビット短縮UUID
を用いて送出しているためと思っています。そしてわかりやすいペリフェラル名
をアドバタイジングパケット
に乗せられているのではないでしょうか。
最後に
NUCLEO
とM5STACK
という人気で遊びやすいデバイスを用いて、BLE
の一部に触れることができIoT
な領域に一歩踏み出せました。同様に遊んでみてなんだかおかしいなとか思っていた方の参考になれば幸いです。また、本文中で解釈が間違っていることが多少あるかとは思いますがご容赦ください。
これからも様々なBLE
デバイスのパケットをのぞいてみたり、デバイス同士でお話ができるスキルを得ていきたいです。