BlueZ5
BlueZ 是官方 Linux Bluetooth 栈,由主机控制接口(Host Control Interface ,HCI)层、Bluetooth 协议核心、逻辑链路控制和适配协议(Logical Link Control and Adaptation Protocol,L2CAP)、SCO 音频层、其他 Bluetooth 服务、用户空间后台进程以及配置工具组成。
BlueZ提供对核心蓝牙层和协议的支持。它灵活,高效,并使用模块化实现。Bluez有很多有趣的特性:
(1)完全的模块化实现
(2)均衡的多处理安全
(3)支持多线程数据处理
(4)支持多个蓝牙设备
(5)真正的硬件抽象
(5)向所有层提供标准socket接口
(6)支持设备和服务级别的安全
目前BlueZ由许多单独的模块组成:
(1)蓝牙内核核心子系统
(2)L2CAP和SCO音频内核层
(3)RFCOMM,BNEP,CMTP和HIDP的内核实现
(4)HCI UART,USB,PCMCIA以及虚拟设备的驱动程序
(5)通用的蓝牙和SDP库以及守护进程
(6)配置和测试程序
(7)协议解码和分析工具
BlueZ目前支持很多蓝牙协议:
(1)底层主机协议栈(Lower level host stack)
Core Specification 4.2 (GAP, L2CAP, RFCOMM, SDP, GATT)
1)Classic Bluetooth (BR/EDR)
2)Bluetooth Smart (Low Energy)
(2)上层协议
由BlueZ提供:
A2DP 1.3
AVRCP 1.5
DI 1.3
HDP 1.0
HID 1.0
PAN 1.0
SPP 1.1
基于GATT(LE)协议:
PXP 1.0
HTP 1.0
HoG 1.0
TIP 1.0
CSCP 1.0
基于OBEX的协议(通过obexd支持):
FTP 1.1
OPP 1.1
PBAP 1.1
MAP 1.0
由oFono项目提供的协议:
HFP 1.6(AG&HF)
BlueZ的内核模块,库和以及实用程序已支持在Linux支持的许多架构上完美工作。这还包括很多单处理器和多处理器平台以及超线程系统:
(1)英特尔和AMD x86
(2)AMD64和EM64T(x86-64)
(3)SUN SPARC 32 / 64bit
(4)PowerPC 32 / 64bit
(5)英特尔StrongARM和XScale
(6)日立/瑞萨SH处理器
(7)摩托罗拉 DragonBall
BlueZ可以在许多Linux发行版中找到,一般来说它与市场上的任何Linux系统兼容:
(1)Debian GNU / Linux
(2)Ubuntu Linux
(3)Fedora Core / Red Hat Linux
(4)OpenSuSE / SuSE Linux
(5)Mandrake Linux
(6)Gentoo Linux
(7)Chrome操作系统
目前最新的BlueZ版本是BlueZ5。下面将以BlueZ5为例,详细说明BlueZ架构:
图1. Bluez5架构.
Bluez5的分为kernel和user space 两部分。
BlueZ的kernel模块从Linux 2.6内核开始就已经包含在linux内核中。
kernel 模块包括:
(1)底层协议(L2CAP,RFCOMM,BNEP,HIDP等)
(2)安全性(SSP,SMP)
(3)硬件驱动程序
(4)提供给user space的基于socket的接口
1)数据接口(L2CAP,RFCOMM,SCO,HCI)
2)控制接口(MGMT,HCl,BNEP,HIDP)
虽然kernel模块早已存在于linux内核中,但是没有user space模块的支持,kernel模块无法发挥作用。最新版本的user space模块的下载地址为:
http://www.kernel.org/pub/linux/bluetooth/bluez-5.44.tar.xz
user space模块包括:
(1)bluetoothd
1)是BlueZ的中央守护进程
2)提供用于UI和其他子系统的D-Bus接口
3)减少暴露底层细节
4)可以通过插件扩展(例如通过neard支持NFC,通过sixaxis支持DS3)
(2)obexd
1)是OBEX协议的守护进程
2)提供用于UI的D-Bus接口
3)具有与bluetoothd类似的架构
(3)工具(tools)
1)bluetoothctl - bluetooth命令行测试工具
2)obexctl - obex命令行测试工具
3)btmon - HCl信息跟踪
4)其他用于测试,开发和跟踪的命令行工具集
(4)上层协议
1)Audio and media (A2DP, AVRCP)
2)Telephony (HFP, HSP)
3)Networking (PAN, 6LoWPAN)
4)Input device (HID, HoG)
5)OBEX (FTP, OPP, MAP, PBAP)
6)Others
下载BlueZ5(bluez-5.44.tar.xz)源代码压缩包,经解压后得到BlueZ5源代码,BlueZ5源代码中有很多目录和文件:
android/ - 用于替代android中bluedroid的android版本bluez源码。
attrib/ - 包含gatttool 源码以及与gatt attribute相关的代码,gatttool程序入口为gatttool.c。
btio/ - 通过的标准socket接口与BlueZ5 kernel模块通信?。
client/ - bluetoothctl源码,程序入口为main.c。
doc/ - BlueZ5 API文档。
emulator/ - 与bluetooth虚拟controller工具相关的代码?。
gdbus/ - BlueZ5自带的内部gdbus库源码。
gobex/ - Blue5自带的内部gobex库源码。
lib/ - libbluetooth.so 源码,提供BlueZ4 API,用来支持某些第三方应用。
monitor/ - btmon源码, 程序入口为main.c。
obexd/ - obexd源码,程序入口为src/main.c。
peripheral/ - 与蓝牙ble的GATT相关的代码?。
plugins/ - BlueZ5插件源码(neard,autopair等插件)。
profiles/ - BlueZ5蓝牙上层协议(a2dp,hid等)源码。
src/ - bluetoothd源码,程序入口为main.c。
test/ - Bluez5测试脚本。
tools/ - Bluez5测试工具集源码。
unit/ - PTS测试相关的一些代码?。
README / INSTALL - 配置,编译,安装Bluez5的说明。
Makefile.obexd - 定义obexd编译规则,此文件被include于Makefile.am中。
Makefile.plugins - 定义BlueZ5的plugins(neard,autopair等)的编译规则, 此文件被include于Makefile.am中。
Makefile.tools - 定义BlueZ5测试工具集的编译规则,此文件被include于 Makefile.am中。
Makefile.am - 定义了Bluez5的编译规则。用于automake工具,生成 Makefile.in文件。
Makefile.in - 用于configure脚本,生成最终的Makefile文件。
configure.ac - 用于autoconf工具,生成configure脚本。
configure - 配置编译选项,生成最终的Makefile文件,以及config.h文件。
BlueZ5提供了很多测试工具:
bccmd - 用于向CSR(Cambridge Silicon Radio)设备发出BlueCore命令。
bluemoon - 是一个Bluemoon配置工具。
bluetoothctl - bluetoothd测试工具。
bluetooth-player - 蓝牙音频测试(AVRCT)工具。
btmgmt - 使用Bluetooth Management API的蓝牙测试工具,通过Bluetooth Management sockets 与 kernel模块通信。
btmon - 用于监控HCI,获取HCI log,解析HCI log,可以替代hcidump。
ciptool - 用于设置,维护和检查Linux内核中蓝牙子系统的CIP配置。
hciattach - 用于将串口连接到蓝牙协议栈作为HCI的传输接口。
hciconfig - 用于配置蓝牙设备。
hcidump - 用于监控HCI,获取HCI log,解析HCI log。
hcitool - 用于配置蓝牙连接并向蓝牙设备发送一些特殊命令。
hex2hcd - 用于将Broadcom设备所需的文件转换为hcd(Broadcom蓝牙固件)格式。
hid2hci - 蓝牙USB Dongle 的HID和HCI模式切换工具。
l2ping - ping扫描到的远端设备。
l2test - l2cap测试工具。
mpris-proxy - 媒体播放器远程接口规范(mpris)是标准的D-Bus接口,其旨在提供用于控制媒体播放器的公共API。mpris-proxy 是一种实现mpris的媒体播放器测试工具(AVRTG?)。更多关于mpris的信息见:
http://specifications.freedesktop.org/mpris-spec/latest/
obexctl - obexd测试工具。
obex-client-tool - obex server端测试工具。
obex-server-tool - obex client端测试工具。
rctest - 用于测试蓝牙协议栈上的RFCOMM通信。
rfcomm - 用于设置,维护和检查Linux内核中蓝牙子系统的RFCOMM配置。
sdptool - 用于在蓝牙设备上执行SDP查询。
所有BlueZ5的API都可以在BlueZ5源码目录下的 doc/ 目录下查询到,同时API 相关代码示例可以在BlueZ5源码目录下的 test/ 目录中查询到。与BlueZ 4相比,BlueZ 5的 D-Bus API有重大更改。大部分更改是由于BlueZ 5中的以下新特性导致的:
1)采用标准D-Bus Properties和ObjectManager接口(在D-Bus specification文档中定义)。
2)引入特定版本的接口(例如org.bluez.Adapter1)。当引入新版本接口时,会尽量同时支持至少两个最新版本。
3)简化或删除单独的profile接口,添加通用的org.bluez.Device1.Connect方法来连接profile。
4)移除org.bluez.Service接口(用于注册SDP records and authorization),引入新的org.bluez.Profile1接口。
5)在设备扫描期间动态创建设备对象。
6)引入新的AgentManager1接口。
7)基本D-Bus路径已移至“/org/bluez”。
(1)D-Bus Properties
D-Bus的Properties接口提供了对object/interface的属性(Properties)的访问方法。BlueZ4在需要提供访问属性方法的每个接口上自定义了GetProperties和SetProperty方法以及PropertyChanged信号。在BlueZ 4发布以后,一个新的的标准的属性接口已经完成了,现在不是在特定的接口上实现这些方法和信号,而是在每个对象(object)上都有一个包含如Set,Get和GetAll方法和PropertiesChanged信号的通用的org.freedesktop.DBus.Properties接口。这些方法和信号包含一部分属性属于消息参数的特定接口。
属性无效化(the invalidation of a property)的特性仅由标准属性接口提供,不存在与BlueZ4中。这个特性对于传递一个不再存在的属性的信息特别方便。
(2)D-Bus ObjectManager
D-Bus的ObjectManager接口是访问D-Bus服务的整个对象层次结构的通用方法。它包括用于监听接口添加和删除的信号(signal)以及一个返回服务中所有可用对象、对象的接口以及接口上的所有属性的GetManagedObjects方法。实际的ObjectManager接口可以在BlueZ服务的根(“/”)路径上找到。
因为大多数管理和发现对象的任务都可以通过ObjectManager接口来完成,BlueZ 4中的许多方法,信号和属性都因为变得多余,而被删除了。其中最著名的一个是旧的org.bluez.Manager接口。这个接口在BlueZ 5中没有任何对应的东西。相反,应用程序将通过ObjectManager.GetManagedObjects方法查找具有“org.bluez.Adapter1”接口的任何返回对象来发现可用的adapter。默认adapter的概念总是有点不清晰,并且adapter值不能改变,所以如果应用需要设置默认的adapter,应选择查找到的第一个adapter。
BlueZ5源代码test/目录下的get-managed-objects测试脚本,可以通过ObjectManager接口来查看bluetoothd上的对象。
(3)Device discovery
Ble的蓝牙地址基本上只是在经典蓝牙地址的基础上扩展了额外的一个位,这个地址还有“随机(ramdom)”还是“公共(public)”之分。这导致调用BlueZ 4 的CreateDevice和CreatePairedDevice API时,由于传入的蓝牙地址参数没有包含任何这个额外的随机/公共信息,bluetoothd不得不维护一个内部缓存来查找必要的信息。另一个问题是,由于BlueZ D-Bus API不区分传统的BR / EDR设备和LE设备,因此基本上有三种可能的地址类型需要缓存:BR / EDR,LE公共和LE随机。
为了解决这个问题,BlueZ5去除了显式CreateDevice / CreatePairedDevice的方法,同时由于远端设备是在设备扫描过程中发现的而采用了动态创建设备对象的方法。由于引入了新的ObjectManager,单独的DeviceFound信号也不再需要了。应用程序可以在设备发现过程中通过通用的ObjectManager信号简单地跟踪新创建的设备对象。BlueZ5引入一个新的Device1.Pair方法(与旧的CreatePairedDevice方法类似)去配对远端设备,同样引入一个新的Device1.Connect方法(与CreateDevice方法类似,但不完全相同)去跳过配对过程,直接连接远端设备。一旦停止了设备发现,未连接或配对的设备将在三分钟内被bluetoothd自动删除。
BlueZ5源代码的test/目录下有一个可以用于测试设备发现功能的test-discovery脚本同时提供了为应用程序实现设备发现功能(涉及哪些方法和信号等)的示例。
(4)General device connection procedure
BlueZ 4有一个通用的Device.Disconnect方法,但没有一个通用的Device.Connect方法。BlueZ4的Audio接口上的Connect方法与BluezZ5 的Device.Connect方法有点类似,该方法将所有音频profile组合到一个协调的连接过程中。
通过内部重构和创建一个更干净的内部profile接口,BlueZ 5已经能够为所有profile文件引入一个新的Device1.Connect方法。任何已经实现的profile都可以选择参与到这个通用连接过程中来,并在用户调用该方法时连接。在此方法内部,此方法包含有关profile的建议连接顺序的一些特殊知识,并将基于此对profile进行排序。以音频配置文件为例,其中首先连接HFP,然后是A2DP,最后是AVRCP。
这个接口对应用程序来说,不需要首先尝试发现设备的支持那些profile,然后确定需要调用哪个接口的Connect方法(例如BlueZ 4中的org.bluez.Input,org.bluez.Audio等),应用程序可以直接调用Device1.Connect方法连接profile,而并不需要关注细节。
要测试Device1.Connect()功能,您可以使用BlueZ5源代码目录下test/目录中的 test-device脚本:test-device connect < remote addr >。
(5)The new Profile1 interface (and removal of org.bluez.Service)
BlueZ5引入了一个新的通用D-Bus接口来实现外部profile。profile(驻留在单独的进程中)实现org.bluez.Profile1接口,并通过BlueZ端的新ProfileManager1接口注册实现它的对象。在RegisterProfile方法中(在ProfileManager1接口上),需要至少提供要注册的profile的UUID。 BlueZ内部有一个默认的公用profile信息表,所以除了UUID外其他信息并不是必要的。当然,如果需要也可以提供profile的完整SDP记录(XML编码),期望的安全级别,启用/禁用授权,版本,特征,角色,名称等的信息。
注册profile后,bluetoothd将接管所有需要完成的任务,直到profile完成连接。这些任务包括注册SDP记录(对于服务器端profile),启动服务器socket或连接客户端socket,以及执行授权(对于服务器端profile)等。一旦bluetoothd完成这些任务,它将通过一个Profile1.NewConnection方法将新的连接传给外部的profile对象。这个方法传递了连接的描述符(socket)以及连接的属性字典。连接的属性包括profile的版本和特性(从远程SDP record提取)或者由bluetoothd内部提供了注册绑定的机制的特定于profile的条目。
由于BlueZ4中的org.bluez.Service接口与此接口与具有相同的功能,旧的org.bluez.Service接口已被删除。在BlueZ 5发布时,包括obexd(所有OBEX profile)和oFono(适用于HFP)在内的项目已转换为使用新的Profile接口。
在BlueZ5源代码的 test/ 目录中有一些测试脚本,如test-profile和test-hfp等,可以作为与ProfileManager1接口交互的示例。
(6)The new AgentManager1 interface
BlueZ需要注册代理(agent)对象来处理配对和连接授权。BlueZ 5不使用旧的Adapter.RegisterAgent方法和CreatePairedDevice方法(BlueZ5中使用Device1.Pair方法),而是使用更通用的AgentManager1接口。所有代理需要通过此接口注册,BlueZ将自动选择调用Device1.Pair的相同应用程序的代理(假设应用程序还拥有该代理)。通过AgentManager1.RequestDefault方法,代理可以请求成为默认代理,在这种情况下,默认代理将处理所有传入(远端发起的)请求。通过Adapter.RegisterAgent注册自身的代理,应该将自己注册为BlueZ 5中的默认代理。
对于Agent1接口还有一些更改(除了将接口名从Agent改为为Agent1)。首先,接口中的ConfirmModeChange方法已被删除。其次,引入了用于可能不与用户交互的配对请求的新的请求授权方法。这种情况的主要发生在传入SSP配对请求将触发just-works模型(?)时。
BlueZ5 源码目录下 test/ 目录中的 simple-agent 脚本,用于测试AgentManager1接口,并提供了如何完成代理注册的示例代码。
下面主要说明如何将BlueZ5移植到yocto平台上。
(1) 在yocto上编译BlueZ5
目前开发使用的yocto2.1.1平台已经默认支持BlueZ5(在poky/meta/conf/bitbake.conf 中定义),yocto平台使用bitbake工具构建,编译整个系统。Bluez5的.bb文件位于poky/meta/recipe-connectivity/bluez5目录下。Bitbake依据Bluez5的.bb文件编译,打包特定版本的BlueZ5。
单独编译BlueZ5模块的方法:
1)重置 BlueZ5编译状态
bitbake -c cleansstate bluez5
2)编译BlueZ
bitbake bluez5
或强制编译模块:
bitbake bluez5 -C compile
BlueZ5编译成功后会在 build/tmp/work/ 目录下生成BlueZ5工作目录,Bluez5工作目录中的 build/ 目录用于存放编译生成的目标文件,image/目录中存放了将打包的目标文件及其路径(do_install任务指定那些目标文件将会被打包),deploy-debs/ 目录下存放了目标文件经打包后生成的deb软件包(pakckages变量定义了打包规则,如果packages未定义,则按照默认规则打包)。
(2)安装Bluez5到yocto
BlueZ5编译成功后,会生成很多软件包,但是这些软件包不一定会安装到yocto系统中,将会安装到yocto系统的deb软件包放在 build/tmp/deploy/deb/aarch64/下面(具体目录不固定,依据不同软件包类型和目标系统架构而定)。如果发现编译好的软件包并没有安装到yocto系统中,此时我们可以将软件包手动添加到用于编译系统镜像的.bb文件的IMAGE_INSTALL变量上。
例如,现在有一个软件包 bluez5_5.37-r0_arm64.deb 未安装进系统,假设编译系统镜像的.bb文件名为xxxx-image-xx.bb,我们可以通过如下方法添加软件包到系统镜像中:
IMAGE_INSTALL += “bluez5”
我们在xxxx-image-xx.bb的IMAGE_INSTALL变量上添加的软件包名,并不需要完整的包名,只需要完整软件包名中的第一个“_”号前面的部分即可。
(3) 初始化蓝牙芯片(brcm)
由于目前yocto平台上使用的Brcom公司蓝牙芯片,蓝牙芯片初始化采用的是brcm_patchram_plus工具,该工具的作用是初始化蓝牙芯片,进行基本参数的配置。
brcm_patchram_plus参数:
–d: 显示调试信息
–enable_hci: 启动hci协议
–enable_lpm: 启动lpm模式
–baudrate: 指定工作时的波特率
–patchram: 指定hcd文件(firmware)
–bd_addr: 加载蓝牙地址
–/dev/ttyxxx: 指定串口
brcm_patchram_plus初始化蓝牙并设置基本参数示例:
1)给蓝牙模块上电
echo 1 > /sys/class/rfkill/rfkill0/state
2)初始化蓝牙芯片
brcm_patchram_plus --d --bd_addr xx:xx:xx:xx:xx:xx --enable_hci --enable_lpm --baudrate 3000000 --patchram /etc/bcm4330.hcd /dev/ttyHS0 &
执行上面的操作后brcm_patchram_plus会开始初始化蓝牙芯片并配置基本参数,加载完成后,我们可以通过hciconfig工具判断加载是否成功,通过hciconfig -a命令,我们可以查看hci信息:
ts@ts:~$ hciconfig -a
hci0: Type: BR/EDR Bus: UART
BD Address: 00:1A:7D:DA:71:13 ACL MTU: 310:10 SCO MTU: 64:8
DOWN
RX bytes:16457693 acl:48845 sco:0 events:268201 errors:0
TX bytes:1438036 acl:408 sco:0 commands:267572 errors:0
Features: 0xff 0xff 0x8f 0xfe 0xdb 0xff 0x5b 0x87
Packet type: DM1 DM3 DM5 DH1 DH3 DH5 HV1 HV2 HV3
Link policy: RSWITCH HOLD SNIFF PARK
Link mode: SLAVE ACCEPT
从上面我们可以看出hci0上的蓝牙设备目前还未打开,通过hciconfig hci0 up命令我们可以尝试打开蓝牙,执行打开蓝牙命令后,再次输入hciconfig -a:
ts@ts:~$ hciconfig hci0 up
ts@ts:~$ hciconfig -a
hci0: Type: BR/EDR Bus: UART
BD Address: 00:1A:7D:DA:71:13 ACL MTU: 310:10 SCO MTU: 64:8
UP RUNNING PSCAN
RX bytes:16459230 acl:48845 sco:0 events:268283 errors:0
TX bytes:1439507 acl:408 sco:0 commands:267652 errors:0
Features: 0xff 0xff 0x8f 0xfe 0xdb 0xff 0x5b 0x87
Packet type: DM1 DM3 DM5 DH1 DH3 DH5 HV1 HV2 HV3
......
如果输入hciconfig -a 或者hciconfig hci0 up后没有任何反应,那么说明加载失败了,如果加载失败的话,我们如何判断问题出在哪里呢?我们可以在执行brcm_patchram_plus 时加上 -d 参数可以打开调试信息,下面是设置蓝牙串口波特率时打印的调试信息:
> writing
> 01 18 fc 06 00 00 c0 c6 2d 00
> received 7
> 04 0e 04 01 18 fc 00
> Done setting baudrate
通过在brcm_patchram_plus 源码中查询“01 18 fc”可以确定“01 18 fc 06 00 00 c0 c6 2d 00“是设置波特率的命令,而”04 0e 04 01 18 fc 00”自然就是命令的执行结果。我们可以看到返回值中也包含了”01 18 fc“字符,这一般表示命令执行成功了,失败的情况下一般没有返回值。
brcm_patchram_plus工具如果设置了“–enable_hci”,那么初始化蓝牙的最后一步是“enable_hci”,这个“enable_hci”到底做了些什么事呢?为了知道“enable_hci”到底做了什么,我们在执行brcm_patchram_plus之前,先开启btmon来监控hci(hci0):btmon -i hci0 &,然后再执行brcm_patchram_plus,这样在“enable_hci”时,我们得到如下log:
> = New Index: 00:00:00:00:00:00 (BR/EDR,UART,hci0) 0.778738 < HCI
> Command: Read Local Supported Features (0x04|0x0003) plen 0
> 0.783448
> > HCI Event: Command Complete (0x0e) plen 12 0.791996
> Read Local Supported Features (0x04|0x0003) ncmd 1
> Status: Success (0x00)
> Features: 0xbf 0xfe 0xcf 0xfe 0xdb 0xff 0x7b 0x87
> 3 slot packets
> 5 slot packets
> Encryption
> Slot offset
> Timing accuracy
> Role switch
> Sniff mode
> Power control requests
> Channel quality driven data rate (CQDDR)
> SCO link
> HV2 packets
> HV3 packets
> u-law log synchronous data
> A-law log synchronous data
> CVSD synchronous data
> Paging parameter negotiation
> Power control
> Transparent synchronous data
> Flow control lag (most significant bit)
> Broadcast Encryption
> Enhanced Data Rate ACL 2 Mbps mode
> Enhanced Data Rate ACL 3 Mbps mode
> Enhanced inquiry scan
> Interlaced inquiry scan
> Interlaced page scan
> RSSI with inquiry results
> Extended SCO link (EV3 packets)
> EV4 packets
> EV5 packets
> AFH capable slave
> AFH classification slave
> LE Supported (Controller)
> 3-slot Enhanced Data Rate ACL packets
> 5-slot Enhanced Data Rate ACL packets
> Sniff subrating
> Pause encryption
> AFH capable master
> AFH classification master
> Enhanced Data Rate eSCO 2 Mbps mode
> Enhanced Data Rate eSCO 3 Mbps mode
> 3-slot Enhanced Data Rate eSCO packets
> Extended Inquiry Response
> Simultaneous LE and BR/EDR (Controller)
> Secure Simple Pairing
> Encapsulated PDU
> Erroneous Data Reporting
> Non-flushable Packet Boundary Flag
> Link Supervision Timeout Changed Event
> Inquiry TX Power Level
> Enhanced Power Control
> Extended features
> < HCI Command: Read Local Version Information (0x04|0x0001) plen 0
> 0.792528
...
...
从上面的log可以看出,“enable_hci”操作其实就是通过HCI接口对芯片(controller)做一些读写操作,获取芯片的相关信息(name,ferature等),同时对芯片一些参数做初始化设置,这与正常打开蓝牙后所做的操作类似。
(4)启动bluetoothd 和 obexd
在蓝牙加载成功后,我们就可以启动bluetoothd和obexd(用于obex文件传输),执行:
/usr/libexec/bluetooth/bluetoothd –compat &
启动bluetoothd。bluetoothd成功启动后会在D-Bus的systembus上注册“org.bluez”,并通过其向外提供BlueZ5 API。我们可以通过D-Bus命令去查询BlueZ提供的API:
dbus-send --system --print-reply --dest=org.bluez org.freedesktop.DBus.Introspectable.Introspect
例如,我们可以通过以下命令查询/org/bluez路径下的bluez5 API。
dbus-send --system --print-reply --dest=org.bluez /org/bluez org.freedesktop.DBus.Introspectable.Introspect
如果查询不到API,则启动bluetoothd加上 -n -d参数,这样就能打开debug,打印出log信息:
/usr/libexec/bluetooth/bluetoothd -d -n –compat &
查询bluetoothd在D-Bus上提供的API时,有时会出现/org/bluez/hci0消失不见了,这种问题一般是由于使用brcm_patchram_plus加载fireware出现问题导致的。
在Bluetoothd成功启动后,如果需要实现obex功能,那么执行:
/usr/libexec/bluetooth/obexd -a -r ‘/’ &
启动obexd,obexd依赖于bluetoothd,obexd启动后将会自动接收文件(-a 参数),同时接收到的文件将会放在 / 目录下(由-r参数指定)。
同样obexd成功启动后,会在D-Bus的session bus上注册“org.bluez.obex”,并向外提供obex相关的API。我们依然可以通过D-Bus命令去查询这些API:
dbus-send --session --print-reply --dest=org.bluez.obex org.freedesktop.DBus.Introspectable.Introspect
例如,我们可以通过以下命令查询/org/bluez/obex路径下的API:
dbus-send --session --print-reply --dest=org.bluez.obex /org/bluez/obex org.freedesktop.DBus.Introspectable.Introspect
如果查询不到API,那么在启动obexd时加上“-n -d”参数就可以打开obexd的debug开关并打印出log:
/usr/libexec/bluetooth/obexd -n -d -a -r ‘/’ &
(5)通过D-Bus API 控制蓝牙
BlueZ5通过D-Bus提供API给第三方应用,所有的BlueZ API可以在BlueZ5源码的doc/目录下查到,这些API可以通过glib2.0中的GDBus来访问,下面的网址提供了所有可用的GDBus API:
https://developer.gnome.org/gio/stable/index.html
常用的Bluez5 API有adapter,agent,device以及profile,下面将具体介绍如何通过GDBus来访问这些常用BlueZ API:
1)监控目标D-Bus
GDBus中的g_bus_watch_name API可以很容易的实现对目标D-Bus的监控并获得GDBusConnection 对象:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
const gchar *service_name= "org.bluez";
gboolean opt_system_bus = TRUE;
guint watcher_id=0;
GBusNameWatcherFlags flags;
void on_name_appeared (GDBusConnection *connection,
const gchar *name,
const gchar *name_owner,
gpointer user_data)
{
g_print("Name %s on %s is owned by %s\n",
name,
opt_system_bus ? "the system bus" : "the session bus",
name_owner);
}
void on_name_vanished (GDBusConnection *connection,
const gchar *name,
gpointer user_data)
{
g_print("Name %s does not exist on %s\n",
name,
opt_system_bus ? "the system bus" : "the session bus");
}
/**
*connect and watch "org.bluez" on dbus.
*/
void watch_dbus(){
flags = G_BUS_NAME_WATCHER_FLAGS_NONE;
g_print("######watch_dbus######\n");
watcher_id = g_bus_watch_name (opt_system_bus ? G_BUS_TYPE_SYSTEM : G_BUS_TYPE_SESSION,
service_name,
flags,
on_name_appeared,
on_name_vanished,
NULL,
NULL);
}
2)创建GDBusProxy
在获得了GDBusConnection对象后,我们可以通过该对象创建GDBusProxy代理。GDBUS通过GDBusProxy来访问dbus 上的接口(interface)。下面以创建adapter接口代理为例说明如何如何创建目标接口代理并监控目标接口属性(Porperties)以及如何访问目标接口提供的API和属性:
1.创建adapter 接口代理并通过 “g-properties-changed” signal监控adapter接口属性改变。
const gchar *service_name= "org.bluez";
const gchar *object_path_bluez_hci0="/org/bluez/hci0";
const gchar *interface_adapter1="org.bluez.Adapter1";
GDBusProxy *default_adapter=NULL;
/**
*property change signal handler.
*/
void on_properties_changed (GDBusProxy *proxy,
GVariant *changed_properties,
const gchar* const *invalidated_properties,
gpointer user_data)
{
/* Note that we are guaranteed that changed_properties and
* invalidated_properties are never NULL
*/
if (g_variant_n_children (changed_properties) > 0)
{
GVariantIter *iter;
const gchar *key;
GVariant *value;
g_print (" *** Properties Changed:\n");
g_variant_get (changed_properties,
"a{sv}",
&iter);
while (g_variant_iter_loop (iter, "{&sv}", &key, &value))
{
gchar *value_str;
value_str = g_variant_print (value, TRUE);
g_print (" %s -> %s\n", key, value_str);
g_free (value_str);
}
g_variant_iter_free (iter);
}
if (g_strv_length ((GStrv) invalidated_properties) > 0)
{
guint n;
g_print (" *** Properties Invalidated:\n");
for (n = 0; invalidated_properties[n] != NULL; n++)
{
const gchar *key = invalidated_properties[n];
g_print (" %s\n", key);
}
}
}
/**
*Async callback of create_GDBusProxy.
*/
void adapter_proxy_ready(GObject *source_object,GAsyncResult *res,
gpointer user_data){
GError *error;
default_adapter=g_dbus_proxy_new_finish (res,&error);
if(default_adapter == NULL){
g_print("create adapter proxy failed.\n");
return;
}else{
g_print("create adapter proxy success.\n");
}
g_signal_connect (default_adapter,
"g-properties-changed",
G_CALLBACK (on_properties_changed),
NULL);
}
/**
*create adapter proxy.
*/
void create_adapter_proxy(GDBusConnection *connection){
if(connection == NULL){
return;
}
g_print("create_adapter_proxy.\n");
gpointer user_data;
g_dbus_proxy_new (connection,
G_DBUS_PROXY_FLAGS_NONE,
NULL,
service_name,
object_path_bluez_hci0,
interface_adapter1,
NULL,
adapter_proxy_ready,
user_data);
}
2.通过GDbusProxy 代理访问adapter接口的API adapter接口中提供了蓝牙扫描功能的相关API(void StartDiscovery() 和void StopDiscovery()),下面详细说明访问这两个API的方法:
/**
*check default adapter.
*/
gboolean check_default_adapter(){
if(default_adapter == NULL){
g_print("default_adapter is not available.\n");
return FALSE;
}
return TRUE;
}
void adapter_callback(GObject *source_object,
GAsyncResult *res,
gpointer user_data){
GError *error = NULL;
g_print("adapter_callback\n");
g_dbus_proxy_call_finish (default_adapter,
res,
&error);
if(error != NULL){
g_print("error: %s\n", g_dbus_error_get_remote_error(error));
}
}
/**
*set scan remote device start/stop.
*/
void cmd_scan(gboolean scan){
if(check_default_adapter() == FALSE){
return;
}
g_print("cmd_scan: %s\n",scan ? "on" : "off");
gchar *method_name;
if(scan){
method_name = "StartDiscovery";
}else{
method_name = "StopDiscovery";
}
gpointer user_data;
g_dbus_proxy_call (default_adapter,
method_name,
NULL,
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL,
adapter_callback,
user_data);
}
3.创建D-Bus Properties接口代理
#define INTERFACE_PROPERTY "org.freedesktop.DBus.Properties"
const gchar *interface_adapter1_property=INTERFACE_PROPERTY;
GDBusProxy *adapter_property_proxy=NULL;
/**
*Async callback of create_GDBusProxy.
*/
void adapter_property_proxy_ready(GObject *source_object,GAsyncResult *res,
gpointer user_data){
GError *error;
adapter_property_proxy=g_dbus_proxy_new_finish (res,&error);
if(adapter_property_proxy == NULL){
g_print("create adapter property proxy failed.\n");
return;
}else{
g_print("create adapter property proxy success.\n");
}
}
/**
*create adapter property proxy.
*/
void create_adapter_property_proxy(GDBusConnection *connection){
if(connection == NULL){
return;
}
g_print("create_adapter_property_proxy.\n");
gpointer user_data;
g_dbus_proxy_new (connection,
G_DBUS_PROXY_FLAGS_NONE,
NULL,
service_name,
object_path_bluez_hci0,
interface_adapter1_property,
NULL,
adapter_property_proxy_ready,
user_data);
}
4.通过通用的D-Bus的Properties接口访问adapter接口属性adapter接口中提供了“Alias”属性,通过Properties接口的“Set”方法设置该属性来改变蓝牙设备名。
/**
*check default adapter.
*/
gboolean check_adapter_property_proxy(){
if(adapter_property_proxy == NULL){
g_print("adapter_property_proxy is not available.\n");
return FALSE;
}
return TRUE;
}
void cmd_set_bluetooth_name(const gchar *name){
if(check_adapter_property_proxy() == FALSE){
return;
}
if(name == NULL){
g_print("error: cmd_set_bluetooth_name: name is null\n");
return;
}
g_print("cmd_set_bluetooth_name: name: %s\n",name);
const gchar *property_name = "Alias";
const gchar *method_name = "Set";
GVariant *alias=g_variant_new_string (name);
GError *error=NULL;
GVariant * result = g_dbus_proxy_call_sync (adapter_property_proxy,
method_name,g_variant_new(" (ssv)",interface_adapter1,property_name,alias),
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL,
&error);
if(error != NULL){
g_print("error: %s\n", g_dbus_error_get_remote_error(error));
}else{
//
}
}
3)创建GDBusObjectManager 代理
在获得了GDBusConnection对象后,我们可通过该对象创建GDBusObjectManager 代理。GDBUS通过GDBusObjectManager 来监控D-Bus上接口的添加删除,下面详细介绍如何创建GDBusObjectManager 代理并通过该代理监控device接口的动态添加删除。
1.创建GDBusObjectManager代理并通过GDBusObjectManager代理监控device接口的动态添加和删除
const gchar *service_name= "org.bluez";
const gchar *interface_device1="org.bluez.Device1";
GDBusObjectManager *objectmanager=NULL;
/**
*callback of "interface-added" signal.
*/
void on_interface_added(GDBusObjectManager *manager,
GDBusObject *object,
GDBusInterface *interface_){
g_print("######on_interface_added######.\n");
}
/**
*callback of "interface-removed" signal.
*/
void on_interface_removed(GDBusObjectManager *manager,
GDBusObject *object,
GDBusInterface *interface_){
g_print("######on_interface_removed######.\n");
}
void on_object_added(GDBusObjectManager *manager,
GDBusObject *object){
g_print("######on_object_added######.\n");
const gchar *object_path=g_dbus_object_get_object_path(object);
if(object_path != NULL){
g_print("object added : %s\n",object_path);
}else{
g_print("object_path is NULL");
return;
}
GDBusInterface *ifc=g_dbus_object_get_interface (object,interface_device1);
if(ifc != NULL){
//device added.
g_print("device added.\n");
}
}
void on_object_removed(GDBusObjectManager *manager,
GDBusObject *object){
g_print("######on_object_removed######.\n");
const gchar *object_path=g_dbus_object_get_object_path(object);
if(object_path != NULL){
g_print("object removed : %s\n",object_path);
}else{
g_print("object_path is NULL");
}
GDBusInterface *ifc=g_dbus_object_get_interface (object,interface_device1);
if(ifc != NULL){
//device removed.
g_print("device removed.\n");
}
}
/**
*async callback.
*/
void GDBusClientManager_Ready(GObject *source_object,GAsyncResult *res,
gpointer user_data){
GError *error=NULL;
objectmanager=g_dbus_object_manager_client_new_finish
(res,&error);
if(objectmanager== NULL){
g_print("create objectmanager failed. Error: %s.\n",
g_dbus_error_get_remote_error(error));
return;
}
g_print("create objectmanager success.\n");
//connect signal.
g_signal_connect (objectmanager,
"interface-added",
G_CALLBACK (on_interface_added),
NULL);
g_signal_connect (objectmanager,
"interface-removed",
G_CALLBACK (on_interface_removed),
NULL);
g_signal_connect (objectmanager,
"object-added",
G_CALLBACK (on_object_added),
NULL);
g_signal_connect (objectmanager,
"object-removed",
G_CALLBACK (on_object_removed),
NULL);
}
void destroyed(gpointer data) {
printf("Got a GDestroyNotify callback\n");
}
/**
*create GDBusClientManager of “org.bluez”
*/
void create_GDbusClientManager(GDBusConnection *connection){
g_print("######create_GDbusClientManager######\n");
if(connection == NULL){
g_print("Error: GDBusConnection is not available!");
return;
}
gpointer get_proxy_type_user_data;
gpointer user_data;
g_dbus_object_manager_client_new(connection,G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE,
service_name,"/",
NULL,user_data,destroyed,
NULL,GDBusClientManager_Ready,
user_data);
}
当然BlueZ5 也提供了bluetoothctl 和 obexctl两个命令行工具来访问Bluez5的D-Bus API,启动bluetoothctl 和 obexctl后输入 help可以查看到所有支持的操作,如果不需要图形界面的话,这两个命令行工具已经实现了大部分蓝牙功能。如果系统支持Gnome,也可以直接移植Gnome-bluetooth。