树莓派开始,玩转Linux10:玩转蓝牙
蓝牙是一个使用广泛的无线通信协议,这两年又随着物联网概念进一步推广。本章介绍蓝牙协议,特别是低功耗蓝牙,并用树莓派来实践。树莓派3中内置了蓝牙模块。树莓派通过UART接口和该模块通信。树莓派1和树莓派2中没有内置的蓝牙模块,不过你可以通过USB安装额外的蓝牙适配器。、本章以树莓派3为基础,介绍蓝牙通信。
蓝牙由爱立信创制,旨在实现不同设备之间的无线连接。蓝牙无线通信的频率为2.4GHz,和Wi-Fi一样,都属于特高频。相对于低频信号来说,高频传输的速度比较快,穿透能力强,但传输距离受限。在没有遮蔽和干扰的情况下,蓝牙设备的最大通信距离能达到30米。但大多数情况下,蓝牙的实际通信距离在2到5米。相比之下,使用低频433MHz的对讲机设备,其通信距离很容易超过百米。因此,蓝牙常用于近距离的无线设备,比如无线鼠标和键盘。蓝牙的标志如图所示。蓝牙的工作流程可以分为下面三个基本步骤。
· 广播/扫描:通信的一方向外广播自己的信息,另一方通过扫描知道自己周边有哪些蓝牙设备在广播,这些设备的地址是什么,以及是否可以连接,如图所示。
· 连接:通信的一方向另一方发起连接请求,双方通过一系列的数据交换建立连接,如图所示。
· 数据通信。
根据细节上的差别,蓝牙通信又细分为两种:经典蓝牙和低功耗蓝牙。
早期的蓝牙通信方式称为经典蓝牙(Classic Bluetooth)。经典蓝牙中的数据传输协议是串行仿真协议RFCOMM。RFCOMM仿真了常见的串口连接。
数据从一端输入,从另一端取出。经典蓝牙的开发非常简单。基于串口开发的有线鼠标程序,就可以直接用于RFCOMM连接的无线鼠标程序。此外,经典蓝牙可以快速传输数据。因此,诺基亚N95这样的早期智能手机,也用RFCOMM来互传图片和文件。
2.BLE介绍:
经典蓝牙的缺点是比较耗电。后来,诺基亚发明了一种可以降低功耗的蓝牙通信方式。2010年出台的蓝牙4.0把这种通信方式规范为"低功耗蓝牙"(BLE,Bluetooth Low Energy)。BLE把通信双方分为非对称的双方,尽量让其中的一方承担主要的开销,减轻另一方的负担。举例来说,当手环与手机通信时,手环电量少,而且需要长时间待机。BLE通信的主要负担可以放在电量较充裕且充电方便的手机一侧,从而减少手环的能耗。
BLE通信一般也包含广播/扫描的步骤。主动发起广播的设备称为外设(Peripheral),扫描设备称为中心设备(Central)。BLE连接成功之后,就可以开始数据传输。BLE的数据传输协议是ATT协议和GATT协议。ATT是GATT的基础。ATT协议把通信双方分为服务器(Server)和客户端(Client)。客户端主动向服务器发起读写操作。需要注意的是,ATT中的服务器和客户端,与广播阶段的外设和中心设备相互独立。当然,在手环这样的应用场景下,外设通常也是服务器。ATT协议以属性(Attribute)为单位进行该数据传输。一个属性的格式有以下四个部分:
我们分别来理解属性的不同部分。
· handle:句柄,包括了属性的唯一编号,长度为16位。
· type:属性类型。每种类型用一个UUID编号。
· value:属性值。
· permission:属性权限,分为无、可读、可写、可读写。
服务器储存了多个属性。当客户端向服务器发起请求时,服务器会把自己的属性列表发给客户端。随后,客户端可以向服务器读取或写入某一个属性值。用读写的方式,通信双方实现了双向通信。
以智能手表为例。智能手表和手机配对后,手机可以用读的方式获得智能手表中某个属性下保存的步数,也可以用写的方式写入另一个属性负责的时间。在读写操作中,都是由客户端主动,服务器只能被动应答。ATT还提供了通知(Notification)的工作方式。当服务器改变了某个属性值时,可以主动通知订阅了该属性值的客户端。智能手表中的手势识别,就可以通过通知的方式告知手机。这样手机就可以实时地获知手势改变信息了。
GATT协议构建在ATT协议之上,为属性提供了组织形式。GATT协议的最小组织单元是特征(Characteristic),可以由数条属性组成。表就是一个特征,用于传输红外测温获得的数据。这个例子来自一款可以进行蓝牙连接的硬件设备 [1] ,该设备用BLE发送温度等传感器的测量数据。
特征的第一条是声明,其类型是0x2803。这条声明的value部分又可
以细分为三部分。
· 最开始的0x12,称为特征属性(Characteristic Properties),是GATT协议层面上的权限控制 。
· 随后的0x25,表示了特征数据所在的句柄。因此,0x25的属性值,就是红外温度的真正数值。我们顺着查看0x25的值,可以看到此时的读数为0。
· 剩下的部分包含了该特征的UUID,总共128位。写成UUID的顺序,即为F000-AA01-0451-4000-B000-000000000000。除了128位的UUID,蓝牙官方还提供了16位的UUID可供使用。可以看到,一个特征至少需要两个属性,一个用于声明,另一个用于储存它的数据。除此之外,特征还有被称为描述符(Descriptor)的额外描述信息。每个描述符占据一行。比如0x0027这个描述符,其属性
值是:
翻译成ASCII就是:
Temp Data是温度数据(Temperature Data)的简写,所以这里说明了数据是温度数据。此外,温度单位、测量频率等描述信息也经常会以描述符的形式放入特征中。在下一个特征声明出现前的属性,都是该特征的描述符。
再来看更高级的组织单位------服务(Service)。一个服务也有行属性作为声明,其类型UUID是0x2800。声明属性的值就是该服务的128位UUID。蓝牙官方也提供了16位的UUID,预留给特定的服务 。在下一个服务声明出现前的属性都属于该服务,比如表12-2中从0x0023到0x002D的属性。
表中包含了一个与红外温度计相关的服务。该服务包括了三个特征。第一个特征从0x24开始,到0x27结束。这个特征就是前面已经介绍过的传输红外感温数据的特征。第二个特征从0x28到0x2A,用于设置红外温度计参数。第三个特征是从0x2B到0x2D,用于设置测温频率。句柄0x002E之后,开始了一个新的服务。服务和特征都是属性的组织形式。客户端可以向服务器请求服务和特征列表,然后对其进行操作。GATT还提供了规范(Profile)。一个规范可以包括多个服务。不过,规范并不像前面两者那样存在于服务器中。规范是一种标准,用于说明一个特型设备应该有哪些服务。比如,HID(Human Interface Device)这种规范,就说明了蓝牙输入设备应该提供的服务。
3.Bluez:
我们用树莓派来深入实践前面学到的蓝牙知识。首先要在树莓派上安装必要的工具。BlueZ是Linux官方的蓝牙协议栈,你可以通过BlueZ提供的接口进行丰富的蓝牙操作。Raspbian中已经安装了BlueZ,笔者使用的BlueZ版本是5.43,你可以检查自己的BlueZ版本:
低版本的BlueZ对低功耗蓝牙的支持有限。如果使用的Bluez版本低于5.43,那么请升级BlueZ的版本。
你可以用下面的命令检查BlueZ的运行状态:
笔者返回结果是:
可以看到,蓝牙服务已经打开,并在正常运行。
你可以用下面的命令手动启动或关闭蓝牙服务:
此外,还可以让蓝牙服务随系统启动:
4.了解树莓派上的蓝牙:
在Raspbian中,基本的蓝牙操作可以通过BlueZ中的bluetoothctl进行。该命令运行后,将进入一个的Shell。这个Shell是由BlueZ提供的,与Linux系统的Shell不同。这个Shell中支持蓝牙相关的一些命令,比如输入:
将显示树莓派上可用的蓝牙模块,如:
运行scan命令,开启扫描:
扫描启动后,用devices命令可以打印扫描到蓝牙设备的MAC地址和名称,如:
此外,还可以用help命令获得帮助,使用结束后,可以用exit命令退出bluetoothctl。
除了bluetoothctl,在系统Shell中可以通过hciconfig来控制蓝牙模块。
比如,我们可以启动蓝牙模块:
下面的命令可以关闭蓝牙模块:
命令中的"hci0"指的是0号HCI设备,即树莓派的蓝牙适配器。
还可以用下面的命令来查看蓝牙设备的工作日志:
BlueZ本身还提供了连接和读写工具,但不同版本的BlueZ相关功能的差异比较大,而且使用起来不太方便,所以下面使用Node.js的工具来实现更深入的开发。
5.树莓派作为BLE外设:
尝试用树莓派进行BLE通信。我们先把一个树莓派改造成BLE外设,同时它也将充当连接建立后的服务器。这个过程较为复杂,你可以借用Node.js下的bleno库。
首先,安装Node.js:
然后,安装bleno:
运行bleno中pizza的例子:
你可以在node_modules/bleno/examples/pizza/中看到源代码,或者到Github网站查看。
这个名为pizza的例子提供了一个关于披萨的服务,它的UUID是
1333-3333-3333-3333-3333-333333333337。
服务中包含了三个特征,
分别是用于披萨饼选项、配料参数和烤披萨,如表所示。
通过这些特征,我们可以对树莓派进行BLE读写。读写操作会作用于一个代表披萨的对象。披萨饼选项如表所示。
配料是一个8位的参数,如表所示,每一位代表了一种配料。当这一位是1时,那么说明添加该配料:
因此,0x1A代表了添加MUSHROOMS、BLACK_OLIVES、CANADIAN_BACON,即蘑菇、黑橄榄、加拿大培根肉,味道应该不错。
对于烤披萨来说,写操作设定了烘烤的温度和时间。时间到了之后,中心设备会发出通知,告诉客户端烘烤完成。下一步将用另一个树莓派作为BLE中心设备。即使你没有另一个树莓派,你也可以用手机App 来测试BLE外设。
6.树莓派作为BLE中心设备:
我们拿另一个作为BLE的中心设备进行扫描,并发起连接请求。连接建立后,该服务器将充当客户端。和bleno对应,Node.js下有一个叫noble的项目,可以便捷地完成这一任务。
首先,安装noble:
noble中有一个同样名为pizza的例子,不过这个例子实现的是客户端。
运行该例子:
这个例子将自动执行扫描、连接、服务发现、数据传输的全过程。如果把bleno和noble部署到两个树莓派上,就可以在这两个树莓派之间进行蓝牙通信了。
如果想自定义开发,那么可以在node_modules/noble/examples/pizza/上参考源代码,或者到Github查看。
7.树莓派作为Beacon:
苹果在BLE的基础上推出了iBeacon协议。iBeacon使用了BLE的广播部分,但不建立连接。
一个遵守iBeacon协议的外设被称为Beacon。Beacon会广播自己的身份信息和发射信号的强度。中心设备接到广播之后,除了可以获知Beacon的身份之外,还能通过信号的衰减算出自己与Beacon的距离。在一个典型的超市应用场景中,每件商品可以带上一个Beacon。消费者可以用手机看到自己周围有哪些商品,工作人员也可以用手机来清点货物。商家还可以在服务器上提供商品相关的质保、促销等信息。用户可以根据Beacon的编号,获得这些附加信息。我们把配备了蓝牙模块的树莓派改造成一个Beacon。既然Beacon只使用了蓝牙中的广播,那么应该关闭树莓派的扫描,打开广播,并且不接受蓝牙连接。用下面的命令来关闭扫描:
然后让蓝牙模块开始广播,并且在广播中不接受连接:
把广播信息改为符合iBeacon协议的内容:
上面的命令附加了一串16进制信息。其中0x08说明了整条信息是蓝
牙命令,0x0008说明后面的内容将作为广播信息。
1E是广播信息开始的标志。按照蓝牙通信的规定,广播信息最多有
31个字节。1E后面的广播信息分为两组。
· 第一组:0201 1A
· 第二组:1A FF 4C 00 02 15 63 6F 3F 8F 64 91 4B EE 95 F7 D8
CC 64 A8 63 B5 4B EE 00 02 C5
每一组开始的一个字节说明了该组信息的长度。02说明了两字节,
1A说明是26个字节。随后一个字节说明了该组信息的类型。第一组的
01说明了该组信息是蓝牙控制标志,第二组的FF说明了该组是蓝牙制造
商相关信息。
我们来看第二组信息的细节:
· 4C 00是制造商信息,即苹果。
· 02 15是iBeacon协议标识。
· 63 6F 3F 8F 64 91 4B EE 95 F7 D8 CC 64 A8 63 B5是设备的
UUID,通常是用户编号。
· 00 01是主编号(Major)。
· 00 02是次编号(Minor)。
把UUID、主编号、次编号合在一起,我们可以确定Beacon的唯一
身份。
最后的C5说明了蓝牙信号强度,即在1米处测得的该Beacon的RSSI
值。中心设备把接收到的信号强度和该信号强度对比,就可以知道信号
衰减了多少,从而推算出自己与Beacon的距离。由于我这里写入的C5没
有经过校准,所以距离测量可能不准确。
用手机上探测Beacon的App来测试。当进入树莓派的广播范围时,
应用就会显示出手机距离树莓派的距离。
使用结束后,可以用下面的命令停止广播:
用下面的命令来恢复扫描: