# ESP32 低功耗蓝牙广播包中自定义厂商数据的编码与解析:兼容 iOS/Android 的细节 ## 一、BLE 广播包结构基础 BLE 广播包(Advertising Packet)由若干 AD Structure(Advertising Data Structure)组成,每个结构包含: - **长度字节(Length)**:表示后续数据的总字节数(Type + Data)。 - **类型字节(Type)**:标识数据类型,例如 0x01(Flags)、0xFF(Manufacturer Specific Data)等。 - **数据字节(Data)**:具体内容,长度可变。 对于厂商自定义数据,Type 固定为 0xFF。其 Data 部分必须包含: - **Company Identifier(2 字节)**:由 Bluetooth SIG 分配的厂商 ID,例如 0x004C 为 Apple,0x0059 为 Nordic,0xFFFF 为测试用。 - **自定义数据(0~28 字节)**:用户自由定义的内容。 **注意**:广播包总长度(含所有 AD Structure)不能超过 31 字节(传统广播)或 255 字节(扩展广播,但受限于实际 PHY)。ESP32 默认使用传统广播,因此自定义数据长度需严格控制。 ## 二、iOS 与 Android 的解析差异 ### 2.1 iOS 的 CoreBluetooth 限制 - iOS 只能通过 `CBAdvertisementDataManufacturerDataKey` 获取厂商数据,但该 key 仅在设备作为 peripheral 广播时,central 端才能读取。 - iOS 对广播包中的厂商数据有严格校验:Company ID 必须有效(非 0xFFFF),且数据长度不能超过 28 字节(因为广播包总长 31 字节,减去 3 字节头部)。 - iOS 不支持读取广播包中的自定义服务 UUID(除非使用标准服务),因此厂商数据是主要透传渠道。 ### 2.2 Android 的 BLE 扫描 - Android 通过 `ScanRecord.getManufacturerSpecificData()` 获取厂商数据,返回一个 SparseArray,key 为 Company ID。 - Android 对长度限制较宽松,但同样受广播包总长限制。 - Android 允许 Company ID 为 0xFFFF,但 iOS 会忽略该数据,因此若需跨平台,建议使用真实厂商 ID(如 0x1234 自定义,但需注意 SIG 保留范围)。 **关键细节**:iOS 要求 Company ID 必须是大端序(Big-Endian),而 Android 在解析时也按大端序处理,但某些第三方库可能按小端序,因此编码时务必统一。 ## 三、ESP32 端编码实现(Arduino 框架) ### 3.1 配置 BLE 广播数据 使用 `BLEAdvertising` 类设置广播数据。以下代码演示如何将传感器数据(如温度、湿度)编码为厂商数据并广播。 ```c #include #include #include // 自定义厂商 ID(例如 0x1234,需自行申请或使用测试值) #define COMPANY_ID 0x1234 void setup() { Serial.begin(115200); BLEDevice::init("ESP32_BLE"); // 创建广播对象 BLEAdvertising *pAdvertising = BLEDevice::getAdvertising(); // 准备厂商数据(例如:温度 25.6°C,湿度 60%) uint8_t temp = 256; // 实际值需转换,这里简化 uint8_t hum = 60; // 构建厂商数据数组:Company ID (2字节) + 自定义数据 uint8_t manData[4]; manData[0] = (COMPANY_ID >> 8) & 0xFF; // 高字节 manData[1] = COMPANY_ID & 0xFF; // 低字节 manData[2] = temp; // 温度整数部分 manData[3] = hum; // 湿度 // 创建 AD Structure 数据包 BLEAdvertisementData advData; advData.setManufacturerData(std::string((char*)manData, sizeof(manData))); // 设置广播数据(可同时设置名称、服务 UUID 等) pAdvertising->setAdvertisementData(advData); // 启动广播 pAdvertising->start(); Serial.println("Broadcasting with manufacturer data"); } void loop() { // 可定期更新广播数据 delay(10000); } ``` ### 3.2 动态更新广播数据 实际项目中,传感器数据会变化,需定期更新广播内容。注意:更新广播数据前需停止广播,重新设置后再启动。 ```c void updateBroadcast(uint8_t temp, uint8_t hum) { BLEAdvertising *pAdvertising = BLEDevice::getAdvertising(); pAdvertising->stop(); uint8_t manData[4]; manData[0] = (COMPANY_ID >> 8) & 0xFF; manData[1] = COMPANY_ID & 0xFF; manData[2] = temp; manData[3] = hum; BLEAdvertisementData advData; advData.setManufacturerData(std::string((char*)manData, sizeof(manData))); pAdvertising->setAdvertisementData(advData); pAdvertising->start(); } ``` ## 四、解析端代码示例(Android / iOS) ### 4.1 Android 解析(Kotlin) ```kotlin val scanResult = result // ScanResult 对象 val record = scanResult.scanRecord val manufacturerData = record?.getManufacturerSpecificData(0x1234) if (manufacturerData != null) { val temp = manufacturerData[0].toInt() // 注意:数组索引从0开始,但厂商数据中前两字节是 Company ID,所以实际数据从索引2开始 // 但 getManufacturerSpecificData 返回的是去掉 Company ID 后的数据,因此直接取索引0 val temp = manufacturerData[0] val hum = manufacturerData[1] Log.d("BLE", "Temp: $temp, Hum: $hum") } ``` **注意**:Android 的 `getManufacturerSpecificData()` 返回的字节数组已自动跳过 Company ID,因此直接读取自定义部分。 ### 4.2 iOS 解析(Swift) ```swift func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) { if let manData = advertisementData[CBAdvertisementDataManufacturerDataKey] as? Data { // 前两个字节是 Company ID(小端序?需注意) let companyID = manData.prefix(2).reduce(0) { ($0 << 8) + UInt16($1) } // 大端序 if companyID == 0x1234 { let temp = manData[2] // 自定义数据从索引2开始 let hum = manData[3] print("Temp: \(temp), Hum: \(hum)") } } } ``` **注意**:iOS 返回的 `Data` 包含完整的厂商数据(含 Company ID),且 Company ID 为大端序,需手动转换。 ## 五、注意事项与常见坑 - **长度限制**:传统广播包最大 31 字节,若包含设备名称(默认 9 字节)、Flags(3 字节),则厂商数据最多约 19 字节。建议精简设备名称或使用扩展广播(ESP32 支持)。 - **字节序**:Company ID 必须大端序,自定义数据建议统一大端序,避免跨平台解析混乱。 - **iOS 兼容性**:避免使用 0xFFFF 作为 Company ID,否则 iOS 会忽略整个厂商数据。 - **广播间隔**:频繁更新广播数据会增加功耗,建议根据业务需求调整(如 100ms~1s)。 - **扫描响应包**:若数据量较大,可使用扫描响应包(Scan Response)扩展,但需在连接前发送,且同样受 31 字节限制。 - **调试工具**:使用 nRF Connect 或 LightBlue 工具查看广播包,验证编码是否正确。 ## 六、总结 本文详细介绍了 ESP32 低功耗蓝牙广播包中厂商数据的编码与解析,重点强调了 iOS 与 Android 的差异。通过合理设计数据结构、严格遵循字节序和长度限制,可以轻松实现跨平台的数据透传。实际开发中,建议先使用调试工具验证广播内容,再编写解析代码,以减少调试时间。 掌握这些细节,你的嵌入式 BLE 应用将更加健壮和兼容。