嘉立创产业服务站群
发作品签到
专业版

基于ESP32的桌面助手

工程标签

402
0
0
0

简介

1、实现chatGPT语音对话。 2、实现获取电脑运行状态 3、实现蓝牙键盘 4、实现温湿度检测 5、实现未来三天的天气监测 6、实现APP的快捷启动

简介:1、实现chatGPT语音对话。 2、实现获取电脑运行状态 3、实现蓝牙键盘 4、实现温湿度检测 5、实现未来三天的天气监测 6、实现APP的快捷启动
基于ESP32物联网征集令

开源协议

GPL 3.0

(未经作者授权,禁止转载)
创建时间:2024-08-12 11:07:50更新时间:2024-09-18 01:25:19

描述

视频链接:

B站视频--功能演示及介绍

项目简介

本项目旨在开发一款集ChatGPT语音助手、蓝牙键盘功能、应用快速启动、电脑性能监控、天气时间信息显示及音乐播放于一体的多功能桌面助手,利用ESP32微控制器作为核心控制单元,结合多种传感器和外围设备,为用户提供便捷、智能的桌面交互体验。

项目功能

1.ChatGPT语音助手
设计语音识别算法,将用户语音转换为文本指令。通过ESP32的Wi-Fi功能,将文本指令发送至ChatGPT服务器,获取响应结果。将响应结果转换为语音输出,或通过显示屏展示给用户。
2.蓝牙键盘功能
编写蓝牙配对与连接程序,使桌面助手能够与电脑建立蓝牙连接。设计键盘映射逻辑,将桌面助手的按键操作转换为电脑可识别的键盘输入。支持自定义快捷键,实现应用快速启动等功能。
3.应用快速启动
编写应用管理程序,识别并管理电脑中常用的应用程序。设计用户界面,允许用户通过桌面助手快速启动或切换应用程序。
4.电脑性能监控
通过USB接口与电脑通信,获取CPU、内存、磁盘等系统性能数据。在显示屏上实时展示性能数据,提供直观的性能监控界面。
5.天气时间信息显示
集成天气预报API,定期获取并展示当前及未来几天的天气信息。设计时钟显示界面,实时更新并显示当前时间。

项目参数

采用ESP32S3 作为主控。
MAX9814作为语音输入
MAX98357 I2S作为语音输出
GY-SHT30-D作为温湿度检测

原理解析(硬件说明)

第一版:采用陶晶驰的串口电容屏。感觉很灵敏。后面发现界面UI做多了。屏幕自带内存已经存储不下了。导致很多想做的东西做不出了,只能放弃。
第二版:采用2.8寸电阻屏,做完UI后感觉电阻屏非常卡。。。。。。还是达不到想要的效果。
第三版:采用2.8寸电容屏幕。

软件代码

使用vscode+platfromio+lvgl+Squareline studio+usart HIM来开发。

1、通过使用火山大模型的豆包来实现GPT问答。

#include 

String apiUrl = "https://ark.cn-beijing.volces.com/api/v3/chat/completions";
const char *doubao_apiKey = "c0c9a9df-c65b-4e8f-a5dd-65e4c501ea49";

String getGPTAnswer(String inputText)
{
  HTTPClient http;
  http.setTimeout(20000);
  http.begin(apiUrl);
  http.addHeader("Content-Type", "application/json");
  String token_key = "Bearer " + String(doubao_apiKey);
  http.addHeader("Authorization", token_key);

  String payload = "{\"model\":\"ep-20240901212403-z677d\",\"messages\":[{\"role\":\"system\",\"content\":\"你是我的AI助手vor,你必须用中文回答且字数不超过85个\"},{\"role\":\"user\",\"content\":\"" + inputText + "\"}],\"temperature\": 0.3}";
  int httpResponseCode = http.POST(payload);

  if (httpResponseCode != 200)
  {
    Serial0.println("HTTP Request Failed");
    http.end();
    return "";
  }

  String reason = http.getString();
  http.end();
  DynamicJsonDocument jsonDoc1(1024);
  deserializeJson(jsonDoc1, reason);
  return jsonDoc1["choices"][0]["message"]["content"];
}

2、通过MinMax来实现语音播报。

#include 

const char *url = "https://api.minimax.chat/v1/t2a_pro?GroupId=1828638795172417679";
const char *apiKey = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJHcm91cE5hbWUiOiLlrabnlJ8iLCJVc2VyTmFtZSI6IuWtpueUnyIsIkFjY291bnQiOiIiLCJTdWJqZWN0SUQiOiIxODI4NjM4Nzk1MTgwODA2Mjg3IiwiUGhvbmUiOiIxOTE4ODM3MzYwMyIsIkdyb3VwSUQiOiIxODI4NjM4Nzk1MTcyNDE3Njc5IiwiUGFnZU5hbWUiOiIiLCJNYWlsIjoiIiwiQ3JlYXRlVGltZSI6IjIwMjQtMDktMDMgMTc6MDg6MjAiLCJpc3MiOiJtaW5pbWF4In0.SnCyHWctvTXvFISy04BvT1-EYqflqaeXuF1reK-U0ZMpomQ6Nc4F_KwsbgXDtvxx3rQWlGGeC_V4ykp-kr9oYLhX5blMuRRxYIaR0G_UxJ0ftC1bEAimX4-niwp9BmsfNeBNbCPf13NOcOt_Mm7mLBDPbva7wXhK4Fw74yII_3mmpkVYM2M_aO0uVdEBhDn75MxkjonTK3tHiN3UIaDZoW0x87ASSZcDeX2zkruTmjmXPItXFSGzBHJFLN7zJbu2PAvt4PUC1IGtxqfkijVu8-CyG-iuH3yLES3ayPjcZoSCVXmrYtSFiZKhQWRjvY3QWD31NCQZkdtfY0dO2jnX1Q"; // Replace with your actual API key

