Raspberry Pi 3でD-BusからBLEデバイスにアクセスする

 Raspberry PiからBLEデバイスへの接続についていろいろ調べている中でD-Busについても調べたので、D-BusからBlueZを使ってBLEデバイスにアクセスしてみました。D-Busについては下記サイトでわかりやすく解説されていて、とても参考になりました。

www.silex.jp

 D-Busについての詳細は上記サイトを参照いただくとしてここでは割愛しますが、すごくざっくり言うと、コンピュータ上の複数のプログラム間で情報のやり取りをするためのIPC(Inter Process Communication)で、オブジェクト(プログラム)間でメッセージ(データ)を届けるためのものです。BlueZもD-Busのサービスの一つとして登録されるので、D-BusからBlueZを使用してBLEデバイスに接続することができます。

 また、下記資料も参考にさせていただきました。

Bluetooth on modern Linux
http://events.linuxfoundation.org/sites/events/files/slides/Bluetooth%20on%20Modern%20Linux_0.pdf

 接続対象のBLEデバイスとしては、LightBlueというアプリでiPhoneを擬似BLEデバイスとして使用してみました。

LightBlue Explorer - Bluetooth Low Energy

LightBlue Explorer - Bluetooth Low Energy

  • Punch Through
  • ユーティリティ
  • 無料

 いろいろな種類の擬似Peripheralを作成できるので、今回はHeart Rateを作成して使用してみます。Body Sensor Locationキャラクタリスティックを読み書きすることを目標とします。

f:id:akanuma-hiroaki:20170610095856p:plain:w300 f:id:akanuma-hiroaki:20170610095902p:plain:w300

コマンドラインからD-Busを操作する

 Linux上でD-Busを操作するには dbus-send というコマンドを使います。

dbus-send
https://dbus.freedesktop.org/doc/dbus-send.1.html

 まずはD-Busにどんなサービスが登録されているかを確認してみます。

pi@raspberrypi:~ $ sudo dbus-send --print-reply --system --dest=org.freedesktop.DBus / --type=method_call org.freedesktop.DBus.ListNames                                                                                                      
method return sender=org.freedesktop.DBus -> dest=:1.6 reply_serial=2
   array [
      string "org.freedesktop.DBus"
      string ":1.3"
      string "org.freedesktop.login1"
      string "org.freedesktop.systemd1"
      string "org.freedesktop.Avahi"
      string ":1.0"
      string ":1.5"
      string "org.bluez"
      string ":1.1"
      string ":1.6"
      string ":1.2"
   ]

 サービス名は慣習としてサービスの開発元のドメイン名が使われます。サービス登録時にはサービス名を明示しなくても登録できますが、その場合には上記の string ":1.3" のようにD-Busサーバが適当に生成した数字が使われます。

 上記の結果の中の string "org.bluez" がBlueZのサービス名になります。BlueZのサービスが持つオブジェクトのリストを表示してみます。 --dest オプションで対象のサービスを指定し、引数として対象の階層を指定します。今回はサービス配下のトップレベルのオブジェクトのリストを表示するために引数として / を指定します。また、オブジェクトのリストを表示するためのメソッドは org.freedesktop.DBus.Introspectable.Introspect ですので、 --type オプションで method_call を指定し、引数にメソッド名を渡します。

pi@raspberrypi:~ $ sudo dbus-send --print-reply --system --dest=org.bluez / --type=method_call org.freedesktop.DBus.Introspectable.Introspect
method return sender=:1.3 -> dest=:1.7 reply_serial=2
   string "<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node><interface name="org.freedesktop.DBus.Introspectable"><method name="Introspect"><arg name="xml" type="s" direction="out"/>
</method></interface><interface name="org.freedesktop.DBus.ObjectManager"><method name="GetManagedObjects"><arg name="objects" type="a{oa{sa{sv}}}" direction="out"/>
</method><signal name="InterfacesAdded"><arg name="object" type="o"/>
<arg name="interfaces" type="a{sa{sv}}"/>
</signal>
<signal name="InterfacesRemoved"><arg name="object" type="o"/>
<arg name="interfaces" type="as"/>
</signal>
</interface><node name="org"/></node>"

 上記のように結果はXMLで出力されます。 <node name="org"/> とあることから、トップレベルのパスの配下に org というパスがあることがわかりますので、そこを掘り下げてみます。 --dest オプションの引数に /org を指定します。

pi@raspberrypi:~ $ sudo dbus-send --print-reply --system --dest=org.bluez /org --type=method_call org.freedesktop.DBus.Introspectable.Introspect                                                                                              
method return sender=:1.3 -> dest=:1.8 reply_serial=2
   string "<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
  <node name="bluez"/>
</node>
"

 するとさらに <node name="bluez"/> となって /org/bluez というパスがあることがわかるのでさらに掘り下げます。

pi@raspberrypi:~ $ sudo dbus-send --print-reply --system --dest=org.bluez /org/bluez --type=method_call org.freedesktop.DBus.Introspectable.Introspect                                                                                        
method return sender=:1.3 -> dest=:1.9 reply_serial=2
   string "<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node><interface name="org.freedesktop.DBus.Introspectable"><method name="Introspect"><arg name="xml" type="s" direction="out"/>
</method></interface><interface name="org.bluez.AgentManager1"><method name="RegisterAgent"><arg name="agent" type="o" direction="in"/>
<arg name="capability" type="s" direction="in"/>
</method><method name="UnregisterAgent"><arg name="agent" type="o" direction="in"/>
</method><method name="RequestDefaultAgent"><arg name="agent" type="o" direction="in"/>
</method></interface><interface name="org.bluez.ProfileManager1"><method name="RegisterProfile"><arg name="profile" type="o" direction="in"/>
<arg name="UUID" type="s" direction="in"/>
<arg name="options" type="a{sv}" direction="in"/>
</method><method name="UnregisterProfile"><arg name="profile" type="o" direction="in"/>
</method></interface><node name="hci0"/></node>"

 さらに <node name="hci0"/> となって、 /org/bluez/hci0 というパスがあることがわかります。 hci0 はBluetoothアダプタになりますので、複数のBluetoothアダプタがある場合は末尾の数字が連番になっていきます。さらに掘り下げてこのBluetoothアダプタが持っているオブジェクトのリストを表示します。

pi@raspberrypi:~ $ sudo dbus-send --print-reply --system --dest=org.bluez /org/bluez/hci0 --type=method_call org.freedesktop.DBus.Introspectable.Introspect                                                                                   
method return sender=:1.3 -> dest=:1.10 reply_serial=2
   string "<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node><interface name="org.freedesktop.DBus.Introspectable"><method name="Introspect"><arg name="xml" type="s" direction="out"/>
