硬件准备

  • ESP32或ESP8266开发板
  • USB转串口模块(如CH340)
  • 连接线

软件准备

  • Arduino IDE或PlatformIO
  • Coze API密钥
  • 串口通信库(如SoftwareSerial)

配置ESP开发环境

安装必要的库文件,包括WiFi库和HTTP客户端库。在Arduino IDE中,通过库管理器搜索并安装这些库。

修改开发板的串口通信设置,确保波特率匹配。通常使用115200波特率进行调试。

连接Coze API

获取Coze API的访问密钥,并在代码中配置API端点。确保网络连接正常,ESP能够访问互联网。

编写HTTP请求代码,构造符合Coze API要求的请求格式。包括设置请求头、请求体和必要的认证信息。

实现串口通信

初始化串口通信,设置正确的波特率。在代码中实现串口数据的读取和发送功能。

处理接收到的串口数据,将其转换为Coze API能够理解的格式。发送到Coze智能体后,接收返回结果并通过串口输出。

示例代码片段

#include <WiFi.h>
#include <HTTPClient.h>

const char* ssid = "your_SSID";
const char* password = "your_PASSWORD";
const char* cozeApiUrl = "https://api.coze.com/v1/chat";

void setup() {
  Serial.begin(115200);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
}

void loop() {
  if (Serial.available()) {
    String input = Serial.readString();
    sendToCoze(input);
  }
}

void sendToCoze(String message) {
  HTTPClient http;
  http.begin(cozeApiUrl);
  http.addHeader("Content-Type", "application/json");
  http.addHeader("Authorization", "Bearer your_API_KEY");
  
  String payload = "{\"message\":\"" + message + "\"}";
  int httpCode = http.POST(payload);
  
  if (httpCode > 0) {
    String response = http.getString();
    Serial.println(response);
  }
  http.end();
}

 

调试与优化

测试串口通信的稳定性,确保数据传输没有丢失或错误。调整缓冲区大小和超时设置以获得最佳性能。

优化网络请求,添加错误处理和重试机制。考虑实现异步请求以避免阻塞主循环。

监控内存使用情况,确保长时间运行不会导致内存泄漏或崩溃。定期重启可能有助于维持系统稳定性。

安全注意事项

保护API密钥,避免将其硬编码在代码中。考虑使用安全存储方式或动态获取机制。

实现输入验证,防止恶意数据导致系统异常。限制输入长度和内容类型,确保系统安全。

使用HTTPS协议进行通信,确保数据传输加密。验证服务器证书,防止中间人攻击。

完整代码:

#include <ESP8266WiFi.h>
#include <WiFiClientSecure.h>
#include <ArduinoJson.h>

// ------------------- 必须修改这5行(按Coze文档配置) -------------------
const char* WIFI_SSID = "123";       // 你的WiFi名称
const char* WIFI_PASS = "12345678";   // 你的WiFi密码
const char* COZE_API_KEY = "pat_DFxxx";  // 确保无多余空格
const char* COZE_BOT_ID = "xxxx";  // 你的Coze机器人ID
const char* COZE_USER_ID = "123";      // 自定义用户ID(固定不变)
// -----------------------------------------------------------------------

const char* COZE_API_DOMAIN = "api.coze.cn";  // Coze v3 API域名
const int COZE_API_PORT = 443;                // HTTPS固定端口

WiFiClientSecure client;  // ESP8266 HTTPS客户端
String inputBuffer = "";  // 串口输入缓冲区

// 解析最终回复(适配ArduinoJson 7.x + Coze v3响应格式)
String processAnswer(DynamicJsonDocument& resDoc) {
  // ArduinoJson 7.x:用[]直接访问键,无需getMember()
  if (resDoc["code"].as<int>() != 0) {
    return "❌ 应答异常:" + resDoc["msg"].as<String>();
  }

  JsonArray data = resDoc["data"].as<JsonArray>();
  String aiReply = "无回复内容";
  String followUps = "";
  int count = 0;

  // ArduinoJson 7.x:遍历数组用auto(无需&,避免引用绑定错误)
  for (auto item : data) {
    String type = item["type"].as<String>();
    if (type == "answer") {
      aiReply = item["content"].as<String>();
    } else if (type == "follow_up") {
      count++;
      followUps += "☆ 问题" + String(count) + ":" + item["content"].as<String>() + "\n";
    }
  }

  String result = "✅ AI:" + aiReply + "\n";
  if (count > 0) {
    result += "参考提问:\n" + followUps;
  }
  return result;
}