/**
 * @brief 获取TTS答案
 *
 * 发送HTTP POST请求到指定的URL,使用给定的输入文本获取TTS答案。
 *
 * @param inputText 输入文本
 *
 * @return 返回TTS答案的音频文件链接
 */
String getTTSAnswer(String inputText)
{
  HTTPClient http1;
  http1.setTimeout(10000);
  http1.begin(url);
  http1.addHeader("Content-Type", "application/json");
  String token_key = "Bearer " + String(apiKey);
  http1.addHeader("Authorization", token_key);

  StaticJsonDocument<1000> doc;
  doc["text"] = inputText;
  doc["model"] = "speech-01";
  doc["audio_sample_rate"] = 32000;
  doc["bitrate"] = 128000;
  doc["voice_id"] = "presenter_male";

  String jsonString;
  serializeJson(doc, jsonString);
  int httpResponseCode1 = http1.POST(jsonString);

  if (httpResponseCode1 != 200)
  {
    Serial0.println("HTTP Request Failed");
    http1.end();
    return "";
  }

  String reason = http1.getString();
  http1.end();
  DynamicJsonDocument jsonDoc1(1024);
  deserializeJson(jsonDoc1, reason);
  return jsonDoc1["audio_file"];
}

3、通过百度智能云实现语音转文字。

HTTPClient http_client;
uint16_t adc_data[data_len];
uint8_t adc_start_flag = 0;
uint8_t adc_complete_flag = 0;
char data_json[45000];
String response, question, answer;
DynamicJsonDocument jsonDoc(1024);

uint32_t num = 0;
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;
hw_timer_t *timer = NULL;

void IRAM_ATTR onTimer()
{
  portENTER_CRITICAL_ISR(&timerMux);
  if (adc_start_flag == 1)
  {
    adc_data[num] = analogRead(ADC);
    num++;
    if (num >= data_len)
    {
      adc_complete_flag = 1;
      adc_start_flag = 0;
      num = 0;
    }
  }
  portEXIT_CRITICAL_ISR(&timerMux);
}

void setupRecorder()
{
  pinMode(ADC, ANALOG);
  pinMode(key, INPUT_PULLUP);

  timer = timerBegin(0, 80, true);
  timerAlarmWrite(timer, 125, true);
  timerAttachInterrupt(timer, &onTimer, true);
  timerAlarmEnable(timer);
  timerStop(timer);
}

void handleRecognition()
{
  if (digitalRead(key) == 0)
  {
    Serial0.printf("Start recognition\r\n\r\n");
    adc_start_flag = 1;
    timerStart(timer);
    while (!adc_complete_flag)
    {
      ets_delay_us(10);
    }
    timerStop(timer);
    adc_complete_flag = 0; // 清标志

    memset(data_json, '\0', strlen(data_json)); // 将数组清空
    strcat(data_json, "{");
    strcat(data_json, "\"format\":\"pcm\",");
    strcat(data_json, "\"rate\":8000,");                                                                         // 采样率    如果采样率改变了,记得修改该值,只有16000、8000两个固定采样率
    strcat(data_json, "\"dev_pid\":1537,");                                                                      // 中文普通话
    strcat(data_json, "\"channel\":1,");                                                                         // 单声道
    strcat(data_json, "\"cuid\":\"666666\",");                                                                   // 识别码    随便打几个字符,但最好唯一
    strcat(data_json, "\"token\":\"24.6030159d191427253c659ecc913283ae.2592000.1728034958.282335-114728200\","); // token	这里需要修改成自己申请到的token
    strcat(data_json, "\"len\":32000,");                                                                         // 数据长度  如果传输的数据长度改变了,记得修改该值,该值是ADC采集的数据字节数,不是base64编码后的长度
    strcat(data_json, "\"speech\":\"");
    strcat(data_json, base64::encode((uint8_t *)adc_data, sizeof(adc_data)).c_str()); // base64编码数据
    strcat(data_json, "\"");
    strcat(data_json, "}");
    int httpCode;
    http_client.setTimeout(5000);
    http_client.begin("http://vop.baidu.com/server_api"); // https://vop.baidu.com/pro_api
    http_client.addHeader("Content-Type", "application/json");
    httpCode = http_client.POST(data_json);

    if (httpCode == 200)
    {
      if (httpCode == HTTP_CODE_OK)
      {
        response = http_client.getString();
        http_client.end();
        deserializeJson(jsonDoc, response);
        String question = jsonDoc["result"][0];
        Serial0.println("\n Input:" + question);
        answer = getGPTAnswer(question);
        Serial0.println("Answer: " + answer);
        String len = getTTSAnswer(answer);
        set_voice(len);
      }
      else
      {
        Serial0.printf("[HTTP] GET... failed, error: %s\n", http_client.errorToString(httpCode).c_str());
      }
    }
    Serial0.printf("Recognition complete\r\n");
  }
  vTaskDelay(1);
}