</method></interface><interface name="org.bluez.Adapter1"><method name="StartDiscovery"></method><method name="SetDiscoveryFilter"><arg name="properties" type="a{sv}" direction="in"/>
</method><method name="StopDiscovery"></method><method name="RemoveDevice"><arg name="device" type="o" direction="in"/>
</method><property name="Address" type="s" access="read"></property><property name="Name" type="s" access="read"></property><property name="Alias" type="s" access="readwrite"></property><property name="Class" type="u" access="read"></prop
erty><property name="Powered" type="b" access="readwrite"></property><property name="Discoverable" type="b" access="readwrite"></property><property name="DiscoverableTimeout" type="u" access="readwrite"></property><property name="Pairable
" type="b" access="readwrite"></property><property name="PairableTimeout" type="u" access="readwrite"></property><property name="Discovering" type="b" access="read"></property><property name="UUIDs" type="as" access="read"></property><pro
perty name="Modalias" type="s" access="read"></property></interface><interface name="org.freedesktop.DBus.Properties"><method name="Get"><arg name="interface" type="s" direction="in"/>
<arg name="name" type="s" direction="in"/>
<arg name="value" type="v" direction="out"/>
</method><method name="Set"><arg name="interface" type="s" direction="in"/>
<arg name="name" type="s" direction="in"/>
<arg name="value" type="v" direction="in"/>
</method><method name="GetAll"><arg name="interface" type="s" direction="in"/>
<arg name="properties" type="a{sv}" direction="out"/>
</method><signal name="PropertiesChanged"><arg name="interface" type="s"/>
<arg name="changed_properties" type="a{sv}"/>
<arg name="invalidated_properties" type="as"/>
</signal>
</interface><interface name="org.bluez.GattManager1"><method name="RegisterApplication"><arg name="application" type="o" direction="in"/>
<arg name="options" type="a{sv}" direction="in"/>
</method><method name="UnregisterApplication"><arg name="application" type="o" direction="in"/>
</method></interface><interface name="org.bluez.Media1"><method name="RegisterEndpoint"><arg name="endpoint" type="o" direction="in"/>
<arg name="properties" type="a{sv}" direction="in"/>
</method><method name="UnregisterEndpoint"><arg name="endpoint" type="o" direction="in"/>
</method><method name="RegisterPlayer"><arg name="player" type="o" direction="in"/>
<arg name="properties" type="a{sv}" direction="in"/>
</method><method name="UnregisterPlayer"><arg name="player" type="o" direction="in"/>
</method></interface><interface name="org.bluez.NetworkServer1"><method name="Register"><arg name="uuid" type="s" direction="in"/>
<arg name="bridge" type="s" direction="in"/>
</method><method name="Unregister"><arg name="uuid" type="s" direction="in"/>
</method></interface><node name="dev_49_25_2A_9A_90_44"/><node name="dev_43_19_24_21_00_5F"/><node name="dev_88_4A_EA_8A_3F_2B"/><node name="dev_FC_E9_98_21_23_B7"/></node>"

  <node name="dev_49_25_2A_9A_90_44"/> のようにこのBluetoothアダプタに検知されているBLEデバイスが表示されています。デバイスの情報を見る前にこのBluetoothアダプタのプロパティの一覧を表示してみます。プロパティ表示用のメソッドは org.freedesktop.DBus.Properties.GetAll で、引数にBlueZのインタフェース名を指定します。先ほどのIntrospectの結果に <interface name="org.bluez.Adapter1"> という内容が含まれていて、 org.bluez.Adapter1 というインタフェースがあることがわかりますので、これを指定します。

pi@raspberrypi:~ $ sudo dbus-send --print-reply --system --dest=org.bluez /org/bluez/hci0 --type=method_call org.freedesktop.DBus.Properties.GetAll string:org.bluez.Adapter1
method return sender=:1.3 -> dest=:1.11 reply_serial=2
   array [
      dict entry(
         string "Address"
         variant             string "B8:27:EB:19:76:07"
      )
      dict entry(
         string "Name"
         variant             string "raspberrypi"
      )
      dict entry(
         string "Alias"
         variant             string "raspberrypi"
      )
      dict entry(
         string "Class"
         variant             uint32 0
      )
      dict entry(
         string "Powered"
         variant             boolean true
      )
      dict entry(
         string "Discoverable"
         variant             boolean false
      )
      dict entry(
         string "DiscoverableTimeout"
         variant             uint32 180
      )
      dict entry(
         string "Pairable"
         variant             boolean true
      )
      dict entry(
         string "PairableTimeout"
         variant             uint32 0
      )
      dict entry(
         string "Discovering"
         variant             boolean false
      )
      dict entry(
         string "UUIDs"
         variant             array [
               string "00001801-0000-1000-8000-00805f9b34fb"
               string "0000110e-0000-1000-8000-00805f9b34fb"
               string "00001200-0000-1000-8000-00805f9b34fb"
               string "00001800-0000-1000-8000-00805f9b34fb"
               string "0000110c-0000-1000-8000-00805f9b34fb"
            ]
      )
      dict entry(
         string "Modalias"
         variant             string "usb:v1D6Bp0246d052D"
      )
   ]

 MACアドレスや、デバイスのスキャンを行なっているか(Discovering)といった情報が確認できます。

 それではhci0配下のデバイスの情報を見てみます。 --dest オプションの引数のパスにデバイスを追加します。

pi@raspberrypi:~ $ sudo dbus-send --print-reply --system --dest=org.bluez /org/bluez/hci0/dev_49_25_2A_9A_90_44 --type=method_call org.freedesktop.DBus.Introspectable.Introspect
method return sender=:1.3 -> dest=:1.9 reply_serial=2
   string "<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node><interface name="org.freedesktop.DBus.Introspectable"><method name="Introspect"><arg name="xml" type="s" direction="out"/>
</method></interface><interface name="org.bluez.Device1"><method name="Disconnect"></method><method name="Connect"></method><method name="ConnectProfile"><arg name="UUID" type="s" direction="in"/>
</method><method name="DisconnectProfile"><arg name="UUID" type="s" direction="in"/>
</method><method name="Pair"></method><method name="CancelPairing"></method><property name="Address" type="s" access="read"></property><property name="Name" type="s" access="read"></property><property name="Alias" type="s" access="readwrite"></property><property name="Class" type="u" access="read"></property><property name="Appearance" type="q" access="read"></property><property name="Icon" type="s" access="read"></property><property name="Paired" type="b" access="read"></property><property name="Trusted" type="b" access="readwrite"></property><property name="Blocked" type="b" access="readwrite"></property><property name="LegacyPairing" type="b" access="read"></property><property name="RSSI" type="n" access="read"></property><property name="Connected" type="b" access="read"></property><property name="UUIDs" type="as" access="read"></property><property name="Modalias" type="s" access="read"></property><property name="Adapter" type="o" access="read"></property><property name="ManufacturerData" type="a{qv}" access="read"></property><property name="ServiceData" type="a{sv}" access="read"></property><property name="TxPower" type="n" access="read"></property><property name="ServicesResolved" type="b" access="read"></property></interface><interface name="org.freedesktop.DBus.Properties"><method name="Get"><arg name="interface" type="s" direction="in"/>
<arg name="name" type="s" direction="in"/>
<arg name="value" type="v" direction="out"/>
</method><method name="Set"><arg name="interface" type="s" direction="in"/>
<arg name="name" type="s" direction="in"/>
<arg name="value" type="v" direction="in"/>
</method><method name="GetAll"><arg name="interface" type="s" direction="in"/>
<arg name="properties" type="a{sv}" direction="out"/>
</method><signal name="PropertiesChanged"><arg name="interface" type="s"/>
<arg name="changed_properties" type="a{sv}"/>
<arg name="invalidated_properties" type="as"/>
</signal>
</interface></node>"

 そしてこのデバイスに接続します。

pi@raspberrypi:~ $ sudo dbus-send --print-reply --system --dest=org.bluez /org/bluez/hci0/dev_49_25_2A_9A_90_44 --type=method_call org.bluez.Device1.Connect
method return sender=:1.3 -> dest=:1.14 reply_serial=2

 そして再度オブジェクトのリストを表示してみます。