// 轮询获取聊天结果(GET /v3/chat/retrieve + GET /v3/chat/message/list)
String getChatResult(String conversationId, String chatId) {
  String retrieveUrl = "/v3/chat/retrieve?conversation_id=" + conversationId + "&chat_id=" + chatId;
  String msgListUrl = "/v3/chat/message/list?chat_id=" + chatId + "&conversation_id=" + conversationId;
  String params = "bot_id=" + String(COZE_BOT_ID) + "&task_id=" + chatId;

  int maxRetries = 20;  // 最大轮询次数(20秒超时)
  int retryCount = 0;

  while (retryCount < maxRetries) {
    retryCount++;
    Serial.printf("🤔 轮询中(%d/%d)...", retryCount, maxRetries);

    // 1. 发送GET请求查询状态(/retrieve)
    if (client.connect(COZE_API_DOMAIN, COZE_API_PORT)) {
      client.print("GET " + retrieveUrl + " HTTP/1.1\r\n");
      client.print("Host: " + String(COZE_API_DOMAIN) + "\r\n");
      client.print("Authorization: Bearer " + String(COZE_API_KEY) + "\r\n");
      client.print("Connection: close\r\n\r\n");

      // 读取状态响应
      String retrieveResp = "";
      while (client.connected() || client.available()) {
        if (client.available()) {
          retrieveResp += client.readString();
        }
      }
      client.stop();

      // 解析状态响应
      int jsonStart = retrieveResp.indexOf("{");
      if (jsonStart != -1) {
        String jsonStr = retrieveResp.substring(jsonStart);
        DynamicJsonDocument resDoc(1024);  // 适配ESP8266内存
        DeserializationError error = deserializeJson(resDoc, jsonStr);

        if (!error && resDoc["code"].as<int>() == 0) {
          // 嵌套键访问:data.status
          String status = resDoc["data"]["status"].as<String>();
          Serial.println("状态:" + status);

          if (status == "completed") {
            // 2. 状态完成,获取消息列表(/message/list)
            if (client.connect(COZE_API_DOMAIN, COZE_API_PORT)) {
              client.print("GET " + msgListUrl + "&" + params + " HTTP/1.1\r\n");
              client.print("Host: " + String(COZE_API_DOMAIN) + "\r\n");
              client.print("Authorization: Bearer " + String(COZE_API_KEY) + "\r\n");
              client.print("Connection: close\r\n\r\n");

              // 读取消息响应
              String msgResp = "";
              while (client.connected() || client.available()) {
                if (client.available()) {
                  msgResp += client.readString();
                }
              }
              client.stop();

              // 解析消息响应
              int msgJsonStart = msgResp.indexOf("{");
              if (msgJsonStart != -1) {
                String msgJsonStr = msgResp.substring(msgJsonStart);
                DynamicJsonDocument msgDoc(2048);  // 适配回复长度
                DeserializationError msgError = deserializeJson(msgDoc, msgJsonStr);
                if (!msgError) {
                  return processAnswer(msgDoc);  // 解析并返回回复
                } else {
                  return "❌ 消息解析错误:" + String(msgError.c_str());
                }
              }
              return "❌ 消息响应无JSON";
            } else {
              return "❌ 连接消息接口失败";
            }
          } else if (status == "failed") {
            String errMsg = resDoc["data"]["error_msg"].as<String>();
            return "❌ 任务失败:" + errMsg;
          }
        } else {
          Serial.println("状态解析错误");
        }
      } else {
        Serial.println("状态响应无JSON");
      }
    } else {
      Serial.println("连接状态接口失败");
    }

    delay(1000);  // 每秒轮询一次
  }

  return "❌ 轮询超时(20秒)";
}