4、通过调用和风天气的API实现未来三天的天气监测。


const char *privateKey = "SWx9ptkZnxlmmy3V5";
const char *city = "百色";
const char *language = "zh-Hans";
HTTPClient client1;

void fetchWeatherData()
{
  String getUrl = "/v3/weather/daily.json?key=" + String(privateKey) + "&location=" + String(city) + "&language=" + String(language);
  client1.begin("http://api.seniverse.com" + getUrl);
  int httpResponseCode = client1.GET();

  if (httpResponseCode > 0)
  {
    String payload = client1.getString();
    DynamicJsonDocument doc(1400);
    deserializeJson(doc, payload);

    WeatherData weatherdata;
    strcpy(weatherdata.city, doc["results"][0]["location"]["name"].as());
    strcpy(weatherdata.weather, doc["results"][0]["daily"][0]["text_day"].as());
    strcpy(weatherdata.high, doc["results"][0]["daily"][0]["high"].as());
    strcpy(weatherdata.low, doc["results"][0]["daily"][0]["low"].as());
    strcpy(weatherdata.humi, doc["results"][0]["daily"][0]["humidity"].as());

    Serial0.println("City: " + String(weatherdata.city));
    Serial0.println("Weather: " + String(weatherdata.weather));
    Serial0.println("High: " + String(weatherdata.high));
    Serial0.println("Low: " + String(weatherdata.low));
    Serial0.println("Humidity: " + String(weatherdata.humi));
  }
  else
  {
    Serial0.println("Error fetching weather data");
  }
  client1.end();
}

5、通过AIDA64来实现获取电脑的运行状态和时间。

  client.print(String("GET ") + getUrl + " HTTP/1.1\r\n" + "Content-Type=application/json;charset=utf-8\r\n" + "Host: " + host + "\r\n" + "User-Agent=ESP32\r\n" + "Connection: close\r\n\r\n");
  Serial0.println("Get send");
  char endOfHeaders[] = "\n\n";
  bool ok = client.find(endOfHeaders);
  if (!ok)
  {
    Serial0.println("No response or invalid response!");
  }
  Serial0.println("Skip headers");

  String line = "";

  line += client.readStringUntil('\n');

  Serial0.println("Content:");
  Serial0.println(line);

  int16_t dataStart = 0;
  int16_t dataEnd = 0;
  int16_t cpu_frequence;
  String dataStr;

  char cpuFreq[] = "CPU fequence";
  dataStart = line.indexOf(cpuFreq) + strlen(cpuFreq);
  dataEnd = line.indexOf("MHz", dataStart);
  dataStr = line.substring(dataStart, dataEnd);
  cpu_frequence = dataStr.toInt();
  Serial.print("CPU usage :");
  Serial.println(cpu_frequence);

6、通过陶晶驰串口屏显示信息。

注意事项

1、一定要选好屏幕不然刷新率太低了。我建议使用ISP电容触控屏。或者8080并口屏
2、一定选用合适的喇叭不然声音会很小。
3、采用AIDA64来实现获取电脑的运行状态是把自己电脑的网址设为固定就不用每次重新设置了。
4、ESP32移植lvgl时无限重启。把ESP32的栈区调大。
5、使用lvgl做UI的时候注意文件大小,最好自己加一个SD卡
6、在使用陶晶驰串口屏时采用X系列的。
7、注意ESP32同时启用蓝牙和WIFI的时候步骤。
8、给电路一个稳定的电源。

组装流程

后补

实物图

2D83A9F35F56E21680CD287ACD263F6A.jpg

030C788BE43037608C15D4FB84AD5FBF.jpg

70BA7A78AA3EDABDD391CA447753DA7D.jpg

72539C6A833254ED2898A65C0119EE6C.jpg

Snipaste_2024-09-15_19-41-08.jpg

设计图

未生成预览图,请在编辑器重新保存一次

BOM

暂无BOM

附件

序号文件名称下载次数
1
UI2.pptx
14
2
20240818_1.HMI
27
3
870e6e38cd1d3368df83d4c53b26c7d0.mp4
4
4
c4ed896d81deb3920c9bce94d069f447.mp4
4
5
ESP32test01.zip
53
克隆工程
添加到专辑
0
0
分享
侵权投诉

工程成员

评论

全部评论(1)
按时间排序|按热度排序
粉丝0|获赞0
相关工程
暂无相关工程

底部导航