pi@raspberrypi:~ $ sudo dbus-send --print-reply --system --dest=org.bluez /org/bluez/hci0/dev_49_25_2A_9A_90_44 --type=method_call org.freedesktop.DBus.Introspectable.Introspect
method return sender=:1.3 -> dest=:1.16 reply_serial=2
   string "<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node><interface name="org.freedesktop.DBus.Introspectable"><method name="Introspect"><arg name="xml" type="s" direction="out"/>
</method></interface><interface name="org.bluez.Device1"><method name="Disconnect"></method><method name="Connect"></method><method name="ConnectProfile"><arg name="UUID" type="s" direction="in"/>
</method><method name="DisconnectProfile"><arg name="UUID" type="s" direction="in"/>
</method><method name="Pair"></method><method name="CancelPairing"></method><property name="Address" type="s" access="read"></property><property name="Name" type="s" access="read"></property><property name="Alias" type="s" access="readwrite"></property><property name="Class" type="u" access="read"></property><property name="Appearance" type="q" access="read"></property><property name="Icon" type="s" access="read"></property><property name="Paired" type="b" access="read"></property><property name="Trusted" type="b" access="readwrite"></property><property name="Blocked" type="b" access="readwrite"></property><property name="LegacyPairing" type="b" access="read"></property><property name="RSSI" type="n" access="read"></property><property name="Connected" type="b" access="read"></property><property name="UUIDs" type="as" access="read"></property><property name="Modalias" type="s" access="read"></property><property name="Adapter" type="o" access="read"></property><property name="ManufacturerData" type="a{qv}" access="read"></property><property name="ServiceData" type="a{sv}" access="read"></property><property name="TxPower" type="n" access="read"></property><property name="ServicesResolved" type="b" access="read"></property></interface><interface name="org.freedesktop.DBus.Properties"><method name="Get"><arg name="interface" type="s" direction="in"/>
<arg name="name" type="s" direction="in"/>
<arg name="value" type="v" direction="out"/>
</method><method name="Set"><arg name="interface" type="s" direction="in"/>
<arg name="name" type="s" direction="in"/>
<arg name="value" type="v" direction="in"/>
</method><method name="GetAll"><arg name="interface" type="s" direction="in"/>
<arg name="properties" type="a{sv}" direction="out"/>
</method><signal name="PropertiesChanged"><arg name="interface" type="s"/>
<arg name="changed_properties" type="a{sv}"/>
<arg name="invalidated_properties" type="as"/>
</signal>
</interface><node name="service0006"/><node name="service000a"/><node name="service000e"/><node name="service0014"/><node name="service0019"/><node name="service0023"/><node name="service002f"/><node name="service0034"/><node name="service0039"/><node name="service004c"/></node>"

 接続前は表示されていなかった、 <node name="service0006"/> などのBLEサービスの情報が表示されているのがわかります。また、 org.bluez.Device1 というインタフェースがあることがわかります。

 デバイスのプロパティも確認してみます。

pi@raspberrypi:~ $ sudo dbus-send --print-reply --system --dest=org.bluez /org/bluez/hci0/dev_49_25_2A_9A_90_44 --type=method_call org.freedesktop.DBus.Properties.GetAll string:org.bluez.Device1
method return sender=:1.3 -> dest=:1.27 reply_serial=2
   array [
      dict entry(
         string "Address"
         variant             string "49:25:2A:9A:90:44"
      )
      dict entry(
         string "Name"
         variant             string "iPhone"
      )
      dict entry(
         string "Alias"
         variant             string "iPhone"
      )
      dict entry(
         string "Appearance"
         variant             uint16 64
      )
      dict entry(
         string "Icon"
         variant             string "phone"
      )
      dict entry(
         string "Paired"
         variant             boolean false
      )
      dict entry(
         string "Trusted"
         variant             boolean false
      )
      dict entry(
         string "Blocked"
         variant             boolean false
      )
      dict entry(
         string "LegacyPairing"
         variant             boolean false
      )
      dict entry(
         string "RSSI"
         variant             int16 -44
      )
      dict entry(
         string "Connected"
         variant             boolean true
      )
      dict entry(
         string "UUIDs"
         variant             array [
               string "00001800-0000-1000-8000-00805f9b34fb"
               string "00001801-0000-1000-8000-00805f9b34fb"
               string "00001805-0000-1000-8000-00805f9b34fb"
               string "0000180a-0000-1000-8000-00805f9b34fb"
               string "0000180d-0000-1000-8000-00805f9b34fb"
               string "0000180f-0000-1000-8000-00805f9b34fb"
               string "7905f431-b5ce-4e99-a40f-4b1e122d00d0"
               string "89d3502b-0f36-433a-8ef4-c502ad55f8dc"
               string "9fa480e0-4967-4542-9390-d343dc5d04ae"
               string "d0611e78-bbb4-4591-a5f8-487910ae4366"
            ]
      )
      dict entry(
         string "Adapter"
         variant             object path "/org/bluez/hci0"
      )
      dict entry(
         string "ServicesResolved"
         variant             boolean true
      )
   ]

 MACアドレスやデバイス名などが確認できます。

 続けてデバイス配下のBLEサービスの情報を参照してみます。

pi@raspberrypi:~ $ sudo dbus-send --print-reply --system --dest=org.bluez /org/bluez/hci0/dev_49_25_2A_9A_90_44/service004c --type=method_call org.freedesktop.DBus.Introspectable.Introspect
method return sender=:1.3 -> dest=:1.43 reply_serial=2
   string "<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node><interface name="org.freedesktop.DBus.Introspectable"><method name="Introspect"><arg name="xml" type="s" direction="out"/>
</method></interface><interface name="org.bluez.GattService1"><property name="UUID" type="s" access="read"></property><property name="Device" type="o" access="read"></property><property name="Primary" type="b" access="read"></property><property name="Includes" type="ao" access="read"></property></interface><interface name="org.freedesktop.DBus.Properties"><method name="Get"><arg name="interface" type="s" direction="in"/>
<arg name="name" type="s" direction="in"/>
<arg name="value" type="v" direction="out"/>
</method><method name="Set"><arg name="interface" type="s" direction="in"/>
<arg name="name" type="s" direction="in"/>
<arg name="value" type="v" direction="in"/>
</method><method name="GetAll"><arg name="interface" type="s" direction="in"/>
<arg name="properties" type="a{sv}" direction="out"/>
</method><signal name="PropertiesChanged"><arg name="interface" type="s"/>
<arg name="changed_properties" type="a{sv}"/>
<arg name="invalidated_properties" type="as"/>
</signal>
</interface><node name="char004d"/><node name="char0050"/><node name="char0052"/></node>"

 string:org.bluez.GattService1 というインタフェースがあることがわかりますのでプロパティを参照してみます。

pi@raspberrypi:~ $ sudo dbus-send --print-reply --system --dest=org.bluez /org/bluez/hci0/dev_49_25_2A_9A_90_44/service004c --type=method_call org.freedesktop.DBus.Properties.GetAll string:org.bluez.GattService1
method return sender=:1.3 -> dest=:1.44 reply_serial=2
   array [
      dict entry(
         string "UUID"
         variant             string "0000180d-0000-1000-8000-00805f9b34fb"
      )
      dict entry(
         string "Device"
         variant             object path "/org/bluez/hci0/dev_49_25_2A_9A_90_44"
      )
      dict entry(
         string "Primary"
         variant             boolean true
      )
      dict entry(
         string "Includes"
         variant             array [
            ]
      )
   ]

 さらにそのサービス配下のキャラクタリスティックを見てみます。

