Home -- Michael's Blog :: Just Do IT -- Manuals

24.4. 蓝牙

作者: Pav Lucistnik. 中文翻译: 张 雪平 和 袁 苏义.

24.4.1. 简介

Bluetooth(蓝牙)是一项无线技术,用于建立带宽为2.4GHZ,波长为10米的私有网络。 网络一般是由便携式设备,比加手机(cellular phone),掌上电脑(handhelds)和膝上电脑(laptops))以ad-hoc形式组成。 不象其它流行的无线技术——Wi-Fi,Bluetooth提供了更高级的服务层面,像类FTP的文件服务、文件推送(file pushing)、语音传送、串行线模拟等等。

在FreeBSD里,蓝牙栈(Bluetooth stack)通过使用Netgraph框架(请看netgraph(4))来的实现。 大量的"Bluetooth USB dongle"由ng_ubt(4)驱动程序支持。 基于Broadcom BCM2033芯片组的Bluetooth设备可以通过ubtbcmfw(4)ng_ubt(4)驱动程序支持。 3Com Bluetooth PC 卡 3CRWB60-A 由 ng_bt3c(4) 驱动程序支持。 基于Serial 和 UART 的蓝牙设备由sio(4)ng_h4(4)hcseriald(8)。 本节介绍USB Bluetooth dongle的使用。 在FreeBSD 5.0及更新的系统里,都可以支持Bluetooth。

24.4.2. 插入设备

默认的Bluetooth设备驱动程序已存在于内核模块里。 接入设备前,你需要将驱动程序加载入内核:

# kldload ng_ubt

如果系统启动时Bluetooth设备已经存在于系统里,那么从/boot/loader.conf里加载这个模块:

ng_ubt_load="YES"

插入USB dongle。 控制台(console)(或syslog中)会出现类似如下的信息:

ubt0: vendor 0x0a12 product 0x0001, rev 1.10/5.25, addr 2 ubt0: Interface 0 endpoints: interrupt=0x81, bulk-in=0x82, bulk-out=0x2 ubt0: Interface 1 (alt.config 5) endpoints: isoc-in=0x83, isoc-out=0x3, wMaxPacketSize=49, nframes=6, buffer size=294

复制/usr/share/examples/netgraph/bluetooth/rc.bluetooth到一个合适的地方,如/etc/rc.bluetooth。 这个脚本用于启动和停止Bluetooth stack(蓝牙栈)。 最好在拔出设备前停止stack(stack),当然也不是非做不可。 启动stack(栈)时,会得到如下的输出:

# /etc/rc.bluetooth start ubt0 BD_ADDR: 00:02:72:00:d4:1a Features: 0xff 0xff 0xf 00 00 00 00 00 <3-Slot> <5-Slot> <Encryption> <Slot offset> <Timing accuracy> <Switch> <Hold mode> <Sniff mode> <Park mode> <RSSI> <Channel quality> <SCO link> <HV2 packets> <HV3 packets> <u-law log> <A-law log> <CVSD> <Paging scheme> <Power control> <Transparent SCO data> Max. ACL packet size: 192 bytes Number of ACL packets: 8 Max. SCO packet size: 64 bytes Number of SCO packets: 8

24.4.3. 主控制器接口(HCI)

主控制器接口(HCI)提供了通向基带控制器和连接管理器的命令接口及访问硬件状态字和控制寄存器的通道。 这个接口提供了访问蓝牙基带(Bluetooth baseband)功能的统一方式。 主机上的HCI层与蓝牙硬件上的HCI固件交换数据和命令。 主控制器的传输层(如物理总线)驱动程序提供两个HCI层交换信息的能力。

为每个蓝牙(Bluetooth)设备创建一个hci类型的Netgraph结点。 HCI 结点一般连接蓝牙设备的驱动结点(下行流)和L2CAP结点(上行流)。 所有的HCI操作必须在HCI结点上进行而不是设备驱动结点。 HCI结点的默认名是``devicehci''。 更多细节请参考ng_hci(4)的联机手册。

