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

ESP32 BLE Arduino调用writeValue致死机,如何实现ANCS通知属性获取?

问题修复与ANCS正确实现方案

核心问题定位

ESP32调用BLERemoteCharacteristic::writeValue时死机,主要原因有两个:

  1. 写入方式错误:ANCS Control Point要求使用带响应的写入(Write with Response),若用无响应写入会导致BLE栈状态异常;
  2. 上下文违规:在BLE回调函数(如通知回调、特征发现回调)中直接执行写入操作,这类回调运行在中断上下文,阻塞操作会触发系统死锁;
  3. 命令格式不符合ANCS规范:字节顺序、UUID长度或属性ID错误,导致通信异常进而引发死机。

修复步骤与关键实现

1. 定义ANCS标准UUID

#define ANCS_SERVICE_UUID        "7905F431-B5CE-4E99-A40F-4B1E122D00D0"
#define ANCS_CP_CHAR_UUID        "69D1D8F3-45E1-49A8-9821-9BBDFDAAD9D9"
#define ANCS_NS_CHAR_UUID        "9FBF120D-6301-42D9-8C58-25E699A21DBD"
#define ANCS_ND_CHAR_UUID        "22EAC6E9-24D6-4BB5-BE44-B36ACE7C7BFB"

2. 全局变量与状态标记

避免在回调中直接执行写入,用状态标记让主循环处理:

BLERemoteCharacteristic *pControlPointChar = nullptr;
uint8_t notificationUUID[8]; // 存储iOS推送的通知UUID
bool shouldFetchAttributes = false;

3. 订阅Notification Source获取通知UUID

在连接回调中完成特征订阅,获取iOS设备推送的通知UUID:

void onNotificationSourceNotify(BLERemoteCharacteristic* pChar, uint8_t* pData, size_t length, bool isNotify) {
  if (length >= 9) {
    // 第2-9字节为通知UUID,按ANCS规范提取
    memcpy(notificationUUID, pData + 1, 8);
    shouldFetchAttributes = true; // 标记主循环执行属性获取
  }
}

4. 主循环中执行Control Point写入

将写入操作放到主循环,避开中断上下文:

void loop() {
  if (shouldFetchAttributes && pControlPointChar != nullptr) {
    shouldFetchAttributes = false;
    
    // 构造获取通知属性的命令(严格遵循ANCS格式)
    uint8_t cmd[12];
    cmd[0] = 0x02; // 命令ID:Get Notification Attributes
    memcpy(cmd + 1, notificationUUID, 8); // 目标通知UUID
    cmd[9] = 0x02; // 要获取的属性数量:标题+内容
    cmd[10] = 0x01; // 属性ID:通知标题
    cmd[11] = 0x03; // 属性ID:通知内容

    // 必须使用带响应的写入(第三个参数为true)
    bool writeSuccess = pControlPointChar->writeValue(cmd, sizeof(cmd), true);
    if (!writeSuccess) {
      Serial.println("Control Point写入失败");
    }
  }
}

5. 解析Notification Data获取通知内容

订阅Notification Data特征,回调中解析iOS返回的属性数据:

void onNotificationDataNotify(BLERemoteCharacteristic* pChar, uint8_t* pData, size_t length, bool isNotify) {
  if (length < 4 || pData[0] != 0x02) return; // 只处理属性获取响应

  uint8_t attrId = pData[1];
  uint16_t attrLen = (pData[2] << 8) | pData[3];
  if (attrLen + 4 > length) return;

  char attrValue[attrLen + 1];
  memcpy(attrValue, pData + 4, attrLen);
  attrValue[attrLen] = '\0';

  switch(attrId) {
    case 0x01:
      Serial.print("标题: ");
      Serial.println(attrValue);
      break;
    case 0x03:
      Serial.print("内容: ");
      Serial.println(attrValue);
      break;
    // 可扩展处理副标题(0x02)、应用ID(0x04)等属性
  }
}

6. 连接初始化流程

在BLE客户端连接回调中完成服务与特征的初始化:

void onConnect(BLEClient* pClient) {
  BLERemoteService* pAncsService = pClient->getService(ANCS_SERVICE_UUID);
  if (!pAncsService) {
    Serial.println("未找到ANCS服务");
    return;
  }

  // 初始化Control Point特征
  pControlPointChar = pAncsService->getCharacteristic(ANCS_CP_CHAR_UUID);
  if (!pControlPointChar) {
    Serial.println("未找到Control Point特征");
    return;
  }

  // 订阅Notification Source
  BLERemoteCharacteristic* pNsChar = pAncsService->getCharacteristic(ANCS_NS_CHAR_UUID);
  if (pNsChar) {
    pNsChar->registerForNotify(onNotificationSourceNotify);
    // 写入CCCD开启通知(带响应)
    pNsChar->getDescriptor(BLEUUID((uint16_t)0x2902))->writeValue((uint8_t*)"\x01\x00", 2, true);
  }

  // 订阅Notification Data
  BLERemoteCharacteristic* pNdChar = pAncsService->getCharacteristic(ANCS_ND_CHAR_UUID);
  if (pNdChar) {
    pNdChar->registerForNotify(onNotificationDataNotify);
    pNdChar->getDescriptor(BLEUUID((uint16_t)0x2902))->writeValue((uint8_t*)"\x01\x00", 2, true);
  }
}

关键注意事项

  • 禁止在BLE回调中直接执行writeValue,必须放到主循环或FreeRTOS任务中;
  • 所有涉及ANCS的写入操作(包括CCCD)必须使用带响应模式;
  • 严格遵循Apple ANCS规范的字节格式,UUID长度、命令ID、属性ID不能出错;
  • 确保ESP32的BLE栈版本兼容,建议使用Arduino ESP32核心2.0.0以上版本。

内容的提问来源于stack exchange,提问作者Đức Thanh Nguyễn

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 16:55:02