【ESP32】【LLM API】LLM Control ESP32 and GPIO

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

// WiFi配置
const char* ssid = "6xxx";
const char* password = "12345678";

// API配置
const String apiUrl = "https://spark-api-open.xf-yun.com/v1/chat/completions";
const String apiPassword = "xxxxxpsrxk";
const String appId = "6cxxf";
String userId = "exxxp";

// GPIO配置
const int D2_PIN = 2;  // ESP32的D2引脚对应GPIO2

// 内存优化配置
const size_t MAX_RESPONSE_SIZE = 150000;   // 最大响应长度
const size_t JSON_BUFFER_SIZE = 225000;    // JSON缓冲区大小
const size_t LINE_LENGTH = 200;            // 每行显示字符数

bool isProcessing = false;                 // 请求标记 正在发送请求为true 请求结束为flase
bool promptDisplayed = false;              // 输入提示标记 正在输入true,输入结束为false

// 函数前向声明
void handleSerialInput();
void processRequest(String &input);
void addMessage(JsonArray &arr, const char* role, const char* content);
void handleResponse(HTTPClient &http);
void processResponseBuffer(String &buffer, bool &isComplete);
void extractContent(JsonDocument &doc);
String cleanResponse(String text);
void checkForGPIOCommands(String &text);

void setup() {
  Serial.begin(115200);
  
  // 初始化D2引脚
  pinMode(D2_PIN, OUTPUT);
  digitalWrite(D2_PIN, LOW);  // 默认设为低电平
  
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("\nWiFi connected");
  Serial.println("请输入您的问题(以回车结束):"); // 初始提示
}

void loop() {
  handleSerialInput();
}

// 串口输入处理(优化输入提示)
void handleSerialInput() {
  static String inputBuffer;
  
  while (Serial.available()) {
    char c = Serial.read();
    
    // 显示输入提示
    if (!promptDisplayed && inputBuffer.length() == 0) {
      Serial.print("\n你的提问为:");
      promptDisplayed = true;
    }

    // 同时检查CR和NL作为有效的行结束符
    if (c == '\n' || c == '\r') {
      if (!isProcessing && inputBuffer.length() > 0) {
        Serial.println(); // 结束输入行
        processRequest(inputBuffer); //调用HTTP请求函数 并携带提问的问题
        inputBuffer = ""; //提问完成后将,缓存清空。
        promptDisplayed = false;//输入结束标记为false
      }
    } else {
      inputBuffer += c;
      Serial.print(c);    // 回显输入字符
    }
  }
}

// HTTP请求处理(增加超时设置)
void processRequest(String &input) {
  isProcessing = true;

  JsonDocument doc;
  doc["model"] = "4.0Ultra";
  doc["user"] = userId;

  JsonArray messages = doc.createNestedArray("messages");
  // 简化system提示,仅包含高低电平控制
  addMessage(messages, "system", "你是知识渊博的助理,也能控制硬件。如果用户要求控制D2引脚,请明确在回复中包含以下指令词之一:'D2引脚高电平'或'D2引脚低电平'。");
  addMessage(messages, "user", input.c_str());

  HTTPClient http;//创建HTTP客户端
  http.begin(apiUrl);//配置发送HTTP请求的目标apiUrl
  http.addHeader("Content-Type", "application/json");//设置HTTP 请求头 的方法 告诉服务器,客户端发送的请求体(payload)是 JSON 格式的数据。
  http.addHeader("Authorization", "Bearer " + apiPassword);//向服务器提供身份认证凭证
  
  // 增加超时设置(60秒)
  http.setTimeout(60000); 
  
  String payload;
  serializeJson(doc, payload);//序列化为JSON格式的字符串,并将结果存储在`payload`中

  int httpCode = http.POST(payload);//客户端POST方法用于向服务器发送请求 并payload作为请求体,
  //返回值httpCode是服务器返回的HTTP状态码,比如200表示成功,404表示未找到等。
  if (httpCode > 0) {
    handleResponse(http);
  } else {
    Serial.print("HTTP Error: ");
    Serial.println(httpCode);
    if(httpCode == -11){
      Serial.println("可能原因:");
      Serial.println("1. 网络连接不稳定");
      Serial.println("2. 响应数据过大(建议缩短请求内容)");
      Serial.println("3. 服务器响应超时");
    }
  }

  http.end();
  isProcessing = false;
}

// 添加消息到JSON数组
void addMessage(JsonArray &arr, const char* role, const char* content) {
  JsonObject obj = arr.createNestedObject();
  obj["role"] = role;
  obj["content"] = content;
}

// 响应处理(优化错误处理)
void handleResponse(HTTPClient &http) {
  WiFiClient *stream = http.getStreamPtr();
  String responseBuffer;//用于存储 从 HTTP 响应流中逐步读取的原始数据片段
  unsigned long timeout = millis() + 30000;  // 延长超时到30秒
  bool isComplete = false;//用于标记是否超时 未超时为 false,超时为true

  while (!isComplete && (millis() < timeout)) {
    if (stream->available()) {
      char c = stream->read();
      responseBuffer += c;

      // 实时处理流数据
      if (responseBuffer.length() >= 512 || !stream->available()) {
        processResponseBuffer(responseBuffer, isComplete);
      }
    }
    delay(10);
  }

  // 处理剩余数据
  if (!isComplete && responseBuffer.length() > 0) {
    processResponseBuffer(responseBuffer, isComplete);
    if (!isComplete) {
      Serial.println("\n[警告] 响应数据不完整");
    }
  }
}

