# ESP32實作溫濕度智慧監測結合DHT11及LED開發
### 成果展示


### 接線圖

### 網頁連結
[智慧監測數據網頁](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)