公司动态

XIAO ESP32C3接入ChatGPT API:物联网设备实现AI对话全流程实战

📅 2026/8/2 15:22:32
XIAO ESP32C3接入ChatGPT API:物联网设备实现AI对话全流程实战
1. 项目概述当XIAO ESP32C3遇上ChatGPT如果你手头有一块小巧的XIAO ESP32C3开发板并且对让它“开口说话”、接入智能对话充满了好奇那么这篇实战记录就是为你准备的。我们这次的目标很直接让这块小小的物联网板子通过WiFi连接到互联网并使用HTTP协议与ChatGPT的API进行对话。这听起来像是把大象装进冰箱分三步联网、发请求、收回复。但实际操作中从网络连接到数据解析每一步都有不少细节需要琢磨。我最近刚用XIAO ESP32C3完整走通了这个流程过程中既体验了它作为RISC-V核心MCU的流畅也踩了一些关于网络稳定性和数据处理的坑。这篇文章我就把这些从硬件准备到代码调试的完整经验毫无保留地分享出来无论你是刚接触物联网的新手还是想寻找一个轻量级AIoT方案的开发者都能找到可以直接“抄作业”的步骤和避坑指南。整个项目的核心围绕着两个关键的Arduino库展开WiFiClient和HTTPClient。前者负责建立和管理到无线路由器的TCP连接是设备接入网络世界的“网卡驱动”后者则是在这个TCP连接之上构建符合HTTP协议比如我们用的POST请求的数据包并处理服务器响应的“快递员”。而ChatGPT的API就是我们对话的“云端大脑”。我们将通过向这个特定的API地址发送一个结构化的请求包含你的API密钥和问题来获取AI生成的文本回复。对于XIAO ESP32C3来说它的ESP32-C3芯片原生集成了WiFi和蓝牙内存和计算资源应对这种网络交互任务绰绰有余关键是写出稳定、健壮的代码。2. 硬件准备与开发环境搭建2.1 认识你的武器XIAO ESP32C3开发板在写代码之前我们得先熟悉手里的这块板子。Seeed Studio的XIAO ESP32C3以其极小的尺寸约21x17.5mm和完整的ESP32-C3功能而备受青睐。ESP32-C3是一款基于32位RISC-V架构的单核芯片主频高达160MHz内置400KB SRAM和4MB Flash。更重要的是它集成了2.4GHz WiFi和低功耗蓝牙5.0。对于我们的项目WiFi功能是基石。板子上的USB-C接口不仅用于供电还直接连接到了芯片的USB串口这意味着我们不需要额外的USB转串口芯片开发调试会非常方便。拿到板子后第一件事是确认其Bootloader模式以便后续上传程序。XIAO ESP32C3有两个按键“B”Boot和“R”Reset。常规的上传流程是先按住“B”键不松开再按一下“R”键然后松开最后松开“B”键。此时板子会进入下载模式串口会识别为一个新的设备。当然现在Arduino IDE和PlatformIO的插件通常能自动处理这个流程但手动操作在遇到问题时是很好的排查手段。2.2 软件环境配置Arduino IDE与核心库安装我选择使用Arduino IDE进行开发主要是因为其库管理生态丰富对于快速原型开发非常友好。当然你也可以使用PlatformIO其工程管理更专业但本文以Arduino IDE为例进行说明。安装Arduino IDE从Arduino官网下载并安装最新版本的IDE。添加ESP32开发板支持打开Arduino IDE进入“文件”-“首选项”。在“附加开发板管理器网址”中填入以下网址https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json然后点击“确定”。安装ESP32开发板包打开“工具”-“开发板”-“开发板管理器”。在搜索框中输入“esp32”找到由“Espressif Systems”发布的“esp32”开发板包点击安装。这个过程可能会比较慢取决于你的网络环境。选择正确的开发板和端口安装完成后在“工具”-“开发板”中选择“XIAO_ESP32C3”。然后用USB线连接板子和电脑在“工具”-“端口”中选择新出现的串口通常名称里包含“USB”或“XIAO”。注意首次安装ESP32核心包后可能需要重启Arduino IDE才能正确识别开发板和端口。2.3 关键库的引入WiFi与HTTP我们需要的两个库WiFi和HTTPClient通常已经包含在ESP32的Arduino核心包中无需额外安装。你可以在代码中直接#include WiFi.h和#include HTTPClient.h来使用它们。为了后续与ChatGPT API交互时处理JSON数据我们还需要一个JSON解析库。Arduino生态下最常用的是ArduinoJson。你可以通过“项目”-“加载库”-“管理库”搜索“ArduinoJson”并安装由Benoît Blanchon维护的版本目前最新为v6.x。至此软硬件环境就准备妥当了。接下来我们将深入代码看看如何让这块小板子“活”起来。3. 核心代码逻辑与网络连接实现3.1 项目基础框架与WiFi连接任何物联网项目的起点都是网络连接。下面是一个最基础的代码框架包含了WiFi连接和主循环。#include WiFi.h #include HTTPClient.h #include ArduinoJson.h // 请替换为你自己的WiFi信息 const char* ssid 你的WiFi名称; const char* password 你的WiFi密码; // ChatGPT API配置 const char* apiKey 你的OpenAI API密钥; // 例如sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx const char* chatgptEndpoint https://api.openai.com/v1/chat/completions; void setup() { Serial.begin(115200); delay(1000); // 给串口监控一个启动时间 // 连接WiFi WiFi.begin(ssid, password); Serial.print(正在连接到WiFi: ); Serial.println(ssid); while (WiFi.status() ! WL_CONNECTED) { delay(500); Serial.print(.); } Serial.println(); Serial.println(WiFi连接成功); Serial.print(IP地址: ); Serial.println(WiFi.localIP()); } void loop() { // 主循环这里将放置我们与ChatGPT交互的逻辑 // 为了演示我们每10秒执行一次 static unsigned long lastTime 0; if (millis() - lastTime 10000) { lastTime millis(); chatWithGPT(你好请用一句话介绍你自己。); } // 其他任务可以在这里执行 }这段代码在setup()函数中完成了串口初始化和WiFi连接。WiFi.begin()函数是发起连接的起点while循环会持续检查连接状态直到成功。这里有一个实操心得在实际产品中这种阻塞式的连接循环while...可能不是最佳选择因为它会卡住整个程序。更健壮的做法是使用非阻塞的状态机或者在连接失败多次后进入深度睡眠/重启。但对于我们的实验和快速原型这种方式最简单直接。3.2 构建与发送HTTP请求到ChatGPT API核心功能在chatWithGPT函数中实现。ChatGPT的聊天补全API/v1/chat/completions要求我们以POST方式发送一个JSON格式的请求体。我们需要构建这个JSON并设置正确的HTTP头部。void chatWithGPT(const char* userMessage) { // 0. 检查网络连接 if (WiFi.status() ! WL_CONNECTED) { Serial.println(WiFi未连接无法发送请求。); return; } // 1. 创建HTTPClient对象 HTTPClient http; // 2. 指定请求的URL http.begin(chatgptEndpoint); // 3. 添加必要的HTTP头部 http.addHeader(Content-Type, application/json); http.addHeader(Authorization, String(Bearer ) apiKey); // 注意Bearer后面有个空格 // 4. 构建请求JSON数据 // 使用ArduinoJson库动态创建JSON文档 // 根据API文档我们需要一个类似这样的结构 // { // model: gpt-3.5-turbo, // messages: [{role: user, content: 你的问题}], // max_tokens: 150 // } DynamicJsonDocument requestDoc(1024); // 根据预估的JSON大小分配内存 requestDoc[model] gpt-3.5-turbo; // 也可以使用gpt-4等但需账户支持 JsonArray messages requestDoc.createNestedArray(messages); JsonObject message messages.createNestedObject(); message[role] user; message[content] userMessage; requestDoc[max_tokens] 150; // 限制回复长度节省token和响应时间 String requestBody; serializeJson(requestDoc, requestBody); // 将JSON对象序列化成字符串 Serial.println(请求体: requestBody); // 5. 发送POST请求并获取响应代码 int httpResponseCode http.POST(requestBody); // 6. 处理响应 if (httpResponseCode 0) { Serial.printf(HTTP响应代码: %d\n, httpResponseCode); String response http.getString(); Serial.println(响应内容: response); // 这里可以调用函数解析响应JSON parseGPTResponse(response); } else { Serial.printf(POST请求失败错误: %s\n, http.errorToString(httpResponseCode).c_str()); } // 7. 释放资源 http.end(); }关键点解析与避坑指南http.begin()这个函数初始化HTTP客户端并指定目标URL。对于HTTPSURL以https://开头ESP32的HTTPClient库底层会使用TLS进行加密通信你通常不需要额外配置证书库已内置常见CA证书但这也意味着通信过程是加密的会消耗更多计算资源和时间。http.addHeader()设置HTTP头至关重要。Content-Type: application/json告诉服务器我们发送的是JSON数据。Authorization: Bearer 你的API密钥是OpenAI API进行身份验证的方式格式必须严格正确Bearer后面有一个空格。JSON构建与内存管理使用DynamicJsonDocument时构造函数中指定的容量如1024是预分配的字节数。如果JSON结构非常复杂或内容很长这个值需要增大否则在序列化时会导致内存分配失败。一个实用技巧是可以先用一个较大的值比如2048在串口打印出序列化后的字符串长度然后根据实际需求调整到一个安全且不浪费内存的值。http.POST()这个函数执行POST请求并返回HTTP状态码。200表示成功401通常表示API密钥错误429表示请求速率超限等等。http.getString()获取服务器返回的整个响应体。对于ChatGPT API这同样是一个JSON字符串。3.3 解析ChatGPT的JSON响应服务器返回的响应也是一个JSON对象结构比请求复杂一些。我们需要从中提取出AI回复的文本内容。void parseGPTResponse(String jsonResponse) { // 动态分配JSON文档内存响应可能比请求大 DynamicJsonDocument doc(2048); DeserializationError error deserializeJson(doc, jsonResponse); if (error) { Serial.print(JSON解析失败: ); Serial.println(error.c_str()); return; } // 导航到回复文本的位置 // 响应结构大致为{choices:[{message:{role:assistant,content:回复内容}}]} if (doc.containsKey(choices) doc[choices].isJsonArray()) { JsonArray choices doc[choices]; if (choices.size() 0) { JsonObject firstChoice choices[0]; if (firstChoice.containsKey(message) firstChoice[message][role] assistant) { const char* content firstChoice[message][content]; Serial.println(\n ChatGPT 回复 ); Serial.println(content); Serial.println(\n); // 在这里你可以将content用于其他用途比如通过串口发送到其他设备或者显示在屏幕上。 } } } else { Serial.println(响应JSON中未找到预期的choices字段。); // 有时API返回错误信息也在JSON中可以打印出来看看 if (doc.containsKey(error)) { Serial.print(API错误: ); serializeJsonPretty(doc[error], Serial); Serial.println(); } } }解析注意事项内存大小响应JSON可能包含很长的文本所以DynamicJsonDocument的容量这里用了2048要设置得足够大。如果解析失败首先检查这个值是否太小。健壮性检查代码中使用了containsKey()和isJsonArray()等方法来检查JSON结构这是防止程序因意外响应格式而崩溃的好习惯。错误处理API可能返回错误信息例如额度不足、模型不可用这些信息通常包含在error字段中。我们的代码对此做了检查能帮助快速定位问题。将parseGPTResponse函数添加到前面的chatWithGPT函数中一个完整的、能与ChatGPT对话的XIAO ESP32C3程序就基本成型了。上传代码后打开串口监视器波特率115200你应该能看到WiFi连接成功的提示然后每隔10秒会看到发送的请求和接收到的AI回复。4. 稳定性优化与高级功能探讨基础功能跑通只是第一步。在实际应用中我们需要考虑网络的不稳定性、API的调用限制以及如何实现更自然的交互。4.1 网络连接的重连与看门狗在loop()函数中我们可以添加一个WiFi状态检查在断线时尝试重连。void loop() { // 检查WiFi连接如果断开则尝试重连 if (WiFi.status() ! WL_CONNECTED) { Serial.println(WiFi连接丢失尝试重连...); WiFi.disconnect(); WiFi.reconnect(); // 可以加入一个短暂的非阻塞延迟避免重连过于频繁 delay(2000); } // 原有的主业务逻辑 static unsigned long lastTime 0; if (millis() - lastTime 10000) { lastTime millis(); chatWithGPT(继续用一句话说说物联网。); } }此外ESP32-C3内置了硬件看门狗定时器。对于长时间运行的任务启用软件看门狗task watchdog或硬件看门狗可以防止程序因未知错误而完全死锁。在Arduino环境中有时网络操作或复杂的JSON解析可能偶尔卡住看门狗能自动重启设备。#include esp_task_wdt.h // 包含看门狗头文件 void setup() { // ... 其他初始化代码 esp_task_wdt_init(10, true); // 初始化看门狗超时时间10秒启用panic模式重启 esp_task_wdt_add(NULL); // 将当前任务主循环添加到看门狗监控列表 } void loop() { esp_task_wdt_reset(); // 在主循环中定期“喂狗”表示程序运行正常 // ... 你的主循环代码 }4.2 实现多轮对话上下文上面的例子是单轮对话ChatGPT不会记住之前的问题。要实现多轮对话上下文我们需要在请求的messages数组中不仅包含当前用户的问题还要包含之前对话的历史记录。我们需要一个全局或静态的数组来存储对话历史。由于ESP32-C3的内存有限我们需要设定一个历史记录的最大条数或总字符数限制。#include vector // 使用简单的数组也可以 struct Message { String role; String content; }; std::vectorMessage conversationHistory; // 存储对话历史 const int MAX_HISTORY 5; // 最多保存5轮对话包括用户和助理的回复 void addToHistory(const String role, const String content) { Message msg {role, content}; conversationHistory.push_back(msg); // 如果历史记录超过限制移除最旧的一条 if (conversationHistory.size() MAX_HISTORY * 2) { // *2 因为一轮对话包含user和assistant两条 conversationHistory.erase(conversationHistory.begin()); } } void chatWithGPTWithContext(const char* userMessage) { // ... 前面的网络检查、HTTPClient初始化代码相同 // 构建JSON时将历史记录也加入messages数组 DynamicJsonDocument requestDoc(2048); // 需要更大的内存来容纳历史 requestDoc[model] gpt-3.5-turbo; JsonArray messages requestDoc.createNestedArray(messages); // 1. 先添加历史记录 for (const auto msg : conversationHistory) { JsonObject histMsg messages.createNestedObject(); histMsg[role] msg.role; histMsg[content] msg.content; } // 2. 再添加当前用户消息 JsonObject currentMsg messages.createNestedObject(); currentMsg[role] user; currentMsg[content] userMessage; requestDoc[max_tokens] 150; String requestBody; serializeJson(requestDoc, requestBody); // ... 发送请求的代码相同 if (httpResponseCode 200) { String response http.getString(); // 解析响应获取助理回复 DynamicJsonDocument respDoc(2048); deserializeJson(respDoc, response); const char* assistantReply respDoc[choices][0][message][content]; // 3. 将本轮对话加入历史 addToHistory(user, userMessage); addToHistory(assistant, assistantReply); Serial.println(助理回复: String(assistantReply)); } // ... 清理资源 }这样ChatGPT就能根据之前的对话历史来生成更有连续性的回复了。注意保存历史会显著增加每次请求的数据量消耗更多的API token费用和内存需要根据实际情况权衡。4.3 功耗考虑与深度睡眠对于电池供电的XIAO ESP32C3项目功耗是关键。我们的当前代码会让ESP32-C3一直处于活动状态WiFi常开这非常耗电。一个常见的优化模式是设备大部分时间处于深度睡眠Deep Sleep状态定时唤醒连接WiFi执行一次ChatGPT查询然后将结果通过其他方式如蓝牙发送到手机或存储在Flash中保存或输出最后再次进入深度睡眠。// 在setup()中我们可以判断唤醒原因 void setup() { esp_sleep_wakeup_cause_t wakeup_reason; wakeup_reason esp_sleep_get_wakeup_cause(); switch(wakeup_reason) { case ESP_SLEEP_WAKEUP_TIMER: Serial.println(由定时器唤醒); // 执行我们的主任务连接WiFi与ChatGPT对话 connectWiFiAndChat(); break; default: Serial.println(非深度睡眠唤醒如上电复位); // 首次启动也执行一次任务 connectWiFiAndChat(); break; } // 主任务完成后配置定时器并进入深度睡眠 Serial.println(准备进入深度睡眠60秒后唤醒...); esp_sleep_enable_timer_wakeup(60 * 1000000); // 微秒为单位这里是60秒 esp_deep_sleep_start(); // 这行代码之后的都不会执行因为芯片进入深度睡眠了 } void loop() { // 在深度睡眠模式下loop()函数永远不会被执行 }在connectWiFiAndChat()函数中你需要包含之前的所有WiFi连接和HTTP请求代码。这种模式下设备平均功耗可以降到微安级别非常适合由电池长期供电的物联网应用比如一个每天自动获取AI生成的名言并显示的桌面摆件。5. 常见问题排查与实战心得在实际操作中你几乎一定会遇到一些问题。下面是我总结的一些常见问题及其解决方法。5.1 编译与上传问题错误Failed to connect to ESP32: Timed out waiting for packet header原因板子没有进入下载模式或串口被占用。解决确保在上传前按正确顺序操作Boot和Reset键先按Boot不放再按Reset松开Reset最后松开Boot。关闭可能占用串口的其他软件如串口监视器、其他IDE。错误A fatal error occurred: Failed to connect to ESP32: Invalid head of packet原因串口选择错误或板子型号选择错误。解决检查“工具”-“端口”和“开发板”选择是否正确。尝试拔插USB线或换一个USB口。5.2 网络与API连接问题WiFi连接一直失败原因SSID或密码错误路由器设置了MAC地址过滤信号太弱。解决检查密码大小写将路由器加密方式暂时改为WPA2-PSKAES试试让设备靠近路由器。HTTP请求返回错误代码401原因API密钥错误或格式不对。解决仔细检查apiKey变量中的字符串确保没有多余空格或换行。确认你的OpenAI账户有可用额度并且API密钥有效。HTTP请求返回错误代码429原因请求速率超过API限制免费用户或新账户有每分钟/每天的请求次数限制。解决降低请求频率在代码中增加delay()。对于免费额度OpenAI的限制比较严格耐心等待一段时间再试。请求超时或无响应原因网络不稳定DNS解析失败OpenAI API服务器访问不畅。解决增加HTTP客户端的超时设置http.setTimeout(10000); // 10秒超时。可以尝试在代码中直接使用IP地址不推荐因为IP可能会变。对于网络环境问题可能需要检查网络配置。5.3 内存与性能问题设备运行一段时间后崩溃或重启原因内存泄漏。在loop中频繁创建HTTPClient和DynamicJsonDocument对象而没有正确释放或重用。解决确保每次请求后都调用http.end()。对于DynamicJsonDocument它会在函数结束时自动析构但如果是在全局或静态作用域需要注意。也可以考虑将HTTPClient和大的JSON文档对象声明为全局变量在loop中复用而不是每次都在函数内创建。JSON解析失败返回InvalidInput或NoMemory原因DynamicJsonDocument分配的容量不足或者响应根本不是有效的JSON。解决首先在解析前打印出原始的response字符串看看服务器到底返回了什么可能是HTML错误页面。其次逐步增大DynamicJsonDocument的容量例如从2048增加到4096。可以使用serializeJsonPretty(doc, Serial);来漂亮地打印解析后的JSON帮助调试结构。5.4 实战心得与技巧从串口调试开始Serial.print是你的最佳朋友。在代码的关键节点连接WiFi前/后、发送请求前、收到响应后打印状态信息能极大简化调试过程。先测试简单的HTTP请求在对接复杂的ChatGPT API之前可以先尝试用HTTPClient访问一个简单的公共API比如http://httpbin.org/get来验证你的网络连接和基础HTTP功能是否正常。管理好你的API密钥将API密钥硬编码在代码中并上传到公开仓库是极其危险的。对于个人项目可以暂时这样但最好能将其存储在外部如SPIFFS文件系统或者通过串口在启动时输入。对于产品需要考虑更安全的密钥管理方案。理解API计费ChatGPT API是按token收费的。你的请求内容和AI的回复都计入token。在代码中设置max_tokens参数可以限制回复长度从而控制单次请求的成本。在开发调试阶段可以用简单的问题进行测试。XIAO ESP32C3的引脚复用这个板子引脚有限如果你计划在此基础上添加传感器或执行器比如按钮触发提问、OLED显示回复需要仔细查看引脚定义图避免冲突。例如一些引脚在启动时有特殊电平要求不当使用可能导致设备无法启动。通过这个项目你不仅学会了如何在XIAO ESP32C3上使用WiFiClient和HTTPClient更重要的是掌握了一套让微控制器接入云服务、处理JSON数据、构建稳定物联网应用的通用方法。这套方法稍加修改就可以用于连接其他无数的Web API开启更多智能硬件的可能性。