1. 前言
如果您阅读本文的时候有障碍或者有疑问,可以评论区留言或者看看本专栏的前部分内容。
从这一章节开始,我们不再仅仅讲解源码当中的例子了,因为越来越深入了。
特征的读写是BLE通讯中较为常用的数据交换手段了,因为源码给的例子中没有专门针对这个部分的内容,所以在这里我们自己制定一个实战DEMO。
2. 涉及到的API
2.1. write_gatt_char
写特征,在对特征进行写时,我们要提前获取到它的UUID或者Handle或者其他能体现特征身份的相关信息。在API里面有这样的解释道:
char_specifier (BleakGATTCharacteristic, int, str or UUID): The characteristic to write
to, specified by either integer handle, UUID or directly by the
BleakGATTCharacteristic object representing it.
完整的API源码是这样的:
async def write_gatt_char(
self,
char_specifier: Union[BleakGATTCharacteristic, int, str, uuid.UUID],
data: Union[bytes, bytearray, memoryview],
response: bool = False,
) -> None:
"""Perform a write operation of the specified GATT characteristic.
Args:
char_specifier (BleakGATTCharacteristic, int, str or UUID): The characteristic to write
to, specified by either integer handle, UUID or directly by the
BleakGATTCharacteristic object representing it.
data (bytes or bytearray): The data to send.
response (bool): If write-with-response operation should be done. Defaults to `False`.
"""
if not isinstance(char_specifier, BleakGATTCharacteristic):
characteristic = self.services.get_characteristic(char_specifier)
else:
characteristic = char_specifier
if not characteristic:
raise BleakError("Characteristic {} was not found!".format(char_specifier))
response = (
GattWriteOption.WRITE_WITH_RESPONSE
if response
else GattWriteOption.WRITE_WITHOUT_RESPONSE
)
buf = Buffer(len(data))
buf.length = buf.capacity
with memoryview(buf) as mv:
mv[:] = data
_ensure_success(
await characteristic.obj.write_value_with_result_async(buf, response),
None,
f"Could not write value {data} to characteristic {characteristic.handle:04X}",
)
2.2. read_gatt_char
读特征,在对特征进行读取时,同样需要提前获取到它的UUID或者Handle或者其他能体现特征身份的相关信息。
完整的API源码是这样的:
async def read_gatt_char(
self,
char_specifier: Union[BleakGATTCharacteristic, int, str, uuid.UUID],
**kwargs,
) -> bytearray:
"""Perform read operation on the specified GATT characteristic.
Args:
char_specifier (BleakGATTCharacteristic, int, str or UUID): The characteristic to read from,
specified by either integer handle, UUID or directly by the
BleakGATTCharacteristic object representing it.
Keyword Args:
use_cached (bool): ``False`` forces Windows to read the value from the
device again and not use its own cached value. Defaults to ``False``.
Returns:
(bytearray) The read data.
"""
use_cached = kwargs.get("use_cached", False)
if not isinstance(char_specifier, BleakGATTCharacteristic):
characteristic = self.services.get_characteristic(char_specifier)
else:
characteristic = char_specifier
if not characteristic:
raise BleakError("Characteristic {0} was not found!".format(char_specifier))
value = bytearray(
_ensure_success(
await characteristic.obj.read_value_async(
BluetoothCacheMode.CACHED
if use_cached
else BluetoothCacheMode.UNCACHED
),
"value",
f"Could not read characteristic handle {characteristic.handle}",
)
)
logger.debug(f"Read Characteristic {characteristic.handle:04X} : {value}")
return value
3. 实战
现在要实现一个这样的功能:
用nRF Connect模拟一个设备,将GATT SERVICE里面一个特征设置成同时可读可写的属性。然后使用Bleak的在电脑上对刚刚模拟的设备进行读写操作(先写数据,再读取出来,看看是不是相同的内容)。
3.1 nRF Connect模拟设备
将Generic Access中的设备名字设置成可读可写属性:
3.2 代码编写
这次我们基于官方例子 service_explorer.py 进行二次开发,因为在对特征进行读取之前,我们需要先获取到它的句柄或者UUID相关信息。
对nRF Connect模拟的设备进行连接时,最好是通过名字进行扫描及搜索。对 service_explorer.py 进行一番简化及修改:
- 通过设备名字扫描设备,获取到其设备地址
- 通过扫描获取到的设备发起连接
import sys
import platform
import asyncio
import logging
from bleak import BleakClient
from bleak import BleakScanner
logger = logging.getLogger(__name__)
DEVICE_NAME = "dabai"
async def main():
device = await BleakScanner.find_device_by_filter(
lambda d, ad: d.name and d.name.lower() == DEVICE_NAME.lower()
)
print(device)
async with BleakClient(device.address) as client:
logger.info(f"Connected: {client.is_connected}")
for service in client.services:
logger.info(f"[Service] {service}")
for char in service.characteristics:
if "read" in char.properties:
try:
value = bytes(await client.read_gatt_char(char.uuid))
logger.info(
f"\t[Characteristic] {char} ({','.join(char.properties)}), Value: {value}"
)
except Exception as e:
logger.error(
f"\t[Characteristic] {char} ({','.join(char.properties)}), Value: {e}"
)
else:
value = None
logger.info(
f"\t[Characteristic] {char} ({','.join(char.properties)}), Value: {value}"
)
for descriptor in char.descriptors:
try:
value = bytes(
await client.read_gatt_descriptor(descriptor.handle)
)
logger.info(
f"\t\t[Descriptor] {descriptor}) | Value: {value}")
except Exception as e:
logger.error(
f"\t\t[Descriptor] {descriptor}) | Value: {e}")
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)
asyncio.run(main())
运行结果,目前仅对目标进行了发现服务特征的操作:
EE:A6:84:0B:23:20: dabai
INFO:bleak.backends.winrt.client:Services resolved for BleakClientWinRT (EE:A6:84:0B:23:20)
INFO:__main__:Connected: True
INFO:__main__:[Service] 00001800-0000-1000-8000-00805f9b34fb (Handle: 1): Generic Access Profile
INFO:__main__: [Characteristic] 00002a00-0000-1000-8000-00805f9b34fb (Handle: 2): (read,write), Value: b'dabai'
INFO:__main__: [Characteristic] 00002a01-0000-1000-8000-00805f9b34fb (Handle: 4): (read), Value: b'\xc1\x00'
INFO:__main__: [Characteristic] 00002a04-0000-1000-8000-00805f9b34fb (Handle: 6): (read), Value: b'P\x00\xa0\x00\x00\x00\x90\x01'
INFO:__main__: [Characteristic] 00002aa6-0000-1000-8000-00805f9b34fb (Handle: 8): (read), Value: b'\x01'
INFO:__main__:[Service] 00001801-0000-1000-8000-00805f9b34fb (Handle: 10): Generic Attribute Profile
INFO:__main__: [Characteristic] 00002a05-0000-1000-8000-00805f9b34fb (Handle: 11): (indicate), Value: None
INFO:__main__: [Descriptor] 00002902-0000-1000-8000-00805f9b34fb (Handle: 13): Client Characteristic Configuration) | Value: b'\x02\x00'
INFO:__main__:[Service] 0000180d-0000-1000-8000-00805f9b34fb (Handle: 14): Heart Rate
INFO:__main__: [Characteristic] 00002a37-0000-1000-8000-00805f9b34fb (Handle: 15): (notify), Value: None
INFO:__main__: [Descriptor] 00002902-0000-1000-8000-00805f9b34fb (Handle: 17): Client Characteristic Configuration) | Value: b'\x00\x00'
INFO:__main__: [Characteristic] 00002a38-0000-1000-8000-00805f9b34fb (Handle: 18): (read), Value: b'\x03'
INFO:__main__:[Service] 0000180f-0000-1000-8000-00805f9b34fb (Handle: 20): Battery Service
INFO:__main__: [Characteristic] 00002a19-0000-1000-8000-00805f9b34fb (Handle: 21): (read), Value: b'a'
INFO:__main__:[Service] 0000180a-0000-1000-8000-00805f9b34fb (Handle: 23): Device Information
INFO:__main__: [Characteristic] 00002a29-0000-1000-8000-00805f9b34fb (Handle: 24): (read), Value: b'YFTech'
INFO:__main__: [Characteristic] 00002a24-0000-1000-8000-00805f9b34fb (Handle: 26): (read), Value: b'COROS M1D_4R9'
INFO:__main__: [Characteristic] 00002a25-0000-1000-8000-00805f9b34fb (Handle: 28): (read), Value: b'4B948172B1E2'
INFO:__main__: [Characteristic] 00002a27-0000-1000-8000-00805f9b34fb (Handle: 30): (read), Value: b'UVWbejJZpqrsPtwuvxy'
INFO:__main__: [Characteristic] 00002a28-0000-1000-8000-00805f9b34fb (Handle: 32): (read), Value: b'V 2.70.0'
INFO:__main__:[Service] 6e400001-b5a3-f393-e0a9-77656c6f6f70 (Handle: 34): Unknown
INFO:__main__: [Characteristic] 6e400003-b5a3-f393-e0a9-77656c6f6f70 (Handle: 35): (notify), Value: None
INFO:__main__: [Descriptor] 00002902-0000-1000-8000-00805f9b34fb (Handle: 37): Client Characteristic Configuration) | Value: b'\x00\x00'
INFO:__main__: [Characteristic] 6e400002-b5a3-f393-e0a9-77656c6f6f70 (Handle: 38): (write-without-response,write), Value: None
INFO:__main__:[Service] 6e400001-b5a3-f393-e0a9-77757c7f7f70 (Handle: 40): Unknown
INFO:__main__: [Characteristic] 6e400003-b5a3-f393-e0a9-77757c7f7f70 (Handle: 41): (notify), Value: None
INFO:__main__: [Descriptor] 00002902-0000-1000-8000-00805f9b34fb (Handle: 43): Client Characteristic Configuration) | Value: b'\x00\x00'
INFO:__main__: [Characteristic] 6e400002-b5a3-f393-e0a9-77757c7f7f70 (Handle: 44): (write-without-response,write), Value: None
INFO:__main__:[Service] 6e400001-b5a3-f393-e0a9-e50e24dcca9e (Handle: 46): Nordic UART Service
INFO:__main__: [Characteristic] 6e400003-b5a3-f393-e0a9-e50e24dcca9e (Handle: 47): (notify), Value: None
INFO:__main__: [Descriptor] 00002902-0000-1000-8000-00805f9b34fb (Handle: 49): Client Characteristic Configuration) | Value: b'\x00\x00'
INFO:__main__: [Characteristic] 6e400002-b5a3-f393-e0a9-e50e24dcca9e (Handle: 50): (write-without-response,write), Value: None
INFO:__main__:[Service] 0000fee7-0000-1000-8000-00805f9b34fb (Handle: 52): Tencent Holdings Limited
INFO:__main__: [Characteristic] 0000fea1-0000-1000-8000-00805f9b34fb (Handle: 53): (read,notify,indicate), Value: b'\x01\x00\x00\x00'
INFO:__main__: [Descriptor] 00002902-0000-1000-8000-00805f9b34fb (Handle: 55): Client Characteristic Configuration) | Value: b'\x00\x00'
INFO:__main__: [Characteristic] 0000fea2-0000-1000-8000-00805f9b34fb (Handle: 56): (read,write,indicate), Value: b"\x01\x10'\x00"
INFO:__main__: [Descriptor] 00002902-0000-1000-8000-00805f9b34fb (Handle: 58): Client Characteristic Configuration) | Value: b'\x00\x00'
INFO:__main__: [Characteristic] 0000fec9-0000-1000-8000-00805f9b34fb (Handle: 59): (read), Value: b'\xee\xa6\x84\x0b# '
INFO:__main__:[Service] 00001814-0000-1000-8000-00805f9b34fb (Handle: 61): Running Speed and Cadence
INFO:__main__: [Characteristic] 00002a53-0000-1000-8000-00805f9b34fb (Handle: 62): (notify), Value: None
INFO:__main__: [Descriptor] 00002902-0000-1000-8000-00805f9b34fb (Handle: 64): Client Characteristic Configuration) | Value: b'\x00\x00'
INFO:__main__: [Characteristic] 00002a54-0000-1000-8000-00805f9b34fb (Handle: 65): (read), Value: b'\x05\x00'
INFO:__main__:[Service] 00001816-0000-1000-8000-00805f9b34fb (Handle: 67): Cycling Speed and Cadence
INFO:__main__: [Characteristic] 00002a5b-0000-1000-8000-00805f9b34fb (Handle: 68): (notify), Value: None
INFO:__main__: [Descriptor] 00002902-0000-1000-8000-00805f9b34fb (Handle: 70): Client Characteristic Configuration) | Value: b'\x00\x00'
INFO:__main__: [Characteristic] 00002a5c-0000-1000-8000-00805f9b34fb (Handle: 71): (read), Value: b'\x07\x00'
INFO:__main__: [Characteristic] 00002a5d-0000-1000-8000-00805f9b34fb (Handle: 73): (read), Value: b'\x04'
INFO:__main__: [Characteristic] 00002a55-0000-1000-8000-00805f9b34fb (Handle: 75): (write,indicate), Value: None
INFO:__main__: [Descriptor] 00002902-0000-1000-8000-00805f9b34fb (Handle: 77): Client Characteristic Configuration) | Value: b'\x00\x00'
我们主要在意这部分输出结果:
可以得知我们设置的这个特征的UUID是这样的🎬,Handle 等于 2,属性为可读可写,内容是"dabai"。
现在我们要开始进入正题了,只需添加这几行代码,就可以验证读写接口以及是否操作成功了:
d_name = "dabaiqianrushi"
await client.write_gatt_char(2, str.encode(d_name))
value = bytes(await client.read_gatt_char(2))
logger.info(f"Value: {value}")
目的是将Device Name特征值由"dabai" 修改成"dabaiqianrushi"。
输出结果:
在nRF Connect中读取出来看看,说明我们修改成功了:
4. 写在最后
本文的内容比较简单,主要使用是两个接口的使用。因为实际项目中比较常用,所以单独列为一章了。
看完觉得有收获点个赞吧 ❤️ 或者关注Bleak专栏查看更多Bleak相关使用⏩