好记性不如铅笔头

Bluetooth, python && jython, 编程, 网络通讯

使用gatttool和bluepy来测试ESP32蓝牙实例

本文笔记了如何使用Linux命令行工具和bluepy来测试ESP32蓝牙实例。

CONTENTS

参考链接

【 https://cstriker1407.info/blog/esp32-simple-introduce-note/
【 https://cstriker1407.info/blog/bluetooth-hcitool-gatttool/
【 https://cstriker1407.info/blog/install-bt-stack-on-linux/

ESP32官方IDF中包含了很多蓝牙实例,这里以【 gatt_server_service_table 】为例子,笔记下在Linux下如何连接上该蓝牙Server。

ESP32代码分析

连接上ESP32开发板,然后直接编译,烧录【 gatt_server_service_table 】。

esp/esp-idf/examples/bluetooth/bluedroid/ble/gatt_server_service_table$  idf.py flash
esp/esp-idf/examples/bluetooth/bluedroid/ble/gatt_server_service_table$  idf.py monitor

该实例中的gatt的设置代码如下:

/* Full Database Description - Used to add attributes into the database */
static const esp_gatts_attr_db_t gatt_db[HRS_IDX_NB] =
{
    // Service Declaration
    [IDX_SVC]        =
    {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&primary_service_uuid, ESP_GATT_PERM_READ,
      sizeof(uint16_t), sizeof(GATTS_SERVICE_UUID_TEST), (uint8_t *)&GATTS_SERVICE_UUID_TEST}},

    /* Characteristic Declaration */
    [IDX_CHAR_A]     =
    {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ,
      CHAR_DECLARATION_SIZE, CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_read_write_notify}},

    /* Characteristic Value */
    [IDX_CHAR_VAL_A] =
    {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&GATTS_CHAR_UUID_TEST_A, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE,
      GATTS_DEMO_CHAR_VAL_LEN_MAX, sizeof(char_value), (uint8_t *)char_value}},

    /* Client Characteristic Configuration Descriptor */
    [IDX_CHAR_CFG_A]  =
    {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_client_config_uuid, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE,
      sizeof(uint16_t), sizeof(heart_measurement_ccc), (uint8_t *)heart_measurement_ccc}},

    /* Characteristic Declaration */
    [IDX_CHAR_B]      =
    {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ,
      CHAR_DECLARATION_SIZE, CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_read}},

    /* Characteristic Value */
    [IDX_CHAR_VAL_B]  =
    {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&GATTS_CHAR_UUID_TEST_B, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE,
      GATTS_DEMO_CHAR_VAL_LEN_MAX, sizeof(char_value), (uint8_t *)char_value}},

    /* Characteristic Declaration */
    [IDX_CHAR_C]      =
    {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ,
      CHAR_DECLARATION_SIZE, CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_write}},

    /* Characteristic Value */
    [IDX_CHAR_VAL_C]  =
    {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&GATTS_CHAR_UUID_TEST_C, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE,
      GATTS_DEMO_CHAR_VAL_LEN_MAX, sizeof(char_value), (uint8_t *)char_value}},

};

通过分析gatt的设置代码表可知:
GATT Server建立了一个primary service,UUID为0xFF(GATTS_SERVICE_UUID_TEST),该service下面有3个Characteristic:
第一个0xFF01(GATTS_CHAR_UUID_TEST_A),可读可写可通知(0x1A = ESP_GATT_CHAR_PROP_BIT_WRITE | ESP_GATT_CHAR_PROP_BIT_READ | ESP_GATT_CHAR_PROP_BIT_NOTIFY)
第二个0xFF02(GATTS_CHAR_UUID_TEST_B),可读(0x02 = ESP_GATT_CHAR_PROP_BIT_READ)
第二个0xFF03(GATTS_CHAR_UUID_TEST_C),可写(0x08 = ESP_GATT_CHAR_PROP_BIT_WRITE)
继续分析其他代码,可以发现,当Notify设置发生变化时,ESP32会主动发送一个Notify/Indicate通知,由于第一个Characteristic功能最为丰富,我们主要测试第一个Characteristic。

命令行连接

首先使用hcitool搜索该Server,如下:

$ sudo hcitool lescan
。。。。。
C4:4F:33:16:F8:AF ESP_GATTS_DEMO
。。。。。

可以看到,该Server的Mac地址为【 C4:4F:33:16:F8:AF 】,然后使用gatttool连接,如下:

