# ESP32實作溫濕度智慧監測結合DHT11及LED開發 ### 成果展示 ![image](https://hackmd.io/_uploads/BJIKpI_Sye.png) ![image](https://hackmd.io/_uploads/BJJZofYr1l.png) ### 接線圖 ![ss (1) (1)](https://hackmd.io/_uploads/SkC2PHYBke.png) ### 網頁連結 [智慧監測數據網頁](http://iotsvm.shop/mqtt/iot.html) ### 使用材料 | 材料 | 數量 | | -------- | -------- | | ESP32 | 1 | | RGB LED | 2 | | DHT11 | 1 | | LCD1602 | 1 | | 麵包板 | 1 | ### 程式碼Arduino部分 ```csharp= #include <WiFi.h> // 引入Wifi #include <PubSubClient.h> // 引入 PubSubClient使用 MQTT #include <HTTPClient.h> #include <DHT.h> #include <ArduinoJson.h> #include <LiquidCrystal_I2C.h> #define DHT11PIN 16 LiquidCrystal_I2C lcd(0x27, 16, 2); DHT dht(DHT11PIN, DHT11); //red 15 //green 2 //blue 4 // WiFi 設定 const char* ssid = "KS_3F-3B_2.4G"; // WiFi SSID const char* password = "12345678900000"; // WiFi密碼 // MQTT 設定 const char* mqtt_server = "34.81.46.57"; // MQTT 位置 const int mqtt_port = 1883; // MQTT 埠號 const char* mqtt_user = "a79899569"; // MQTT帳號 const char* mqtt_pass = "a79899578"; // MQTT密碼 // const char* mqtt_topic = "test/temp"; // 主題 const char* serverName = "http://34.81.46.57/insert_data.php"; // 伺服器及插入資料庫php位置 WiFiClient espClient; // WiFi PubSubClient client(espClient); // MQTT // 連接至WiFi void setup_wifi() { delay(10); Serial.println(); Serial.print("Connecting to WiFi..."); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(1000); Serial.print("."); } Serial.println("Connected to WiFi"); Serial.print("IP Address: "); Serial.println(WiFi.localIP()); } //連接到MQTT void reconnect() { // 不斷嘗試連線 while (!client.connected()) { Serial.print("Connecting to MQTT..."); // 嘗試連接,傳入 ID、帳號和密碼 if (client.connect("ESP32Client", mqtt_user, mqtt_pass)) { Serial.println("Connected to MQTT"); } else { Serial.print("Failed, rc="); Serial.print(client.state()); Serial.println(" Trying again in 5 seconds..."); delay(5000); } } } const int redPin = 15; const int greenPin = 2; const int bluePin = 4; const int humi_redPin = 27; const int humi_greenPin = 26; const int humi_bluePin = 25; float humi; float temp; // 初始化 void setup() { Serial.begin(115200); dht.begin(); // lcd設置 lcd.init(); // LCD初始化 lcd.backlight(); // 開啟背光 lcd.clear(); // 清除先前畫面 pinMode(redPin, OUTPUT); pinMode(greenPin, OUTPUT); pinMode(bluePin, OUTPUT); pinMode(humi_redPin, OUTPUT); pinMode(humi_greenPin, OUTPUT); pinMode(humi_bluePin, OUTPUT); setup_wifi(); // 連接WiFi client.setServer(mqtt_server, mqtt_port); //連接至MQTT } void humi_temp() { humi = dht.readHumidity(); temp = dht.readTemperature(); Serial.print("-------------------------------------\n"); Serial.print("溫度:"); Serial.print(temp); Serial.print("濕度:"); Serial.println(humi); // lcd上顯示溫溼度 lcd.clear(); // 清除先前畫面 // 設定游標位置在第0列第0行 lcd.setCursor(0, 0); // 顯示相對濕度字串 lcd.print("RH: "); lcd.print(humi); lcd.print("%"); // 設定游標位置在第1列第0行 lcd.setCursor(0, 1); // 顯示溫度字串 lcd.print("Temp: "); lcd.print(temp); lcd.print(F("\xdf")); lcd.print("C"); } void countdown(int seconds) { for (int i = seconds; i >= 0; i--) { lcd.setCursor(15, 1); // LCD第1列第14行 lcd.print(" "); // 清除前一個數字 lcd.setCursor(14, 1); lcd.print(i); // 顯示倒數數字 delay(1000); } } void temp_color (unsigned char red, unsigned char green, unsigned char blue) { analogWrite(redPin, red); analogWrite(greenPin,green); analogWrite(bluePin, blue); } void humi_color (unsigned char red, unsigned char green, unsigned char blue) { analogWrite(humi_redPin, red); analogWrite(humi_greenPin,green); analogWrite(humi_bluePin, blue); } void loop() { humi_temp(); if (temp > 35) { Serial.print("溫度超過35,"); temp_color(255,0,0); } if (temp >= 20 && temp <35) { Serial.print("溫度超過20且小於35,"); temp_color(0,255,0); } if (temp < 20) { Serial.print("溫度低於20,"); temp_color(0,0,255); } if (humi > 70) { Serial.print("濕度超過70\n"); humi_color(255,0,0); } if (humi >= 60 && humi <70) { Serial.print("濕度超過60且小於70\n"); humi_color(0,255,0); } if (humi < 60) { Serial.print("低於60\n"); humi_color(0,0,255); } // 連接到MQTT伺服器 if (!client.connected()) { reconnect(); } // 啟動MQTT client.loop(); StaticJsonDocument<200> jsonDoc; //傳送MQTT JSON jsonDoc["temperature"] = temp; jsonDoc["humidity"] = humi; //String tempStr = String(temp, 2); // 保留2位小数 char buffer[256]; size_t n = serializeJson(jsonDoc, buffer); client.publish("test/temp", buffer, n); // 向伺服器傳送HTTP if (WiFi.status() == WL_CONNECTED) { HTTPClient http; String url = String(serverName) + "?temperature=" + String(temp) + "&humidity=" + String(humi); http.begin(url); // 指定的伺服器 int httpCode = http.GET(); // GET if (httpCode > 0) { // 檢查是否有錯 Serial.println("資料已傳送"); Serial.print("-------------------------------------\n"); } else { Serial.println("Error sending data"); } http.end(); // 結束 } else { Serial.println("WiFi not connected"); } countdown(10); // 每次偵測完後倒數10秒 } ``` ### 程式碼HTML、JS、CSS、MySQL部分 #### HTML ```html= <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous"> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css"> <script src="https://code.jquery.com/jquery-3.7.1.js" integrity="sha256-eKhayi8LEQwp4NKxN+CfCh+3qOVUtJn3QNZ0TciWLP4=" crossorigin="anonymous"></script> <script src="https://unpkg.com/mqtt/dist/mqtt.min.js"></script> <link rel="stylesheet" href="./style.css"> <script src="https://cdn.jsdelivr.net/npm/echarts@5.5.1/dist/echarts.min.js"></script> </head> <body> <nav class="navbar navbar-expand-lg bg-body-tertiary shadow"> <div class="container-fluid"> <a class="navbar-brand text-center mx-auto" href="#">智慧工廠</a> </div> </nav> <div class="container"> <div class="row"> <div class="col-12 col-md-12 mt-4"> <div class="card"> <div class="card-body"> <div class="row"> <div class="col-6"> <img class="d-flex mx-auto text-center mb-2" src="./webimg/thermometer-half.svg" style="width: 40px;height: 40px;"> <div class="thermometer bg-light mx-auto"> <div class="thermometer-bar" id="fill"></div> <div class="temperature-label" id="temperature">0°C</div> </div> </div> <div class="col-6"> <img class="d-flex mx-auto text-center mb-2" src="./webimg/droplet-half.svg" style="width: 40px;height: 40px;"> <div class="humi bg-light mx-auto"> <div class="humi-bar" id="humi_fill"></div> <div class="humi-label" id="humi_label">0%</div> </div> </div> </div> <div class="card-text text-center"> <p class="badge text-bg-primary">溫濕度資訊10秒更新一次</p> </div> </div> </div> </div> <div class="col-12 col-md-12 mt-3"> <div class="card"> <div class="card-body"> <div id="echarts_temp" class="w-auto" style="width: 100%;height:400px;"></div> </div> </div> </div> <div class="col-12 col-md-12 mt-3"> <div class="card"> <div class="card-body"> <div id="echarts_humi" class="w-auto" style="width: 100%;height:400px;"></div> </div> </div> </div> </div> </div> <script src="./script.js"></script> <script src="./view_temp_humi.js"></script> </body> </html> ``` #### JS - 溫濕度計顯示 ```javascript= // 取得溫度計的填充層和顯示溫度的元素 const fill = document.getElementById('fill'); const temperatureLabel = document.getElementById('temperature'); const humi_fill = document.getElementById('humi_fill'); const humi_label = document.getElementById('humi_label'); // 連接MQTT var client = mqtt.connect('ws://iotsvm.shop:8083/'); // 這是WebSocket端點 client.on('connect', function () { console.log('連接至MQTT伺服器'); client.subscribe('test/temp', function (err) { if (err) { console.log('連接失敗'); } }); }); let latestTempData = null; // 存放最新一筆數據 let latestHumiData = null; // 存放最新一筆數據 // 訂閱取得最新數據 client.on("message", (topic, message) => { try { // 解析 JSON const data = JSON.parse(message.toString()); console.log("解析後的JSON:", data.temperature); latestTempData = data.temperature; // 更新最新的溫度數據 latestHumiData = data.humidity; // 更新最新的溼度數據 } catch (error) { console.error("JSON 解析失敗:", error); } }); // 更新溫度計 setInterval(() => { if (latestTempData !== null && latestHumiData!=null) { // 更新溫度顯示 temperatureLabel.textContent = `${latestTempData}°C`; humi_label.textContent = `${latestHumiData}%` // 計算高度 const height = (latestTempData / 50) * 100; const humi_height = Math.min(((latestHumiData - 20) / (90 - 20)) * 100, 100); // 更新溫度計算高度 fill.style.height = `${height}%`; humi_fill.style.height = `${humi_height}%`; // 根據溫度設置範圍顯示的顏色 if (latestTempData >= 35) { //溫度超過35度 $('#fill').removeClass('bg-danger bg-success').addClass('bg-danger'); } else if (latestTempData >= 20 && latestTempData <35) {//溫度超過20度 $('#fill').removeClass('bg-danger bg-primary').addClass('bg-success'); } else if (latestTempData < 20) { //溫度低於10度 $('#fill').removeClass('bg-success bg-danger').addClass('bg-primary'); } // 根據濕度設置範圍顯示的顏色 if (latestHumiData >= 70) { $('#humi_fill').removeClass('bg-danger bg-success').addClass('bg-danger'); } else if (latestHumiData >= 60 && latestHumiData < 70) { $('#humi_fill').removeClass('bg-success bg-danger').addClass('bg-success'); } else if (latestHumiData < 60) { $('#humi_fill').removeClass('bg-success bg-primary').addClass('bg-primary'); } } }, 2000); // 每2秒执行一次 ``` #### JS - 溫濕度折線圖 ```javascript= var echarts_temp = echarts.init(document.getElementById('echarts_temp')); var echarts_humi = echarts.init(document.getElementById('echarts_humi')); // 溫度圖表 function fetchTempData() { $.ajax({ url: './fetch_data.php', type: 'GET', dataType: 'json', success: function (data) { var temperatures = data.temperature; var timestamps = data.timestamps; var option = { tooltip: { trigger: 'axis', position: function (pt) { return [pt[0], '10%',]; } }, title: { left: 'center', text: '溫度', }, xAxis: { type: 'category', data: timestamps, }, yAxis: { type: 'value', axisLabel: { formatter: '{value}°C', align:'right', } }, series: [ { name:'溫度', data: temperatures, // Use dynamic temperature data type: 'line', smooth: true, lineStyle: { color: 'green', // 設定線條顏色 (橘紅色) width: 2 // 可選:設定線條寬度 }, itemStyle: { color: 'green' // 可選:設定數據點顏色 } }, ] }; echarts_temp.setOption(option); } }); } // 濕度圖表 function fetchHumiData() { $.ajax({ url: './fetch_data.php', type: 'GET', dataType: 'json', success: function (data) { var humidity = data.humidity; var timestamps = data.timestamps; var option = { tooltip: { trigger: 'axis', position: function (pt) { return [pt[0],'10%',]; } }, title: { left: 'center', text: '濕度' }, xAxis: { type: 'category', data: timestamps, }, yAxis: { type: 'value', axisLabel: { formatter: '{value}%' } }, series: [ { name:'濕度', data: humidity, // Use dynamic temperature data type: 'line', smooth: true } ] }; echarts_humi.setOption(option); } }); } fetchTempData(); fetchHumiData(); setInterval(fetchTempData, 1000); setInterval(fetchHumiData, 1000); ``` #### CSS - 溫度計樣式 ```css= body{ background-color: rgba(131, 247, 247, 0.733); } /* 溫度計樣式 */ .thermometer , .humi { width: 70px; height: 300px; border-radius: 15px; border: 3px solid #000; background-color: #f5f5f5; position: relative; margin-bottom: 25px; } .thermometer-bar , .humi-bar{ position: absolute; bottom: 0; width: 100%; border-radius: 15px; transition: height 0.3s ease; } .temperature-label , .humi-label{ position: absolute; bottom: 10px; left: 50%; transform: translateX(-50%); font-weight: bold; font-size: 18px; } ``` #### MySQL 建立`sensor_data`資料庫 ```sql= CREATE DATABASE sensor_data; ``` #### MySQL 建立`temperature_readings`資料表 ```sql= USE sensor_data; CREATE TABLE `temperature_readings` ( `id` int NOT NULL, `temperature` text NOT NULL, `humidity` text NOT NULL, `vibration` text NOT NULL, `timestamp` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; ALTER TABLE `temperature_readings` ADD PRIMARY KEY (`id`); ALTER TABLE `temperature_readings` MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=1243; COMMIT; ``` #### PHP - 新增資料進資料庫 ```php= <?php $servername = ""; // 伺服器 $username = ""; // 資料庫帳戶名稱 $password = ""; // 資料庫密碼 $dbname = "sensor_data"; // 資料庫名稱 // 建立連接 $conn = new mysqli($servername, $username, $password, $dbname); // 檢查接 if ($conn->connect_error) { die("Connection failed: " . $conn->connect_error); } // 使用 GET 取得溫度 濕度值 if (isset($_GET['temperature']) && isset($_GET['humidity'])) { $temperature = $_GET['temperature']; $humidity=$_GET['humidity']; // SQL 插入語法 $sql = "INSERT INTO temperature_readings (temperature,humidity) VALUES ($temperature,$humidity)"; if ($conn->query($sql) === TRUE) { echo "Data inserted successfully"; } else { echo "Error: " . $sql . "<br>" . $conn->error; } } $conn->close(); ?> ``` #### PHP - 連接資料庫 ```php= <?php try{ $pdo = new PDO( 'mysql:host=;port=3306; dbname=sensor_data; charset=utf8mb4',//連線字串,port預設為3306,若不是則新增port=XXXX '',//帳號 '',//密碼 [ //設定 PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC ] ); } catch (Exception $a) { //如果上方有錯誤則顯示錯誤訊息 echo '連線錯誤'; echo $a -> getMessage(PDO::FETCH_ASSOC);//拋出錯誤訊息 exit();//跳出 } ?> ``` ### PHP - 查詢資料庫數據 ```php= <?php require_once('./AI_sensor.php'); // 引入資料庫連線檔案 // 獲取最新數據的函數 function fetchLatestData() { global $pdo; // 查詢最近的 10 條溫度數據(最新在前) $Sensor = $pdo->prepare("SELECT * FROM temperature_readings ORDER BY timestamp DESC LIMIT 10"); $Sensor->execute(); $tempData = []; $timestamps = []; $humiData=[]; // 將查詢結果存儲在陣列中 while ($row = $Sensor->fetch(PDO::FETCH_ASSOC)) { $tempData[] = $row['temperature']; // 溫度數據 $humiData[]= $row['humidity']; $timestamps[] = mb_substr($row['timestamp'], 11, 5); // 時間戳,提取時分部分 } // 將數據反轉,使其按時間從舊到新排列 $tempData = array_reverse($tempData); $humiData = array_reverse($humiData); $timestamps = array_reverse($timestamps); // 返回溫度數據和時間戳數據 return [ 'temperature' => $tempData, 'humidity'=> $humiData, 'timestamps' => $timestamps ]; } // 輸出數據為 JSON 格式 echo json_encode(fetchLatestData()); ?> ``` --- 延伸閱讀 1. [Google Cloud Platform VM個體使用 - 1](https://hackmd.io/@0q3lEDkPQdaD6eZ8vpOC_A/S1jjJDdrJx) 2. [Google Cloud Platform VM個體使用 - 2](https://hackmd.io/@0q3lEDkPQdaD6eZ8vpOC_A/HkkKfOur1g) 3. [Arduino ESP32使用步驟](https://hackmd.io/@0q3lEDkPQdaD6eZ8vpOC_A/SJRUP4xBke) 4. [ESP32-WROOM-32 30P 全彩LED模組](https://hackmd.io/@0q3lEDkPQdaD6eZ8vpOC_A/r1u5SKxr1g) 5. [ESP32使用DHT11溫濕度辨識結合LED燈](https://hackmd.io/@0q3lEDkPQdaD6eZ8vpOC_A/Bke9M_zB1e) 6. [ESP32實作溫濕度智慧監測結合DHT11及LED開發](https://hackmd.io/@0q3lEDkPQdaD6eZ8vpOC_A/H10M6LOr1g)