回到首页 返回首页
回到顶部 回到顶部
返回上一页 返回上一页

小智语音获取ESP32 S3 DHT11/22数据及控制小风扇|极简MCP插件教程 简单

头像 rzyzzxw 2025.08.16 26 0

8.19

 

【项目目标】

通过小智MCP服务远程获取ESP32 S3外接DHT11/22模块测得温湿度数据,并通过继电器控制小风扇改善湿度,从而完成一个微型的环境监测模型。

a597453fbad21f262f8babd852feaf6.jpg
80148e788a0567d9ffeed8eb14119e6.jpg

材料清单

  • DHT22/DHT11 X1
  • I2C128X64 1036驱动 LED屏 X1
  • ESP32 S3开发板 X1
  • 3V继电器 X1

步骤1 相关库安装及DHT11/22数据显示到OLED屏测试

温湿度模块我选用的是DFrobot DHT11/22模块。

1、搜索 ​​DHT sensor library​​ → 选择 ​​Adafruit​​ 维护的版本(当前最新为1.4.4+)库依赖 ​​Adafruit Unified Sensor Library​​(需同步安装)。

image.png

本测试项目中DHT模块接线。

image.png

(DF DHT11/22模块上有上拉电阻)

2、安装库:U8g2库,用于支持在SSD1036驱动I2C OLED屏上显示数据。

image.png

3、0.96 128X64 SSD1036驱动屏接线

c5957e8966ed252aad31b3892e8c081.jpg
image.png

4、代码测试读取温湿度数据并显示在OLED屏。

代码
#include <DHT.h>
#include <U8g2lib.h>
#include <Arduino.h>
#include <FreeRTOS.h>

// 硬件配置
#define DHTPIN 4           // DHT11数据引脚(GPIO4)
#define DHTTYPE DHT22
#define SCREEN_SDA 8       // OLED SDA (GPIO8)
#define SCREEN_SCL 9       // OLED SCL (GPIO9)

// 全局变量
volatile float temperature = NAN;
volatile float humidity = NAN;

// OLED对象 (软件I2C)
U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2(
  U8G2_R0, 
  SCREEN_SCL, 
  SCREEN_SDA, 
  U8X8_PIN_NONE
);

DHT dht(DHTPIN, DHTTYPE);  // 传感器对象

//==================== OLED显示任务 ====================//
void displayTask(void *pvParameters) {
  for(;;) {
    u8g2.clearBuffer();
    u8g2.setFont(u8g2_font_wqy14_t_gb2312); // 使用14像素字体
    
    // 标题行(顶部居中)
    u8g2.drawUTF8((128 - u8g2.getUTF8Width("环境监测"))/2, 12, "环境监测");
    
    // 温度显示(左对齐)
    u8g2.drawUTF8(0, 32, "温度:");
    u8g2.setCursor(40, 32);
    if(!isnan(temperature)) u8g2.print(temperature, 1);
    else u8g2.print("--.-");
    u8g2.drawUTF8(70, 32, "°C");
    
    // 湿度显示(右对齐)
    u8g2.drawUTF8(0, 52, "湿度:");
    u8g2.setCursor(40, 52);
    if(!isnan(humidity)) u8g2.print(humidity, 1);
    else u8g2.print("--.-");
    u8g2.drawUTF8(70, 52, "%");
    
    u8g2.sendBuffer();
    vTaskDelay(1000 / portTICK_PERIOD_MS); // 1秒刷新
  }
}

//==================== 传感器任务 ====================//
void sensorTask(void *pvParameters) {
  for(;;) {
    float t = dht.readTemperature();
    float h = dht.readHumidity();
    
    if (!isnan(t) && !isnan(h)) {
      temperature = t;
      humidity = h;
    }
    vTaskDelay(3000 / portTICK_PERIOD_MS);  // 3秒读取
  }
}