$ gatttool -I
[                 ][LE]> help
help                                           Show this help
exit                                           Exit interactive mode
quit                                           Exit interactive mode
connect         [address [address type]]       Connect to a remote device
disconnect                                     Disconnect from a remote device
primary         [UUID]                         Primary Service Discovery
included        [start hnd [end hnd]]          Find Included Services
characteristics [start hnd [end hnd [UUID]]]   Characteristics Discovery
char-desc       [start hnd] [end hnd]          Characteristics Descriptor Discovery
char-read-hnd   <handle>                       Characteristics Value/Descriptor Read by handle
char-read-uuid  <UUID> [start hnd] [end hnd]   Characteristics Value/Descriptor Read by UUID
char-write-req  <handle> <new value>           Characteristic Value Write (Write Request)
char-write-cmd  <handle> <new value>           Characteristic Value Write (No response)
sec-level       [low | medium | high]          Set security level. Default: low
mtu             <value>                        Exchange MTU for GATT/ATT
[                 ][LE]> connect C4:4F:33:16:F8:AF #连接ESP32
Attempting to connect to C4:4F:33:16:F8:AF
Connection successful
[C4:4F:33:16:F8:AF][LE]> primary 
attr handle: 0x0001, end grp handle: 0x0005 uuid: 00001801-0000-1000-8000-00805f9b34fb
attr handle: 0x0014, end grp handle: 0x001c uuid: 00001800-0000-1000-8000-00805f9b34fb
attr handle: 0x0028, end grp handle: 0xffff uuid: 000000ff-0000-1000-8000-00805f9b34fb
[C4:4F:33:16:F8:AF][LE]> characteristics 
handle: 0x0002, char properties: 0x20, char value handle: 0x0003, uuid: 00002a05-0000-1000-8000-00805f9b34fb
handle: 0x0015, char properties: 0x02, char value handle: 0x0016, uuid: 00002a00-0000-1000-8000-00805f9b34fb
handle: 0x0017, char properties: 0x02, char value handle: 0x0018, uuid: 00002a01-0000-1000-8000-00805f9b34fb
handle: 0x0019, char properties: 0x02, char value handle: 0x001a, uuid: 00002aa6-0000-1000-8000-00805f9b34fb
handle: 0x0029, char properties: 0x1a, char value handle: 0x002a, uuid: 0000ff01-0000-1000-8000-00805f9b34fb
#操作handle为0x0029,属性为可读可写可通知0x1A,value的handle为0x2a,UUID为0xFF01

handle: 0x002c, char properties: 0x02, char value handle: 0x002d, uuid: 0000ff02-0000-1000-8000-00805f9b34fb
handle: 0x002e, char properties: 0x08, char value handle: 0x002f, uuid: 0000ff03-0000-1000-8000-00805f9b34fb
[C4:4F:33:16:F8:AF][LE]> char-desc 
handle: 0x0001, uuid: 00002800-0000-1000-8000-00805f9b34fb
handle: 0x0002, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x0003, uuid: 00002a05-0000-1000-8000-00805f9b34fb
handle: 0x0004, uuid: 00002902-0000-1000-8000-00805f9b34fb
handle: 0x0014, uuid: 00002800-0000-1000-8000-00805f9b34fb
handle: 0x0015, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x0016, uuid: 00002a00-0000-1000-8000-00805f9b34fb
handle: 0x0017, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x0018, uuid: 00002a01-0000-1000-8000-00805f9b34fb
handle: 0x0019, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x001a, uuid: 00002aa6-0000-1000-8000-00805f9b34fb
handle: 0x0028, uuid: 00002800-0000-1000-8000-00805f9b34fb
handle: 0x0029, uuid: 00002803-0000-1000-8000-00805f9b34fb
#操作handle为0x002b的UUID为0x2803(ESP_GATT_UUID_CHAR_DECLARE),表示characteristics的定义
handle: 0x002a, uuid: 0000ff01-0000-1000-8000-00805f9b34fb
#操作handle为0x002a的UUID为0xff01,表示characteristics的值所在的uuid
handle: 0x002b, uuid: 00002902-0000-1000-8000-00805f9b34fb
#操作handle为0x002b的UUID为0x2902(ESP_GATT_UUID_CHAR_CLIENT_CONFIG),表示characteristics的Notify配置

