send-dbus で bluez とおしゃべり

Linux の Bluetooth ドライバ bluez には、[色々なツールが付属している](https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/tools)が、対話的に使う正式な方法は bluetoothctl コマンドで、プログラム的には bluetoothd の提供する DBus API を使うのが正しい方法らしい。なので BT の動作を確認するために send-dbus を使ってみた。

まず、BT Adapter があるかどうか調べる。`dbus-send` がターミナルで dbus と対話するコマンドだが、オプションの意味は以下の通り。

* `--print-reply`: 応答を待って返す。`=literal` を付けると応答をそのまま表示するので整形したりするのに便利。`--type=method_call` が指定されたとみなされる。
* `--system`: DBus には全ユーザ共通の system bus と各ユーザごとの session bus という二つのバスがある。bluez は system bus しかないので --system を指定する。
* `--dest=org.bluez`: 接続先
* `/org/bluez`: オブジェクトパス
* `org.freedesktop.DBus.Introspectable.Introspect`: インタフェース.メンバ (メソッドやシグナルの名前)
* ` | xmllint --format -`: 読みやすく整形します。

オブジェクト指向的に考えると、接続先とオブジェクトパスの組み合わせでレシーバを指定して、インタフェース.メンバがメッセージという事になる。

```
# dbus-send --print-reply=literal --system --dest=org.bluez /org/bluez org.freedesktop.DBus.Introspectable.Introspect | xmllint --format -
<?xml version="1.0"?>
<!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"/>` が BT Adapter を表す。

ここで使ったメソッド org.freedesktop.DBus.Introspectable.Introspect は DBus で共通に使われるメソッドで、オブジェクトパスの内容を調べるのに使う。この応答を見ると、`/org/bluez/` では `org.bluez.AgentManager1` などのインタフェースが使えたり、子要素 `hci0` がある事が分かる。API については bluez のドキュメントに解説がある。例えば `org.bluez.AgentManager1` なら [agent-api.txt](https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/agent-api.txt) に書いてある。

次に、ペアリングされたデバイスを探す。[device-api.txt](https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/device-api.txt) によると、デバイスを表すオブジェクトは `/org/bluez/{hci0,hci1,...}/dev_XX_XX_XX_XX_XX_XX` に出てくるので `/org/bluez/hci0` を Introspect する。

```
# dbus-send --print-reply=literal --system --dest=org.bluez /org/bluez/hci0 org.freedesktop.DBus.Introspectable.Introspect | xmllint --format -
...
  <node name="dev_B8_8A_60_E6_20_61"/>
  <node name="dev_D4_0B_1A_F2_FE_DC"/>
</node>

```

ここで表示されるデバイスのうち、ペアリングされているかどうかを示す情報は Properties として実装されている

    boolean Paired [readonly]

ので、とりあえず全部のプロパティを表示する org.freedesktop.DBus.Properties.GetAll を使う。

```
# dbus-send --print-reply=literal --system --dest=org.bluez /org/bluez/hci0/dev_D4_0B_1A_F2_FE_DC org.freedesktop.DBus.Properties.GetAll string:org.bluez.Device1
   array [
      dict entry(
         Address         variant             D4:0B:1A:F2:FE:DC      )
      dict entry(
         Name         variant             M00213      )
      dict entry(
         Alias         variant             M00213      )
      dict entry(
         Class         variant             uint32 5898764
      )
      dict entry(
         Icon         variant             phone      )
      dict entry(
         Paired         variant             boolean true
      )
...
```

これで、このデバイスが接続中である事が分かる。[DBus.Properties.html](https://dbus.freedesktop.org/doc/dbus-java/api/org/freedesktop/DBus.Properties.html) によると、GetAll を使うには引数としてインターフェース名を指定する。Introspect すると分かるが、`dev_XX_XX_XX_XX_XX_XX` で使えるインタフェースには org.freedesktop.DBus.Introspectable、org.bluez.Device1、org.freedesktop.DBus.Properties、org.bluez.Network1、org.bluez.MediaControl1 があってそれぞれにプロパティがあるので、必要なインタフェースを指定しなくてはならない。

ちなみに dbus-send の引数を指定する時は `string:org.bluez.Device1` のように型を指定する。

実際のプログラムでは GetAll では無く freedesktop.DBus.Properties.Get でプロパティの一つを取ってくる機会が多いと思う。

```
# dbus-send --print-reply=literal --system --dest=org.bluez /org/bluez/hci0/dev_D4_0B_1A_F2_FE_DC org.freedesktop.DBus.Properties.Get string:org.bluez.Device1 string:Paired
   variant       boolean true
```

## 参考

* [The upcoming Management Interface ](http://www.bluez.org/the-management-interface/)
    * なぜ hci socket では無く bluetoothd を使うようになったかが書いてある。
* [dbus-send(1)](https://dbus.freedesktop.org/doc/dbus-send.1.html)
* [bluez.git root/doc](https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc)
* [D-Bus のはなし](http://www.silex.jp/blog/wireless/2017/01/d-bus.html)