//==================== 主设置函数 ====================//
void setup() {
  Serial.begin(115200);
  
  // 初始化硬件
  dht.begin();
  
  // OLED初始化
  u8g2.begin();
  u8g2.enableUTF8Print();
  u8g2.setFont(u8g2_font_wqy14_t_gb2312);
  u8g2.clearBuffer();
  
  // 居中显示启动信息
  u8g2.drawUTF8(
    (128 - u8g2.getUTF8Width("系统启动中..."))/2,
    35, 
    "系统启动中..."
  );
  
  u8g2.sendBuffer();
  delay(2000); // 显示2秒启动画面

  // 创建FreeRTOS任务
  xTaskCreatePinnedToCore(
    sensorTask, 
    "SensorTask", 
    2048,   // 优化内存占用
    NULL, 
    1, 
    NULL, 
    0
  );
  xTaskCreatePinnedToCore(
    displayTask, 
    "DisplayTask", 
    2048, 
    NULL, 
    1, 
    NULL, 
    0
  );
}

//==================== 主循环 ====================//
void loop() {
  // 空循环,所有功能在FreeRTOS任务中运行
  delay(1000);
}

【注】上面代码修改#define DHTTYPE DHT11为#define DHTTYPE DHT22,就可以使用DHT22模块。

4bfc88456782cfc24590ccc74a48760.jpg

步骤2 小智入场,语音控制

我们想实现小智语音控制指令​

数据查询:当前温度/当前湿度,DHT11模块接在IO4。

风扇控制:打开风扇/关闭风扇,风扇用接在IO6的继电器控制开关。

image.png

系统结构

image.png

基本功能

image.png

【注】

#define DHTTYPE DHT11换成#define DHTTYPE DHT22,就可以更换模块。

09760040641c31364306f3e8670b9c6.jpg

代码
#include <WiFi.h>
#include <WebSocketMCP.h>
#include <DHT.h>
#include <U8g2lib.h>
#include <ArduinoJson.h>
#include <FreeRTOS.h>

// 硬件配置
#define DHTPIN 4           // DHT11数据引脚(GPIO4)
#define DHTTYPE DHT11
#define RELAY_PIN 6        // 继电器控制风扇(GPIO6)
#define SCREEN_SDA 8       // OLED SDA (GPIO8)
#define SCREEN_SCL 9       // OLED SCL (GPIO9)

// 全局变量
volatile float temperature = NAN;
volatile float humidity = NAN;
volatile bool fanState = false;

// OLED对象 (软件I2C)
U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2(
  U8G2_R0, 
  SCREEN_SCL, 
  SCREEN_SDA, 
  U8X8_PIN_NONE
);

DHT dht(DHTPIN, DHTTYPE);  // 传感器对象

// 网络配置
// WiFi配置
const char* ssid = "your-ssid";
const char* password = "your-password";
const char* mcpEndpoint = "ws://your-mcp-server:port/path"; // 替换完整token
WebSocketMCP mcpClient;

//==================== 前置声明 ====================//
void onConnectionStatus(bool connected);
void registerMcpTools();
void displayTask(void *pvParameters);
void sensorTask(void *pvParameters);
WebSocketMCP::ToolResponse fanControlCallback(const String& args);
WebSocketMCP::ToolResponse sensorQueryCallback(const String& args);

//==================== OLED显示任务 ====================//
void displayTask(void *pvParameters) {
  for(;;) {
    u8g2.clearBuffer();
    u8g2.setFont(u8g2_font_wqy12_t_gb2312); // 改用12像素字体节省空间
    
    // 标题行(顶部居中)
    u8g2.drawUTF8((128 - u8g2.getUTF8Width("环境监测"))/2, 12, "环境监测");
    
    // 温度显示(左对齐)
    u8g2.drawUTF8(0, 28, "温度:");
    u8g2.setCursor(40, 28);
    if(!isnan(temperature)) u8g2.print(temperature, 1);
    else u8g2.print("--.-");
    u8g2.drawUTF8(70, 28, "°C");
    
    // 湿度显示(右对齐)
    u8g2.drawUTF8(0, 44, "湿度:");
    u8g2.setCursor(40, 44);
    if(!isnan(humidity)) u8g2.print(humidity, 1);
    else u8g2.print("--.-");
    u8g2.drawUTF8(70, 44, "%");
    
    // 风扇状态(底部居中)
    String fanText = "风扇:" + String(fanState ? "开启" : "关闭");
    u8g2.drawUTF8((128 - u8g2.getUTF8Width(fanText.c_str()))/2, 60, fanText.c_str());
    
    u8g2.sendBuffer();
    vTaskDelay(1000 / portTICK_PERIOD_MS);
  }
}

