【Bleak】八、特征的读写

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相关使用⏩

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

强人电子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值