pi@raspberrypi:~ $ sudo dbus-send --print-reply --system --dest=org.bluez /org/bluez/hci0/dev_49_25_2A_9A_90_44/service004c/char0050 --type=method_call org.freedesktop.DBus.Introspectable.Introspect                                        
method return sender=:1.3 -> dest=:1.59 reply_serial=2
   string "<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node><interface name="org.freedesktop.DBus.Introspectable"><method name="Introspect"><arg name="xml" type="s" direction="out"/>
</method></interface><interface name="org.bluez.GattCharacteristic1"><method name="ReadValue"><arg name="options" type="a{sv}" direction="in"/>
<arg name="value" type="ay" direction="out"/>
</method><method name="WriteValue"><arg name="value" type="ay" direction="in"/>
<arg name="options" type="a{sv}" direction="in"/>
</method><method name="StartNotify"></method><method name="StopNotify"></method><property name="UUID" type="s" access="read"></property><property name="Service" type="o" access="read"></property><property name="Value" type="ay" access="re
ad"></property><property name="Notifying" type="b" access="read"></property><property name="Flags" type="as" access="read"></property></interface><interface name="org.freedesktop.DBus.Properties"><method name="Get"><arg name="interface" t
ype="s" direction="in"/>
<arg name="name" type="s" direction="in"/>
<arg name="value" type="v" direction="out"/>
</method><method name="Set"><arg name="interface" type="s" direction="in"/>
<arg name="name" type="s" direction="in"/>
<arg name="value" type="v" direction="in"/>
</method><method name="GetAll"><arg name="interface" type="s" direction="in"/>
<arg name="properties" type="a{sv}" direction="out"/>
</method><signal name="PropertiesChanged"><arg name="interface" type="s"/>
<arg name="changed_properties" type="a{sv}"/>
<arg name="invalidated_properties" type="as"/>
</signal>
</interface></node>"
pi@raspberrypi:~ $ 
pi@raspberrypi:~ $ sudo dbus-send --print-reply --system --dest=org.bluez /org/bluez/hci0/dev_49_25_2A_9A_90_44/service004c/char0050 --type=method_call org.freedesktop.DBus.Properties.GetAll string:org.bluez.GattCharacteristic1
method return sender=:1.3 -> dest=:1.58 reply_serial=2
   array [
      dict entry(
         string "UUID"
         variant             string "00002a38-0000-1000-8000-00805f9b34fb"
      )
      dict entry(
         string "Service"
         variant             object path "/org/bluez/hci0/dev_49_25_2A_9A_90_44/service004c"
      )
      dict entry(
         string "Value"
         variant             array [
            ]
      )
      dict entry(
         string "Flags"
         variant             array [
               string "read"
            ]
      )
   ]

 org.bluez.GattCharacteristic1 インタフェースには ReadValue メソッドがあり、キャラクタリスティックの値を参照するために使えます。ただ、このメソッドの引数には空の配列を渡したいのですが、 dbus-sendコマンドのドキュメントには下記のような記述があり、dbus-sendコマンドは空の配列やネストされた配列には対応してないようです。

D-Bus supports more types than these, but dbus-send currently does not. Also, dbus-send does not permit empty containers or nested containers (e.g. arrays of variants).

https://dbus.freedesktop.org/doc/dbus-send.1.html

 なので dbus-send での操作は一旦ここで諦め、RubyからD-Busを操作してみたいと思います。

RubyからD-Busを操作する

 RubyからD-Busを操作するためのgemとして、 ruby-dbus というgemが公開されているのでこちらを使用します。

github.com

 gemをインストールした上でirbを起動してrequireします。

pi@raspberrypi:~ $ sudo bundle exec irb
irb(main):001:0> require 'dbus'
=> true

 まずはD-Busのシステムバスのインスタンスを取得します。

irb(main):002:0> bus = DBus::SystemBus.instance
/home/pi/vendor/bundle/ruby/2.4.0/gems/ruby-dbus-0.13.0/lib/dbus/marshall.rb:299: warning: constant ::Fixnum is deprecated
=> #<DBus::SystemBus:0x56b32d08 @message_queue=#<DBus::MessageQueue:0x56b32cc0 @address="unix:path=/var/run/dbus/system_bus_socket", @buffer="l\x04\x01\x01\n\x00\x00\x00\x02\x00\x00\x00\x8D\x00\x00\x00\x01\x01o\x00\x15\x00\x00\x00/org/fre
edesktop/DBus\x00\x00\x00\x02\x01s\x00\x14\x00\x00\x00org.freedesktop.DBus\x00\x00\x00\x00\x03\x01s\x00\f\x00\x00\x00NameAcquired\x00\x00\x00\x00\x06\x01s\x00\x05\x00\x00\x00:1.46\x00\x00\x00\b\x01g\x00\x01s\x00\x00\a\x01s\x00\x14\x00\x00
\x00org.freedesktop.DBus\x00\x00\x00\x00\x05\x00\x00\x00:1.46\x00", @is_tcp=false, @socket=#<Socket:fd 9>, @client=#<DBus::Client:0x56b32570 @socket=#<Socket:fd 9>, @state=:Authenticated, @auth_list=[DBus::DBusCookieSHA1, DBus::Anonymous]
, @authenticator=#<DBus::External:0x56b31c70>>>, @unique_name=":1.46", @method_call_replies={}, @method_call_msgs={}, @signal_matchrules={}, @proxy=nil, @object_root=<DBus::Node {}>, @service=#<DBus::Service:0x56b17870 @name=":1.46", @bus
=#<DBus::SystemBus:0x56b32d08 ...>, @root=<DBus::Node {}>>>

 そして dbus-send の時と同様に、D-Busに登録されているサービスを確認してみます。

irb(main):003:0> bus.proxy.ListNames[0]
=> ["org.freedesktop.DBus", ":1.3", "org.freedesktop.login1", "org.freedesktop.systemd1", ":1.46", ":1.31", "org.freedesktop.Avahi", ":1.0", "org.bluez", ":1.1", ":1.2"]

 ruby-dbus でD-Busのサービスを参照するには、 service メソッドでサービス名を指定します。

irb(main):004:0> bluez = bus.service('org.bluez')
=> #<DBus::Service:0x56b0e8e8 @name="org.bluez", @bus=#<DBus::SystemBus:0x56b32d08 @message_queue=#<DBus::MessageQueue:0x56b32cc0 @address="unix:path=/var/run/dbus/system_bus_socket", @buffer="l\x04\x01\x01\n\x00\x00\x00\x02\x00\x00\x00\x
8D\x00\x00\x00\x01\x01o\x00\x15\x00\x00\x00/org/freedesktop/DBus\x00\x00\x00\x02\x01s\x00\x14\x00\x00\x00org.freedesktop.DBus\x00\x00\x00\x00\x03\x01s\x00\f\x00\x00\x00NameAcquired\x00\x00\x00\x00\x06\x01s\x00\x05\x00\x00\x00:1.46\x00\x00
\x00\b\x01g\x00\x01s\x00\x00\a\x01s\x00\x14\x00\x00\x00org.freedesktop.DBus\x00\x00\x00\x00\x05\x00\x00\x00:1.46\x00", @is_tcp=false, @socket=#<Socket:fd 9>, @client=#<DBus::Client:0x56b32570 @socket=#<Socket:fd 9>, @state=:Authenticated,
 @auth_list=[DBus::DBusCookieSHA1, DBus::Anonymous], @authenticator=#<DBus::External:0x56b31c70>>>, @unique_name=":1.46", @method_call_replies={}, @method_call_msgs={}, @signal_matchrules={}, @proxy=nil, @object_root=<DBus::Node {}>, @ser
