跳转至

Python库:Bleak

Python库:Bleak

这个库比较新,试一下:https://github.com/hbldh/bleak

用这个库写了一个小工具可以体验一下:BLE-Security-Tool

扫描周围蓝牙设备

import asyncio
from bleak import BleakScanner

async def scan_devices():
    devices = await BleakScanner.discover()
    #这里可以通过adapter="hci0"指定蓝牙适配器
    #例如devices = await BleakScanner.discover(adapter="hci1")
    for d in devices:
        print(d)

通过 BleakScanner.discover() 扫描出来的结果是一个列表,列表的每个是一个 bleak.backends.device.BLEDevice 类,bleak 的源代码中定义在这里:bleak\backends\device.py

1650348375192-6d84accf-2ba8-4881-a215-6bf869530900.png

可以看出来每个设备有 address(设备地址,字符串)、name(设备名,字符串)、details(详情,包含了所有的信息,字典)、rssi(信号强度,整数)、metadata(包含服务 UUID 和 manufacturer_data,字典)

连接与断开设备

client = BleakClient(address,adapter="hci0")#使用adapter指定蓝牙适配器
await client.connect()
await client.disconnect()

获取服务与特性

async def scan_services(address):
    async with BleakClient(address) as client:
        svcs = await client.get_services()
        for service in svcs:
            print(service.uuid)

这个 .get_services() 返回的也是一个列表,里面每个都是一个 bleak.backends.bluezdbus.service.BleakGATTServiceBlueZDBus 类,可以用 .uuid 获取 uuid(字符串)

async def scan_characteristics(address):
    async with BleakClient(address) as client:
        svcs = await client.get_services()
        print("Services:")
        for service in svcs:
            if service.uuid == "000000ff-0000-1000-8000-00805f9b34fb":
                for char in service.characteristics:
                    print(char.uuid + "  -->  Handle:" + str(hex(char.handle)))

对于一个服务来说用 .characteristics 获取 characteristic 列表,对于获取到的 characteristic 列表可以遍历出每一个,通过 .uuid 获取 uuid,通过 .handle 获取句柄的值(十进制整数)通过 properties 来获取特性的属性(读写啥的)

读写属性值

获取到之后对于 read 的特征属性可以通过 read_gatt_char(char.uuid) 来获取相应的值

await client.read_gatt_char(char.uuid)

这样读出来是一个 bytearray,使用 value = bytes() 可以转成 bytes

可以通过 write_gatt_char 写相应的值,传递的参数是特征的 UUID 或者句柄

await client.write_gatt_char(0x2b,b'd205303e099ceff44835')

问题是传递的值有时候传字符串,有时候传十六进制,例如 BLECTF 的这一关这一关

关于这俩格式的转换,这个下面的代码可以看出来,所以分成两个函数来用

1650622167672-9ae4ba89-ac1e-498c-afd5-6241be49dce0.png

这里写的数据都是可以是这里面的任何一种 bytes, bytearray, memoryview,所以代码中的 #1 这里的 bytes 可有可无

async def write_value_raw(client,charuuid,value):
    try:
        value = bytearray.fromhex(value)      #1
        await client.write_gatt_char(charuuid,value)
        print(green("[+]SEND RAW SUCCESS!"))
    except:
        print(red("[x]ERROR: Can't write value to " + charuuid))

async def write_value_str(client,charuuid,value):
    try:
        await client.write_gatt_char(charuuid,value.encode())
        print(green("[+]SEND STR SUCCESS!"))
    except:
        print(red("[x]ERROR: Can't write value to " + charuuid))

监听通知或指示

好像是这俩放在一个函数实现了,我确实也可以用这一个函数监听的到

address = "08:3A:F2:B9:85:92"
char_uuid = "0000ff0c-0000-1000-8000-00805f9b34fb"

def callback(sender: int, data: bytearray):
    print(f"{sender}: {data}")

async def main(address, char_uuid):
    async with BleakClient(address) as client:
        print(f"Connected: {client.is_connected}")
        await client.start_notify(char_uuid, callback)
        await client.write_gatt_char(char_uuid,b"hello")
        await asyncio.sleep(5.0)
        await client.stop_notify(char_uuid)

asyncio.run(main(address, char_uuid))

start_notify()传入的参数是特征的 uuid,以及回调函数,即监听到之后干啥

想要监听到通知或者指示需要先使能特征值,这个 write 应该就是使能的过程

最后使用 stop_notify(char_uuid) 停止

原文: https://www.yuque.com/hxfqg9/iot/psuwyl