【低功耗蓝牙】③ 蓝牙服务和特性的创建
我是鹏老师
2022年01月29日 18:32
收录于文集
共6篇

摘要

本文章主要基于ESP32的MicroPython平台,讲解了蓝牙服务和特性的建立,以及基于特性的数据交互,实现手机于蓝牙模块相互通信。

UUID

UUID是 University Unique Identifie 的缩写,翻译成中文为 通用唯一标识符。是蓝牙组织联盟定义的用于区分蓝牙服务和特性的的标识符,总长度为128 Bit。例如:

代码块
HTML
自动换行
复制代码
03B80E5A-EDE8-4B33-A751-6CE34EC4C700

7772E5DB-3868-4112-A1A9-F2669D106BF3
复制成功

128Bit的UUID占用16个字节,在变成个传输的时候都很不方便,所以蓝牙联盟定义了一个UUID的基地址,允许在此基础上使用16Bit的UUID。

UUID基地址:0x0000xxxx-0000-1000-8000-00805F9B34FB

比如16Bit的UUID : 0x2A37  转换成128Bit的UUID为:

0x00002A37-0000-1000-8000-00805F9B34FB

服务和特性

低功耗蓝牙设备之间通信,都是基于服务和特性。一个蓝牙设备中可以包含若干个服务,一个服务中可以包含若干个特性,每一个服务或者特性都要有一个UUID。蓝牙的数据交互都是基于一个个特性进行的,数据交互的方式有五种,分别是Read,Write,Write WithOutRespons,Notify,Indication。

蓝牙服务和特性结构图

服务和特性特创建

蓝牙设备要在进入广播态之前创建服务和特性,在MicroPython中大概分为四步:① 创建要使用的UUID;② 使用UUID创建特性并设置特性的读写权限;③ 将创建好的特性添加到服务集合中;④ 将服务集合注册到协议栈中。

比如我们要创建一个UUID为9011的服务,该服务里面包含两个特性,这两个特性的UUID分别是9012和9013,9012的特性拥有Read和Write的权限,9013的特性拥有Read和Notify的权限。

可以使用如下代码实现:

代码块
Python
自动换行
复制代码
from machine import Pin
from time import sleep_ms
import ubluetooth        #导入BLE功能模块

ble = ubluetooth.BLE()   #创建BLE设备
ble.active(True)         #打开BLE

#创建要使用的UUID
SERVER_1_UUID = ubluetooth.UUID(0x9011) 
CHAR_A_UUID = ubluetooth.UUID(0x9012) 
CHAR_B_UUID = ubluetooth.UUID(0x9013)

#创建特性并设置特性的读写权限
CHAR_A = (CHAR_A_UUID, ubluetooth.FLAG_READ | ubluetooth.FLAG_WRITE, )
CHAR_B = (CHAR_B_UUID, ubluetooth.FLAG_READ | ubluetooth.FLAG_NOTIFY, )

SERVER_1 = (SERVER_1_UUID, (CHAR_A , CHAR_B, ) , ) #把特性A和特性B放入服务1
SERVICES = (SERVER_1, ) #把服务1放入服务集和中
((char_a, char_b), ) = ble.gatts_register_services(SERVICES) #注册服务到gatts

#设置BLE广播数据并开始广播
ble.gap_advertise(100, adv_data = b'\x02\x01\x06\x03\x09\x41\x42')
复制成功

运行代码,通过微信小程序“谷雨蓝牙”连接设备后,可以参看到蓝牙设备上的服务可特性列表如下:

谷雨蓝牙

下面给出了实现两个服务的参考代码:

代码块
Python
自动换行
复制代码
from machine import Pin
from time import sleep_ms
import ubluetooth         #导入BLE功能模块

ble = ubluetooth.BLE()    #创建BLE设备
ble.active(True)          #打开BLE

#创建要使用的UUID
SERVER_1_UUID = ubluetooth.UUID(0x9011) 
CHAR_A_UUID = ubluetooth.UUID(0x9012) 
CHAR_B_UUID = ubluetooth.UUID(0x9013)

SERVER_2_UUID = ubluetooth.UUID(0x9021) 
CHAR_C_UUID = ubluetooth.UUID(0x9022) 
CHAR_D_UUID = ubluetooth.UUID(0x9023)

#创建特性并设置特性的读写权限
CHAR_A = (CHAR_A_UUID, ubluetooth.FLAG_READ | ubluetooth.FLAG_WRITE, )
CHAR_B = (CHAR_B_UUID, ubluetooth.FLAG_READ | ubluetooth.FLAG_NOTIFY, )
SERVER_1 = (SERVER_1_UUID, (CHAR_A , CHAR_B, ) , ) #把特性A和特性B放入服务1

