高校生の個人ブログ

[趣味] ESP32 スマート噴水冷却システム

プロジェクトのハイライト

  • [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サーバーを起動し、スマホやPCで接続するとリアルタイム温度と給水状態が確認でき、ボタンで手動制御または自動化を有効にできます。

主な機能:

  • ルートパスにアクセス / 制御ページを返す
  • /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);
    }
}