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