handle: 0x002c, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x002d, uuid: 0000ff02-0000-1000-8000-00805f9b34fb
handle: 0x002e, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x002f, uuid: 0000ff03-0000-1000-8000-00805f9b34fb
[C4:4F:33:16:F8:AF][LE]> char-read-hnd 0x0029  #读一下0xFF01的定义
Characteristic value/descriptor: 1a 2a 00 01 ff 
[C4:4F:33:16:F8:AF][LE]> char-read-hnd 0x002a  #读一下0xFF01的值
Characteristic value/descriptor: 11 22 33 44 
[C4:4F:33:16:F8:AF][LE]> char-read-hnd 0x002b #读一下0xFF01的Notify配置
Characteristic value/descriptor: 00 00 
[C4:4F:33:16:F8:AF][LE]> char-write-cmd 0x002a aabbccdd #更改0xFF01的值
[C4:4F:33:16:F8:AF][LE]> char-read-hnd 0x002a #读一下0xFF01的值
Characteristic value/descriptor: aa bb cc dd 
[C4:4F:33:16:F8:AF][LE]> char-write-req 0x002a 12345678 #更改0xFF01的值,需要回复
Characteristic value was written successfully
[C4:4F:33:16:F8:AF][LE]> char-read-hnd 0x002a #读一下0xFF01的值
Characteristic value/descriptor: 12 34 56 78 
[C4:4F:33:16:F8:AF][LE]> char-read-hnd 0x002b #读一下0xFF01的Notify配置
Characteristic value/descriptor: 00 00 
[C4:4F:33:16:F8:AF][LE]> char-write-cmd 0x002b 0100 #打开Notify
Notification handle = 0x002a value: 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 
[C4:4F:33:16:F8:AF][LE]> char-write-cmd 0x002b 0200 #打开Indicate
Indication   handle = 0x002a value: 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 
[C4:4F:33:16:F8:AF][LE]> char-write-cmd 0x002b 0000 #关闭
[C4:4F:33:16:F8:AF][LE]> disconnect  #解除连接

bluepy连接

bluepy的API都非常简洁明了,这里就直接贴代码和日志输出,通过对比日志和GATT表,可以很清晰的了解API:

#!/usr/bin/env python
# coding=utf-8

from bluepy.btle import *
import time

def ble_adv_scan(scan_time):
    ble_scanner = Scanner()
    
    print("Ble Scan Begin. scan_time: {}   -->>".format(scan_time))
    ble_devices = ble_scanner.scan(scan_time)
    for dev in ble_devices:
        dev_mac = dev.addr
        dev_addr_type = dev.addrType
        dev_rssi = dev.rssi
        print("++ Find Dev:{}({}):{}".format(dev_mac, dev_addr_type, dev_rssi))
        for (adtype, desc, value) in dev.getScanData():
            print("---- {}({}):{}".format(adtype, desc, value))

    print("Ble Scan Finish. scan_time: {}   --<<".format(scan_time))


def ble_services_enum(mac_addr):
    remote_dev = Peripheral(mac_addr)
    
    print("Remote Services:")
    remote_services = remote_dev.getServices()
    for single_service in remote_services:
        print("\tservice uuid:{}".format(single_service.uuid))
        chars = single_service.getCharacteristics()
        print("\t\t<handle>\t<property>\t<can read>\tuuid")
        for single_char in chars:
            print("\t\t{}\t{}({})\t{}\t{}".format
            (hex(single_char.getHandle()),single_char.propertiesToString(),hex(single_char.properties),  single_char.supportsRead(), single_char.uuid )  )

    print("Remote Characteristics:")
    remote_chars = remote_dev.getCharacteristics()
    print("\t<handle>\t<can read>\t<property>\tuuid")
    for single_char in remote_chars:    
        print("\t{}\t{}({})\t{}\t{}".format
        (hex(single_char.getHandle()),single_char.propertiesToString(),hex(single_char.properties),  single_char.supportsRead(), single_char.uuid )  )

    print("Remote Descriptors:")
    remote_decs = remote_dev.getDescriptors()
    for remote_decs in remote_decs:
        print("\t{}".format(remote_decs)    )

    remote_dev.disconnect()


class NotifyDelegate(DefaultDelegate):
    def __init__(self):
        DefaultDelegate.__init__(self)

    def handleNotification(self, cHandle, data):
        print("Rev Data<{}>:{}".format(hex(cHandle),bytes.hex(data)))

def ble_char_operate():
    remote_dev = Peripheral("C4:4F:33:16:F8:AF")
    main_service = remote_dev.getServiceByUUID(0x000000ff)
    target_char = main_service.getCharacteristics(0x0000ff01)[0]
    target_char_handle = target_char.getHandle()
    
    print("-- Characteristics Operate --")
    print("ReadHex:{}".format(bytes.hex(target_char.read())))
    print("Write with No Response")
    target_char.write(b"AABBCCDD")
    print("Write with Response")
    target_char.write(bytes.fromhex("223344"), True)

    print("-- Peripheral Operate --")
    print("ReadHex:{}".format(bytes.hex(remote_dev.readCharacteristic(target_char_handle))))
    print("Write with No Response")
    remote_dev.writeCharacteristic(target_char_handle, b"55667788")
    print("Write with Response")
    remote_dev.writeCharacteristic(target_char_handle, bytes.fromhex("44332211"), True)
    

    print("-- Characteristics Notify/Indicate Operate --")
    remote_dev.withDelegate(NotifyDelegate())
    target_char_notify_handle = target_char_handle+1
    print("Enable Notify")
    remote_dev.writeCharacteristic(target_char_notify_handle, bytes.fromhex("0100"))
    remote_dev.waitForNotifications(10)

    print("Enable Indicate")
    remote_dev.writeCharacteristic(target_char_notify_handle, bytes.fromhex("0200"))
    remote_dev.waitForNotifications(10)

    print("Disable All")
    remote_dev.writeCharacteristic(target_char_notify_handle, bytes.fromhex("0000"))
    time.sleep(3)

    remote_dev.disconnect()

