天天看点

【蓝牙】一文入门Bluez的BLE基础开发 - BLE数据收发(Python)

在阅读本文章之前,请先按照 Bluez的BLE基础开发 - BLE广播完成环境的搭建和跑完例程.

Bluez 源码下载以及环境搭建

下载地址

Bluez源码下载地址 bluez-5.58

其它版本请自行在官网进行下载Bluez官网

Bluez 源码编译与安装

  1. 第三方依赖库安装
sudo apt install libdbus-1-dev libudev-dev libical-dev libreadline-dev
           
  1. 打开一个终端,进入bluez-5.58的顶层目录,执行./configure 进行Makefile文件的生成以及环境变量配置(在本文章的例程中按照默认进行配置)
➜  bluez-5.58 ./configure
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for a thread-safe mkdir -p... /bin/mkdir -p
checking for gawk... no
checking for mawk... mawk
....
           
  1. 在./configure 执行成功后,会在bluez-5.58 的顶层目录下生成Makefile文件. 运行make命令进行bluez源码的编译.
➜  bluez-5.58 make
  GEN      ell/shared
make --no-print-directory all-am
  GEN      src/bluetooth.service
  CCLD     src/bluetoothd
...
           
  1. 在bluez-5.58编译成功后,通过执行sudo make install 进行相关文件的安装.
➜  bluez-5.58 sudo make install
[sudo] password for apollo: 
make --no-print-directory install-am
 /bin/mkdir -p '/usr/local/bin'
  /bin/bash ./libtool   --mode=install /usr/bin/install -c client/bluetoothctl monitor/btmon tools/rctest tools/l2test tools/l2ping tools/bluemoon tools/hex2hcd tools/mpris-proxy tools/btattach '/usr/local/bin'
libtool: install: /usr/bin/install -c client/bluetoothctl /usr/local/bin/bluetoothctl
libtool: install: /usr/bin/install -c monitor/btmon /usr/local/bin/btmon
libtool: install: /usr/bin/install -c tools/rctest /usr/local/bin/rctest
libtool: install: /usr/bin/install -c tools/l2test /usr/local/bin/l2test
libtool: install: /usr/bin/install -c tools/l2ping /usr/local/bin/l2ping
libtool: install: /usr/bin/install -c tools/bluemoon /usr/local/bin/bluemoon
...
           
  1. 在bluez 相关的可执行程序和配置文件更新后,进行bluetooth服务的重启
➜  bluez-5.58 systemctl daemon-reload  
➜  bluez-5.58 service bluetooth restart
           

bluez-5.58源码基本介绍

bluez-5.58目录结构

➜  bluez-5.58 tree -L 1 --dirsfirst 
.
├── android
├── attrib
├── btio
├── client
├── completion
├── doc
├── ell
├── emulator
├── gdbus
├── gobex
├── lib
├── mesh
├── monitor
├── obexd
├── PaxHeaders.21757
├── peripheral
├── plugins
├── profiles
├── src
├── test
├── tools
├── unit
           

bluez-5.58提供的工具

  1. bluetoothctl
➜  client tree
.
├── advertising.c
├── advertising.h
├── advertising.o
├── adv_monitor.c
├── adv_monitor.h
├── adv_monitor.o
├── agent.c
├── agent.h
├── agent.o
├── bluetoothctl
├── display.c
├── display.h
├── display.o
├── gatt.c
├── gatt.h
├── gatt.o
├── main.c
├── main.o

           

如上图所示,bluetoothctl实质上是由bluez源码下的client目录的相关文件编译生成的可执行程序(命令). 从文件构成也可以看出来bluetoothctl 主要是bluez官方提供的一个命令行交互的一个客户端,用于和bluetoothd的通信进行BLE广播包的设置、BLE相关配置、创建服务、特征等. 具体的功能请打开终端,运行bluetoothctl 进行查看.

➜  client bluetoothctl
Agent registered
[CHG] Controller BC:83:85:05:80:91 Pairable: yes
[bluetooth]# help
Menu main:
Available commands:
-------------------
advertise                                         Advertise Options Submenu
monitor                                           Advertisement Monitor Options Submenu
scan                                              Scan Options Submenu
gatt                                              Generic Attribute Submenu
list                                              List available controllers
show [ctrl]                                       Controller information
select <ctrl>         
...
           
  1. hcidump

    需要注意的是,在编译bluez源码时,在bluez5.58 这一版本中hcidump默认已经被废弃不会编译生成. 需要用的话需要在运行./configure 时增加参数- --enable-deprecated

➜  bluez-5.58 ./configure  --enable-deprecated
           

