概述
本文档用于阐述如何以与具体运行芯片、OS 无关的方式,基于 xiaozhi ws 协议来对接百宝箱 AIoT 平台。
与 xiaozhi ws 协议一致的部分,本文不做赘述。主要关注差异部分,用以指示任意三方平台快速对接。
相关演示代码仍然采用 esp32 ESP-IDF 框架下的 c++ 代码,其他平台移植时应提供相关平替(主要是 HMAC 算法)。
关键差异
前提
你已在百宝箱智能体平台上开通 AIoT SDK 发布渠道,这意味着你能看到【开放平台】->【AIoT】这样的导航路径。
相较 xiaozhi ws 协议的差异
OTA 上行
增加了 Device-Key头,带上 device key(https://www.tbox.cn/open/iot 页面右上角 Device Key: xxxxxxxxx 部分):
Http* Ota::SetupHttp() { ...... auto http = board.CreateHttp(); http->SetHeader("Device-Id", SystemInfo::GetMacAddress().c_str()); // 在你的智能体里做了导入的设备的 mac 地址 http->SetHeader("Device-Key", device_key.c_str()); // 从 https://www.tbox.cn/open/iot 页面右上角复制过来
WebSocket 建连
ws 建连时 http header 中的: Authorization 对应的值变为一个以设备 MAC 地址、device key、服务端下发的 token 经由 HMAC 算法(Hash-based Message Authentication Code)计算出来的签名,实现如下:
#define HMAC_OUTPUT_LEN 32 // HMAC-SHA256 输出长度 #define MAC_ADDRESS_LEN 18 // MAC 地址长度 std::vector<unsigned char> hexStringToByteArray(const std::string& hex_str) { // 每个字节由两个字符表示,因此需要的字节数是字符串长度的一半 std::vector<unsigned char> byteArray; if (hex_str.length() % 2 != 0) { throw std::invalid_argument("Hex string must have an even length"); } for (size_t i = 0; i < hex_str.length(); i += 2) { // 取出每两个字符 std::string byteString = hex_str.substr(i, 2); // 将字符转换为十进制整数 unsigned char byte = static_cast<unsigned char>(strtol(byteString.c_str(), nullptr, 16)); byteArray.push_back(byte); } return byteArray; } void generate_signature(const char *mac, const char *token, const unsigned char *device_key, unsigned char *signature) { char message[MAC_ADDRESS_LEN + 256]; // 存储 MAC + token snprintf(message, sizeof(message), "%s%s", mac, token); // 合并 MAC 和 token // 使用 HMAC-SHA256 生成签名 mbedtls_md_context_t ctx; const mbedtls_md_info_t *info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); mbedtls_md_init(&ctx); mbedtls_md_setup(&ctx, info, 1); // 1表示使用HMAC mbedtls_md_hmac_starts(&ctx, device_key, HMAC_OUTPUT_LEN); mbedtls_md_hmac_update(&ctx, (const unsigned char *)message, strlen(message)); mbedtls_md_hmac_finish(&ctx, signature); mbedtls_md_free(&ctx); } bool WebsocketProtocol::OpenAudioChannel() { if (websocket_ != nullptr) { delete websocket_; } // 获取设备唯一标识符 std::string mac_address = SystemInfo::GetMacAddress(); ESP_LOGW(TAG, "Device mac addr: %s\n", mac_address.c_str()); // 生成签名 Settings settings("websocket", false); std::string url = settings.GetString("url"); std::string token = settings.GetString("token"); auto device_key_array = hexStringToByteArray(device_key); unsigned char signature[HMAC_OUTPUT_LEN] = { 0 }; // 存储签名 generate_signature(mac_address.c_str(), token.c_str(), device_key_array.data(), signature); std::string signature_string = bytesToHexString(signature, HMAC_OUTPUT_LEN); int version = settings.GetInt("version"); if (version != 0) { version_ = version; } error_occurred_ = false; websocket_ = Board::GetInstance().CreateWebSocket(); if (!signature_string.empty()) { // If token not has a space, add "Bearer " prefix if (signature_string.find(" ") == std::string::npos) { signature_string = "Bearer " + signature_string; } // ESP_LOGI(TAG, "signature_string %s", signature_string.c_str()); websocket_->SetHeader("Authorization", signature_string.c_str()); }