vice=#<DBus::Service:0x56b17870 @name=":1.46", @bus=#<DBus::SystemBus:0x56b32d08 ...>, @root=<DBus::Node {}>>>, @root=<DBus::Node {}>>

 introspect メソッドでオブジェクトのリストを参照できます。

irb(main):005:0> bluez.introspect
=> #<DBus::Service:0x560d6990 @name="org.bluez", @bus=#<DBus::SystemBus:0x560f9448 @message_queue=#<DBus::MessageQueue:0x560f9388 @address="unix:path=/var/run/dbus/system_bus_socket", @buffer="", @is_tcp=false, @socket=#<Socket:fd 9>, @client=#<DBus::Client:0x560f8a58 @socket=#<Socket:fd 9>, @state=:Authenticated, @auth_list=[DBus::DBusCookieSHA1, DBus::Anonymous], @authenticator=#<DBus::External:0x560f89e0>>>, @unique_name=":1.6", @method_call_replies={36=>#<Proc:0x564acfb0@/home/pi/vendor/bundle/ruby/2.4.0/gems/ruby-dbus-0.13.0/lib/dbus/bus.rb:339>, 38=>#<Proc:0x5649f408@/home/pi/vendor/bundle/ruby/2.4.0/gems/ruby-dbus-0.13.0/lib/dbus/bus.rb:339>, 142=>#<Proc:0x55bc79b0@/home/pi/vendor/bundle/ruby/2.4.0/gems/ruby-dbus-0.13.0/lib/dbus/bus.rb:339>, 196=>#<Proc:0x56500038@/home/pi/vendor/bundle/ruby/2.4.0/gems/ruby-dbus-0.13.0/lib/dbus/bus.rb:339>, 216=>#<Proc:0x55dcebf0@/home/pi/vendor/bundle/ruby/2.4.0/gems/ruby-dbus-0.13.0/lib/dbus/bus.rb:339>, 224=>#<Proc:0x56135508@/home/pi/vendor/bundle/ruby/2.4.0/gems/ruby-dbus-0.13.0/lib/dbus/bus.rb:339>}, @method_call_msgs={36=>#<DBus::Message:0x564ad010 @message_type=1, @flags=0, @protocol=1, @body_length=0, @signature="", @serial=36, @params=[], @destination="org.bluez", @interface="org.bluez.Device1", @error_name=nil, @member="Connect", @path="/org/bluez/hci0/dev_53_B5_AD_3D_0E_8D", @reply_serial=nil, @sender=":1.6">, 38=>#<DBus::Message:0x5649f468 @message_type=1, @flags=0, @protocol=1, @body_length=0, @signature="", @serial=38, @params=[], @destination="org.bluez", @interface="org.bluez.Device1", @error_name=nil, @member="Disconnect", @path="/org/bluez/hci0/dev_53_B5_AD_3D_0E_8D", @reply_serial=nil, @sender=":1.6">, 142=>#<DBus::Message:0x55bc7a28 @message_type=1, @flags=0, @protocol=1, @body_length=0, @signature="", @serial=142, @params=[], @destination="org.bluez", @interface="org.bluez.Device1", @error_name=nil, @member="Connect", @path="/org/bluez/hci0/dev_55_6D_EC_78_2A_61", @reply_serial=nil, @sender=":1.6">, 196=>#<DBus::Message:0x565001d0 @message_type=1, @flags=0, @protocol=1, @body_length=34, @signature="s", @serial=196, @params=[["s", "org.bluez.GattCharacteristic1"]], @destination="org.bluez", @interface="org.freedesktop.DBus.Properties", @error_name=nil, @member="GetAll", @path="/org/bluez/hci0/dev_68_32_1E_76_3F_5D/service004c/char0050", @reply_serial=nil, @sender=":1.6">, 216=>#<DBus::Message:0x55dcf028 @message_type=1, @flags=0, @protocol=1, @body_length=16, @signature="aya{sv}", @serial=216, @params=[["ay", [3]], ["a{sv}", {}]], @destination="org.bluez", @interface="org.bluez.GattCharacteristic1", @error_name=nil, @member="WriteValue", @path="/org/bluez/hci0/dev_68_32_1E_76_3F_5D/service004c/char0050", @reply_serial=nil, @sender=":1.6">, 224=>#<DBus::Message:0x56135688 @message_type=1, @flags=0, @protocol=1, @body_length=16, @signature="aya{sv}", @serial=224, @params=[["ay", [3]], ["a{sv}", {}]], @destination="org.bluez", @interface="org.bluez.GattCharacteristic1", @error_name=nil, @member="WriteValue", @path="/org/bluez/hci0/dev_68_32_1E_76_3F_5D/service004c/char0050", @reply_serial=nil, @sender=":1.6">}, @signal_matchrules={}, @proxy=nil, @object_root=<DBus::Node {}>, @service=#<DBus::Service:0x560e0158 @name=":1.6", @bus=#<DBus::SystemBus:0x560f9448 ...>, @root=<DBus::Node {}>>>, @root=<DBus::Node 2b28631c {org => {bluez => 2b091c4c {hci0 => 2b0c82a0 {dev_04_05_F2_46_99_7D => 2b078efc {},dev_20_16_06_23_98_85 => 2adec438 {},dev_34_36_3B_C7_FB_E9 => 2b268f94 {},dev_43_19_24_21_00_5F => 2b0b3b64 {},dev_47_03_50_13_27_49 => 2b271ebc {},dev_55_6D_EC_78_2A_61 => 2b24713c {},dev_5A_81_CF_0B_28_3B => 2b094730 {},dev_5E_2A_AF_7F_B9_29 => 2ade5ab4 {},dev_68_32_1E_76_3F_5D => 2b264a34 {},dev_68_81_60_74_D0_31 => 2b23aa34 {},dev_69_96_A2_B0_24_B2 => 2af6fb88 {},dev_78_C3_DC_6E_59_BE => 2b275ad4 {},dev_88_4A_EA_8A_39_D2 => 2b242de0 {},dev_88_4A_EA_8A_3F_2B => 2af7da00 {},dev_FC_E9_98_21_23_B7 => 2afc0108 {}}}}}>>

 また、 root メソッドで配下のオブジェクトのツリー構造を確認することができます。

irb(main):006:0> bluez.root
=> <DBus::Node 2b28631c {org => {bluez => 2b091c4c {hci0 => 2b0c82a0 {dev_04_05_F2_46_99_7D => 2b078efc {},dev_20_16_06_23_98_85 => 2adec438 {},dev_34_36_3B_C7_FB_E9 => 2b268f94 {},dev_43_19_24_21_00_5F => 2b0b3b64 {},dev_47_03_50_13_27_49 => 2b271ebc {},dev_55_6D_EC_78_2A_61 => 2b24713c {},dev_5A_81_CF_0B_28_3B => 2b094730 {},dev_5E_2A_AF_7F_B9_29 => 2ade5ab4 {},dev_68_32_1E_76_3F_5D => 2b264a34 {},dev_68_81_60_74_D0_31 => 2b23aa34 {},dev_69_96_A2_B0_24_B2 => 2af6fb88 {},dev_78_C3_DC_6E_59_BE => 2b275ad4 {},dev_88_4A_EA_8A_39_D2 => 2b242de0 {},dev_88_4A_EA_8A_3F_2B => 2af7da00 {},dev_FC_E9_98_21_23_B7 => 2afc0108 {}}}}}>

 オブジェクトのインスタンスを取得するには object メソッドを使用します。hci0インタフェース配下のデバイスのインスタンスを取得してみます。