// 处理响应缓冲区
void processResponseBuffer(String &buffer, bool &isComplete) {
  JsonDocument doc;
  DeserializationError error = deserializeJson(doc, buffer);

  if (!error) {
    extractContent(doc);// 提取内容
    isComplete = doc["is_end"] | false; // 判断是否结束
    buffer = ""; // 清空已处理数据
  } 
  else if (error.code() != DeserializationError::IncompleteInput) {
    // 过滤EmptyInput错误
    if(error.code() != DeserializationError::EmptyInput){
      Serial.print("JSON解析错误: ");
      Serial.println(error.c_str());
    }
    buffer = "";
  }
}

// 内容提取(简化版)
void extractContent(JsonDocument &doc) {
  static String fullResponse;

  if (doc.containsKey("choices")) {
    const char* content = doc["choices"][0]["message"]["content"];
    if (content) {
      String cleaned = cleanResponse(content);
      fullResponse += cleaned;
      
      // 分页显示
      while (fullResponse.length() > LINE_LENGTH) {
        Serial.println(fullResponse.substring(0, LINE_LENGTH));
        fullResponse = fullResponse.substring(LINE_LENGTH);
      }
      Serial.print(fullResponse); // 显示剩余部分
      
      // 检查是否包含GPIO控制命令
      checkForGPIOCommands(fullResponse);
    }
  }

  if (doc["is_end"] == true) {
    // 响应结束时再次检查完整回复
    checkForGPIOCommands(fullResponse);
    Serial.println("\n[END OF RESPONSE]");
    fullResponse = "";
  }
}

// 响应清洗(优化中文处理)
String cleanResponse(String text) {
  // 保留中文标点
  text.replace("。", "。");
  text.replace(",", ",");
  
  // 处理其他字符
  text.replace("\r\n", " ");
  text.replace('\n', ' ');
  text.trim();

  // 合并空格
  int newLength = text.length();
  do {
    newLength = text.length();
    text.replace("  ", " ");
  } while (newLength != text.length());

  return text;
}

// 检查GPIO控制指令(简化版,只有高电平和低电平)
void checkForGPIOCommands(String &text) {
  // 检查高电平指令
  if (text.indexOf("D2引脚高电平") >= 0 || 
      (text.indexOf("D2") >= 0 && 
       (text.indexOf("通高电平") >= 0 || text.indexOf("设为高") >= 0 || 
        text.indexOf("打开") >= 0 || text.indexOf("HIGH") >= 0 || 
        text.indexOf("点亮") >= 0 || text.indexOf("通电") >= 0))) {
    
    digitalWrite(D2_PIN, HIGH);
    Serial.println("\n[系统] D2引脚已设置为高电平");
  }
  
  // 检查低电平指令
  else if (text.indexOf("D2引脚低电平") >= 0 || 
           (text.indexOf("D2") >= 0 && 
            (text.indexOf("通低电平") >= 0 || text.indexOf("设为低") >= 0 || 
             text.indexOf("关闭") >= 0 || text.indexOf("LOW") >= 0 || 
             text.indexOf("熄灭") >= 0 || text.indexOf("断电") >= 0))) {
    
    digitalWrite(D2_PIN, LOW);
    Serial.println("\n[系统] D2引脚已设置为低电平");
  }
}
### ESP32与大型语言模型的应用 ESP32作为一种资源受限的微控制器,在处理复杂的计算密集型任务如大型语言模型(LLM)方面面临挑战。然而,通过特定的技术手段可以实现在ESP32上部署简化版的语言模型或利用边缘智能技术来增强其能力。 #### 边缘智能与设备协同推理 为了克服硬件限制并充分利用现有资源,采用基于DNN划分和适当规模调整的方法能够有效支持像ESP32这样的小型嵌入式系统的智能化需求[^2]。这种方法允许将部分计算卸载到更强大的云端服务器或其他边缘节点执行,从而减轻本地处理器负担的同时保持实时响应性能。 #### 动态KV缓存管理优化 对于在ESP32上的实际应用而言,引入高效的内存管理和调度策略至关重要。InfiniGen提出的动态KV缓存管理系统提供了一种可能方案用于提高生成推断效率,尤其是在面对较大尺寸的语言模型时可以通过减少不必要的重复运算达到节省存储空间的目的[^1]。 #### 实现案例分析 尽管直接运行完整的大型预训练模型存在困难,但仍可通过量化、剪枝等压缩方法获得适合于低功耗平台的小型化版本。例如: - **关键词识别**:构建轻量级神经网络以检测语音命令中的特定词语; - **简单对话交互**:运用精简后的Transformer架构完成基础问答功能; - **环境感知服务**:结合传感器数据输入预测天气变化趋势或空气质量状况; ```cpp // 示例代码片段展示如何初始化WiFi连接以便后续向远程API发送请求获取LMM结果 #include <WiFi.h> const char* ssid = "your_SSID"; const char* password = "your_PASSWORD"; void setup() { Serial.begin(115200); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(1000); Serial.println("Connecting to WiFi..."); } Serial.println("Connected!"); } void loop() {} ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hmywillstronger

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值