D-Bus — шина данных в Linux на примере BlueZ

Одной из наиболее востребованных технологий передачи данных среди устройств интернета вещей является Bluetooth Low Energy (BLE). Однако начать работу с ним не очень просто по ряду причин, как аппаратного, так и программного характера.

Аппаратные ограничения накладывает сама спецификация Bluetooth, основной сложностью является синхронизация и временные зависимости передачи данных между устройствами. Как следствие, значительно усложняется отладка протоколов и требуются специальные средства (например, от TI или Nordic). По этим же самым практически невозможно начать работу с BLE с нуля. Решением в данном случае являются библиотеки BLE. Однако, и тут всё не так просто, например, наиболее распространённая ОС в России Windows 7 не поддерживает работу с BLE (поддержка появилась только в версиях начиная с 8, которые устанавливались на мобильные устройства, где BLE широко распространён). На Linux ситуация гораздо лучше, однако, тоже не лишена своих недостатков. BlueZ включён в ядро Linux, поддерживает множество платформ и дистрибьютивов. BlueZ первоначально был разработан компанией Qualcomm в середине нулевых и в дальнейшем доработан сообществом. В стандартном пакете BlueZ включены 2 полезные утилиты позволяющие работать с Bluettooth устройствами через консоль:

Однако, по личному опыту можем отметить, что поведение данных утилит может отличаться в зависимости от версии стека и дистрибьютива. К сожалению, периодически некоторые функцию перестают работать, как ожидается, а понять это сразу зачастую бывает сложно, так не очевиден источник проблемы — отлаживаемая программа, конечное устройство, стек или драйвера ОС. Решение в данном случае получается очень простое и красивое, однако не очевидное для большинства. Начиная с версии 5.0 BlueZ работает через D-Bus. D-Bus — система межпроцессорного взаимодействия разработки FreeDesktop. Рассмотрим работу с шиной D-Bus напрямую без использования утилит.

Кратко можно представить работу следующим образом : существует общая шина в виде дерева, представляющая те или иные объекты, именуемые сервисами, которые могут иметь дочерние объекты и функции, к которым можно обращаться. Каждый участник взаимодействия посылает сообщение другому участнику или группе (возможны широковещательные посылки). С точки зрения пользователя достаточно подключиться к шине (системной или сессионной) в нужном месте (например, к BLE-адаптеру или любому другому устройству), вызвать нужную функцию (например, сканировать доступные устройства в радиоэфире, см. пример ниже), при необходимости обновить дерево (сканирование добавляет в дерево новые дочерние узлы/ветви), а в дальнейшем работать уже с уточнёнными узлами дерева, (например характеристиками подключённого устройства). С другой стороны можно повесить функцию-обработчик на событие, который будет ждать сообщений по шине D-Bus от других устройств в соответствии с шаблоном. Это полезно для фильтрации только своих сообщений, так как через D-Bus может проходить множество данных. Данный способ передачи данных нашёл широкое примение в Linux в целом. Существует версия под Android, а под Windows находится в стадии разработки. Поэтому данный инструмент видится очень перспективным.

Рассмотрим средства разработки и взаимодействия с D-Bus. Существуют удобные графические инструменты для работы с D-Bus, например, D-Feet или QDBusViewer, которые отображает дерево в удобочитаемом виде и позволяет произвести основные операции: найти нужный сервис по маске, прочитать или записать в него, вызвать функцию с аргументом. Для первого шага — самое оно, пройтись по структуре и понять с чем же предстоит работать и каким образом.

Screenshot_20180211_163235

Также стоит отметить, что для работы с D-Bus существуют библиотеки под множество языков программирования. В итоге с применением D-Bus задача взаимодействия с BLE устройствами из под Linux значительно упрощается.

Рассмотрим пример кода на Python. Сразу стоит отметить, что существует 2 похожие библиотеки для работы с D-Bus из Python. Необходимо использовать pydbus, как наиболее современный и соответствующий философии Python.

import pydbus
from gi.repository import GObject
from gi.repository import GLib
import time
import threading
import sys

Функция поиска устройств BLE запускается командой StartDiscovery() из дерева.

