#概要、そして、おことわり
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
)
が出てこない場合を想定しています。出てきたらどうするの?というのは別記事かかないと級なのですが、とりあえず他の方が書かれた記事 を紹介しておきます。
#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個の項目、というか「アトリビュート」がある構造を例示として考えます。(例は適当に作ったもので、実在のデバイスを参考にしたものではないです。)
以下、注目すべきポイントを全体/サービス(グループ)/サービスに含まれるもの、の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
の出力結果は下記のような構造であると分かります。
未特定のハンドルがないため、「それ以外のディスクリプタ」は無いことも判明します。
(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