项目亮点

  • [x] 网页操控:访问设备 IP 即可实时查看温度与自动化状态
  • [x] 手动/自动双模式:通过网页随时开关浇水(手动模式会临时退出自动化,避免浪费水)
  • [x] 智能温控:自动模式下,温度 ≥30℃ 自动开水,<30℃ 自动停水
  • [ ] 网页暂未添加 CSS,仅做功能演示,可根据需要自行美化

起因:空调罢工,问题出在外机

2024 年湖北的夏天格外难熬,家里空调制冷效果奇差,基本形同虚设。
一番折腾后,通过 控制变量法 逐项排查,最终锁定真凶——外机背板散热不畅(被柜板挡住,热量排不出去)。

临时想到的解决方案简单粗暴:用水泵朝外机喷水,强制降温

继电器

TB 采购水泵、继电器等模块,接通电源,能跑起来,第一步成功!


新问题:一直喷水太浪费了

手动开关不仅麻烦,人不在家时要么干烧要么浪费水。
我开始思考如何让系统根据温度自动启停

忽然想起去年做火箭姿态矫正时留下的 MPU6050 六轴传感器,它内部集成了一颗 NTC 温度模块。
虽然原本是用来检测芯片温度的,但 MPU6050 自身发热极小,完全可以近似看作环境温度。
于是把它从火箭上“拆借”过来,搭配 ESP32-C3 实现温控。


硬件接线

MPU6050 与 ESP32-C3 的接线非常简单(左:MPU6050,右:ESP32-C3):

MPU6050ESP32-C3
VCC3V3
GNDGND
SDAP8 (SDA)
SCLP9 (SCL)

硬件接线


软件设计

开发环境:Arduino IDE
核心思路:ESP32-C3 启动一个轻量 Web 服务器,手机或电脑连上后即可看到实时温度与浇水状态,并可通过按钮手动控制或开启自动化。

主要功能:

  • 访问根路径 / 返回控制页面
  • /dht 接口返回传感器数据(实时温度、浇水状态、自动状态)
  • /set 接口切换浇水开关(同时关闭自动模式)
  • /auto 接口切换自动模式
  • 自动化逻辑:温度 ≥30℃ 开水,<30℃ 停水

以下为完整代码:

#include <Adafruit_MPU6050.h>
#include <Adafruit_Sensor.h>
#include <WiFi.h>
#include <ESPAsyncWebServer.h>

AsyncWebServer server(80);   // 端口80,可直接通过IP访问

// 网页 HTML(存储于 Flash)
const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML>
<html>
<head>
    <meta charset="utf-8">
    <title>空调浇水控制</title>
</head>
<body>
    <h2>空调浇水控制</h2>
    <div id="dht"></div>
    <button onclick="set()">开启/关闭浇水</button>
    <button onclick="autoset()">开启/关闭自动</button>
</body>
<script>
    function set() {
        var xhr = new XMLHttpRequest();
        xhr.open("GET", "/set?value=ESP32", true);
        xhr.send();
    }
    function autoset() {
        var xhr = new XMLHttpRequest();
        xhr.open("GET", "/auto?value=ESP32", true);
        xhr.send();
    }
    // 每秒更新一次数据
    setInterval(function () {
        var xhttp = new XMLHttpRequest();
        xhttp.onreadystatechange = function () {
            if (this.readyState == 4 && this.status == 200) {
                document.getElementById("dht").innerHTML = this.responseText;
            }
        };
        xhttp.open("GET", "/dht", true);
        xhttp.send();
    }, 1000);
</script>
</html>
)rawliteral";

Adafruit_MPU6050 mpu;
#define MPU6050_INTERVAL 100       // 传感器读取间隔(毫秒)
unsigned long mpu6050Times = 0;
float mpu6050Temp = 0;

float xAcceleration, yAcceleration, zAcceleration;
float xAccele, yAccele, zAccele;
float xGyro = 0, yGyro = 0, zGyro = 0;
float gravity = 9.8;

bool iswater = false;  // 浇水状态
bool isauto = true;    // 自动模式默认开启

