BlueZ 开发学习指南(一) — D-Bus介绍
一、 BlueZ与D-Bus简介
Linux使用的蓝牙协议栈是Blue Z,不同于我们以往的开发方式,Blue Z提供的API 并不是通过头文件这样的形式, 而是通过D-Bus的方式来提供的。
Blue Z提供的是Host端的协议栈,Control端的协议往往由专门的芯片厂提供。Host和Control通过HCI接口来通讯。HCI接口可以是UART、USB、SD卡,也可以是虚拟的。
下图是应用通过D-Bus与Blue Z进行通讯的过程。D-Bus最初是作为一种标准的基于消息的通信系统创建的,以促进个人计算机上X-Windows桌面环境中组件之间的互操作性。但D-Bus比设计桌面GUI组件的通信具有更广泛的实用性。 自Blue Z 5.52版本以来,它已经成为Linux上运行的蓝牙应用程序与BlueZ本身之间的标准接口。了解如何使用D-Bus是再Linux下做BlueZ相关开发的关键。
BlueZ中提供的API都在代码仓库的doc目录中,也可以通过下面链接查看: https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc
BlueZ应用程序不直接调用BlueZ函数,也不直接从BlueZ中接收回调。 无需根据BlueZ头文件编译应用程序代码。 D-Bus将应用程序与BlueZ完全解耦, 因此蓝牙应用程序开发工作,主要涉及使用D-Bus API发送或接收消息。
二、 D-Bus的基本概念
消息总线
D-Bus 进程间通信以消息总线为中心。 消息由一个进程放置在总线上,然后沿着总线传输到连接到同一总线的一个或多个其他进程。
有两种类型的消息总线,分别是系统总线和会话总线。
- 系统总线只有一个实例,BlueZ 使用的就是系统总线。
- Linux 中每个用户登录会话都存在一个会话总线
客户端、 服务端、 连接
D-Bus 通信需要将进程连接到消息总线, 连接到总线的进程称为客户端,监听并接受连接的进程称为服务端。 客户端和服务端只针对建立连接的过程,建立连接后,客户端和服务端之间的区别就不存在了。
当一个应用连接到总线时,它会被分配一个以冒号开头的唯一连接名称, 例如: 1.16。 各个应用程序之间进行通信是通过在他们所连接的总线上发送和接收各种类型的D-Bus消息来实现的。
对象、 接口、 方法、 信号、 属性
D-Bus使用了面向对象的概念, 使用D-Bus的应用程序会包含各种对象。对象是由一个或多个函数或方法来组成的。
接口以点作为分隔符,这个和域名的分割规则很像。 例如:org.freedesktop.DBus.Introspectable 和 org.bluez.GattManager1 就是两个接口的名称。
一个应用程序可以通过连接的D-Bus发送一个特殊的消息来调用另一个程序对象中的方法。 发送的消息会沿着总线,通过连接传递到拥有目标对象的应用程序中。
方法可以返回一个结果,也可能不返回。 如果结果是通过方法产生的,则将其返回到原始内容。
为了使自己的方法可以被其他应用程序所调用,对象必须在D-Bus守护进程中进行注册。这种在D-Bus中注册对象的行为称为exporting( 就翻译为出口吧)。 每个对象都有一个唯一采用路径形式的唯一标识符。例如,代表蓝牙设备的对象可能具有/org/bluez/hci0/dev_4c_4c_d7_64_cd_22_0a的路径标识符。 对象使用其路径的注册使D-Bus守护程序可以路由通往对象的识别路径,这是通过适当的连接到拥有应用程序的识别路径。
需要注意的是,路径具有层次结构,路径的后端是被包含在路径的前端中的。例如路径(/org/bluez/hci0/dev_4c_d7_64_cd_22_0a)中设备dev_4c_d7_64_cd_22_0a是有bluetooth apapter对象所有, bluetooth apapter的路径为/org/bluez/hci0/ 。
一个对象的接口可以发出(Signal)信号, 信号可以认为是一个事件。 应用程序可以订阅或者注册自己感兴趣的信号, 一个信号可以被一个或多个应用程序来注册。 信号触发后,会被传送到没一个注册该信号的应用程序中。
对象中可以具有属性, 属性可以使用Get操作来获取属性值,也可以使用Set操作来改变属性值。 属性可以通过名称引用,也可以通过对象实现的接口来访问。
代理对象
一些D-Bus API支持代理对象的概念, 代理对象可以理解为另一个应用程序中的远程对象。但是,在本地过程中实例化了代理对象,其方法与其代表的远程对象相同,可以通过应用程序代码直接调用。 然后,代理对象照顾将这些本地方法调用转换为D-Bus消息的发送和接收。 代理对象使D-Bus编程变得更加容易,并减轻了应用程序开发人员必须直接使用D-Bus消息的负担,而D-Bus消息可能需要一些相当低的级别编程。
众所周知的名称
我们上面讲到每个D-Bus有一个连接名称,例如:1.16。 但是应用程序还可以注册一个可以访问到的名称来代替连接名称。 任何应用程序都可以做到这一点,比如 蓝牙守护进程就是一台D-Bus服务器,它拥有一个众所周知的名称:org.bluez。
标准接口
当使用BlueZ开发的时候,经常会用到一些标准接口。比如:
**org.freedesktop.DBus.ObjectManager **
这个接口定义了信号接口InterfacesAdded 和 InterfacesRemoved 。 当BlueZ发现新设备的时候,会发送InterfacesAdded信号,当Blue Z丢失该设备的时候,会发送Interfaces Removed功能。
该接口还定义了getManageBjects方法, 这允许应用程序发现D-Bus连接过程所具有的所有对象。
org.freedesktop.DBus.ObjectManager
该接口定义了允许检索或者设置属性值的方法和信号, 当对象的属性发生改变的时候会发送一条 PropertiesChanged 指令,例如,当蓝牙的RSSI属性发生变化的时候,Bluez Device对象会实现属性接口并发出PropertiesChanged信号。
三、 D-Bus 工具
D-Bus开发人员的主要使用工具由D-feet和DBus-Monitor。
D-feet
D-feet是一个GUI应用程序,允许查看与系统或会话总线连接的应用程序,并浏览其导出的对象和接口。 它还允许执行方法,可用于测试目的。 下图显示了在Raspberry Pi上使用的D-Feet的屏幕截图,并使用远程桌面工具VNC访问。
org.bluez服务已在左侧窗格中选择,这导致服务所包含的对象,每个对象由层次路径识别,要列出。可以扩展对象以显示其受支持的界面,并且在接口中我们可以看到它们的方法。
D-feet是测试以及探索和发展对D-BU的理解的绝佳工具。安装d-feet可以使用如下命令:
sudo apt-get install d-feet
dbus-monitor
DBU-Monitor是一种命令行工具,允许与DBUS守护程序交换的消息实时查看。 运行DBU-Monitor时,必须选择系统总线或会话总线。 仅当启用监听时,某些系统消息才能看到。
监控系统总线的命令:
sudo dbus-monitor --system
监控会话总线的命令:
sudo dbus-monitor --session
如果要启用监控,需要用Root用户来启用安全策略来监听, 例如创建一个类似与system-local.conf这样的文件。当然,为了让监听生效,还需要重启下设备。
pi@raspberrypi:~ $ sudo cat /etc/dbus-1/system-local.conf
<!DOCTYPE busconfig PUBLIC
"-//freedesktop//DTD D-Bus Bus Configuration 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
<policy user="root">
<allow eavesdrop="true"/>
<allow eavesdrop="true" send_destination="*"/>
</policy>
</busconfig>
dbus-send
dbus-send是一个命令行工具,允许用户将消息发送到D-Bus总线上,这对测试程序来说非常方便,我们可以发送一个条测试消息到指定的应用,然后查看该消息是否被正确的接收。例如:
dbus-send --system --type=signal / com.studyguide.greeting_signal string:"hello world!"
下面是一个D-Bus调用一个消息的示例:
method call time=1634811621.566895 sender=:1.52 -> destination=:1.16 serial=10 path=/org/bluez/hci0/dev_EB_EE_7B_08_EC_3D; interface=org.bluez.Device1; member=Connect
该D-Bus消息是由Connect ID 为1.52 的应用发送给Connect ID为1.16的应用。目的地址的应用拥有一个蓝牙设备对象,该对象的路径标识符为:/org/bluez/hci0/dev_eb_ee_7b_08_ec_3d。 该对象实现了一个org.bluez.device1接口,其中包括了要执行迭代方法和连接。我们可以再下图中看到该设备表示为D-Bus对象。
D-Bus信号消息
signal time=1634895336.839277 sender=:1.33 -> destination=(null destination) serial=349 path=/org/bluez/hci0/dev_EE_CB_92_97_DB_18/service0025/char0026; interface=org.freedesktop.DBus.Properties; member=PropertiesChanged
string "org.bluez.GattCharacteristic1"
array [
dict entry(
string "Value"
variant array of bytes [12]
)
]
array []
该D-Bus消息是从Bluez守护程序发送的信号,并传递到所有已注册的应用程序中。
该信号是有路径标识为 /org/bluez/hci0/dev_EE_CB_92_97_DB_18/service0025/char0026的特征对象发出的。 并且已字节数组的形势包含通知值。只有一个对象可以发出信号,注册的应用程序可以接收该信号。
四、编程语言和API
下面是D-Bus绑定的一些编程语言:https://www.freedesktop.org/wiki/Software/DBusBindings/
编程语言与D-Bus API之间的关系不是一对一的关系。 某些语言可以使用多个API。 例如,C语言开发者可以使用GIO或嵌入式Linux库(ELL)。 Ell看起来友好一些,但没有API文档,要求开发人员通过查看一些示例来学习如何使用它。 而Gio文档的质量参差不齐,并且肯定有一个学习曲线可以开始使用。 Python开发人员至少有几个选择。 Bluez Project本身将DBU-PYTHON用于测试工具,该工具包含在Bluez分布中。 它的文档将自己描述为D-BUS协议的参考实现。 但是,D-Bus绑定的官方列表(参考上面的链接)将DBus-Python列为过时的库,而是建议使用Pydbus。 也就是说,dbus-python绝对是一个活跃的项目,2021年就已经释放正式版本了。