BluezをD-busから使う LEデバイスをスキャンする

BlueZをD-Busから使う方法について

LEデバイスをスキャンする方法を調べました。

# lescan.py
* lescan と同等の表示をする。
* Bluezのtest-discovery を元に作成

```py:lescan.py
#!/usr/bin/python

import dbus
import dbus.mainloop.glib
import gobject as GObject
import bluezutils

devices = {}

def print_compact(properties):
        name = "unknown"
        address = "<unknown>"

        for key, value in properties.iteritems():
                if type(value) is dbus.String:
                        value = unicode(value).encode('ascii', 'replace')
                if (key == "Name"):
                        name = value
                if (key == "Address"):
                        address = value

        print("%s (%s)" % (address, name))

def interfaces_added(path, interfaces):
        properties = interfaces["org.bluez.Device1"]

        if not properties:
                return

        if path in devices:
                devices[path] = dict(devices[path].items() + properties.items())
        else:
                devices[path] = properties

        print_compact(devices[path])

def properties_changed(interface, changed, invalidated, path):
        if interface != "org.bluez.Device1":
                return

        if path in devices:
                devices[path] = dict(devices[path].items() + changed.items())
        else:
                devices[path] = changed

        print_compact(devices[path])

if __name__ == '__main__':
        dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)

        bus = dbus.SystemBus()
        adapter = bluezutils.find_adapter()


        bus.add_signal_receiver(interfaces_added,
                        dbus_interface = "org.freedesktop.DBus.ObjectManager",
                        signal_name = "InterfacesAdded")

        bus.add_signal_receiver(properties_changed,
                        dbus_interface = "org.freedesktop.DBus.Properties",
                        signal_name = "PropertiesChanged",
                        arg0 = "org.bluez.Device1",
                        path_keyword = "path")

        om = dbus.Interface(bus.get_object("org.bluez", "/"), "org.freedesktop.DBus.ObjectManager")
        objects = om.GetManagedObjects()

        for path, interfaces in objects.iteritems():
                if "org.bluez.Device1" in interfaces:
                        devices[path] = interfaces["org.bluez.Device1"]

        scan_filter = dict()
        scan_filter.update({ "Transport": "le" })
        adapter.SetDiscoveryFilter(scan_filter)
        adapter.StartDiscovery()

        mainloop = GObject.MainLoop()
        mainloop.run()
```
* print_compact()はLEデバイスのBDアドレスとBDネームを表示
* interfaces_add()はデバイスが最初に見つかった時の処理
* properties_changed()はデバイスのプロパティに変更があった時の処理
* メイン
 * dbusメインループのおまじない
 * busとadapterを取得
 * イベント受信処理 interfaces_add()とpropoeties_changed()を登録
 * bluetoothdが保持しているデバイス情報を取得して 変数devicesに保存する
 * leのみ通知するように受信フィルタに設定
 * StartDiscovery()でディスカバリを開始
 * メインループを開始

# 実行例

```
pi@raspberrypi: $ ./lescan.py
B0:B4:48:C0:C7:13 (CC2650 SensorTag)
52:0C:20:EA:75:14 (unknown)
B0:B4:48:C0:C7:13 (CC2650 SensorTag)
B0:B4:48:C0:C7:13 (CC2650 SensorTag)
B0:B4:48:C0:C7:13 (CC2650 SensorTag)
B0:B4:48:C0:C7:13 (CC2650 SensorTag)
52:0C:20:EA:75:14 (unknown)
B0:B4:48:C0:C7:13 (CC2650 SensorTag)
B0:B4:48:C0:C7:13 (CC2650 SensorTag)
B0:B4:48:C0:C7:13 (CC2650 SensorTag)
52:0C:20:EA:75:14 (unknown)
B0:B4:48:C0:C7:13 (CC2650 SensorTag)
```
* lescanと違ってsudoしなくても使えるのが良いです。

* イベントの実際の挙動
 * デバイスが初めて見つかった時にInterfacesAddedが飛んでくる。
 * それ以外は定期的に、RSSIのPropertiesChangedが飛んでくる。たまにUUIDのPropertiesChangedが飛んでくる。

* このスクリプトだと、RPA(resolvable private address)なランダムアドレスは、アドレス解決した後のアドレスが表示されるようです。
 * bluetoothdには、アドレス解決した後のアドレスが格納されているみたいです。

# 参考
* Bluez の test/test-discovery http://git.kernel.org/cgit/bluetooth/bluez.git/tree/test/test-discovery
* Bluez の doc/adapter-api.txt https://git.kernel.org/cgit/bluetooth/bluez.git/tree/doc/adapter-api.txt