// 构造返回给网页的 HTML 数据
String Merge_Data(void) {
    String dataBuffer = "<p>";
    dataBuffer += "<h1>传感器数据</h1>";
    dataBuffer += "<b>温度: </b>";
    dataBuffer += String(mpu6050Temp, 1) + " ℃";
    dataBuffer += "<br />";
    dataBuffer += "<b>当前浇水状态: </b>";
    dataBuffer += iswater ? "开启" : "关闭";
    dataBuffer += "<br />";
    dataBuffer += "<b>当前自动状态: </b>";
    dataBuffer += isauto ? "开启" : "关闭";
    dataBuffer += "<br /></p>";
    return dataBuffer;
}

// 手动切换浇水(同时关闭自动模式)
void Config_Callback(AsyncWebServerRequest *request) {
    iswater = !iswater;
    isauto = false;
    request->send(200, "text/plain", "OK");
}

// 切换自动模式
void Auto_Callback(AsyncWebServerRequest *request) {
    isauto = !isauto;
    request->send(200, "text/plain", "OK");
}

void setup() {
    Serial.begin(115200);

    // 连接 WiFi(请替换为实际 SSID 和密码)
    WiFi.begin("你的WiFi名", "你的WiFi密码");
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.println("正在连接WiFi...");
    }
    Serial.println("WiFi 连接成功!");
    Serial.print("IP 地址: ");
    Serial.println(WiFi.localIP());

    pinMode(1, OUTPUT);   // GPIO1 控制继电器(水泵)

    // 配置 Web 服务器路由
    server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
        request->send_P(200, "text/html", index_html);
    });
    server.on("/dht", HTTP_GET, [](AsyncWebServerRequest *request) {
        request->send_P(200, "text/plain", Merge_Data().c_str());
    });
    server.on("/set", HTTP_GET, Config_Callback);
    server.on("/auto", HTTP_GET, Auto_Callback);
    server.begin();
    Serial.println("HTTP 服务器已启动");

    // 初始化 MPU6050
    if (!mpu.begin()) {
        Serial.println("未找到 MPU6050 芯片!");
        while (1) { delay(1000); }  // 停止运行
    }
    mpu.setAccelerometerRange(MPU6050_RANGE_16_G);
    mpu.setGyroRange(MPU6050_RANGE_250_DEG);
    mpu.setFilterBandwidth(MPU6050_BAND_21_HZ);
    Serial.println("MPU6050 初始化成功!");
}

void loop() {
    getMpu6050Data();   // 读取温度及姿态数据

    if (isauto) {
        // 自动模式:根据温度控制水泵
        if (mpu6050Temp >= 30.0) {
            digitalWrite(1, HIGH);
            iswater = true;
        } else {
            digitalWrite(1, LOW);
            iswater = false;
        }
    } else {
        // 手动模式:直接遵从按钮状态
        digitalWrite(1, iswater ? HIGH : LOW);
    }
}

void getMpu6050Data() {
    if (millis() - mpu6050Times >= MPU6050_INTERVAL) {
        mpu6050Times = millis();

        sensors_event_t a, g, temp;
        mpu.getEvent(&a, &g, &temp);

        mpu6050Temp = temp.temperature;

        xAcceleration = a.acceleration.x;
        yAcceleration = a.acceleration.y;
        zAcceleration = a.acceleration.z;

        xAccele = xAcceleration / gravity;   // 转换为 g 为单位
        yAccele = yAcceleration / gravity;
        zAccele = zAcceleration / gravity;

        xGyro = g.gyro.x;
        yGyro = g.gyro.y;
        zGyro = g.gyro.z;

        // 串口输出,便于调试(保留了姿态数据,方便后续扩展)
        Serial.print("温度: "); Serial.print(mpu6050Temp);
        Serial.print(" , x加速: "); Serial.print(xAccele);
        Serial.print(" , y加速: "); Serial.print(yAccele);
        Serial.print(" , z加速: "); Serial.print(zAccele);
        Serial.print(" , x角速度: "); Serial.print(xGyro);
        Serial.print(" , y角速度: "); Serial.print(yGyro);
        Serial.print(" , z角速度: "); Serial.println(zGyro);
    }
}

最终效果