BLE を bluepy等で扱うときの handle を gatttool で確認するには


#概要、そして、おことわり
python で BLE を扱うのに bluepy を使う場合における「handle」というものを指定しますが、これをどう (bluez の) gatttool で特定するかの理解に特化した記事です。
（やり方だけを知りたいなら別のサイトのほうが速いかもしれないです。）

secondary service と included service については考慮していません。primary service だけの機器を想定しています。
言い換えると `gatttool --char-desc` の出力結果に uuid 
・`00002801-0000-1000-8000-00805f9b34fb` (16bit表記では `0x2801`)
・`00002802-0000-1000-8000-00805f9b34fb` (同`0x2802`)
が**出てこない**場合を想定しています。出てきたらどうするの？というのは別記事かかないと級なのですが、とりあえず[他の方が書かれた記事](https://blog.reinforce-lab.com/2013/08/13/2013-08-13-blebook-ch2-ble-spec/) を紹介しておきます。

#uuid について
参考サイト: (http://jellyware.jp/kurage/bluejelly/uuid.html)
16bit の uuid と 128bit の uuid があり、「Bluetooth SIGという団体にて標準で定義されて**いる**uuid」 は 16bit の uuid として短縮が許されているそうです。
その場合、`0xHHHH` という4桁のuuid は 128bit 表記すると `0000HHHH-0000-1000-8000-00805f9b34fb` となる。5～8文字目以外は固定。（自分が今まで見た中では先頭4文字が0000であれば常にコレでした。下記の例でもそうなっています。）
以下、16進数4桁のuuid については「このuuidは～を示している」と唐突に文中に出てきますが、それは標準で決まっているものということです。

Bluetooth SIGという団体にて標準で定義されて**いない**uuid は、128bit で表記されます。
たとえば、オムロン環境センサUSB型(2JCIE-BU)の説明書では
Base uuid: `AB70XXXX-0A3A-11E8-BA89-0ED5F89F718B` となっていて、説明書の先の項目ではいちいちフルで書かず 0xXXXX と書いてあったりするのですが
`0x5000` であれば `ab705000-0a3a-11e8-ba89-0ed5f89f718b` のことであると解釈し、gatttool には長い uuid を渡すことになります。(`0x5000`は不可。ハイフン必要)

#GATT構造（例）
以下のような18個の項目、というか「アトリビュート」がある構造を例示として考えます。(例は適当に作ったもので、実在のデバイスを参考にしたものではないです。）

![image.png](https://qiita-image-store.s3.amazonaws.com/0/324273/5e54b8e2-2b51-e568-cd42-d8247dbb115a.png)

以下、注目すべきポイントを全体/サービス（グループ）/サービスに含まれるもの、の3種類に分けて説明します。

###1. 全体について
・こういうデバイスを `gatttool --primary` すると4行出てきます。各行がグループを示し、各グループの handle の範囲が解ります。
・こういうデバイスを `gatttool --char-desc` すると、18行(中身のある handle `0x0001`～`0x0012` の 18行）出てきます。各行が「アトリビュート」の情報を示します。アトリビュートには「サービス宣言」「キャラクタリスティク宣言」「キャラクタリスティクの値(Value)」「キャラクタリスティクのディスクリプタ(Descriptor)」等の種類があります。(`char-desc` はその名前にも関わらず、種類関係なしに全てのアトリビュートが表示されるようです）
・各アトリビュートはハンドルという一意の番号(16進数表記の16bit値)で特定され、また、各アトリビュートには内容(type)を示すuuid が1つあります。`char-desc` では handle と uuid のペアが18個表示されます。
・アトリビュートのuuidは、type(どういうデータを格納しているアトリビュートであるか）を示しています
　- type が16bit uuid で示されているものは標準で定義されているため、中身がわかります。
　- type が16bitでない 128bit uuid で示されるものはデバイスの説明書等の機器固有の情報を確認することで中身がわかります。
　- 同じuuid が色々な場所に出てきます。たとえば「（プライマリ）サービス宣言」は4つありますが、全て uuid `0x2800` です。（type が同じなので同じuuid）

###2. サービス宣言のアトリビュートとグループについて
・サービス宣言のアトリビュート (プライマリなら uuid `0x2800`) はグループの先頭となり、次のサービス宣言のアトリビュートが現れるまで（もしくは最後= `0xffff`まで）が1つのグループを構成します。
・グループはハンドルの範囲で示されます。（上の図では、便宜上人間向きに1～4の番号をつけましたが、そういう番号をプログラムが使うわけではないです）
・上記例では、 `0x0001-0x0009` が1つめのグループ、`0x000a`が2つめのグループ、`0x000b-0x000f` が3つめのグループ、`0x0010-0xffff`が4つめのグループ。
・グループは（プライマリサービスだけのデバイスであれば）、 `gatttool --primary` で確認できます。
・グループは必ずしもキャラクタリスティクを含むわけではないです（例でいえば、2つめのグループは含まない）	

###3. サービスに含まれるキャラクタリスティク宣言、value, descriptor について
・サービス宣言から始まるグループの中にキャラクタリスティク宣言(uuid `0x2803`) があるならば、そこから「次のキャラクタリスティク宣言またはサービスが来るまで」が当該キャラクタリスティクに関するアトリビュートになります。キャラクタリスティクは1つの value を含みます。

たとえば、キャラクタリスティク uuid `ab70500b-0a3a-11e8-ba89-0ed5f89f718b` の notification を on にしたい場合は、handle `0x0015-0x0017` がそのキャラクタリスティクの内容なので、その範囲内にある CCCD すなわち handle `0x0017` に対して
`myperiperal.writeCharacteristic(0x002e, "\x01\x00", True)` のようにしてやればOKとなります。(bluepy では descriptor に書くときもwriteCharacteristicです)



・`gatttool --characteristics` で対象デバイスの全てのキャラクタリスティク（宣言とvalue)のhandle が確認できます。
・また、キャラクタリスティクは、descriptor 含むこともあります。特に **notify や indicate が可能なキャラクタリスティクは CCCD(Client Characteristic Configuration Descriptor: uuid `0x2902`)というdescriptorを必ず含みます。**
	
#実際のデバイスにおけるGATT構造の把握:まずはグループわけを把握
コマンド実行の実例なしでここまで来てしまいましたが、ようやく実践編です。

オムロン環境センサUSB型(2JCIE-BU)の例でいきますが、このデバイスは `gatttool --char-desc` すると146行も出てしまうので、先に `gatttool --primary` を実行してグループを特定します。

```
$ gatttool -t random -b D5:16:E2:B2:xx:xx --primary
attr handle = 0x0001, end grp handle = 0x0009 uuid: 00001800-0000-1000-8000-00805f9b34fb
attr handle = 0x000a, end grp handle = 0x000a uuid: 00001801-0000-1000-8000-00805f9b34fb
attr handle = 0x000b, end grp handle = 0x001d uuid: ab705000-0a3a-11e8-ba89-0ed5f89f718b
attr handle = 0x001e, end grp handle = 0x0024 uuid: ab705200-0a3a-11e8-ba89-0ed5f89f718b
attr handle = 0x0025, end grp handle = 0x004f uuid: ab705210-0a3a-11e8-ba89-0ed5f89f718b
attr handle = 0x0050, end grp handle = 0x0056 uuid: ab705400-0a3a-11e8-ba89-0ed5f89f718b
attr handle = 0x0057, end grp handle = 0x0066 uuid: ab705010-0a3a-11e8-ba89-0ed5f89f718b
attr handle = 0x0067, end grp handle = 0x0079 uuid: ab705110-0a3a-11e8-ba89-0ed5f89f718b
attr handle = 0x007a, end grp handle = 0x0083 uuid: ab705030-0a3a-11e8-ba89-0ed5f89f718b
attr handle = 0x0084, end grp handle = 0x0087 uuid: 0000fe59-0000-1000-8000-00805f9b34fb
attr handle = 0x0088, end grp handle = 0xffff uuid: 0000180a-0000-1000-8000-00805f9b34fb
```

これが1行1グループを示していて、グループの開始ハンドル、終了ハンドル、当該グループの先頭で宣言されているサービスのuuid が解ります。最後のグループの end grp handle は `0xffff` になります。
2行目などは開始＝終了なので、キャラクタリスティクなどを含まないサービスであることが解ります。

ちょっと混乱しやすいところなのですが、各行にあるuuidは「サービスのuuid」であって、アトリビュートのuuidではありません。
`0x2800` (`00002800-0000-1000-8000-00805f9b34fb`) は「プライマリサービスの宣言をするアトリビュート」のuuid で、`--char-desc` で出てくるのはこちら、
`ab705000-0a3a-11e8-ba89-0ed5f89f718b` は「サービスのuuid（上記アトリビュートを読むと出てくる値）」で、`--primary` で出てくるのはこちら。
なので両方の結果を見比べると「一見同じハンドルに2種類の uuid があるように見える」のですが、意味合いが違います。

#実際のデバイスにおけるGATT構造の把握:グループの中身をさらに詳しく
各グループのハンドル範囲がわかったので、説明書などからお目当てのサービス（お目当てのキャラクタリスティクが含まれるサービス）の uuidを特定して、その範囲を掘り下げます。今回は例として サービスuuid `ab705000-0a3a-11e8-ba89-0ed5f89f718b` に狙いを定めて handle 範囲が `0x000b-0x001d` のグループに含まれるアトリビュートを `gatttool --char-desc` で見てみましょう。オプションの `-s` `-e` はスタートとエンドのhandle指定です。

```
$ gatttool -t random -b D5:16:E2:B2:xx:xx --char-desc -s 0x000b -e 0x001d
handle = 0x000b, uuid = 00002800-0000-1000-8000-00805f9b34fb
handle = 0x000c, uuid = 00002803-0000-1000-8000-00805f9b34fb
handle = 0x000d, uuid = ab705004-0a3a-11e8-ba89-0ed5f89f718b
handle = 0x000e, uuid = 00002803-0000-1000-8000-00805f9b34fb
handle = 0x000f, uuid = ab705005-0a3a-11e8-ba89-0ed5f89f718b
handle = 0x0010, uuid = 00002803-0000-1000-8000-00805f9b34fb
handle = 0x0011, uuid = ab705006-0a3a-11e8-ba89-0ed5f89f718b
handle = 0x0012, uuid = 00002803-0000-1000-8000-00805f9b34fb
handle = 0x0013, uuid = ab70500a-0a3a-11e8-ba89-0ed5f89f718b
handle = 0x0014, uuid = 00002902-0000-1000-8000-00805f9b34fb
handle = 0x0015, uuid = 00002803-0000-1000-8000-00805f9b34fb
handle = 0x0016, uuid = ab70500b-0a3a-11e8-ba89-0ed5f89f718b
handle = 0x0017, uuid = 00002902-0000-1000-8000-00805f9b34fb
handle = 0x0018, uuid = 00002803-0000-1000-8000-00805f9b34fb
handle = 0x0019, uuid = ab70500c-0a3a-11e8-ba89-0ed5f89f718b
handle = 0x001a, uuid = 00002902-0000-1000-8000-00805f9b34fb
handle = 0x001b, uuid = 00002803-0000-1000-8000-00805f9b34fb
handle = 0x001c, uuid = ab70500d-0a3a-11e8-ba89-0ed5f89f718b
handle = 0x001d, uuid = 00002902-0000-1000-8000-00805f9b34fb
```

はい19個のアトリビュートが出ました。これが先ほどの画像のようにサービスだったりキャラクタリスティクだったりdescriptor だったりします……が、どうやったら解るのか？って話ですよね。

これを、構造が解るように解釈する。そのために、以下を念頭におきます。
・`uuid 0x2800` は 「primary service uuid」 を持つグループ先頭とサービスを示す アトリビュートである。
・`uuid 0x2803` は 「characteristic の宣言」 (読み書き可能か等を示すproperty, キャラクタリスティクvalue の uuid などを含む)を持つアトリビュートである。
　これは次のキャラクタリスティク宣言(`0x2803`)またはサービス宣言(`0x2800`)が来るまでは当該キャラクタリスティクに関する項目が続く。(範囲指定のため表示されていませんが、handle `0x001d` の先の `0x001e` には次のグループの先頭行＝サービス宣言があります。)
・キャラクタリスティク宣言があれば、後に必ずキャラクタリスティクのvalueが1つはついてくる。どれがそうであるかは `gatttool --characteristics` で把握可能。
・uuid `0x2902` は CCCD (前述の)であることを示す。これは直近に宣言されたキャラクタリスティクに含まれるdescriptorである。
・それ以外の descriptor もあるかもしれない（ないかもしれない）

ということで、まずは `gatttool --characteristics` してみましょう。先ほどと同じハンドルの範囲指定をします。

```
$ gatttool -t random -b D5:16:E2:B2:xx:xx --characteristics -s 0x000b -e 0x001d
handle = 0x000c, char properties = 0x02, char value handle = 0x000d, uuid = ab705004-0a3a-11e8-ba89-0ed5f89f718b
handle = 0x000e, char properties = 0x08, char value handle = 0x000f, uuid = ab705005-0a3a-11e8-ba89-0ed5f89f718b
handle = 0x0010, char properties = 0x02, char value handle = 0x0011, uuid = ab705006-0a3a-11e8-ba89-0ed5f89f718b
handle = 0x0012, char properties = 0x10, char value handle = 0x0013, uuid = ab70500a-0a3a-11e8-ba89-0ed5f89f718b
handle = 0x0015, char properties = 0x10, char value handle = 0x0016, uuid = ab70500b-0a3a-11e8-ba89-0ed5f89f718b
handle = 0x0018, char properties = 0x10, char value handle = 0x0019, uuid = ab70500c-0a3a-11e8-ba89-0ed5f89f718b
handle = 0x001b, char properties = 0x10, char value handle = 0x001c, uuid = ab70500d-0a3a-11e8-ba89-0ed5f89f718b
```
左端の handle = がキャラクタリスティク宣言のハンドルです。また、char value handle = というのがキャラクタリスティクValue のハンドルです。

これに、サービス宣言の uuid(`0x2800`)、 CCCDの uuid (`0x2902`) を考慮すると、先ほどの `char-desc` の出力結果は下記のような構造であると分かります。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/324273/149165f9-cfa3-8f01-3033-b7ebceed0602.png)

未特定のハンドルがないため、「それ以外のディスクリプタ」は無いことも判明します。
(https://blog.reinforce-lab.com/2013/08/13/2013-08-13-blebook-ch2-ble-spec/ には他のディスクリプタにはどういうものがあるか載っています）

##ここまで来たら
handle を bluepy などのプログラムに活用できます。具体的には

・目当ての Value を読み書きしたいときは bluepy の read/writeCharacteristic の handle には value アトリビュートのハンドルを渡す。
・notify の on/off などをしたい時は、writeCharacteristic の handle にはそのキャラクタリスティクのCCCDのハンドルを渡す。コールバック処理は handleNotification に来た handle が value アトリビュートのハンドルになるので、何の値かを判別して処理する。

といったことが出来ます。

たとえば、キャラクタリスティク uuid `ab70500b-0a3a-11e8-ba89-0ed5f89f718b` の notification を on にしたい場合は、handle `0x0015-0x0017` がそのキャラクタリスティクの内容なので、その範囲内にある CCCD すなわち handle `0x0017` が対象になり、`myperiperal.writeCharacteristic(0x0017, "\x01\x00", True)` のようになります（bluepy では、descriptor に書くときも writeCharacteristic)。callback では handle が `0x0016` (valueのハンドル) で通知が来ます。

#参考にしたサイト
めっちゃいろいろ読みました…

https://blog.reinforce-lab.com/2013/08/13/2013-08-13-blebook-ch2-ble-spec/
https://micro.rohm.com/jp/techweb_iot/knowledge/iot02/s-iot02/04-s-iot02/3088
https://micro.rohm.com/jp/techweb_iot/knowledge/iot02/s-iot02/04-s-iot02/3662
http://jellyware.jp/kurage/bluejelly/ble_guide.html
http://jellyware.jp/kurage/bluejelly/uuid.html
http://www.silex.jp/blog/wireless/2017/06/gatt-1.html
http://yegang.hatenablog.com/entry/2014/08/09/195246
https://www.ratoc-e2estore.com/blog/bluepy_classes
https://www.ratoc-e2estore.com/blog/bluepy_peripheral