背景介绍
公司需要做一个桌面端的程序来连接蓝牙设备,并向特征值uuid进行读写各种不同指令的操作。利用的是python和bleak库实现的,我负责的就是走通命令行的功能,界面不用我实现。接下来,简单介绍一下是如何实现的。
安装bleak
连接蓝牙是利用bleak库,需要安装一下,输入命令
pip install bleak
扫描发现蓝牙设备,获取蓝牙设备列表
运行结果如下,可以看到扫描到了一堆蓝牙设备,框出来的就是需要连接并通信的设备。
根据蓝牙设备的名称name过滤出指定设备
获取到设备列表以后,根据device.name对设备进行过滤,打印出来的就是目标设备。
填写蓝牙设备的地址address来连接我们想连接的设备
写一个方法用来连接设备以及对设备进行后续的操作。使用BleakClient()构造函数,并以设备地址address作为参数,创建了一个client对象。client就是一个客户端实例,通过它可以连接蓝牙设备、获取列表、读写特征值、开启/关闭通知等操作。
在连接设备之前需要输入设备的地址,并把设备地址传入方法中。连接到设备,就可以打印出设备的服务列表了,也可以分别打印出设备的服务uuid和服务对应的特征值uuid,打印出特征值是否可读、可写、支持通知等功能,在打印的内容中就可以找到我们想要的特征值了。
运行代码,效果如下,粉色框起来的就是我想要的uuid,其中fee2是只读的,fee3是可读写的,fee4是可接收通知的。
以下这几个内容就是重点,后续工作都是围绕它们展开的。
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发送的。
先调用write_gatt_char()方法发送指令,然后设置延时,调用read_gatt_char()方法读取特征值。这个0x12指令的作用是获取时间的,在读取以后,还需要解析读取的内容,把读取到的内容提取后四个字节才行,因为后4个字节才是有效的时间戳字节,再把时间戳转换成实际时间即可。
注意:要设置延时。刚发送完不会就立即能读取并打印出来的,需要给它一个缓冲时间!
验证一下:询问了同事,这个指令设置的时间是2022-01-01 08:00:00,那么我们发送读取指令读取出来的时间应该也不会相差太多。 运行结果如下,可以看到读取指令是成功的。
验证指令
读取到的指令是:0x7 0xe1 0x12 0x42 0x9a 0xcf 0x61
**低位------------------->高位**
这个指令的第一个字节0x7代表字节位数,0xe1代表和校验,0x12代表命令,后面的才是有效数据,这里的后面4位绿色部分的内容是时间戳的字节,要把有效几个字节转换成十进制,这样转换的才是对的。 上面的例子中,已经输出了十进制时间戳和实际时间。如何知道是正确的呢?可以去验证一下。我们可以去计算器中验证一下十进制时间戳是否正确,把计算器切换成程序员模式。输入的时候要从高位开始输入(千万注意!!!),也就是61CF9A42,计算器就会自动转换成十进制的时间戳。
接着,把计算器转换好的十进制时间戳放到转换工具中tool.lu/timestamp/,点击转换就可以看到时间了,和代码输出的是一样的时间,都是2022-01-01 08:03:17,说明是对的。
这就说明获取到的时间是正确的。
设置/写入指令
每次连接蓝牙设备的时候就进行一次时间设置,先获取到当前系统的实际时间并把它写入到设置时间的指令中。
注意:Python获取当前系统时间(这个获取的时间是浮点数),需要转成整型,需要把获取的时间转成字节,再去进行组装指令,组装指令的时候会用到和校验算法,然后再进行设置语句!
发送指令以后,读取到的数据中,第3个字节到第6个字节就是要获取的时间戳的有效字节。
组装指令的示例如下:
过程总结:
代码如下所示:
bytearray 是 Python 中的一种可变字节数组类型,它是 bytes 类型的可变版本。与 bytes 类型不同,bytearray 对象可以进行原地修改,添加、删除或替换其中的字节内容。
运行结果如下,可以看到时间设置成功!
从上面的运行结果中可以看到计算的和校验是A4。我们可以使用bdo工具用来检测和校验计算得是否正确。
输入bdo的命令,如果不知道和校验是多少,可以把和校验的字节写成00,再把其他字节补齐,bdo工具会自动帮我们计算出和校验位。可以看到运行出来以后,下面框出来的就是计算好和校验的字节了。
可以看到,拿到获取到的当前时间,设置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,就可以开启通知了,开启通知接收一个回调函数,这个回调函数是处理接收到的通知数据的。
连接蓝牙设备以后,拿一个磁铁靠近蓝牙设备,这是模拟设备的开关情况,可以看到收到的通知如下:
注意: 一定要写成异步的等待!不要写成time.sleep(5),因为这是同步的,同步会阻碍别的内容!!
这就是我要分享的内容了,欢迎大家给出好的意见~