ESP32接入ChatGPT API:打造智能语音交互硬件原型
1. 项目概述当ESP32遇见ChatGPT最近在捣鼓ESP32想给它加点“脑子”。ESP32本身是个很棒的物联网微控制器Wi-Fi、蓝牙、低功耗该有的都有但它本质上还是个执行预设逻辑的设备。我就琢磨能不能让它接入像ChatGPT这样的AI大语言模型让它能理解自然语言甚至进行简单的对话和推理这样一来ESP32就不再只是个“开关”或“传感器”它能变成一个能听懂人话、能简单思考的智能终端。这个想法听起来有点天马行空但实现路径其实很清晰。核心就是让ESP32通过Wi-Fi连接到互联网然后调用OpenAI提供的ChatGPT API。我们不需要在ESP32上跑模型——那根本不现实——我们只需要让它成为一个智能的“提问者”和“回答展示者”。比如你可以对着一个麦克风模块说话ESP32把语音转成文本发给ChatGPT拿到回复后再通过喇叭播出来或者显示在屏幕上。这就能做出一个极简版的智能语音助手硬件。这个项目适合谁呢如果你对物联网开发感兴趣已经玩过ESP32的基础项目想挑战一下更有趣的集成应用那这个项目正合适。它不要求你有AI算法背景但需要你熟悉Arduino IDE或PlatformIO开发环境了解基本的HTTP客户端请求和JSON数据解析。整个过程就像搭积木把网络通信、API调用、外设驱动这几个模块拼起来最终收获一个能和你“对话”的小硬件成就感十足。2. 核心思路与方案选型要让ESP32和ChatGPT对话本质上是一个典型的“设备-云-服务”架构。ESP32作为客户端向云端服务器OpenAI API发送请求并处理返回的结果。这里面的技术选型每一步都值得推敲。2.1 为什么是HTTP Client而非MQTT很多人做物联网项目第一反应是用MQTT轻量、高效、适合设备间通信。但在这个场景下HTTP/HTTPS客户端是更直接的选择。原因很简单OpenAI的API是标准的RESTful API它通过HTTP POST请求接收JSON格式的输入并返回JSON格式的输出。MQTT虽然轻量但它是一个发布/订阅的消息协议并不直接适配这种请求-响应模式的API调用。用HTTP Client库我们可以更直观地构建请求头、请求体并解析响应代码逻辑更清晰也更符合Web开发者的习惯。ESP32的Arduino核心库自带的HTTPClient或者更轻量的WiFiClientSecure配合手动组包都能很好地完成任务。2.2 网络连接与安全通信OpenAI API要求使用HTTPS这意味着我们的ESP32必须支持SSL/TLS加密连接。幸运的是ESP32的芯片内置了加密硬件加速器处理TLS握手和加密解密效率很高。我们需要在代码中正确配置根证书CA Certificate。这里有个关键点OpenAI API服务器的证书是由受信任的CA签发的我们需要在ESP32的代码中嵌入该CA的根证书或者使用一个更通用的方法——使用WiFiClientSecure的setInsecure()方法。但请注意setInsecure()会跳过证书验证仅用于测试和学习在生产环境中绝对不要使用因为它有中间人攻击的风险。对于学习项目我们可以暂时用它来绕过证书验证的麻烦快速验证通信链路是否通畅。2.3 数据交换格式JSON的解析与构建与ChatGPT API交互输入和输出都是JSON格式。例如一个最简单的请求体看起来像这样{ model: gpt-3.5-turbo, messages: [{role: user, content: Hello!}], max_tokens: 100 }ESP32需要构建这样的字符串并在收到响应后从类似{choices:[{message:{content:Hi there!}}]}的JSON字符串中提取出content字段。在资源受限的单片机上我们不能使用像ArduinoJson这样相对重型的库吗恰恰相反ArduinoJson库经过高度优化在ESP32上运行效率很高是处理JSON的不二之选。我们需要仔细计算JSON文档的预期大小来分配合适的缓冲区避免内存溢出或解析失败。2.4 外设扩展从文本到语音的闭环基础版本只需要串口打印出ChatGPT的回复。但要让体验更完整我们可以加入输入和输出外设。输入最简单的是用串口监视器输入文本。进阶一点可以连接一个MAX9814这类带自动增益控制的麦克风模块配合一个语音转文本的云服务如腾讯云、百度云的短语音识别API它们通常有免费额度实现语音输入。这样就从“打字对话”变成了“说话对话”。输出除了串口输出可以连接一个OLED屏幕SSD1306来显示对话。或者连接一个音频解码模块如MAX98357结合TTS文本转语音服务将回复的文字合成语音播放出来。这样就构成了一个完整的智能语音交互硬件原型。3. 硬件准备与开发环境搭建工欲善其事必先利其器。这个项目对硬件要求不高但正确的环境配置是成功的第一步。3.1 硬件清单你需要准备以下硬件ESP32开发板任何一款ESP32开发板都可以比如ESP32 DevKit C、NodeMCU-32S等。确保它带有USB转串口芯片方便烧录和调试。USB数据线用于供电和程序烧录。可选外设用于功能扩展OLED显示屏I2C接口用于显示问题和回答推荐0.96寸128x64分辨率的SSD1306。麦克风模块如MAX9814用于采集语音。喇叭与音频放大模块如MAX98357 I2S功放模块连接一个小喇叭用于播放语音。按键与LED用于交互指示比如一个按键触发录音一个LED指示网络连接状态。硬件连接非常简单。对于基础版只需要用USB线连接ESP32和电脑。如果连接OLED将其SDA引脚接ESP32的GPIO21SCL接GPIO22这是ESP32默认的I2C引脚VCC和GND对应接好即可。3.2 软件开发环境配置我强烈推荐使用PlatformIO作为开发环境它比Arduino IDE更专业库管理、项目结构、调试都更强大。你可以将其作为VSCode的插件安装。安装PlatformIO在VSCode的扩展商店搜索“PlatformIO IDE”并安装。创建新项目打开PIO点击“New Project”项目名称可以叫esp32-chatgptBoard选择“Espressif ESP32 Dev Module”Framework选择“Arduino”。安装必要的库打开项目后在PIO Home的“Libraries”标签页或直接修改platformio.ini文件来添加依赖。我们需要的核心库有WiFi(通常内置)HTTPClient(通常内置)ArduinoJson(by Benoit Blanchon)用于处理JSON。Adafruit SSD1306和Adafruit GFX Library如果使用OLED。 在platformio.ini的[env]部分添加lib_deps bblanchon/ArduinoJson ^6.21.3 adafruit/Adafruit SSD1306 ^2.5.7 adafruit/Adafruit GFX Library ^1.11.9保存后PIO会自动下载这些库。3.3 获取OpenAI API密钥这是与ChatGPT对话的“门票”。你需要访问OpenAI的官网注册账号并登录到API管理页面。在账户设置中你可以生成一个新的API密钥API Key。这个密钥非常重要相当于你的密码绝对不能泄露或直接硬编码在提交到公开仓库的代码中。我们接下来会将其保存在一个单独的配置文件中。4. 核心代码实现与解析让我们从最核心的代码开始一步步构建起ESP32与ChatGPT的通信桥梁。我会先实现一个基础版本仅通过串口进行文本交互。4.1 网络连接与基础配置首先我们创建一个config.h文件来存放敏感信息和配置。切记这个文件不要上传到任何公开的代码仓库可以通过.gitignore忽略。config.h:// config.h - 配置文件请根据实际情况修改 #define WIFI_SSID 你的Wi-Fi名称 #define WIFI_PASSWORD 你的Wi-Fi密码 #define OPENAI_API_KEY 你的OpenAI API密钥 // 例如sk-...你的密钥... #define OPENAI_API_HOST api.openai.com #define OPENAI_API_PORT 443 #define OPENAI_MODEL gpt-3.5-turbo // 也可选用 gpt-4 等注意费用不同主程序文件main.cpp的开头部分#include WiFi.h #include WiFiClientSecure.h #include ArduinoJson.h #include config.h // 引入配置文件 // 全局对象 WiFiClientSecure client; HTTPClient https; void setup() { Serial.begin(115200); delay(1000); // 连接Wi-Fi Serial.println(正在连接Wi-Fi: String(WIFI_SSID)); WiFi.begin(WIFI_SSID, WIFI_PASSWORD); while (WiFi.status() ! WL_CONNECTED) { delay(500); Serial.print(.); } Serial.println(\nWi-Fi连接成功); Serial.print(IP地址: ); Serial.println(WiFi.localIP()); // 配置HTTPS客户端 // 注意setInsecure()跳过了证书验证仅用于测试 client.setInsecure(); // 生产环境务必使用setCACert()设置正确的根证书 }注意client.setInsecure()这行代码是测试阶段的“捷径”。在生产项目中你应该使用client.setCACert()并传入OpenAI API服务器的根证书例如ISRG Root X1证书以确保通信安全。你可以从权威网站获取PEM格式的根证书并将其以字符串形式存储在代码中。4.2 构建并发送HTTP请求这是最核心的函数它负责组装符合OpenAI API格式的请求并发送出去。String chatWithGPT(const String userMessage) { // 1. 确保客户端连接 if (!client.connect(OPENAI_API_HOST, OPENAI_API_PORT)) { Serial.println(连接OpenAI服务器失败); return 连接错误; } // 2. 构建JSON请求体 const size_t capacity JSON_OBJECT_SIZE(3) JSON_ARRAY_SIZE(1) JSON_OBJECT_SIZE(2) 200 userMessage.length(); DynamicJsonDocument requestDoc(capacity); JsonObject root requestDoc.toJsonObject(); root[model] OPENAI_MODEL; JsonArray messages root.createNestedArray(messages); JsonObject msg messages.createNestedObject(); msg[role] user; msg[content] userMessage; root[max_tokens] 150; // 限制回复的最大长度控制成本 String requestBody; serializeJson(requestDoc, requestBody); // 3. 构建并发送HTTP POST请求 https.begin(client, OPENAI_API_HOST, OPENAI_API_PORT, /v1/chat/completions); https.addHeader(Content-Type, application/json); https.addHeader(Authorization, String(Bearer ) OPENAI_API_KEY); Serial.println(发送请求到OpenAI...); int httpCode https.POST(requestBody); String response ; // 4. 处理响应 if (httpCode HTTP_CODE_OK) { response https.getString(); Serial.println(收到响应:); Serial.println(response); } else { Serial.printf(HTTP请求失败错误码: %d\n, httpCode); response HTTP错误: String(httpCode); } https.end(); return response; }代码解析与注意事项动态JSON文档大小DynamicJsonDocument的大小需要仔细估算。这里我们计算了基础对象、数组、消息对象的大小并额外预留了200字节和用户消息长度的空间。如果响应内容很长导致解析失败可能需要增大这个值。一个实用的调试技巧是先分配一个较大的空间如2048成功后再根据实际使用的内存可通过requestDoc.memoryUsage()查看进行优化。API端点我们使用的是/v1/chat/completions这是ChatGPT模型的标准对话端点。认证头Authorization头必须以Bearer开头后面跟上你的API密钥。错误处理我们检查了HTTP状态码。常见的错误码有401API密钥无效、429请求过快达到速率限制、500服务器内部错误等。在实际项目中应该对这些错误进行更细致的处理例如重试或给出用户友好的提示。4.3 解析JSON响应并提取回复收到响应后我们需要从复杂的JSON结构中提取出我们需要的文本回复。String parseGPTResponse(const String jsonResponse) { // 分配一个足够大的文档来解析响应 DynamicJsonDocument doc(2048); DeserializationError error deserializeJson(doc, jsonResponse); if (error) { Serial.print(JSON解析失败: ); Serial.println(error.c_str()); return 解析响应失败; } // 导航到回复内容response.choices[0].message.content if (doc.containsKey(choices) doc[choices].isJsonArray() doc[choices].size() 0) { JsonObject firstChoice doc[choices][0]; if (firstChoice.containsKey(message) firstChoice[message][content].isString()) { return firstChoice[message][content].asString(); } } else if (doc.containsKey(error)) { // 如果API返回错误信息 String errorMsg doc[error][message].asString(); return API错误: errorMsg; } return 无法从响应中提取内容; }这个函数通过deserializeJson解析字符串然后按照choices[0].message.content的路径去提取内容。代码中加入了大量的条件判断这是非常必要的。因为网络返回的数据可能不符合预期如果没有这些判断直接访问不存在的键会导致程序崩溃空指针异常。稳健的代码必须对每一步都进行防御性检查。4.4 主循环与串口交互最后我们在loop函数中实现一个简单的串口对话循环。void loop() { // 检查串口是否有输入 if (Serial.available() 0) { String userInput Serial.readStringUntil(\n); userInput.trim(); // 去除首尾空格和换行符 if (userInput.length() 0) { Serial.println(你: userInput); Serial.println(思考中...); String rawResponse chatWithGPT(userInput); String reply parseGPTResponse(rawResponse); Serial.println(ChatGPT: reply); Serial.println(\n--- 等待下一个问题 ---\n); } } // 可以添加一个延时避免loop空转消耗CPU delay(100); }现在将代码编译并上传到ESP32。打开串口监视器波特率115200连接Wi-Fi成功后你就可以直接输入问题看到ChatGPT的回复了。这是整个项目的基石后面的所有扩展都基于这个通信链路。5. 功能扩展一添加OLED屏幕显示让回复显示在屏幕上比只看串口监视器直观得多。我们使用最普遍的SSD1306 OLED屏I2C接口。5.1 硬件连接与库引入按照之前说的连接SDA到GPIO21SCL到GPIO22。在main.cpp开头添加库引用和对象声明#include Adafruit_SSD1306.h #include Adafruit_GFX.h #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 #define OLED_RESET -1 // 重置引脚共享Arduino重置引脚 Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, Wire, OLED_RESET);在setup()函数中Wi-Fi连接成功后初始化显示屏// 初始化OLED if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // 地址0x3C取决于你的模块 Serial.println(F(SSD1306分配失败)); for(;;); // 卡住 } display.clearDisplay(); display.setTextSize(1); display.setTextColor(SSD1306_WHITE); display.setCursor(0,0); display.println(Wi-Fi Connected!); display.println(WiFi.localIP().toString()); display.display(); delay(2000);5.2 编写显示函数我们需要一个函数来在屏幕上优雅地显示多行文本因为ChatGPT的回复可能很长。void displayMessage(const String sender, const String message) { display.clearDisplay(); display.setCursor(0,0); display.setTextSize(1); // 显示发送者 display.println(sender :); display.println(-------------); // 处理长文本换行 int16_t x 0, y 16; // 起始位置 int16_t maxWidth SCREEN_WIDTH - x; uint16_t currentY y; for (uint16_t i 0; i message.length(); i) { char c message[i]; display.write(c); // 获取当前光标位置Adafruit库没有直接方法我们模拟 // 更准确的方法是使用display.getCursorX()但这里我们简单处理换行逻辑 // 实际上我们可以分段显示 } // 由于Adafruit库自动换行功能有限更稳妥的做法是将字符串按长度分割 // 这里提供一个简化版的自动换行显示函数 display.setCursor(0, 16); int charsPerLine 21; // 128像素 / 6像素每个字符 ≈ 21字符 for (int i 0; i message.length(); i charsPerLine) { String line message.substring(i, min(i charsPerLine, message.length())); display.println(line); } display.display(); }然后在loop中当收到回复后调用displayMessage(GPT, reply);来更新屏幕。同时也可以在发送问题时显示displayMessage(You, userInput);。这样一个简单的对话界面就出现在了OLED屏幕上。实操心得在小型OLED上显示长文本是个挑战。上面的简化换行算法在单词中间截断影响阅读。更好的方法是实现一个“按单词换行”的函数或者使用专门的文本渲染库。对于快速原型也可以限制每次发送和接收的文本长度比如通过substring(0, 100)截断前100个字符显示。6. 功能扩展二实现语音输入与输出这是让项目“活”起来的关键一步实现真正的语音交互。由于ESP32的算力和内存有限本地运行高质量的语音识别ASR和语音合成TTS模型非常困难。因此我们采用云服务方案。6.1 语音输入连接麦克风与云ASR硬件上连接一个MAX9814麦克风模块到ESP32的某个模拟输入引脚如GPIO34。在软件上我们需要录音并上传到云语音识别API。步骤简述录音采样使用ESP32的ADC和I2S外设进行音频采样。I2S可以获取更高质量的音频数据。我们需要配置采样率通常16000 Hz、位深度16位和录音时长。音频编码原始PCM数据很大需要编码。最常用的轻量级编码是WAV头部 PCM或直接上传原始PCM。有些API也支持OPUS等格式。调用云ASR API国内可以选择百度语音识别、腾讯云语音识别等它们都有免费的额度。你需要注册相应的云平台获取API Key和Secret。流程与调用OpenAI API类似构建HTTP请求上传音频数据解析返回的JSON文本结果。将识别文本发送给ChatGPT拿到ASR返回的文本后直接将其作为userMessage传入我们之前写好的chatWithGPT函数。关键代码片段概念性// 1. 录音需使用I2S库如ES8388或自定义驱动 void recordAudio(int16_t* audioBuffer, size_t samples) { // 配置I2S并录音填充audioBuffer } // 2. 构建WAV头并上传以百度语音识别为例 String speechToText(int16_t* pcmData, size_t dataSize) { // 将PCM数据加上WAV头或直接按API要求准备数据 // 使用WiFiClientSecure连接语音识别API服务器 // 构建Multipart/form-data格式的POST请求包含音频文件 // 发送请求并解析返回的JSON提取result字段 // return recognizedText; }这个过程代码量较大涉及到具体的云服务商SDK和音频处理。一个更简单的替代方案是使用一些集成了ASR的硬件模块比如科大讯飞的离线语音识别模块它们通过串口直接输出文本可以大大简化开发。6.2 语音输出调用云TTS服务拿到ChatGPT的文本回复后我们调用云TTS服务将其转为音频文件通常是MP3格式然后通过I2S接口播放出来。步骤简述调用云TTS API同样百度、腾讯、阿里云等都提供TTS服务。发送一个包含文本、发音人、语速、音调等参数的POST请求。接收并解码音频流API会返回一个音频文件如MP3。ESP32需要接收这个二进制数据流。由于内存有限我们无法将整个MP3文件载入内存需要流式播放。流式音频播放这是最具挑战的部分。我们需要一个支持MP3解码的库如ESP32-audioI2S库中的AudioFileSourceHTTPStream和AudioGeneratorMP3并实现一个回调机制一边从网络接收数据一边解码并送入I2S驱动播放。关键代码片段使用ESP32-audioI2S库#include Audio.h #include WiFiClientSecure.h AudioGeneratorMP3 *mp3; AudioFileSourceHTTPStream *file; AudioOutputI2S *out; void textToSpeechAndPlay(const String text) { // 1. 构建TTS API请求URL例如百度TTS String url https://tsn.baidu.com/text2audio?tex URLEncode(text) tok你的访问令牌cuid设备idctp1lanzhper0; // 2. 初始化音频组件 file new AudioFileSourceHTTPStream(url.c_str()); out new AudioOutputI2S(); out-SetPinout(26, 25, 22); // BCK, WS, DATA引脚根据你的接线调整 mp3 new AudioGeneratorMP3(); // 3. 开始流式播放 mp3-begin(file, out); } void loop() { if (mp3 mp3-isRunning()) { if (!mp3-loop()) { mp3-stop(); // 播放结束 delete mp3; delete file; delete out; } } // ... 其他逻辑 }重要提示流式播放MP3对ESP32的网络稳定性和处理能力有一定要求。在网络不佳时可能会出现卡顿或破音。此外云TTS服务通常有QPS每秒查询率限制频繁调用可能会被限流。7. 功耗优化与稳定性设计一个始终在线的对话设备功耗和稳定性是关键。我们不能让它动不动就重启或者把电池很快耗光。7.1 深度睡眠与唤醒如果设备是电池供电并且不需要持续监听可以引入深度睡眠模式。例如我们可以用一个按键连接到ESP32的EN引脚或某个GPIO来唤醒设备。设备被唤醒后快速连接Wi-Fi处理完一次对话后再次进入深度睡眠。#define BUTTON_PIN 0 // 假设按键接GPIO0许多开发板的BOOT按钮 void setup() { // 检查唤醒原因 esp_sleep_wakeup_cause_t wakeup_reason esp_sleep_get_wakeup_cause(); if(wakeup_reason ESP_SLEEP_WAKEUP_EXT0) { Serial.println(被外部按键唤醒); } // ... 正常的Wi-Fi连接和初始化 } void enterDeepSleep() { Serial.println(进入深度睡眠按按键唤醒...); // 配置GPIO0为唤醒源高电平唤醒 esp_sleep_enable_ext0_wakeup(GPIO_NUM_0, 1); delay(100); esp_deep_sleep_start(); } void loop() { // 在一次对话结束后判断是否该睡眠 if (shouldSleep) { enterDeepSleep(); } }在深度睡眠模式下ESP32的电流可以降到10微安左右非常省电。7.2 网络异常处理与重连机制Wi-Fi可能不稳定API调用也可能失败。健壮的程序必须有重试和恢复机制。Wi-Fi断线重连在loop中定期检查WiFi.status()如果断开则尝试重新连接。void checkWiFi() { static unsigned long lastCheck 0; if (millis() - lastCheck 10000) { // 每10秒检查一次 lastCheck millis(); if (WiFi.status() ! WL_CONNECTED) { Serial.println(Wi-Fi断开尝试重连...); WiFi.disconnect(); WiFi.reconnect(); // 可以添加一个重连次数限制避免无限重试 } } }API请求重试修改chatWithGPT函数加入简单的重试逻辑。例如如果返回的HTTP代码是5xx服务器错误或429速率限制可以等待一段时间后重试。String chatWithGPT(const String userMessage, int maxRetries 3) { for (int i 0; i maxRetries; i) { String result // ... 发送请求的代码 if (!result.startsWith(HTTP错误: 5) !result.startsWith(HTTP错误: 429)) { return result; // 成功或非可重试错误直接返回 } Serial.printf(请求失败第%d次重试...\n, i1); delay(1000 * (i 1)); // 指数退避延迟 } return 请求失败已达最大重试次数; }7.3 内存管理与防崩溃长时间运行内存碎片或泄漏可能导致崩溃。我们需要关注使用String类要谨慎频繁的字符串拼接会产生很多临时对象导致内存碎片。在性能关键或内存紧张的地方可以考虑使用C风格的字符数组char[]或std::string如果启用STL。及时释放资源确保HTTPClient和WiFiClientSecure对象在使用后调用end()方法。动态创建的对象如JSON文档、音频流对象在使用完毕后要及时删除delete。看门狗定时器ESP32有硬件看门狗。如果主循环loop()卡住超过一定时间默认约5秒看门狗会触发复位。对于可能长时间阻塞的操作如网络请求可以使用feedDog()函数定期喂狗或者将耗时任务分解成非阻塞的状态机模式。8. 项目总结与进阶思考把这个项目做下来你会发现让硬件“听懂人话”并“开口回答”的核心其实是将多个云端服务Wi-Fi连接、语音识别、大语言模型、语音合成通过一个轻量级的硬件终端串联起来。ESP32在这里扮演了一个智能网关和交互界面的角色它本身不负责复杂的计算而是负责调度、通信和展示。我个人在实际操作中的体会是项目的难点往往不在主流程而在“边角料”的稳定性处理上。比如网络闪断时如何优雅重连而不死机TTS返回的音频流如何流畅播放不卡顿在有限的OLED屏幕上如何更好地显示长文本对话记录。解决这些问题才是把一个Demo变成可用产品的关键。这个项目还有巨大的扩展空间多轮对话上下文目前的代码每次都是独立的问答。你可以修改messages数组将历史对话也包含进去让ChatGPT拥有上下文记忆实现真正的连续对话。本地知识库与Function Calling结合ESP32的SD卡或SPIFFS文件系统可以存储一些本地知识如设备控制指令。当用户提问时可以先在本地知识库匹配匹配不上再问ChatGPT。或者利用OpenAI的Function Calling功能让ChatGPT的回复结构化直接解析成控制指令如“打开客厅灯”然后ESP32执行对应的GPIO操作。离线唤醒词为了省电和随时响应可以增加一个离线语音唤醒模块如LD3320或更先进的AI芯片只有听到“小爱同学”这样的唤醒词后才启动完整的录音、识别、对话流程。集成Home Assistant将ESP32设备接入Home Assistant通过ChatGPT理解的自然语言指令来控制家中的其他智能设备打造一个真正懂你的语音控制中心。最后成本控制是一个现实问题。OpenAI API是按Token收费的虽然单次对话花费极低几分钱甚至更少但长期不间断使用仍会产生费用。在项目规划时可以设置每天或每月的使用上限或者探索使用一些开源或免费的本地大模型虽然ESP32跑不动但可以通过局域网内的树莓派等更强设备来部署ESP32作为客户端。