做者:Vamei 出处:http://www.cnblogs.com/vamei 严禁转载。html
蓝牙是一个使用普遍的无线通讯协议,这两年又随着物联网概念进一步推广。我将介绍蓝牙协议,特别是低功耗蓝牙,并用树莓派来实践。树莓派3中内置了蓝牙模块。树莓派经过UART接口和该模块通讯。树莓派1和树莓派2中没有内置的蓝牙模块,不过你能够经过USB安装额外的蓝牙适配器。node
蓝牙最初由爱立信创制,旨在实现可不一样设备之间的无线链接。蓝牙无线通讯的频率在2.4GHz附近,和WiFi同样,都属于特高频。相对于低频信号来讲,高频传输的速度比较快,穿透能力强,但传输距离比较受限。在没有遮蔽和干扰的状况下,蓝牙设备的最大通讯距离能达到30米。但在大多数状况下,蓝牙的实际通讯距离在2到5米。相比之下,低频433MHz设备的通讯距离很容易超过百米。所以,蓝牙经常使用于近距离的无线设备,好比无线鼠标和键盘。git
蓝牙的标志 github
蓝牙的基本工做流程以下:web
根据细节上的差异,蓝牙通讯又细分为两种:经典蓝牙和低功耗蓝牙。早期的蓝牙通讯方式称为经典蓝牙(classic bluetooth)。经典蓝牙中的数据传输协议是串行仿真协议RFCOMM。RFCOMM仿真了常见的串口链接。数据从一端输入,从另外一端取出。经典蓝牙的开发很是简单。基于串口开发的有线键鼠程序,就能够直接用于RFCOMM链接的无线键鼠。此外,经典蓝牙能够快速传输数据。所以,诺基亚N95这样的早期智能手机,也用RFCOMM来互传图片和文件。shell
RFCOMM通讯npm
经典蓝牙的缺点是比较耗电。后来,诺基亚发明了一种能够下降功耗的蓝牙通讯方式。2010年出台的蓝牙4.0把这种通讯方式规范为“低功耗蓝牙”(BLE,Bluetooth Low Energy)。BLE把通讯双方分为非对称的双方,尽可能让其中的一方承担主要的开销,减小另外一方的负担。举例来讲,手环电量少,并且须要长时间待机。BLE通讯的主要负担能够放在电量较充裕且充电方便的手机一侧,从而减小手环的能耗。bash
手环做为外设服务器
BLE通讯通常也包含广播/扫描的步骤。主动发起广播的设备称为外设(Peripheral),扫描设备称为中心设备(Central)。BLE链接成功以后,就能够开始数据传输。BLE的数据传输协议是ATT和GATT协议。ATT是GATT的基础。ATT协议把通讯双方分为服务器(server)和客户(client)。客户主动向服务器发起读写操做。须要注意的是,ATT中的服务器和客户,与广播阶段的外设和中心设备相互独立。固然,在手环这样的应用场景下,外设一般也是服务器。ATT协议以属性(attribute)为单位进行该数据传输。一个属性的格式以下:并发
ATT属性
咱们分别来理解属性的不一样部分:
服务器储存了多个属性。当客户向服务器请求时,服务器会把本身的属性列表发给客户。随后,客户能够向服务器读取或写入某一个属性值。用读写的方式,通讯双方实现了双向通讯。
以智能手表为例。智能手表和手机配对后,手机能够用读的方式得到智能手表中某个属性下保存的步数,也能够用写的方式写入另外一个属性负责的时间。在读写操做中,都是由客户采起主动,服务器只能被动应答。ATT还提供了通知(notification)的工做方式。当服务器改变了某个属性值时,能够主动通知订阅了该属性值的客户。智能手表中的手势识别,就能够经过通知的方式告知手机。这样的话,手机就能够实时地获知手势改变信息。
GATT协议构建在ATT协议之上,为属性提供了组织形式。GATT的最小组织单元是Characteristic,能够由数条属性组成。下图中就是一个Characteristic,用于传输红外测温得到的数据。这个例子来自TI的SensorTag:
从左到右:handle(16进制),handle(10进制),type(16进制),type(文字说明),value(16进制),permission,备注
Characteristc的第一条属性用于声明属性,其类型老是0x2803。这条声明的value部分又能够细分为三部分。第一个部分是0x12,称为Characteristic Properties,是GATT协议层面上的权限控制。其具体含义可参考资料。第二部分0x0025,是Characteristic值的handle。找到handle为0x0025的属性,就在声明属性的下面一行。0x0025的value部分就是红外温度的真正数值。剩下的部分是该Characteristic的UUID,总共128位:
F000-AA01-0451-4000-B000-000000000000
检查Characteristic值的那一行属性,也就是0x0025属性。它的类型也是该Characteristic UUID。除了128位的UUID,蓝牙官方还提供了16位的UUID可供使用,可参考资料。
能够看到,一个Characterstic至少须要两个属性,一个用于声明,一个用于储存它的数据。除此以外,Characteristic还有称为Descriptor的额外描述信息。每一个Decriptor占据一行。好比0x0027这个Descriptor,其属性值是54:65:6D:70:7E:20:44:61:74:61,翻译成ASCII就是:
Temp~ Data
此外,温度单位、测量频率等描述信息也常常会以Descriptor的形式放入到Characteristic中。在下一个Characteristic声明出现前的属性,都是该Characteristic的Descriptor。
咱们再来看更高级的组织单位Service。一个Service也有行属性做为声明,其类型UUID是0x2800。声明属性的值就是该Service的128位UUID。蓝牙官方也提供了16位的UUID,预留给特定的Service,可参考资料。在下一个Service声明出现前的属性,都属于该Service,好比下图中从0x0023到0x002D的属性:
图中包含了一个与红外温度计相关的Service。Service里又有三个Characteristic,分别0x0024-0x002七、0x0028-0x002A、0x002B-0x002D。我已经介绍过第一个Characteristic。第二个Characteristic用于传输温度计参数,第三个用于设置测温频率。
Service和Characteristic都是属性的组织形式。客户能够向服务器请求Service和Characteristic列表,而后对其进行操做。GATT还提供了Profile,能够包括多个Service。不过,Profile并不像前面二者那样存在于服务器。Profile是一种标准,用于说明一个特型设备应该有哪些Service。好比说,HID(Human Interface Device)这种Profile,就说明了蓝牙输入设备应该提供的Service。蓝牙官方定义的Profile可参考资料。
咱们用树莓派来深刻实践上面学到的蓝牙知识。首先要在树莓派上安装必要的工具。BlueZ是Linux官方的蓝牙协议栈。你能够经过BlueZ提供的接口,进行丰富的蓝牙操做。Raspbian中已经安装了BlueZ。我使用的版本是5.43。你能够检查本身的BlueZ版本:
bluetoothd -v
低版本的BlueZ对低功耗蓝牙的支持有限。若是你的使用版本低于5.43,那么我建议你升级BlueZ。
你能够用下面的命令检查BlueZ的运行状态:
systemctl status bluetooth
个人返回结果是:
● bluetooth.service - Bluetooth service
Loaded: loaded (/lib/systemd/system/bluetooth.service; enabled)
Active: active (running) since Sun 2017-04-23 19:03:08 CST; 1 day 6h ago
Docs: man:bluetoothd(8)
Main PID: 709 (bluetoothd)
Status: "Running"
CGroup: /system.slice/bluetooth.service
└─709 /usr/lib/bluetooth/bluetoothd -C
能够看到,蓝牙服务已经打开,并在正常运行。
你能够用下面命令手动启动或关闭蓝牙服务:
sudo systemctl start bluetooth sudo systemctl stop bluetooth
此外,你还可让蓝牙服务随系统启动:
sudo systemctl enable bluetooth
在Raspbian中,基本的蓝牙操做能够经过bluez中的bluetoothctl命令进行。该命令运行后,将进入到一个新的Shell。在这个shell中输入:
list
将显示树莓派上可用的蓝牙模块,例如:
Controller B8:27:EB:72:47:5E raspberrypi [default]
运行scan命令,开启扫描:
scan on
扫描启动后,用devices命令,能够打印扫描到蓝牙设备的MAC地址和名称,例如:
Device 00:9E:C8:62:AF:55 MiBOX3
Device 4D:CE:7A:1D:B8:6A vamei
此外,你还能够用help命令得到帮助。使用结束后,你能够用exit命令推出bluetoothctl。
除了bluetoothctl,在Raspbian是shell中能够经过hciconfig来控制蓝牙模块。好比开关蓝牙模块:
sudo hciconfig hci0 up #启动hci设备
sudo hciconfig hci0 down #关闭hci设备
命令中的hci0指的是0号HCI设备,即树莓派的蓝牙适配器。
与此同时,你能够用下面命令来查看蓝牙设备的工做日志:
hcidump
bluez自己还提供了链接和读写工具。但不一样版本的bluez相关功能的差别比较大,并且使用起来不太方便,因此我下面使用Node.js的工具来实现相关功能。
下一步,咱们尝试用树莓派进行BLE通讯。咱们先把一个树莓派改形成BLE外设,同时它也将充当链接创建后的服务器。这个过程较为复杂。你能够借用Node.js下的bleno库。首先,安装Node.js:
curl -sL https://deb.nodesource.com/setup_5.x | sudo bash -
sudo apt-get install nodejs
第一行的命令是为了确保安装高版本的Node.js。
安装bleno:
mkdir ble-test-peripheral
cd ble-test-peripheral
npm install bleno
运行pizza的例子:
sudo node node_modules/bleno/examples/pizza/peripheral
你能够在node_modules/bleno/examples/pizza/看到源代码,或者到github查看。这个例子提供了一个Service,它的UUID是1333-3333-3333-3333-3333-333333333337。Service中包含了三个Characteristics,分别是用于披萨饼参数、配料参数和烤披萨:
功能 | 权限 | UUID |
披萨饼选项 | 读/写 | 13333333333333333333333333330001 |
配料 | 读/写 | 13333333333333333333333333330002 |
烤披萨 | 写/通知 | 13333333333333333333333333330003 |
经过这些Characteristic,咱们能够对树莓派进行BLE读写。读写操做会做用于一个表明比萨的对象。披萨饼选项有:
数值 | 描述 |
0x00 | 正常 |
0x01 | 厚 |
0x02 | 薄 |
配料是一个8位的参数,每一位表明了一种配料。当这一位是1时,那么说明添加该配料:
第n位 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
描述 | SAUSAGE | BELL_PEPPERS | PINEAPPLE | CANADIAN_BACON | BLACK_OLIVES | EXTRA_CHEESE | MUSHROOMS | PEPPERONI |
所以,0x1A表明了添加MUSHROOMS、BLACK_OLIVES、CANADIAN_BACON,感受味道还不错。
对于烤披萨来讲,写操做设定了烘烤的温度和时间。时间到了以后,中心设备会发出通知,告诉客户端烘烤完成。咱们下一步将用另外一个树莓派做为BLE中心设备。不过,即便你没有额外的树莓派,你能够用iPhone上LightBlue这样的App来测试这一部分完成的BLE外设。
咱们拿另外一个做为BLE的中心设备进行扫描,并发起链接请求。链接创建后,该服务器将充当客户。和bleno对应,Node.js下有一个叫noble的项目,能够便捷地完成这一任务。首先,安装noble:
mkdir ble-test-central
cd ble-test-central
npm install noble
noble中有一个一样名为pizza的例子,不过这个例子实现的是客户端。运行该例子:
sudo node node_modules/noble/examples/pizza/peripheral
这个例子将自动执行扫描、链接、服务发现、数据传输的全过程。若是你把bleno和noble部署到两个树莓派上,就能够在这两个树莓派之间进行蓝牙通讯了。若是你想自定义开发,那么能够在node_modules/noble/examples/pizza/参考源代码,或者到github查看。
苹果在BLE的基础上推出了iBeacon协议。iBeacon使用了BLE的广播部分,但不创建链接。一个遵照iBeacon协议的外设称为Beacon。Beacon会广播本身的身份信息和发射信号的强度。中心设备接收到广播以后,除了能够获知Beacon的身份以外,还能经过信号的衰减算出本身与Beacon的距离。在一个典型的超市应用场景中,每件商品能够带上一个Beacon。消费者能够用手机看到本身周围有哪些商品,工做人员也能够用手机来清点货物。商家还能够在服务器上提供商品相关的质保、促销等信息。用户能够根据Beacon的编号,得到这些附加信息。
咱们把配备了蓝牙模块的树莓派改形成一个Beacon。既然Beacon只使用了蓝牙中的广播,那么应该关闭树莓派的扫描,打开广播,而且不接受蓝牙链接:
sudo hciconfig hci0 noscan # 再也不扫描 sudo hciconfig hci0 leadv 3 # 开始广播,而且不接受链接
下一步,把广播信息改成符合iBeacon协议的内容:
sudo hcitool -i hci0 cmd 0x08 0x0008 1E 02 01 1A 1A FF 4C 00 02 15 63 6F 3F 8F 64 91 4B EE 95 F7 D8 CC 64 A8 63 B5 00 01 00 02 C5
上面的命令附加了一串16进制信息。其中0x08说明了整条信息是蓝牙命令,0x0008说明后面的内容将做为广播信息。
1E是广播信息开始的标志。按照蓝牙通讯的规定,广播信息最多有31个字节。1E后面的广播信息分为两组:
02 01 1A
1A FF 4C 00 02 15 63 6F 3F 8F 64 91 4B EE 95 F7 D8 CC 64 A8 63 B5 00 01 00 02 C5
每一组一开始的一个字节说明了该组信息的长度。02说明了2个字节,1A说明是26个字节。随后一个字节说明了改组信息的类型。第一组的01说明了该组信息是蓝牙控制标志,第二组的FF说明了该组是蓝牙制造商相关信息。
咱们来看第二组信息的细节:
在iPhone上安装应用Locate Beacon来测试。当我进入到树莓派的广播范围时,该应用就会显示出手机距离树莓派的距离。
使用结束后,能够用下面命令来恢复扫描和中止广播:
sudo hciconfig hci0 piscan # 恢复扫描 sudo hciconfig hci0 noleadv # 中止广播
这里简单介绍了蓝牙协议,特别是低功耗蓝牙。我以树莓派的蓝牙模块为基础,实现了BLE通讯。
欢迎阅读“骑着企鹅采树莓”系列文章
个人博客即将搬运同步至腾讯云+社区,邀请你们一同入驻:https://cloud.tencent.com/developer/support-plan