//==================== 传感器任务 ====================//
void sensorTask(void *pvParameters) {
  for(;;) {
    float t = dht.readTemperature();
    float h = dht.readHumidity();
    
    if (!isnan(t) && !isnan(h)) {
      temperature = t;
      humidity = h;
    }
    vTaskDelay(3000 / portTICK_PERIOD_MS);
  }
}

//==================== MCP工具回调 ====================//
WebSocketMCP::ToolResponse fanControlCallback(const String& args) {
  DynamicJsonDocument doc(256);
  deserializeJson(doc, args);
  String state = doc["state"].as<String>();
  
  if (state == "on") {
    digitalWrite(RELAY_PIN, HIGH);
    fanState = true;
    return WebSocketMCP::ToolResponse("{\"status\":\"FAN_ON\"}"); 
  } else {
    digitalWrite(RELAY_PIN, LOW);
    fanState = false;
    return WebSocketMCP::ToolResponse("{\"status\":\"FAN_OFF\"}"); 
  }
}

WebSocketMCP::ToolResponse sensorQueryCallback(const String& args) {
  String jsonResponse = "{\"temperature\":" + String(temperature) + 
                       ",\"humidity\":" + String(humidity) + "}";
  return WebSocketMCP::ToolResponse(jsonResponse);
}

//==================== 工具注册 ====================//
void registerMcpTools() {
  // 风扇控制工具
  mcpClient.registerTool(
    "fan_control", 
    "控制风扇开关",
    "{\"properties\":{\"state\":{\"enum\":[\"on\",\"off\"]}}}", 
    fanControlCallback
  );
  
  // 温湿度查询工具
  mcpClient.registerTool(
    "read_dht",
    "查询温湿度",
    "{}", 
    sensorQueryCallback
  );
}

//==================== 连接状态回调 ====================//
void onConnectionStatus(bool connected) {
  if (connected) {
    Serial.println("[MCP] 已连接");
    registerMcpTools();  // 关键!连接成功后注册工具
  } else {
    Serial.println("[MCP] 已断开");
  }
}

//==================== 主设置函数 ====================//
void setup() {
  Serial.begin(115200);
  
  // 硬件初始化
  pinMode(RELAY_PIN, OUTPUT);
  digitalWrite(RELAY_PIN, LOW);
  dht.begin();
  
  // OLED初始化
  u8g2.begin();
  u8g2.enableUTF8Print();
  u8g2.setFont(u8g2_font_wqy14_t_gb2312);
  u8g2.clearBuffer();
  u8g2.drawUTF8(20, 35, "系统启动中...");
  u8g2.sendBuffer();

  // WiFi连接(带进度提示)
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    u8g2.clearBuffer();
    u8g2.drawUTF8(0, 30, "WiFi连接中...");
    u8g2.sendBuffer();
    delay(500);
  }

  // MCP连接
  mcpClient.begin(mcpEndpoint, onConnectionStatus);
  
  // 创建FreeRTOS任务
  xTaskCreatePinnedToCore(
    sensorTask, 
    "SensorTask", 
    2048,   // 优化内存占用
    NULL, 
    1, 
    NULL, 
    0
  );
  xTaskCreatePinnedToCore(
    displayTask, 
    "DisplayTask", 
    2048, 
    NULL, 
    1, 
    NULL, 
    0
  );
}

//==================== 主循环 ====================//
void loop() {
  mcpClient.loop();  // 处理MCP通信
  delay(10);
}

上传,测试通过。

小智后台:

image.png

评论

user-avatar