irb(main):007:0> device = bluez.object('/org/bluez/hci0/dev_55_6D_EC_78_2A_61')                                                                                                                                                               
=> #<DBus::ProxyObject:0x561831a8 @bus=#<DBus::SystemBus:0x560f9448 @message_queue=#<DBus::MessageQueue:0x560f9388 @address="unix:path=/var/run/dbus/system_bus_socket", @buffer="", @is_tcp=false, @socket=#<Socket:fd 9>, @client=#<DBus::Cl
ient:0x560f8a58 @socket=#<Socket:fd 9>, @state=:Authenticated, @auth_list=[DBus::DBusCookieSHA1, DBus::Anonymous], @authenticator=#<DBus::External:0x560f89e0>>>, @unique_name=":1.6", @method_call_replies={36=>#<Proc:0x564acfb0@/home/pi/ve
ndor/bundle/ruby/2.4.0/gems/ruby-dbus-0.13.0/lib/dbus/bus.rb:339>, 38=>#<Proc:0x5649f408@/home/pi/vendor/bundle/ruby/2.4.0/gems/ruby-dbus-0.13.0/lib/dbus/bus.rb:339>, 142=>#<Proc:0x55bc79b0@/home/pi/vendor/bundle/ruby/2.4.0/gems/ruby-dbus
-0.13.0/lib/dbus/bus.rb:339>}, @method_call_msgs={36=>#<DBus::Message:0x564ad010 @message_type=1, @flags=0, @protocol=1, @body_length=0, @signature="", @serial=36, @params=[], @destination="org.bluez", @interface="org.bluez.Device1", @err
or_name=nil, @member="Connect", @path="/org/bluez/hci0/dev_53_B5_AD_3D_0E_8D", @reply_serial=nil, @sender=":1.6">, 38=>#<DBus::Message:0x5649f468 @message_type=1, @flags=0, @protocol=1, @body_length=0, @signature="", @serial=38, @params=[
], @destination="org.bluez", @interface="org.bluez.Device1", @error_name=nil, @member="Disconnect", @path="/org/bluez/hci0/dev_53_B5_AD_3D_0E_8D", @reply_serial=nil, @sender=":1.6">, 142=>#<DBus::Message:0x55bc7a28 @message_type=1, @flags
=0, @protocol=1, @body_length=0, @signature="", @serial=142, @params=[], @destination="org.bluez", @interface="org.bluez.Device1", @error_name=nil, @member="Connect", @path="/org/bluez/hci0/dev_55_6D_EC_78_2A_61", @reply_serial=nil, @send
er=":1.6">}, @signal_matchrules={}, @proxy=nil, @object_root=<DBus::Node {}>, @service=#<DBus::Service:0x560e0158 @name=":1.6", @bus=#<DBus::SystemBus:0x560f9448 ...>, @root=<DBus::Node {}>>>, @destination="org.bluez", @path="/org/bluez/h
ci0/dev_68_32_1E_76_3F_5D", @introspected=false, @interfaces={}, @subnodes=[], @api=#<DBus::ApiOptions:0x55d91ee8 @proxy_method_returns_array=true>>
irb(main):008:0> 
irb(main):009:0* device.introspect
=> "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\"\n\"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n<node><interface name=\"org.freedesktop.DBus.Introspectable\"><method name=\"Introspe
ct\"><arg name=\"xml\" type=\"s\" direction=\"out\"/>\n</method></interface><interface name=\"org.bluez.Device1\"><method name=\"Disconnect\"></method><method name=\"Connect\"></method><method name=\"ConnectProfile\"><arg name=\"UUID\" ty
pe=\"s\" direction=\"in\"/>\n</method><method name=\"DisconnectProfile\"><arg name=\"UUID\" type=\"s\" direction=\"in\"/>\n</method><method name=\"Pair\"></method><method name=\"CancelPairing\"></method><property name=\"Address\" type=\"s
\" access=\"read\"></property><property name=\"Name\" type=\"s\" access=\"read\"></property><property name=\"Alias\" type=\"s\" access=\"readwrite\"></property><property name=\"Class\" type=\"u\" access=\"read\"></property><property name=
\"Appearance\" type=\"q\" access=\"read\"></property><property name=\"Icon\" type=\"s\" access=\"read\"></property><property name=\"Paired\" type=\"b\" access=\"read\"></property><property name=\"Trusted\" type=\"b\" access=\"readwrite\">
</property><property name=\"Blocked\" type=\"b\" access=\"readwrite\"></property><property name=\"LegacyPairing\" type=\"b\" access=\"read\"></property><property name=\"RSSI\" type=\"n\" access=\"read\"></property><property name=\"Connect
ed\" type=\"b\" access=\"read\"></property><property name=\"UUIDs\" type=\"as\" access=\"read\"></property><property name=\"Modalias\" type=\"s\" access=\"read\"></property><property name=\"Adapter\" type=\"o\" access=\"read\"></property>
<property name=\"ManufacturerData\" type=\"a{qv}\" access=\"read\"></property><property name=\"ServiceData\" type=\"a{sv}\" access=\"read\"></property><property name=\"TxPower\" type=\"n\" access=\"read\"></property><property name=\"Servi
cesResolved\" type=\"b\" access=\"read\"></property></interface><interface name=\"org.freedesktop.DBus.Properties\"><method name=\"Get\"><arg name=\"interface\" type=\"s\" direction=\"in\"/>\n<arg name=\"name\" type=\"s\" direction=\"in\"
/>\n<arg name=\"value\" type=\"v\" direction=\"out\"/>\n</method><method name=\"Set\"><arg name=\"interface\" type=\"s\" direction=\"in\"/>\n<arg name=\"name\" type=\"s\" direction=\"in\"/>\n<arg name=\"value\" type=\"v\" direction=\"in\"
/>\n</method><method name=\"GetAll\"><arg name=\"interface\" type=\"s\" direction=\"in\"/>\n<arg name=\"properties\" type=\"a{sv}\" direction=\"out\"/>\n</method><signal name=\"PropertiesChanged\"><arg name=\"interface\" type=\"s\"/>\n<ar
g name=\"changed_properties\" type=\"a{sv}\"/>\n<arg name=\"invalidated_properties\" type=\"as\"/>\n</signal>\n</interface></node>"

 Connectメソッドでデバイスに接続して、オブジェクトのリストを表示してみます。