// 核心函数:调用Coze v3 API(完整流程)
void callCozeAI(String userInput) {
  Serial.print("AI:思考中...");

  String postBody = "";
  String response = "";

  // 1. 构建Coze v3 POST请求体(ArduinoJson 7.x语法)
  DynamicJsonDocument reqDoc(1024);  // 适配ESP8266内存
  reqDoc["bot_id"] = COZE_BOT_ID;
  reqDoc["user_id"] = COZE_USER_ID;
  reqDoc["stream"] = false;
  reqDoc["auto_save_history"] = true;

  JsonArray messages = reqDoc.createNestedArray("additional_messages");
  JsonObject userMsg = messages.createNestedObject();
  userMsg["role"] = "user";
  userMsg["content"] = userInput;
  userMsg["content_type"] = "text";

  serializeJson(reqDoc, postBody);  // 转换为JSON字符串
  Serial.printf("\n📤 请求体:%s\n", postBody.c_str());

  // 2. 发送POST请求创建对话(/v3/chat)
  if (client.connect(COZE_API_DOMAIN, COZE_API_PORT)) {
    client.print("POST /v3/chat HTTP/1.1\r\n");
    client.print("Host: " + String(COZE_API_DOMAIN) + "\r\n");
    client.print("Authorization: Bearer " + String(COZE_API_KEY) + "\r\n");
    client.print("Content-Type: application/json\r\n");
    client.print("Content-Length: " + String(postBody.length()) + "\r\n");
    client.print("Connection: close\r\n\r\n");
    client.print(postBody);

    // 读取POST响应
    while (client.connected() || client.available()) {
      if (client.available()) {
        response += client.readString();
      }
    }
    client.stop();
  } else {
    Serial.println("\n❌ 连接Coze失败!检查网络和API Key");
    return;
  }

  // 3. 解析POST响应,提取chat_id和conversation_id(ArduinoJson 7.x语法)
  String chatId = "";
  String conversationId = "";
  int jsonStart = response.indexOf("{");
  if (jsonStart != -1) {
    String jsonStr = response.substring(jsonStart);
    DynamicJsonDocument resDoc(1024);
    DeserializationError error = deserializeJson(resDoc, jsonStr);

    if (!error && resDoc["code"].as<int>() == 0) {
      JsonObject data = resDoc["data"];
      chatId = data["id"].as<String>();
      conversationId = data["conversation_id"].as<String>();
      Serial.printf("\n✅ 对话创建成功(chat_id:%s)\n", chatId.c_str());
    } else {
      Serial.printf("\n❌ POST响应解析错误:%s\n", error.c_str());
      Serial.printf("响应内容:%s\n", jsonStr.c_str());
      return;
    }
  } else {
    Serial.println("\n❌ POST响应无JSON");
    return;
  }

  // 4. 轮询获取结果并打印
  if (chatId != "" && conversationId != "") {
    String aiReply = getChatResult(conversationId, chatId);
    Serial.println("\n" + aiReply + "\n");
    Serial.println("💬 输入下一条消息(输入q退出)~");
  } else {
    Serial.println("\n❌ 未获取到对话ID");
  }
}

void setup() {
  Serial.begin(115200);
  Serial.println("\n📡 ESP8266 Coze AI启动中...");

  // 连接WiFi(带重连)
  WiFi.begin(WIFI_SSID, WIFI_PASS);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.printf("\n✅ WiFi已连接!IP:%s\n", WiFi.localIP().toString().c_str());
  Serial.println("💬 请输入文字按回车对话(输入q退出)~");

  // ESP8266 HTTPS必须关闭证书验证
  client.setInsecure();
}

void loop() {
  // 读取串口输入
  while (Serial.available() > 0) {
    char c = Serial.read();
    if (c == '\n' || c == '\r') {
      if (inputBuffer.length() > 0) {
        Serial.printf("\n🗣️  你:%s\n", inputBuffer.c_str());
        if (inputBuffer == "q") {
          Serial.println("❌ 退出对话,重启后可重新连接~");
          while (1);
        }
        callCozeAI(inputBuffer);
        inputBuffer = "";
      }
    } else {
      inputBuffer += c;
    }
  }

  // WiFi断线自动重连
  if (WiFi.status() != WL_CONNECTED) {
    Serial.println("\n⚠️ WiFi断线,正在重连...");
    WiFi.reconnect();
    while (WiFi.status() != WL_CONNECTED) {
      delay(500);
      Serial.print(".");
    }
    Serial.printf("\n✅ WiFi重连成功!IP:%s\n", WiFi.localIP().toString().c_str());
  }

  delay(100);
}

 

 

Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