理解和使用蓝牙BlueZ中的FTP与PBAP协议
作者:刘旭晖 Raymond转载请注明出处
Email:colorant@163.com
BLOG:http://blog.csdn.net/colorant/
主页:http://sites.google.com/site/rgbbones
在蓝牙Bluetooth协议栈中,文件传输协议FTP(File Transfer Profile)和电话号码簿访问协议PBAP(Phonebook Access Profile)都是构建在对象交换协议OBEX的基础上(此外还有OPP和Synchronization),而在BlueZ的具体实现中,FTP和PBAP都是基于类似的底层API接口和调用路径实现的,两者在内部实现中有很大的相似之处。因为在最近的工作中参与了PBAP的代码实现,所以在这里记录一下自己对这部分内容的理解和相关协议的使用方法。
1 相关说明
1.1 相关网站资源
BlueZ的官方网址:http://www.bluez.org/这里提供最新的Release版本下载
http://git.kernel.org/ BlueZ 相关的几个Git代码仓库都放在这里,包括我们要用到的BlueZ,bluez-gnome,openobex和obexd
Bluez的Wiki:http://wiki.bluez.org/wiki/这里提供Bluez相关的Howto等文档资源
http://en.wikipedia.org/wiki/VCard vCard格式等相关内容
相关邮件列表:
http://vger.kernel.org/vger-lists.html#linux-bluetooth Bluez开发者活动的地方,有什么Bug之类的怀疑,还有编程接口之类的问题,就发到这里吧。
IRC Channel:
irc://freenode/obexd
irc://freenode/bluez
1.2 工作环境
因为在BlueZ中FTP的实现最近做了一些变更,而PBAP则是新实现的Profile,所以要让这两者能正常工作,需要使用大于4.22版本的BlueZ,同时,openobex和obexd需要使用2008/12/24日以后的版本。按Release版本号来说,应该相应的会是1.5和0.9以后的版本(到今天2009/1/4为止,还没有正式发布版本 :)
2 基本工作原理
2.1 代码框架
基本上来说,openobex实现了obex protocol中相关的API接口,实现了对Connect / Disconnect / Get / Put / Setpath / Abort 等command的支持。
而在obexd的代码中,则实现了对FTP、PBAP、OPP等profile的支持(包括Server端的代码和Client端的代码)
2.2 大致工作流程
因为基于OBEX的协议,基本上都会包括Server和Client两端,obexd中实现的FTP和PBAP也不例外。Obexd和上层应用的交互是通过DBUS接口来实现的。详细的dbus接口API定义可以看源码包中doc目录下的相关文档
2.2.1 Server端启动流程
Obexd启动后,首先会初始化Manager模块,在dbus的org.openobex service中注册一个org.openobex.Manager 接口,主要用来注册Agent代理,和发送session、transfer等数据传输相关操作的signal。
在加载Plugin后,obexd会根据启动命令行传入的参数,相应的加载FTP、OPP、PBAP等相关Profile的server端代码,并在SDP数据库中添加相关server的记录,便于客户端查找相关服务。
对于PBAP来说,为了能够支持从不同的数据来源获取电话号码簿信息,例如不同的电话协议栈或者其它联系人地址簿来源(例如从MAIL相关应用的地址簿数据库中),Server端的实现是分成common的接口框架和具体的Plugin来实现的。Common的接口框架负责和obex交互,应答从client端发送过来的请求,并调用具体的Plugin获取应答所需的联系人地址簿信息
对于FTP来说,上层应用还需要通过Manager接口在DBUS上注册一个Agent对象用来获取和处理权限和文件路径等相关信息。在test目录下可以找到一个python脚本的示例程序:simple-agent
2.2.2 Client端调用流程
在Client端,obex-client同样是作为后台守护进程来实现的,启动后,会在dbus的org.openobex.client service 中注册一个org.openobex.Client接口,用来接收上层应用发送的具体操作请求。
对于FTP和PBAP来说,应用程序首先都要先通过org.openobex.Client接口创建一个数据传输的Session并在DBUS上注册Session相关接口和函数。根据创建session时传递的参数,obex-client会加载FTP或PBAP的相关代码,在DBUS中注册FTP或PBAP相关的具体接口函数。应用程序进一步调用这些接口上的相关函数实现所需的操作。
FTP和PBAP client端同样可以通过Session的接口,注册一个Agent(不是必需的)用来显示数据传输的进程,或者中断数据传输。
3 具体使用方法
首先,BlueZ的连接配对等流程和本文档无关,具体可以参考其他相关文档或者我的另一篇文档:ARM平台上BlueZ的移植和使用和配置(当然因为版本和平台不同,多少还是有些区别),这里就不在重复。
因为PBAP和FTP的相似性,而FTP在正式的代码中还没有提交正式的sample测试脚本,所以这里仅以PBAP为例介绍相关的函数接口和调用流程
3.1 PBAP相关函数接口的使用
PBAP的DBUS函数接口API文档可以在doc/client-api.txt中找到,在那里我描述了PBAP具体的每一个函数接口。如果想要更深入的了解相关的API,还可以参考PBAP的SPEC文档:
http://www.bluetooth.com/NR/rdonlyres/58FC38BF-9ED6-49FF-81CF-E0B95B130D72/7761/PBAP_SPEC_V10r00.pdf
此外,test/pbap-client 是一个基于python的sample测试脚本。
基本上,看上述文档和脚本应该就没问题了,不过下面还是就sample脚本中PBAP相关的操作做一些解释吧:
#!/usr/bin/python
import sys
import dbus
bus = dbus.SessionBus()
client = dbus.Interface(bus.get_object("org.openobex.client", "/"),
"org.openobex.Client")
到这里为止得到org.openobex.Client的接口对象,如果obexd已经由Make Install安装的话,obex-client可以不需要手工启动,dbus会根据相关的service文件自动启动obex-client。
session_path = client.CreateSession({"Destination": sys.argv[1], "Target": "PBAP"})
为PBAP创建一个Session,这里Destination后面跟的参数是server端的BT地址,所以该脚本的命令行调用形式类似这样:
./pbap-client 11:22:33:44:55:66
如果在指定的BT地址上没有server端的进程(OBEXD)在运行,或者没有对应的服务,脚本执行到这里就会出错退出。
pbap = dbus.Interface(bus.get_object("org.openobex.client", session_path),
"org.openobex.PhonebookAccess")
得到PBAP对应的接口对象
session = dbus.Interface(bus.get_object("org.openobex.client", session_path),
"org.openobex.Session")
得到session接口对象,这一步如果你不需要注册Agent代理的话,可以不需要。
pbap.Select("int", "pb")
选择server端内部的Phonebook对象。在PBAP协议中,Phonebook对象的位置分为内部和外部SIM卡上的两类。 而具体的phonebook对象,又分pb:联系人地址簿,ich:呼入电话记录,och:呼出电话记录,mch:未接电话记录,cch:复合电话记录(呼入 + 呼出 + 未接)等几类。通常来说Server端都会支持内部的联系人地址簿,而SIM卡上的phonebook对象,有些手机可能不会支持。
ret = pbap.PullAll()
print "%s" % (ret)
获取并打印当前选定的phonebook对象的内容,具体信息是以vCard的格式返回的字符串信息。
ret = pbap.GetSize()
print "Size = %d/n" % (ret)
得到当前phonebook对象的大小(即有多少个vCard条目)
ret = pbap.List()
for item in ret:
print "%s : %s" % (item[0], item[1])
列出当前Phonebook对象的vCard条目,返回结果是以结构体数组的形式组织的,每个结构体内包含一对字符串对,分别对应了vCard条目本身的名字(如0.vcf)和其中联系人的名字信息。
if len(ret) > 0:
ret = pbap.Pull(ret[0][0])
print "%s" % (ret)
Pull函数用来获取当前phonebook对象中,具体的某一条vCard的内容,输入参数为vCard条目的名字,例如1.vcf。需要注意的一点是,vCard条目的名字可能并不是连续的,最好严格按照List函数所获得的信息来调用该函数。
ret = pbap.ListFilterFields()
for item in ret:
print "%s" % (item)
因为PBAP协议中可以设置返回的vCard信息中应该包含的信息,你可以设置PBAP返回vCard中的所有内容,或着只返回名字,电话等部分内容,所以在PBAP Client端的DBUS接口中,我们提供了一系列的函数用来设置Filter参数,sample脚本中示范的是列出当前的Filter参数。
此外,还有一些配置相关的函数没有在sample脚本中一一示例,请参考前面提到的API文档。