irb(main):010:0* device.Connect
=> []
irb(main):011:0>
irb(main):012:0* device.introspect
=> "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\"\n\"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n<node><interface name=\"org.freedesktop.DBus.Introspectable\"><method name=\"Introspe
ct\"><arg name=\"xml\" type=\"s\" direction=\"out\"/>\n</method></interface><interface name=\"org.bluez.Device1\"><method name=\"Disconnect\"></method><method name=\"Connect\"></method><method name=\"ConnectProfile\"><arg name=\"UUID\" ty
pe=\"s\" direction=\"in\"/>\n</method><method name=\"DisconnectProfile\"><arg name=\"UUID\" type=\"s\" direction=\"in\"/>\n</method><method name=\"Pair\"></method><method name=\"CancelPairing\"></method><property name=\"Address\" type=\"s
\" access=\"read\"></property><property name=\"Name\" type=\"s\" access=\"read\"></property><property name=\"Alias\" type=\"s\" access=\"readwrite\"></property><property name=\"Class\" type=\"u\" access=\"read\"></property><property name=
\"Appearance\" type=\"q\" access=\"read\"></property><property name=\"Icon\" type=\"s\" access=\"read\"></property><property name=\"Paired\" type=\"b\" access=\"read\"></property><property name=\"Trusted\" type=\"b\" access=\"readwrite\">
</property><property name=\"Blocked\" type=\"b\" access=\"readwrite\"></property><property name=\"LegacyPairing\" type=\"b\" access=\"read\"></property><property name=\"RSSI\" type=\"n\" access=\"read\"></property><property name=\"Connect
ed\" type=\"b\" access=\"read\"></property><property name=\"UUIDs\" type=\"as\" access=\"read\"></property><property name=\"Modalias\" type=\"s\" access=\"read\"></property><property name=\"Adapter\" type=\"o\" access=\"read\"></property>
<property name=\"ManufacturerData\" type=\"a{qv}\" access=\"read\"></property><property name=\"ServiceData\" type=\"a{sv}\" access=\"read\"></property><property name=\"TxPower\" type=\"n\" access=\"read\"></property><property name=\"Servi
cesResolved\" type=\"b\" access=\"read\"></property></interface><interface name=\"org.freedesktop.DBus.Properties\"><method name=\"Get\"><arg name=\"interface\" type=\"s\" direction=\"in\"/>\n<arg name=\"name\" type=\"s\" direction=\"in\"
/>\n<arg name=\"value\" type=\"v\" direction=\"out\"/>\n</method><method name=\"Set\"><arg name=\"interface\" type=\"s\" direction=\"in\"/>\n<arg name=\"name\" type=\"s\" direction=\"in\"/>\n<arg name=\"value\" type=\"v\" direction=\"in\"
/>\n</method><method name=\"GetAll\"><arg name=\"interface\" type=\"s\" direction=\"in\"/>\n<arg name=\"properties\" type=\"a{sv}\" direction=\"out\"/>\n</method><signal name=\"PropertiesChanged\"><arg name=\"interface\" type=\"s\"/>\n<ar
g name=\"changed_properties\" type=\"a{sv}\"/>\n<arg name=\"invalidated_properties\" type=\"as\"/>\n</signal>\n</interface><node name=\"service0006\"/><node name=\"service000a\"/><node name=\"service000e\"/><node name=\"service0014\"/><no
de name=\"service0019\"/><node name=\"service0023\"/><node name=\"service002f\"/><node name=\"service0034\"/><node name=\"service0039\"/><node name=\"service004c\"/></node>"

 subnodes メソッドを使うと配下のオブジェクトのリストが参照できます。

irb(main):013:0> device.subnodes
=> ["service0006", "service000a", "service000e", "service0014", "service0019", "service0023", "service002f", "service0034", "service0039", "service004c"]

 配下のサービスとキャラクタリスティックを確認してみます。今回対象としているHeart Rateのサービスは service004c になります。

irb(main):014:0> service = bluez.object('/org/bluez/hci0/dev_55_6D_EC_78_2A_61/service004c')                                                                                                                                                  
=> #<DBus::ProxyObject:0x55ef9cd8 @bus=#<DBus::SystemBus:0x560f9448 @message_queue=#<DBus::MessageQueue:0x560f9388 @address="unix:path=/var/run/dbus/system_bus_socket", @buffer="", @is_tcp=false, @socket=#<Socket:fd 9>, @client=#<DBus::Cl
ient:0x560f8a58 @socket=#<Socket:fd 9>, @state=:Authenticated, @auth_list=[DBus::DBusCookieSHA1, DBus::Anonymous], @authenticator=#<DBus::External:0x560f89e0>>>, @unique_name=":1.6", @method_call_replies={36=>#<Proc:0x564acfb0@/home/pi/ve
ndor/bundle/ruby/2.4.0/gems/ruby-dbus-0.13.0/lib/dbus/bus.rb:339>, 38=>#<Proc:0x5649f408@/home/pi/vendor/bundle/ruby/2.4.0/gems/ruby-dbus-0.13.0/lib/dbus/bus.rb:339>}, @method_call_msgs={36=>#<DBus::Message:0x564ad010 @message_type=1, @fl
ags=0, @protocol=1, @body_length=0, @signature="", @serial=36, @params=[], @destination="org.bluez", @interface="org.bluez.Device1", @error_name=nil, @member="Connect", @path="/org/bluez/hci0/dev_53_B5_AD_3D_0E_8D", @reply_serial=nil, @se
nder=":1.6">, 38=>#<DBus::Message:0x5649f468 @message_type=1, @flags=0, @protocol=1, @body_length=0, @signature="", @serial=38, @params=[], @destination="org.bluez", @interface="org.bluez.Device1", @error_name=nil, @member="Disconnect", @
path="/org/bluez/hci0/dev_53_B5_AD_3D_0E_8D", @reply_serial=nil, @sender=":1.6">}, @signal_matchrules={}, @proxy=nil, @object_root=<DBus::Node {}>, @service=#<DBus::Service:0x560e0158 @name=":1.6", @bus=#<DBus::SystemBus:0x560f9448 ...>, 
@root=<DBus::Node {}>>>, @destination="org.bluez", @path="/org/bluez/hci0/dev_55_6D_EC_78_2A_61/service004c", @introspected=false, @interfaces={}, @subnodes=[], @api=#<DBus::ApiOptions:0x55d91ee8 @proxy_method_returns_array=true>>
irb(main):015:0> 
irb(main):016:0* service.introspect
=> "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\"\n\"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n<node><interface name=\"org.freedesktop.DBus.Introspectable\"><method name=\"Introspe
ct\"><arg name=\"xml\" type=\"s\" direction=\"out\"/>\n</method></interface><interface name=\"org.bluez.GattService1\"><property name=\"UUID\" type=\"s\" access=\"read\"></property><property name=\"Device\" type=\"o\" access=\"read\"></pr
operty><property name=\"Primary\" type=\"b\" access=\"read\"></property><property name=\"Includes\" type=\"ao\" access=\"read\"></property></interface><interface name=\"org.freedesktop.DBus.Properties\"><method name=\"Get\"><arg name=\"in
terface\" type=\"s\" direction=\"in\"/>\n<arg name=\"name\" type=\"s\" direction=\"in\"/>\n<arg name=\"value\" type=\"v\" direction=\"out\"/>\n</method><method name=\"Set\"><arg name=\"interface\" type=\"s\" direction=\"in\"/>\n<arg name=
\"name\" type=\"s\" direction=\"in\"/>\n<arg name=\"value\" type=\"v\" direction=\"in\"/>\n</method><method name=\"GetAll\"><arg name=\"interface\" type=\"s\" direction=\"in\"/>\n<arg name=\"properties\" type=\"a{sv}\" direction=\"out\"/>
\n</method><signal name=\"PropertiesChanged\"><arg name=\"interface\" type=\"s\"/>\n<arg name=\"changed_properties\" type=\"a{sv}\"/>\n<arg name=\"invalidated_properties\" type=\"as\"/>\n</signal>\n</interface><node name=\"char004d\"/><no
de name=\"char0050\"/><node name=\"char0052\"/></node>"
irb(main):017:0>
irb(main):018:0* service.subnodes
=> ["char004d", "char0050", "char0052"]

 そして Body Sensor Locationキャラクタリスティックは char0050 です。