最常见的任务是发现在RF proximity中的蓝牙(Bluetooth)设备。 这个就叫做质询(inquiry)。 质询及HCI相关的操作可以由hccontrol(8)工具来完成。 以下的例子展示如何找出范围内的蓝牙设备。 在几秒钟内你应该得到一张设备列表。 注意远程主机只有被置于discoverable(可发现)模式才能答应质询。

% hccontrol -n ubt0hci inquiry
Inquiry result, num_responses=1
Inquiry result #0
       BD_ADDR: 00:80:37:29:19:a4
       Page Scan Rep. Mode: 0x1
       Page Scan Period Mode: 00
       Page Scan Mode: 00
       Class: 52:02:04
       Clock offset: 0x78ef
Inquiry complete. Status: No error [00]

BD_ADDR 是蓝牙设备的特定地址,类似于网卡的MAC地址。 需要用此地址与某个设备进一步地通信。 可以为BD_ADDR分配由人可读的名字(human readable name)。 文件/etc/bluetooth/hosts包含已知蓝牙主机的信息。 下面的例子展示如何获得分配给远程设备的可读名。

% hccontrol -n ubt0hci remote_name_request 00:80:37:29:19:a4
BD_ADDR: 00:80:37:29:19:a4
Name: Pav's T39

如果在远程蓝牙上运行质询,你会发现你的计算机是``your.host.name (ubt0)''。 分配给本地设备的名字可随时改变。

蓝牙系统提供点对点连接(只有两个蓝牙设备参与)和点对多点连接。在点对多点连接中,连接由多个蓝牙设备共享。 以下的例子展示如何取得本地设备的活动基带(baseband)连接列表。

% hccontrol -n ubt0hci read_connection_list
Remote BD_ADDR    Handle Type Mode Role Encrypt Pending Queue State
00:80:37:29:19:a4     41  ACL    0 MAST    NONE       0     0 OPEN

connection handle(连接柄) 在需要终止基带连接时有用。 注意:一般不需要手动完成。 栈(stack)会自动终止不活动的基带连接。

# hccontrol -n ubt0hci disconnect 41
Connection handle: 41
Reason: Connection terminated by local host [0x16]

参考hccontrol help 获取完整的HCI命令列表。 大部分HCI命令不需要超级用户权限。

24.4.4. 逻辑连接控制和适配协议(L2CAP)

逻辑连接控制和适配协议(L2CAP)为上层协议提供面向连接和无连接的数据服务,并提供多协议功能和分割重组操作。 L2CAP充许上层协议和应用软件传输和接收最大长度为64K的L2CAP数据包。

L2CAP 基于 通道(channel)的概念。 通道(Channel)是位于基带(baseband)连接之上的逻辑连接。 每个通道以多对一的方式绑定一个单一协议(single protocol)。 多个通道可以绑定同一个协议,但一个通道不可以绑定多个协议。 每个在通道里接收到的L2CAP数据包被传到相应的上层协议。 多个通道可共享同一个基带连接。

为每个蓝牙(Bluetooth)设备创建一个l2cap类型的Netgraph结点。 L2CAP 结点一般连接HCI结点(下行流)和蓝牙设备的驱动结点(上行流)。 L2CAP结点的默认名是``devicel2cap''。 更多细节请参考ng_l2cap(4)的联机手册。

一个有用的命令是l2ping(8),它可以用来 ping 其它设备。 一些蓝牙实现可能不会返回所有发送给它们的数据,所以下例中的 0 bytes 是正常的。

# l2ping -a 00:80:37:29:19:a4
0 bytes from 0:80:37:29:19:a4 seq_no=0 time=48.633 ms result=0
0 bytes from 0:80:37:29:19:a4 seq_no=1 time=37.551 ms result=0
0 bytes from 0:80:37:29:19:a4 seq_no=2 time=28.324 ms result=0
0 bytes from 0:80:37:29:19:a4 seq_no=3 time=46.150 ms result=0

l2control(8)工具用于在L2CAP上进行多种操作。 以下这个例子展示如何取得本地设备的逻辑连接(通道)和基带连接的列表:

% l2control -a 00:02:72:00:d4:1a read_channel_list
L2CAP channels:
Remote BD_ADDR     SCID/ DCID   PSM  IMTU/ OMTU State
00:07:e0:00:0b:ca    66/   64     3   132/  672 OPEN
% l2control -a 00:02:72:00:d4:1a read_connection_list
L2CAP connections:
Remote BD_ADDR    Handle Flags Pending State
00:07:e0:00:0b:ca     41 O           0 OPEN

