本文笔记了如何使用Linux命令行工具和bluepy来测试ESP32蓝牙实例。
参考链接
【 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的设置代码如下:
static const esp_gatts_attr_db_t gatt_db[HRS_IDX_NB] = |
{{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}}, |
{{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}}, |
{{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}}, |
{{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}}, |
{{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}}, |
{{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}}, |
{{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}}, |
{{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,如下:
C4:4F:33:16:F8:AF ESP_GATTS_DEMO |
可以看到,该Server的Mac地址为【 C4:4F:33:16:F8:AF 】,然后使用gatttool连接,如下:
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 |
Attempting to connect to C4:4F:33:16:F8:AF |
[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: 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: 0x002a, uuid: 0000ff01-0000-1000-8000-00805f9b34fb |
handle: 0x002b, uuid: 00002902-0000-1000-8000-00805f9b34fb |
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 |
Characteristic value /descriptor : 1a 2a 00 01 ff |
[C4:4F:33:16:F8:AF][LE]> char- read -hnd 0x002a |
Characteristic value /descriptor : 11 22 33 44 |
[C4:4F:33:16:F8:AF][LE]> char- read -hnd 0x002b |
Characteristic value /descriptor : 00 00 |
[C4:4F:33:16:F8:AF][LE]> char-write-cmd 0x002a aabbccdd |
[C4:4F:33:16:F8:AF][LE]> char- read -hnd 0x002a |
Characteristic value /descriptor : aa bb cc dd |
[C4:4F:33:16:F8:AF][LE]> char-write-req 0x002a 12345678 |
Characteristic value was written successfully |
[C4:4F:33:16:F8:AF][LE]> char- read -hnd 0x002a |
Characteristic value /descriptor : 12 34 56 78 |
[C4:4F:33:16:F8:AF][LE]> char- read -hnd 0x002b |
Characteristic value /descriptor : 00 00 |
[C4:4F:33:16:F8:AF][LE]> char-write-cmd 0x002b 0100 |
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 |
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:
from bluepy.btle import * |
def ble_adv_scan(scan_time): |
print ( "Ble Scan Begin. scan_time: {} -->>" . format (scan_time)) |
ble_devices = ble_scanner.scan(scan_time) |
dev_addr_type = dev.addrType |
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) ) |
class NotifyDelegate(DefaultDelegate): |
DefaultDelegate.__init__( self ) |
def handleNotification( self , cHandle, data): |
print ( "Rev Data<{}>:{}" . format ( hex (cHandle),bytes. hex (data))) |
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 |
remote_dev.writeCharacteristic(target_char_notify_handle, bytes.fromhex( "0100" )) |
remote_dev.waitForNotifications( 10 ) |
remote_dev.writeCharacteristic(target_char_notify_handle, bytes.fromhex( "0200" )) |
remote_dev.waitForNotifications( 10 ) |
remote_dev.writeCharacteristic(target_char_notify_handle, bytes.fromhex( "0000" )) |
if __name__ = = "__main__" : |
print ( "func ble_adv_scan begin" ) |
print ( "func ble_services_enum begin" ) |
ble_services_enum( "C4:4F:33:16:F8:AF" ) |
print ( "func ble_char_operate begin" ) |
输出如下:
$ sudo python3 hello_bluepy.py |
Ble Scan Begin. scan_time: 5 - - >> |
+ + Find Dev:c4: 4f : 33 : 16 :f8:af(public): - 45 |
- - - - 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 |
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 |
<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 |
Descriptor <Primary Service Declaration> |
Descriptor <Characteristic Declaration> |
Descriptor <Service Changed> |
Descriptor <Client Characteristic Configuration> |
Descriptor <Primary Service Declaration> |
Descriptor <Characteristic Declaration> |
Descriptor <Characteristic Declaration> |
Descriptor <Characteristic Declaration> |
Descriptor <Central Address Resolution> |
Descriptor <Primary Service Declaration> |
Descriptor <Characteristic Declaration> |
Descriptor <Client Characteristic Configuration> |
Descriptor <Characteristic Declaration> |
Descriptor <Characteristic Declaration> |
func ble_char_operate begin |
- - Characteristics Operate - - |
- - Characteristics Notify / Indicate Operate - - |
Rev Data< 0x2a >: 000102030405060708090a0b0c0d0e |
Rev Data< 0x2a >: 000102030405060708090a0b0c0d0e |
wireshark抓包bluepy
因为是本地蓝牙通讯,因此可以通过wireshark抓取本地蓝牙模组的协议报文,这里简单的贴一下。
首先是抓取的adapter,直接选取Bluetooth即可。

ble_adv_scan()

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

ble_char_operate()

发表评论