python利用bleak库连接蓝牙设备并与蓝牙设备进行通信

2,211 阅读7分钟

背景介绍

公司需要做一个桌面端的程序来连接蓝牙设备,并向特征值uuid进行读写各种不同指令的操作。利用的是python和bleak库实现的,我负责的就是走通命令行的功能,界面不用我实现。接下来,简单介绍一下是如何实现的。

安装bleak

连接蓝牙是利用bleak库,需要安装一下,输入命令

pip install bleak

扫描发现蓝牙设备,获取蓝牙设备列表

图片1.png 运行结果如下,可以看到扫描到了一堆蓝牙设备,框出来的就是需要连接并通信的设备。

图片2.png

根据蓝牙设备的名称name过滤出指定设备

获取到设备列表以后,根据device.name对设备进行过滤,打印出来的就是目标设备。

图片3.png

填写蓝牙设备的地址address来连接我们想连接的设备

写一个方法用来连接设备以及对设备进行后续的操作。使用BleakClient()构造函数,并以设备地址address作为参数,创建了一个client对象。client就是一个客户端实例,通过它可以连接蓝牙设备、获取列表、读写特征值、开启/关闭通知等操作。

在连接设备之前需要输入设备的地址,并把设备地址传入方法中。连接到设备,就可以打印出设备的服务列表了,也可以分别打印出设备的服务uuid和服务对应的特征值uuid,打印出特征值是否可读、可写、支持通知等功能,在打印的内容中就可以找到我们想要的特征值了。

图片4.png 运行代码,效果如下,粉色框起来的就是我想要的uuid,其中fee2是只读的,fee3是可读写的,fee4是可接收通知的。

图片5.png

图片6.png

以下这几个内容就是重点,后续工作都是围绕它们展开的。

0000fee2-0000-1000-8000-00805f9b34fb (Handle: 33): Anki: Inc.

特征值uuid: 0000fee2-0000-1000-8000-00805f9b34fb

特征值属性: ['read']

0000fee3-0000-1000-8000-00805f9b34fb (Handle: 35): Anki: Inc.

特征值uuid: **0000fee3-0000-1000-8000-00805f9b34fb**

特征值属性: ['read', 'write']

0000fee4-0000-1000-8000-00805f9b34fb (Handle: 37): Nordic Semiconductor ASA

特征值uuid: 0000fee4-0000-1000-8000-00805f9b34fb

特征值属性: ['notify']

连接成功,拿到需要的uuid与蓝牙设备进行通信

发送指令的方法:write_gatt_char()

读取指令的方法:read_gatt_char()

发送读取指令

读取的指令是0x03ED12,这个0x12指令是给可读写的特征值FEE3发送的。

图片1.png 先调用write_gatt_char()方法发送指令,然后设置延时,调用read_gatt_char()方法读取特征值。这个0x12指令的作用是获取时间的,在读取以后,还需要解析读取的内容,把读取到的内容提取后四个字节才行,因为后4个字节才是有效的时间戳字节,再把时间戳转换成实际时间即可。

图片7.png 注意:要设置延时。刚发送完不会就立即能读取并打印出来的,需要给它一个缓冲时间!

验证一下:询问了同事,这个指令设置的时间是2022-01-01 08:00:00,那么我们发送读取指令读取出来的时间应该也不会相差太多。 运行结果如下,可以看到读取指令是成功的。

图片8.png

验证指令

读取到的指令是:0x7  0xe1  0x12  0x42  0x9a  0xcf  0x61

                         **低位------------------->高位**

这个指令的第一个字节0x7代表字节位数,0xe1代表和校验,0x12代表命令,后面的才是有效数据,这里的后面4位绿色部分的内容是时间戳的字节,要把有效几个字节转换成十进制,这样转换的才是对的。 上面的例子中,已经输出了十进制时间戳和实际时间。如何知道是正确的呢?可以去验证一下。我们可以去计算器中验证一下十进制时间戳是否正确,把计算器切换成程序员模式。输入的时候要从高位开始输入(千万注意!!!),也就是61CF9A42,计算器就会自动转换成十进制的时间戳。

图片11.png

接着,把计算器转换好的十进制时间戳放到转换工具中tool.lu/timestamp/,点击转换就可以看到时间了,和代码输出的是一样的时间,都是2022-01-01 08:03:17,说明是对的。

图片12.png

这就说明获取到的时间是正确的。

设置/写入指令

每次连接蓝牙设备的时候就进行一次时间设置,先获取到当前系统的实际时间并把它写入到设置时间的指令中。

注意:Python获取当前系统时间(这个获取的时间是浮点数),需要转成整型,需要把获取的时间转成字节,再去进行组装指令,组装指令的时候会用到和校验算法,然后再进行设置语句!

发送指令以后,读取到的数据中,第3个字节到第6个字节就是要获取的时间戳的有效字节。

图片5.png 组装指令的示例如下:

图片4.png

过程总结:

图片6.png

代码如下所示:

图片2.png bytearray 是 Python 中的一种可变字节数组类型,它是 bytes 类型的可变版本。与 bytes 类型不同,bytearray 对象可以进行原地修改,添加、删除或替换其中的字节内容。

运行结果如下,可以看到时间设置成功!

图片3.png

从上面的运行结果中可以看到计算的和校验是A4。我们可以使用bdo工具用来检测和校验计算得是否正确。

输入bdo的命令,如果不知道和校验是多少,可以把和校验的字节写成00,再把其他字节补齐,bdo工具会自动帮我们计算出和校验位。可以看到运行出来以后,下面框出来的就是计算好和校验的字节了。

图片7.png 可以看到,拿到获取到的当前时间,设置0x12指令,得到的时间是和获取到的是一样的!!!

和校验的函数

def util_chksum8(pdata, count):  
   if count <= 0:  
       return 0  
  
   # Main summing loop  
   addr = pdata  
   ck_sum = 0  
   for i in range(count):  
       ck_sum = ck_sum + addr[i]  
       ck_sum = ck_sum % 0x100  
   ck_sum = 0xFF - ck_sum  
  
   return ck_sum

打印带空格的字节函数

# 函数print_hex()将该字节串转换为十六进制,并按照空格分隔打印出来  
def print_hex(bytes):  
   l = [hex(int(i)) for i in bytes]  
   print(" ".join(l))

提取指令并转换成对应的数据类型

接收通知

拿到特征值属性为notify的特征值uuid,就可以开启通知了,开启通知接收一个回调函数,这个回调函数是处理接收到的通知数据的。

图片1.png

图片2.png

连接蓝牙设备以后,拿一个磁铁靠近蓝牙设备,这是模拟设备的开关情况,可以看到收到的通知如下:

图片3.png

注意: 一定要写成异步的等待!不要写成time.sleep(5),因为这是同步的,同步会阻碍别的内容!!

这就是我要分享的内容了,欢迎大家给出好的意见~