另一个诊断工具是btsockstat(1)。 它完成与netstat(1)类似的操作,只是用了蓝牙网络相关的数据结构。 以下这个例子显示与l2control(8)相同的逻辑连接。

% btsockstat
Active L2CAP sockets
PCB      Recv-Q Send-Q Local address/PSM       Foreign address   CID   State
c2afe900      0      0 00:02:72:00:d4:1a/3     00:07:e0:00:0b:ca 66    OPEN
Active RFCOMM sessions
L2PCB    PCB      Flag MTU   Out-Q DLCs State
c2afe900 c2b53380 1    127   0     Yes  OPEN
Active RFCOMM sockets
PCB      Recv-Q Send-Q Local address     Foreign address   Chan DLCI State
c2e8bc80      0    250 00:02:72:00:d4:1a 00:07:e0:00:0b:ca 3    6    OPEN

24.4.5. RFCOMM 协议

RFCOMM 协议提供基于L2CAP协议的串行端口模拟。 该协议基于ETSI TS 07.10标准。 RFCOMM是一个简单的传输协议,附加了摸拟9针RS-232(EIATIA-232-E)串行端口的定义。 RFCOMM 协议最多支持60个并发连接 (RFCOMM通道) 。

为了实现RFCOMM,运行于不同设备上的应用程序建立起一条关于它们之间通信段的通信路径。 RFCOMM实际上适用于使用串行端口的应用软件。 通信段是一个设备到另一个设备的蓝牙连接(直接连接)。

RFCOMM关心的只是直接连接设备之间的连接,或在网络里一个设备与modem之间的连接。 RFCOMM 能支持其它的配置,比如在一端通过蓝牙无线技术通讯而在另一端使用有线接口。

在FreeBSD,RFCOMM 协议在蓝牙套接字层(Bluetooth sockets layer)实现。

24.4.6. 设备的结对(Pairing of Devices)

默认情况下,蓝牙通信是不需要验证的,任何设备可与其它任何设备对话。 一个蓝牙设备(比如手机)可以选择通过验证以提供某种特殊服务(比如拨号服务)。 蓝牙验证一般使用PIN码(PIN codes)。 一个PIN码是最长为16个字符的ASCII字符串。 用户需要在两个设备中输入相同的PIN码。 用户输入了PIN 码后,两个设备会生成一个连接密匙(link key)。 接着连接密钥可以存储在设备或存储器中。 连接时两个设备会使用先前生成的连接密钥。 以上介绍的过程被称为结对(pairing)。 注意如果任何一方丢失了连接密钥,必须重新进行结对。

守护进程hcsecd(8)负责处理所有蓝牙验证请求。 默认的配置文件是/etc/bluetooth/hcsecd.conf。 下面的例子显示一个手机的PIN码被预设为``1234'':

device {
        bdaddr  00:80:37:29:19:a4;
        name    "Pav's T39";
        key     nokey;
        pin     "1234";
      }

PIN码没有限制(除了长度)。 有些设备(例如蓝牙耳机)会有一个预置的PIN码。-d 开关强制 hcsecd(8) 守护进程处于前台, 因此很容易看清发生了什么。 设置远端设备准备接收结对(pairing),然后启动蓝牙连接到远端设备。 远端设备应该回应接收了结对并请求PIN码。 输入与hcsecd.conf中一样的PIN码。 现在你的个人计算机已经与远程设备结对了。 另外你也可以在远程设备上初始结点。 以下是简单的 hcsecd 服务输出样本:

hcsecd[16484]: Got Link_Key_Request event from 'ubt0hci', remote bdaddr 0:80:37:29:19:a4
hcsecd[16484]: Found matching entry, remote bdaddr 0:80:37:29:19:a4, name 'Pav's T39', link key doesn't exist
hcsecd[16484]: Sending Link_Key_Negative_Reply to 'ubt0hci' for remote bdaddr 0:80:37:29:19:a4
hcsecd[16484]: Got PIN_Code_Request event from 'ubt0hci', remote bdaddr 0:80:37:29:19:a4
hcsecd[16484]: Found matching entry, remote bdaddr 0:80:37:29:19:a4, name 'Pav's T39', PIN code exists
hcsecd[16484]: Sending PIN_Code_Reply to 'ubt0hci' for remote bdaddr 0:80:37:29:19:a4