def find_ble_devices(bus, hci, timeout=10):
    # Found BLE devices
    om = bus.get('org.bluez', '/')
    devices = {}
    try:
        hci.StartDiscovery()
    except Exception:
        hci.StopDiscovery()
        hci.StartDiscovery()
    time.sleep(timeout)
    objects = om.GetManagedObjects()
    hci.StopDiscovery()
    for path, interfaces in objects.items():
        if "org.bluez.Device1" in interfaces:
            devices[path] = interfaces["org.bluez.Device1"]
    return devices

2 варианта обработчиков подписки на события (Notify) — простой и детальный:

def temp1_notify(source, value, c):
    print("In temp1_notify: source={}, Value={}".format(source, value))

def temp3_notify(args):
    object, dest, path, iface, signal, arg = args
    # print("In temp3_notify: path={}, iface={}, signal={}, arg={}".format(path, iface, signal, arg))
    try:
        print("In temp3_notify: path={}, signal={}, Value={}".format(path, signal, arg[1]['Value']))
    except KeyError:
        print("In temp3_notify: path={}, signal={}, arg={}".format(path, signal, arg[1]))

Функция сканирует дерево bluez подключённого устройства и по UUID характеристики выдаёт путь в дереве (удобно для поиска сервисов BLE).

def path_from_UUID(UUID):
    # main object for bluez tree
    bluez_tree = bus.get("org.bluez",
                         "/"  # Object path
                         )
    list_charact = []

    # scan of characteristics/devices with required name
    for i in bluez_tree.GetManagedObjects():
        # only for services (including characteristics)
        if "service" in i:
            obj = bus.get("org.bluez", i)
            if UUID in obj.UUID:
                list_charact.append(i)
    return (list_charact)

Основной цикл:

if __name__ == "__main__":
    bus = pydbus.SystemBus()

    hci = bus.get("org.bluez",
                  "/org/bluez/hci0"  # Object path
                  )
    devices = find_ble_devices(bus, hci, timeout=3)
    for i in devices.keys():
        # print(i)
        ble_dev_addr = i
        break  # get first key from dict

    # connect to device
    ble_dev = bus.get("org.bluez",
                      ble_dev_addr  # device path
                      )

    # connect and got D-Bus Tree of characteristics
    # required only first time
    ble_dev.Connect()
    ble_dev.Introspect()
    time.sleep(2)

    read_period_path = path_from_UUID("0000b006")[0]
    read_period = bus.get(
        "org.bluez",  # Bus name
        read_period_path
        )

    ble_dev.Connect()

Для характеристики с возможностью чтения и записи:
читаем значение характеристики, перезаписываем её и читаем снова уже новое значение.

    try:
        read_period.ReadValue({})
        print("in " + read_period.UUID + " we have :" + str(read_period.Value))
        read_period.WriteValue([255], {})
        read_period.ReadValue({})
        print("in " + read_period.UUID + " we have :" + str(read_period.Value))
    except (GLib.Error):
        print("GLib.Error", sys.exc_info())
    except:
        print ("Unexpected error:", sys.exc_info())
        raise

Для характеристики с возможностью чтения и оповещения:
читаем (в ручном режиме)

    # Temperature from sensor 1
    temp1_path = path_from_UUID("0000b001")[0]
    temp1 = bus.get(
        "org.bluez",  # Bus name
        temp1_path
        )

    # Temperature from sensor 2
    temp2_path = path_from_UUID("0000b003")[0]
    temp2 = bus.get(
        "org.bluez",  # Bus name
        temp2_path
        )

    print("temp1_path :", temp1_path)
    print("temp2_path :", temp2_path)

и настраиваем 2 варианта подписки на события (Notify), которые генерирует D-Bus в момент, когда характеристика периферийного устрйоства прислало оповещение об изменении состояния.

    temp1.PropertiesChanged.connect(temp1_notify)

    # subscribe to required Signal and object in second way
    bus.con.signal_subscribe(None, None,            # sender, iface
                             "PropertiesChanged",   # signal
                             temp2_path,            # object
                             None, 0,               # arg0, flags
                             lambda *args: temp3_notify(args)   # signal_fired
                            )

    temp1.StartNotify()
    temp2.StartNotify()

В данной статье мы рассмотрели работу с BLE устройствами через D-Bus (BlueZ). Код получается простой , читаемый и легко адаптируемый под задачи.

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход /  Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход /  Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход /  Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход /  Изменить )

w

Connecting to %s