You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ESP32与Python的BLE通信订阅通知报错问题排查

问题:ESP32 BLE通知订阅失败(WinError -2140864509)

问题现象

PC端Python代码能成功连接ESP32 BLE服务器,但订阅通知时抛出以下错误:

Exception in thread Thread-1 (start_ble_loop):
Traceback (most recent call last):
  File "C:\Program Files\Python312\Lib\threading.py", line 1073, in _bootstrap_inner
    self.run()
  File "C:\Program Files\Python312\Lib\threading.py", line 1010, in run
    self._target(*self._args, **self._kwargs)
  File "E:\AC_Work\Task_9_Radar implementation\BLE GUI\gui_code.py", line 42, in start_ble_loop
    loop.run_until_complete(connect_and_receive())
  File "C:\Program Files\Python312\Lib\asyncio\base_events.py", line 684, in run_until_complete
    return future.result()
           ^^^^^^^^^^^^^^^
  File "E:\AC_Work\Task_9_Radar implementation\BLE GUI\gui_code.py", line 28, in connect_and_receive
    await client.start_notify(BLE_CHARACTERISTIC_UUID, notification_handler)
  File "D:\Users\26101179\AppData\Roaming\Python\Python312\site-packages\bleak\__init__.py", line 844, in start_notify
    await self._backend.start_notify(characteristic, wrapped_callback, **kwargs)
  File "D:\Users\26101179\AppData\Roaming\Python\Python312\site-packages\bleak\backends\winrt\client.py", line 981, in start_notify
    await winrt_char.write_client_characteristic_configuration_descriptor_async(
OSError: [WinError -2140864509] The attribute cannot be written

ESP32 Arduino代码

#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
#include "esp_bt_device.h"

#define SERVICE_UUID        "12345678-1234-5678-1234-56789abcdef0"
#define CHARACTERISTIC_UUID "87654321-4321-6789-4321-67890abcdef0"

BLECharacteristic *pCharacteristic;
bool deviceConnected = false;

void printBLEMacAddress() {
    const uint8_t* mac = esp_bt_dev_get_address();
    
    Serial.print("ESP32 BLE MAC Address: ");
    for (int i = 0; i < 6; i++) {
        Serial.printf("%02X", mac[i]);
        if (i < 5) Serial.print(":");
    }
    Serial.println();
}

class MyServerCallbacks: public BLEServerCallbacks {
    void onConnect(BLEServer* pServer) {
        deviceConnected = true;
    }

    void onDisconnect(BLEServer* pServer) {
        deviceConnected = false;
        pServer->getAdvertising()->start(); // Restart advertising
    }
};

void setup() {
    Serial.begin(115200);

    BLEDevice::init("ESP32_BLE");

    // Print BLE MAC Address
    printBLEMacAddress();

    BLEServer *pServer = BLEDevice::createServer();
    pServer->setCallbacks(new MyServerCallbacks());

    BLEService *pService = pServer->createService(SERVICE_UUID);
    pCharacteristic = pService->createCharacteristic(
                      CHARACTERISTIC_UUID,
                      BLECharacteristic::PROPERTY_READ |
                      BLECharacteristic::PROPERTY_NOTIFY
                    );

    pService->start();
    pServer->getAdvertising()->start();
    Serial.println("BLE Server Started");
}

void loop() {
    if (deviceConnected) {
        String millisStr = String(millis());  // Get millis() value
        pCharacteristic->setValue(millisStr.c_str());
        pCharacteristic->notify();  // Send the value
        Serial.println("Sent: " + millisStr);
    }
    delay(1000);
}

Python客户端代码

import tkinter as tk
from bleak import BleakClient, BleakError
import asyncio
import time
import threading

# Values determined through nrfConnect
ESP32_BLE_MAC = "74:4D:BD:61:D2:6D"
BLE_CHARACTERISTIC_UUID = "87654321-4321-6789-4321-67890abcdef0"

async def connect_and_receive():
    while True:  # Keep retrying connection
        try:
            print("Attempting to connect to ESP32 BLE...")
            label.config(text="Connecting to ESP32...")
            
            async with BleakClient(ESP32_BLE_MAC) as client:
                if client.is_connected:
                    print("Connected to ESP32 BLE")
                    label.config(text="Connected to ESP32")

                    def notification_handler(sender, data):
                        """Callback function when new data is received."""
                        millis_value = int.from_bytes(data, byteorder="little")
                        label.config(text=f"Millis: {millis_value}")

                    # Subscribe to notifications
                    await client.start_notify(BLE_CHARACTERISTIC_UUID, notification_handler)

                    while True:
                        await asyncio.sleep(1)  # Keep listening for data
            
        except BleakError as e:
            print(f"Connection failed: {e}")
            label.config(text="ESP32 not found! Retrying...")
            time.sleep(5)  # Wait before retrying

# Function to run asyncio loop in a separate thread
def start_ble_loop():
    loop = asyncio.new_event_loop()
    asyncio.set_event_loop(loop)
    loop.run_until_complete(connect_and_receive())

# Tkinter GUI Setup
root = tk.Tk()
root.title("ESP32 BLE Monitor")

label = tk.Label(root, text="Waiting for connection...", font=("Arial", 16))
label.pack(pady=20)

# Start BLE communication in a separate thread
threading.Thread(target=start_ble_loop, daemon=True).start()

# Run the GUI
root.mainloop()

原因及解决方法

核心原因

错误WinError -2140864509本质是BLE特征未配置可写的客户端特征配置描述符(CCCD)。根据BLE协议,客户端订阅通知时,必须向特征的CCCD写入启用指令,但你的ESP32代码仅声明了PROPERTY_NOTIFY,未允许CCCD被写入,导致Windows端无法完成订阅操作。

另外,Python代码的解析逻辑存在错误:ESP32发送的是ASCII字符串,而Python用int.from_bytes解析会得到错误数值。

解决步骤

  1. 修改ESP32代码,添加CCCD权限
    创建特征时,需添加BLECharacteristic::PROPERTY_WRITE权限,或显式添加标准CCCD描述符(BLE2902),二选一即可:

    // 方法1:添加WRITE权限
    pCharacteristic = pService->createCharacteristic(
                      CHARACTERISTIC_UUID,
                      BLECharacteristic::PROPERTY_READ |
                      BLECharacteristic::PROPERTY_NOTIFY |
                      BLECharacteristic::PROPERTY_WRITE
                    );
    
    // 方法2:显式添加标准CCCD描述符(更规范)
    pCharacteristic = pService->createCharacteristic(
                      CHARACTERISTIC_UUID,
                      BLECharacteristic::PROPERTY_READ |
                      BLECharacteristic::PROPERTY_NOTIFY
                    );
    pCharacteristic->addDescriptor(new BLE2902());
    
  2. 修正Python数据解析逻辑
    将字节数据解码为字符串后再转整数:

    def notification_handler(sender, data):
        """Callback function when new data is received."""
        millis_value = int(data.decode('utf-8'))
        label.config(text=f"Millis: {millis_value}")
    
  3. 可选优化:仅在客户端订阅时发送通知
    避免无效的通知发送操作:

    void loop() {
        if (deviceConnected && pCharacteristic->getSubscribedCount() > 0) {
            String millisStr = String(millis());
            pCharacteristic->setValue(millisStr.c_str());
            pCharacteristic->notify();
            Serial.println("Sent: " + millisStr);
        }
        delay(1000);
    }
    

内容的提问来源于stack exchange,提问作者Mobi Zaman

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 10:25:57