24.4.7. 服务发现协议(SDP)

服务发现协议 (SDP)提供给客户端软件一种方法,它能发现由服务器软件提供的服务及属性。 服务的属性包括所提供服务的类型或类别,使用该服务所需要的机制或协议。

SDP包括SDP服务器和SDP客户端之间的通信。 服务器维护一张服务记录列表,它介绍服务器上服务的特性。 每个服务记录包含关于单个服务的信息。 通过发出SDP请求,客户端会得到服务记录列表的信息。 如果客户端(或者客户端上的应用软件)决定使用一个服务,为了使用这个服务它必须与服务提供都建立一个独立的连接。SDP提供了发现服务及其属性的机制,但它并不提供使用这些服务的机制。

一般地,SDP客户端按照服务的某种期望特征来搜索服务。 但是,即使没有任何关于由SDP服务端提供的服务的预设信息,有时也能令人满意地发现它的服务记录里所描述的是哪种服务类型。 这种发现所提供服务的过程称为浏览(browsing)

蓝牙SDP服务端sdpd(8) 和命令行客户端 sdpcontrol(8) 都包括在了标准的FreeBSD 安装里。 下面的例子展示如何进行SDP浏览查询。

% sdpcontrol -a 00:01:03:fc:6e:ec browse
Record Handle: 00000000
Service Class ID List:
        Service Discovery Server (0x1000)
Protocol Descriptor List:
        L2CAP (0x0100)
                Protocol specific parameter #1: u/int/uuid16 1
                Protocol specific parameter #2: u/int/uuid16 1

Record Handle: 0x00000001
Service Class ID List:
        Browse Group Descriptor (0x1001)

Record Handle: 0x00000002
Service Class ID List:
        LAN Access Using PPP (0x1102)
Protocol Descriptor List:
        L2CAP (0x0100)
        RFCOMM (0x0003)
                Protocol specific parameter #1: u/int8/bool 1
Bluetooth Profile Descriptor List:
        LAN Access Using PPP (0x1102) ver. 1.0

... 等等。 注意每个服务有一个属性(比如RFCOMM通道)列表。 根据服务你可能需要为一些属性做个注释。 有些“蓝牙实现(Bluetooth implementation)”不支持服务浏览,可能会返回一个空列表。 这种情况,可以搜索指定的服务。 下面的例子展示如何搜索OBEX Object Push (OPUSH)服务:

% sdpcontrol -a 00:01:03:fc:6e:ec search OPUSH

要在FreeBSD里为蓝牙客户端提供服务,可以使用sdpd(8)服务:

# sdpd

需要为远端提供蓝牙服务的本地的服务程序会使用本地SDP进程注册服务。 像这样的程序就有rfcomm_pppd(8)。 一旦启动它,就会使用本地SDP进程注册蓝牙LAN服务。

使用本地SDP进程注册的服务列表,可以通过本地控制通道发出SDP浏览查询获得:

# sdpcontrol -l browse

24.4.8. 拨号网络(DUN)和使用PPP(LAN)层面的网络接入

拨号网络(DUN)配置通常与modem和手机一起使用。 如下是这一配置所涉及的内容:

使用PPP(LAN)层面的网络接入常使用在如下情形:

在 FreeBSD中,两个层面使用ppp(8)rfcomm_pppd(8)(一种封装器,可以将RFCOMM蓝牙连接转换为PPP可操作的东西)来实现。 在使用任何层面之前,一个新的PPP标识必须在/etc/ppp/ppp.conf中建立。 想要实例请参考rfcomm_pppd(8)

在下面的例子中,rfcomm_pppd(8)用来在NUN RFCOMM通道上打开一个到BD_ADDR为00:80:37:29:19:a4的设备的RFCOMM连接。 具体的RFCOMM通道号要通过SDP从远端设备获得。 也可以手动指定通RFCOMM,这种情况下rfcomm_pppd(8)将不能执行SDP查询。 使用sdpcontrol(8)来查找远端设备上的RFCOMM通道。