对于蓝牙有一些了解的同学都应该了解,蓝牙协议栈和Controler的通信是基于HCI Interface的,简单来说hcidump就是负责解析Controler 上来的数据和协议栈写入到Controler的数据,简单来说就是能输出Controler(控制器)和上层应该的数据通信.

在运行hcidump之前,先要确认我们需要dump的hci设备, 执行hciconfig -a

➜  bluez-5.58 hciconfig -a
hci1:	Type: Primary  Bus: USB
	BD Address: 00:1A:7D:DA:71:13  ACL MTU: 310:10  SCO MTU: 64:8
	DOWN RUNNING 
	RX bytes:574 acl:0 sco:0 events:30 errors:0
	TX bytes:368 acl:0 sco:0 commands:30 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:	Type: Primary  Bus: USB
	BD Address: BC:83:85:05:80:91  ACL MTU: 8192:128  SCO MTU: 64:128
	DOWN 
	RX bytes:503 acl:0 sco:0 events:22 errors:0
	TX bytes:336 acl:0 sco:0 commands:22 errors:0
	Features: 0xff 0xff 0x8f 0xfe 0x83 0xe1 0x08 0x80
	Packet type: DM1 DM3 DM5 DH1 DH3 DH5 HV1 HV2 HV3 
	Link policy: RSWITCH HOLD SNIFF PARK 
	Link mode: SLAVE ACCEPT 
           

然后选择需要dump的hci设备,在这里我需要调试的是hci1

➜  tools sudo ./hcidump -i hci1
[sudo] password for apollo: 
HCI sniffer - Bluetooth packet analyzer ver 5.58
device: hci1 snap_len: 1500 filter: 0xffffffffffffffff
           

需要注意的是: hcidump 默认的hci device是hci0,所以当系统有多个hci device时,一定要注意通过hciconfig来查看需要调试的hci device.

doc 下ble相关文件简单讲解

如下图所示,bluez-5.58 doc下的文件,是我们主要需要关注的BLE相关文档. 文档都是英文描述,部分的语句为了更好表达我就没有进行翻译,请有需要的同学自己进行翻译的后学习.

➜  doc tree
.
├── adapter-api.txt
├── advertising-api.txt
├── device-api.txt
├── gatt-api.txt
├── health-api.txt
           

Bluez Doc下文档学习 - advertising.txt

Advertising packets are structured data which is broadcast on the LE Advertising channels and available for all devices in range. Because of the limited space available in LE Advertising packets (31 bytes), each packet’s contents must be carefully controlled.

这里需要注意的是: 31bytes的广播包 指的是开发中可以修改的,不是完整的数据包,还有数据头等.

Bluez advertising,

Service org.bluez

Interface org.bluez.LEAdvertisement1

Object path freely definable

Interface下面只有一个Methods

Methods void Release() [noreply] //主要是remove advertisement时会被调用

Properties //主要是广播包数据的填充

dict ManufacturerData  //产商自定义的数据, 需要掌握dict的使用

    array{string} ServiceUUIDs // 将Service UUIDs提前通过广播包进行广播
           

The Advertising Manager allows external applications to register Advertisement Data which should be broadcast to devices. Advertisement Data elements must follow the API for LE Advertisement Data described above.

Service org.bluez

Interface org.bluez.LEAdvertisingManager1

Object path /org/bluez/{hci0,hci1,…}

主要是提供了Methods RegisterAdvertisement进行广播包的注册

bluez下文档学习 - adapter-api.txt

BlueZ D-Bus Adapter API description

Service org.bluez

Interface org.bluez.Adapter1

Object path [variable prefix]/{hci0,hci1,…}

Methods void StartDiscovery() // 从Methods来看, Adapter是对本设备的操作,和device是不同的,device主要是连接上的设备(Remote device)

Bluez下文档学习 - device-api.txt

Device hierarchy 主要是对设备的操作, Methods: Connect、Disconnect、Pair等, Properties部分主要是存储连接上的设备相关属性以及对远程设备属性的改写.

Bluez下文档学习 - gatt-api.txt(重点)

BlueZ D-Bus GATT API description

GATT local and remote services share the same high-level D-Bus API. Local refers to GATT based service exported by a BlueZ plugin or an external application. Remote refers to GATT services exported by the peer.

BlueZ acts as a proxy, translating ATT operations to D-Bus method calls and Properties (or the opposite). Support for D-Bus Object Manager is mandatory for external services to allow seamless GATT declarations (Service, Characteristic and Descriptors) discovery. Each GATT service tree is required to export a D-Bus Object Manager at its root that is solely responsible for the objects that belong to that service.