CHAR_C = (CHAR_C_UUID, ubluetooth.FLAG_READ | ubluetooth.FLAG_WRITE, )
CHAR_D = (CHAR_D_UUID, ubluetooth.FLAG_READ | ubluetooth.FLAG_NOTIFY, )
SERVER_2 = (SERVER_2_UUID, (CHAR_C , CHAR_D, ) , ) #把特性A和特性B放入服务

SERVICES = (SERVER_1, SERVER_2 , ) #把服务1和服务2放入服务集和中
((char_a, char_b), (char_c, char_d), ) = ble.gatts_register_services(SERVICES) #注册服务到gatts

#设置BLE广播数据并开始广播
ble.gap_advertise(100, adv_data = b'\x02\x01\x06\x03\x09\x41\x42')
复制成功

数据交互

低功耗蓝牙之间的数据交互都是基于特性,以手机连接蓝牙模块为例,手机读取蓝牙模块的数据,使用的是Read方法,手机发送数据给蓝牙模块使用的是Write方法,蓝牙模块发送数据到手机,一般使用的是Notify方法,而且手机端还要打开Notify监听。通信示例代码如下:

代码块
Python
自动换行
复制代码
from machine import Pin
from time import sleep_ms
import ubluetooth        #导入BLE功能模块

ble = ubluetooth.BLE()   #创建BLE设备
ble.active(True)         #打开BLE

#创建要使用的UUID
SERVER_1_UUID = ubluetooth.UUID(0x9011) 
CHAR_A_UUID = ubluetooth.UUID(0x9012) 
CHAR_B_UUID = ubluetooth.UUID(0x9013)

#创建特性并设置特性的读写权限
CHAR_A = (CHAR_A_UUID, ubluetooth.FLAG_READ | ubluetooth.FLAG_WRITE | ubluetooth.FLAG_NOTIFY, )
CHAR_B = (CHAR_B_UUID, ubluetooth.FLAG_READ | ubluetooth.FLAG_NOTIFY, )

SERVER_1 = (SERVER_1_UUID, (CHAR_A , CHAR_B, ) , ) #把特性A和特性B放入服务1
SERVICES = (SERVER_1, ) #把服务1放入服务集和中
((char_a, char_b), ) = ble.gatts_register_services(SERVICES) #注册服务到gatts

#设置BLE广播数据并开始广播
ble.gap_advertise(100, adv_data = b'\x02\x01\x06\x03\x09\x41\x42')

def ble_irq(event, data): # 蓝牙中断函数
    if event == 1: #蓝牙已连接
      print("BLE 连接成功")

    elif event == 2: #蓝牙断开连接
      print("BLE 断开连接")
      ble.gap_advertise(100, adv_data = b'\x02\x01\x06\x03\x09\x41\x42')
      
    elif event == 3: #收到数据
      onn_handle, char_handle = data #判断是来自那个特性的消息
      buffer = ble.gatts_read(char_handle) #读取接收到的消息
      print(char_handle, buffer) #打印消息内容
      ble.gatts_notify(0, char_handle, 'Hello') #回复Hello
      
ble.irq(ble_irq)
复制成功

在上述代码中,我们创建了一个UUID为9011的服务,并在该服务中创建了一个UUID为9012的特性,该特性的操作权限是 Read,Write,Notify。在手机端打开谷雨蓝牙小程序,连接设备,选择UUID为9012的特性,进入常规试图,点击监听,随便写入一下数据,可以看到,设备回复给手机Hello。

低功耗蓝牙设备之间通信常用的方式是Read,Write 和 Notify ,必须在创建特性时赋予对应的权限,才能在通信中使用。如果某个特性在创建的时候,没有开启 Write 权限,则手机将无法通过该特性发送数据到设备。

已定义的16Bit UUID

对于一些常用的功能,蓝牙组织联盟已经为其定义好了UUID,我们在开发产品的时候直接使用即可。

16BitUUID定义文档下载地址:https://btprodspecificationrefs.blob.core.windows.net/assigned-values/16-bit%20UUID%20Numbers%20Document.pdf

电池电量指示

在上述文档中定义了电池电量服务的UUID是0x180F,电池电量特性的UUID是0x2A19,我们可以使用这两个UUID实现电池电量指示的功能,代码如下:

代码块
Python
自动换行
复制代码
from machine import Pin
from time import sleep_ms
import ubluetooth        #导入BLE功能模块

ble = ubluetooth.BLE()   #创建BLE设备
ble.active(True)         #打开BLE

#创建电池服务和特性的UUID
BATTERY_SERVER_UUID = ubluetooth.UUID(0x180F) 
BATTERY_CHAR_UUID = ubluetooth.UUID(0x2A19) 

#创建特性并设置特性的读写权限
BATTERY_CHAR = (BATTERY_CHAR_UUID, ubluetooth.FLAG_READ , )

BATTERY_SERVER = (BATTERY_SERVER_UUID, (BATTERY_CHAR, ) , ) #把电量特性放入电池服务
SERVICES = (BATTERY_SERVER, ) #把电池服务服务放入服务集和中

((battery_char,), ) = ble.gatts_register_services(SERVICES) #注册服务到gatts

ble.gatts_write(battery_char, b'\x50') #设置电池电量为80%

#设置BLE广播数据并开始广播
ble.gap_advertise(100, adv_data = b'\x02\x01\x06\x03\x09\x41\x42')

def ble_irq(event, data): # 蓝牙中断函数
    if event == 1: #蓝牙已连接
      print("BLE 连接成功")

    elif event == 2: #蓝牙断开连接
      print("BLE 断开连接")
      ble.gap_advertise(100, adv_data = b'\x02\x01\x06\x03\x09\x41\x42')
      
    elif event == 3: #收到数据
      print("收到新消息")
      
ble.irq(ble_irq)
复制成功

电池电量用一个字节来表示,其单位是电量百分比,16进制数0x50转换成十进制为80,所以运行上述代码,连接该设备后,可以看到该设备的电量显示,如下图所示:

电池电量显示

通过手机微信小程序“谷雨蓝牙”连接该设备后,可以看到如下服务列表:

温湿度传感器

在蓝牙组织联盟发布的16Bit UUID 中,定义了环境传感器服务的UUID是 ,温度特性的UUID是0x2A6E,湿度特性的UUID是0x2A6F。使用如下代码可实现简单温湿度传感器功能:

代码块
Python
自动换行
复制代码
from machine import Pin
from time import sleep_ms
import ubluetooth        #导入BLE功能模块

ble = ubluetooth.BLE()   #创建BLE设备
ble.active(True)         #打开BLE

#创建环境传感器服务和特性的UUID
ENV_SERVER_UUID = ubluetooth.UUID(0x181A) #环境传感器服务
TEM_CHAR_UUID = ubluetooth.UUID(0x2A6E)   #温度特性
HUM_CHAR_UUID = ubluetooth.UUID(0x2A6F)   #湿度特性

#创建特性并设置特性的读写权限
TEM_CHAR = (TEM_CHAR_UUID, ubluetooth.FLAG_READ , )
HUM_CHAR = (HUM_CHAR_UUID, ubluetooth.FLAG_READ , )

ENV_SERVER = (ENV_SERVER_UUID, (TEM_CHAR, HUM_CHAR, ) , ) #把温湿度特性放入环境服务
SERVICES = (ENV_SERVER, ) #把环境服务放入服务集和中

((tem_char, hum_char, ), ) = ble.gatts_register_services(SERVICES) #注册服务到gatts

ble.gatts_write(tem_char, b'\x06\x08') #设置温度为20.54度(0x0806 = 2054)
ble.gatts_write(hum_char, b'\x09\x07') #设置湿度为18.01%(0x0709 = 1801)

#设置BLE广播数据并开始广播
ble.gap_advertise(100, adv_data = b'\x02\x01\x06\x03\x09\x41\x42')

def ble_irq(event, data): # 蓝牙中断函数
    if event == 1: #蓝牙已连接
      print("BLE 连接成功")

    elif event == 2: #蓝牙断开连接
      print("BLE 断开连接")
      ble.gap_advertise(100, adv_data = b'\x02\x01\x06\x03\x09\x41\x42')
      
    elif event == 3: #收到数据
      print("收到新消息")
      
ble.irq(ble_irq)
复制成功

运行上述代码,使用手机APP nRF Connect 连接设备后,可以读取到温湿度数据。温湿度数据使用两个字节来表示(低字节在前,高字节在后),温度的单位是0.01度,湿度的单位是0.01%

温湿度的获取

蓝牙组织联盟定义了很多常用的UUID,上面的示例只选取了其中的两个用作演示,有兴趣的同学可以自行尝试下别的UUID。

下一篇:蓝牙MIDI协议