本文笔记了如何使用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()

发表评论