Releasing a registered GATT service is not defined yet. Any API extension

should avoid breaking the defined API, and if possible keep an unified GATT remote and local services representation.

以上3段话,最有价值的是标红的内容.

Each GATT service tree is required to export a D-Bus Object Manager at its root that is solely responsible for the objects that belong to that service.

也就是对用Bluez提供的示例代码example-gatt-server 中

@dbus.service.method(GATT_CHRC_IFACE, in_signature='aya{sv}')
def WriteValue(self, value, options):
print('Default WriteValue called, returning error')
raise NotSupportedException()
           

External applications implementing local services must register the services using GattManager1 registration method and must implement the methods and properties defined in GattService1 interface.

这里描述解释了官方例子中的关于@dbus.service.method的使用.

Characteristic 的相关操作

ervice org.bluez

Interface org.bluez.GattCharacteristic1

Object path [variable prefix]/{hci0,hci1,…}/dev_XX_XX_XX_XX_XX_XX/serviceXX/charYYYY

Methods         void StartNotify()
    
                void StopNotify()
           

重点讲下StartNotify和StopNotify,

当Char的属性设置为notifications或者indications时,在手机上的nRF Connect连接上设备时,会看到相关的Char中有一个Enable/Disable Notify的按钮,而StartNotify 和 StopNotify 就是由它触发.

array{byte} Value [read-only, optional]

The cached value of the characteristic. This property
	gets updated only after a successful read request and
	when a notification or indication is received, upon
	which a PropertiesChanged signal will be emitted.
           

需要特别注意的是:

The cached value of the characteristic. This property gets updated only after a successful read request and when a notification or indication is received, upon which a PropertiesChanged signal will be emitted.

也就是Value通常情况是只读的,只有在以上描述的条件下才具备其它属性. 另外org.bluez.GattCharacteristic1

---> Properties

               ---->Value

     中的Value用于Write、Read、Notify、Indicate 等的存储空间
           

基于bluez-5.58 下的python实例讲解BLE的数据收发

BLE 广播包创建

Bluez 如何基于Python创建BLE广播

关于Bluez BLE广播的创建请直接学习上述的博客链接.

服务注册,数据收发

在这里是直接使用的bluez-5.58 顶层目test目录下的 example-gatt-server.py 进行服务的注册和收发测试.

详细的代码请下载bluez-5.58源码后进行查看,本文主要重点介绍下 example-gatt-server.py 中关于BLE 的数据收发部分代码.

  1. 以例程example-gatt-server.py 中HeartRateService的HeartRateMeasurementChrc为例子,讲解下notify(BLE设备发数据到连接到BLE设备手机BLE client)
class HeartRateMeasurementChrc(Characteristic):
    HR_MSRMT_UUID = '00002a37-0000-1000-8000-00805f9b34fb'

    def __init__(self, bus, index, service):
        Characteristic.__init__(
                self, bus, index,
                self.HR_MSRMT_UUID,
                ['notify'],
                service)
        self.notifying = False
        self.hr_ee_count = 0
	
	def hr_msrmt_cb(self):
        value = []
        ...
		...
        self.PropertiesChanged(GATT_CHRC_IFACE, { 'Value': value }, [])

        return self.notifying

	def StartNotify(self):
        if self.notifying:
            print('Already notifying, nothing to do')
            return

        self.notifying = True
        self._update_hr_msrmt_simulation()

    def StopNotify(self):
        if not self.notifying:
            print('Not notifying, nothing to do')
            return
        self.notifying = False
        self._update_hr_msrmt_simulation()

           

1)首先在__init__中将Characteristic定义为notify属性,也就是当手机端中的BLE Client App(本文接下来都已nRF Connect为例子)连接上该BLE 设备后,在App enable该Characteristic 的notify后,BLE 设备可以通过该Characteristic主动发送数据.

2)def StartNotify(self) 是重写于Characteristic类的方法,在Characteristic类中已通过@dbus.service.method(GATT_CHRC_IFACE) 对该方法进行修饰,在App enable该Characteristic 的notify后,def StartNotify(self) 会被调用

与之对应def StopNotify(self),在App disable该Characteristic 的notify后会被调用

  1. 在def StartNotify(self) 被触发后,就可以调用self.PropertiesChanged(GATT_CHRC_IFACE, { ‘Value’: value }, []) 进行数据(填充value字段)的发送

总结

对于Bluez BLE感兴趣的同学欢迎留言交流,后续会持续输出关于Bluez BLE的学习经验,特别是关于Bluez的源码分析.

下一篇: 爬虫算法