if __name__ == "__main__":
    print("func ble_adv_scan begin")
    ble_adv_scan(5)

    print("func ble_services_enum begin")
    ble_services_enum("C4:4F:33:16:F8:AF")

    print("func ble_char_operate begin")
    ble_char_operate()

输出如下:

$ sudo python3 hello_bluepy.py 
func ble_adv_scan begin
Ble Scan Begin. scan_time: 5   -->>
。。。。。。
++ Find Dev:c4:4f:33:16:f8:af(public):-45
---- 1(Flags):06
---- 10(Tx Power):eb
---- 3(Complete 16b Services):000000ff-0000-1000-8000-00805f9b34fb
---- 9(Complete Local Name):ESP_GATTS_DEMO
。。。。。。
Ble Scan Finish. scan_time: 5   --<<
func ble_services_enum begin
Remote Services:
	service uuid:00001801-0000-1000-8000-00805f9b34fb
		<handle>	<property>	<can read>	uuid
		0x3	INDICATE (0x20)	False	00002a05-0000-1000-8000-00805f9b34fb
	service uuid:00001800-0000-1000-8000-00805f9b34fb
		<handle>	<property>	<can read>	uuid
		0x16	READ (0x2)	True	00002a00-0000-1000-8000-00805f9b34fb
		0x18	READ (0x2)	True	00002a01-0000-1000-8000-00805f9b34fb
		0x1a	READ (0x2)	True	00002aa6-0000-1000-8000-00805f9b34fb
	service uuid:000000ff-0000-1000-8000-00805f9b34fb
		<handle>	<property>	<can read>	uuid
		0x2a	READ WRITE NOTIFY (0x1a)	True	0000ff01-0000-1000-8000-00805f9b34fb
		0x2d	READ (0x2)	True	0000ff02-0000-1000-8000-00805f9b34fb
		0x2f	WRITE (0x8)	False	0000ff03-0000-1000-8000-00805f9b34fb
Remote Characteristics:
	<handle>	<can read>	<property>	uuid
	0x3	INDICATE (0x20)	False	00002a05-0000-1000-8000-00805f9b34fb
	0x16	READ (0x2)	True	00002a00-0000-1000-8000-00805f9b34fb
	0x18	READ (0x2)	True	00002a01-0000-1000-8000-00805f9b34fb
	0x1a	READ (0x2)	True	00002aa6-0000-1000-8000-00805f9b34fb
	0x2a	READ WRITE NOTIFY (0x1a)	True	0000ff01-0000-1000-8000-00805f9b34fb
	0x2d	READ (0x2)	True	0000ff02-0000-1000-8000-00805f9b34fb
	0x2f	WRITE (0x8)	False	0000ff03-0000-1000-8000-00805f9b34fb
Remote Descriptors:
	Descriptor <Primary Service Declaration>
	Descriptor <Characteristic Declaration>
	Descriptor <Service Changed>
	Descriptor <Client Characteristic Configuration>
	Descriptor <Primary Service Declaration>
	Descriptor <Characteristic Declaration>
	Descriptor <Device Name>
	Descriptor <Characteristic Declaration>
	Descriptor <Appearance>
	Descriptor <Characteristic Declaration>
	Descriptor <Central Address Resolution>
	Descriptor <Primary Service Declaration>
	Descriptor <Characteristic Declaration>
	Descriptor <ff01>
	Descriptor <Client Characteristic Configuration>
	Descriptor <Characteristic Declaration>
	Descriptor <ff02>
	Descriptor <Characteristic Declaration>
	Descriptor <ff03>
func ble_char_operate begin
-- Characteristics Operate --
ReadHex:44332211
Write with No Response
Write with Response
-- Peripheral Operate --
ReadHex:223344
Write with No Response
Write with Response
-- Characteristics Notify/Indicate Operate --
Enable Notify
Rev Data<0x2a>:000102030405060708090a0b0c0d0e
Enable Indicate
Rev Data<0x2a>:000102030405060708090a0b0c0d0e
Disable All

wireshark抓包bluepy

因为是本地蓝牙通讯,因此可以通过wireshark抓取本地蓝牙模组的协议报文,这里简单的贴一下。

首先是抓取的adapter,直接选取Bluetooth即可。

 

ble_adv_scan()

ble_services_enum(“C4:4F:33:16:F8:AF”)

ble_char_operate()

发表评论

1 × 1 =

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据