irb(main):019:0* characteristic = bluez.object('/org/bluez/hci0/dev_55_6D_EC_78_2A_61/service004c/char0050')
=> #<DBus::ProxyObject:0x564f5418 @bus=#<DBus::SystemBus:0x560f9448 @message_queue=#<DBus::MessageQueue:0x560f9388 @address="unix:path=/var/run/dbus/system_bus_socket", @buffer="", @is_tcp=false, @socket=#<Socket:fd 9>, @client=#<DBus::Cl
ient:0x560f8a58 @socket=#<Socket:fd 9>, @state=:Authenticated, @auth_list=[DBus::DBusCookieSHA1, DBus::Anonymous], @authenticator=#<DBus::External:0x560f89e0>>>, @unique_name=":1.6", @method_call_replies={36=>#<Proc:0x564acfb0@/home/pi/ve
ndor/bundle/ruby/2.4.0/gems/ruby-dbus-0.13.0/lib/dbus/bus.rb:339>, 38=>#<Proc:0x5649f408@/home/pi/vendor/bundle/ruby/2.4.0/gems/ruby-dbus-0.13.0/lib/dbus/bus.rb:339>}, @method_call_msgs={36=>#<DBus::Message:0x564ad010 @message_type=1, @fl
ags=0, @protocol=1, @body_length=0, @signature="", @serial=36, @params=[], @destination="org.bluez", @interface="org.bluez.Device1", @error_name=nil, @member="Connect", @path="/org/bluez/hci0/dev_53_B5_AD_3D_0E_8D", @reply_serial=nil, @se
nder=":1.6">, 38=>#<DBus::Message:0x5649f468 @message_type=1, @flags=0, @protocol=1, @body_length=0, @signature="", @serial=38, @params=[], @destination="org.bluez", @interface="org.bluez.Device1", @error_name=nil, @member="Disconnect", @
path="/org/bluez/hci0/dev_53_B5_AD_3D_0E_8D", @reply_serial=nil, @sender=":1.6">}, @signal_matchrules={}, @proxy=nil, @object_root=<DBus::Node {}>, @service=#<DBus::Service:0x560e0158 @name=":1.6", @bus=#<DBus::SystemBus:0x560f9448 ...>, 
@root=<DBus::Node {}>>>, @destination="org.bluez", @path="/org/bluez/hci0/dev_55_6D_EC_78_2A_61/service004c/char0050", @introspected=false, @interfaces={}, @subnodes=[], @api=#<DBus::ApiOptions:0x55d91ee8 @proxy_method_returns_array=true>
>
irb(main):020:0> 
irb(main):021:0* characteristic.introspect                                                                                                                                                                                                    
=> "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\"\n\"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n<node><interface name=\"org.freedesktop.DBus.Introspectable\"><method name=\"Introspe
ct\"><arg name=\"xml\" type=\"s\" direction=\"out\"/>\n</method></interface><interface name=\"org.bluez.GattCharacteristic1\"><method name=\"ReadValue\"><arg name=\"options\" type=\"a{sv}\" direction=\"in\"/>\n<arg name=\"value\" type=\"a
y\" direction=\"out\"/>\n</method><method name=\"WriteValue\"><arg name=\"value\" type=\"ay\" direction=\"in\"/>\n<arg name=\"options\" type=\"a{sv}\" direction=\"in\"/>\n</method><method name=\"StartNotify\"></method><method name=\"StopN
otify\"></method><property name=\"UUID\" type=\"s\" access=\"read\"></property><property name=\"Service\" type=\"o\" access=\"read\"></property><property name=\"Value\" type=\"ay\" access=\"read\"></property><property name=\"Notifying\" t
ype=\"b\" access=\"read\"></property><property name=\"Flags\" type=\"as\" access=\"read\"></property></interface><interface name=\"org.freedesktop.DBus.Properties\"><method name=\"Get\"><arg name=\"interface\" type=\"s\" direction=\"in\"/
>\n<arg name=\"name\" type=\"s\" direction=\"in\"/>\n<arg name=\"value\" type=\"v\" direction=\"out\"/>\n</method><method name=\"Set\"><arg name=\"interface\" type=\"s\" direction=\"in\"/>\n<arg name=\"name\" type=\"s\" direction=\"in\"/>
\n<arg name=\"value\" type=\"v\" direction=\"in\"/>\n</method><method name=\"GetAll\"><arg name=\"interface\" type=\"s\" direction=\"in\"/>\n<arg name=\"properties\" type=\"a{sv}\" direction=\"out\"/>\n</method><signal name=\"PropertiesCh
anged\"><arg name=\"interface\" type=\"s\"/>\n<arg name=\"changed_properties\" type=\"a{sv}\"/>\n<arg name=\"invalidated_properties\" type=\"as\"/>\n</signal>\n</interface></node>"
irb(main):022:0> 
irb(main):023:0* characteristic.subnodes
=> []

 interfaces でオブジェクトが持っているインタフェースのリストを参照できます。

irb(main):024:0> characteristic.interfaces
=> ["org.freedesktop.DBus.Introspectable", "org.bluez.GattCharacteristic1", "org.freedesktop.DBus.Properties"]

 また、プロパティを参照するには GetAll メソッドを使用し、引数にインタフェース名を指定します。

irb(main):025:0> characteristic.GetAll('org.bluez.GattCharacteristic1')
=> [{"UUID"=>"00002a38-0000-1000-8000-00805f9b34fb", "Service"=>"/org/bluez/hci0/dev_55_6D_EC_78_2A_61/service004c", "Value"=>[], "Flags"=>["read"]}]

 そして、dbus-sendではできなかった、ReadValueメソッドを使った値の参照を実行してみます。LightBlueでBody Sensor Locationの値を 0x01 に設定しておきます。

f:id:akanuma-hiroaki:20170610100405p:plain:w300

 ReadValueメソッドで参照してみます。

irb(main):026:0> characteristic.ReadValue([])
=> [[1]]

 配列として値が参照できました。LightBlueで値を 0x02 に変更してもう一度参照してみます。

f:id:akanuma-hiroaki:20170610100721p:plain:w300

irb(main):027:0> characteristic.ReadValue([])
=> [[2]]

 今度はWriteValueメソッドで値を書き込んでみます。値は配列で指定します。また、第二引数にオプションをHashで指定しますが、今回は特にオプションはないので空のHashを渡します。

irb(main):028:0> characteristic.WriteValue([0x03], {})
=> []
irb(main):029:0> 
irb(main):030:0* characteristic.ReadValue([])                                                                                                                                                                                                 
=> [[3]]

 書き込んだ値が参照できました。LightBlue側で見ても値が変わっているのが確認できました。

f:id:akanuma-hiroaki:20170610100922p:plain:w300

 今回は単純な値の読み書きだけで、Notificationなどはまだ使えていないですが、今後Notification周りも使えるようにして、デバイスの値の変化を検知して処理を行うようなものを作ってみたいと思います。