# rfcomm_pppd -a 00:80:37:29:19:a4 -c -C dun -l rfcomm-dialup

为了提供PPP(LAN)网络接入服务,必须运行sdpd(8)服务。 一个新的LAN客户端条目必须在 /etc/ppp/ppp.conf 文件中建立。 想要实例请参考rfcomm_pppd(8)。 最后,在有效地通道号上开始RFCOMM PPP服务。 RFCOMM PPP 服务会使用本地SDP进程自动注册蓝牙LAN服务。 下面的例子展示如何启动RFCOMM PPP服务。

# rfcomm_pppd -s -C 7 -l rfcomm-server

24.4.9. OBEX 对象推送 (OBEX Object Push - OPUSH) 层面

OBEX协议被广泛地用于移动设备之间简单的文件传输。 它的主要用处是在红外线通信领域,被用于笔记本或手持设备之间的一般文件传输。

OBEX 服务器和客户端由第三方软件包obexapp实现,它可以从 comms/obexapp port 安装。

OBEX 客户端用于向OBEX服务器推入或接出对象。 一个对像可以是(举个例子)商业卡片或约会。 OBEX 客户能通过SDP从远程设备取得RFCOMM通道号。 这可以通过指定服务名代替RFCOMM通道号来完成。 支持的服务名是有: IrMC, FTRN and OPUSH. 也可以用数字来指定RFCOMM通道号。 下面是一个OBEX会话的例子,一个设备信息对像从手机中被拉出,一个新的对像被推入手机的目录。

% obexapp -a 00:80:37:29:19:a4 -C IrMC
obex> get
get: remote file> telecom/devinfo.txt
get: local file> devinfo-t39.txt
Success, response: OK, Success (0x20)
obex> put
put: local file> new.vcf
put: remote file> new.vcf
Success, response: OK, Success (0x20)
obex> di
Success, response: OK, Success (0x20)

为了提供OBEX推入服务,sdpd(8)必须处于运行状态。 必须创建一个根目录用于存放所有进入的对象。 根文件夹的默认路径是/var/spool/obex。 最后,在有效的RFCOMM通道号上开始OBEX服务。 OBEX服务会使用SDP进程自动注册OBEX对象推送(OBEX Object Push)服务。 下面的例子展示如何启动OBEX服务。

# obexapp -s -C 10

24.4.10. 串口(SP)层面

串口(SP)层面允许蓝牙设备完成RS232(或类似)串口线的仿真。 这个层面所涉及到情形是,通过虚拟串口使用蓝牙代替线缆来处理以前的程序。

工具rfcomm_sppd(1)来实现串口层。 “Pseudo tty”用来作为虚拟的串口。 下面的例子展示如何连接远程设备的串口服务。 注意你不必指定RFCOMM通道——rfcomm_sppd(1)能够通过SDP从远端设备那里获得。 如果你想代替它的话,可以在命令行里指定RFCOMM通道来实现:

# rfcomm_sppd -a 00:07:E0:00:0B:CA -t /dev/ttyp6 rfcomm_sppd[94692]: Starting on /dev/ttyp6...

一旦连接上,“pseudo tty”就可以充当串口了:

# cu -l ttyp6

24.4.11. 问题解答

24.4.11.1. 不能连接远端设备

一些较老的蓝牙设备并不支持角色转换(role switching)。 默认情况下,FreeBSD接受一个新的连接时,它会尝试进行角色转换并成为主控端(master)。 不支持角色转换的设备将无法连接。 注意角色转换是在新连接建立时运行的,因此如果远程设备不支持角色转换,就不可能向它发出请求。 一个 HCI 选项用来在本地端禁用角色转换。

# hccontrol -n ubt0hci write_node_role_switch 0

24.4.11.2. 有些东西正发生错误,我能查看发生了什么吗?

是的,你能。 使用第三方软件包hcidump-1.5,这个可以从http://www.geocities.com/m_evmenkin/下载到。 工具hcidump 类似于 tcpdump(1)。 它可以用来显示蓝牙数据包的内容并将其存入文件中。

Michael's Blog :: Just Do IT -- manuals