ESP32 Multi-Sensor Environment Monitoring: ระบบตรวจสอบสิ่งแวดล้อมครบวงจร
สร้างระบบตรวจสอบสิ่งแวดล้อมแบบหลายเซ็นเซอร์ด้วย ESP32 เชื่อมต่อ CynoIoT ตรวจจับอุณหภูมิ ความชื้น แสง และคุณภาพอากาศ พร้อม Dashboard และแจ้งเตือนอัตโนมัติ
📑 เนื้อหาในบทความ
ทำไมต้องระบบตรวจสอบสิ่งแวดล้อมแบบ Multi-Sensor?
ในยุค IoT (Internet of Things) ที่เชื่อมต่อทุกอย่างเข้าด้วยกัน การตรวจสอบสิ่งแวดล้อม (Environment Monitoring) ไม่ใช่แค่เรื่องของโรงงานหรือสถานที่เท่านั้น แต่เป็นสิ่งสำคัญสำหรับบ้าน สำนักงาน และโกดังเก็บของด้วย เพราะคุณภาพของอากาศ อุณหภูมิ และความชื้นส่งผลต่อ:
- สุขภาพของผู้อยู่อาศัย - ลดโรคภูมิแพ้ ปัญหาทางหายใจ
- การเก็บรักษาสินค้า - อาหาร ยา และอุปกรณ์ไว้ไฟฟ้า
- ประสิทธิภาพการทำงาน - อุณหภูมิที่เหมาะสมช่วยเพิ่มประสิทธิภาพ
- การประหยัดพลังงาน - ควบคุมเครื่องใช้ไฟฟ้าอัจฉริยะ
💡 ทำไมต้อง Multi-Sensor?
เซ็นเซอร์เดียวไม่เพียงพอ! อุณหภูมิอาจโอเคแต่ความชื้นสูงเกินไป หรืออากาศดูสะอาดแต่มีก๊าซเป็นพิษ การใช้หลายเซ็นเซอร์ช่วยให้เห็นภาพรวมและตัดสินใจได้ถูกต้อง
อุปกรณ์ที่ต้องใช้
Hardware
| อุปกรณ์ | รายละเอียด | ราคาโดยประมาณ |
|---|---|---|
| ESP32 Dev Board | NodeMCU, Wemos, หรือ generic board | ฿120-180 |
| DHT22 Sensor | ตรวจจับอุณหภูมิและความชื้น | ฿50-80 |
| BMP280 Sensor | ตรวจจับความดันบรรยากาศ | ฿60-100 |
| BH1750 Sensor | ตรวจจับความเข้มแสง (Lux) | ฿30-50 |
| MQ135 Sensor | ตรวจจับก๊าซและคุณภาพอากาศ | ฿80-120 |
| Jumper Wires | สายเชื่อมต่อ Male-to-Female | ฿40-60 |
| Breadboard (ถ้าจำเป็น) | สำหรับทดลองเชื่อมต่อ | ฿30-50 |
| USB Cable & Power | Micro USB สำหรับ ESP32 | ฿30-50 |
💡 งบประมาณรวม: โดยประมาณ ฿450-750 (ขึ้นอยู่กับคุณภาพและที่ซื้อ)
⏱️ เวลาที่ใช้: 2-3 ชั่วโมงสำหรับการประกอบและตั้งค่า
Software
- Arduino IDE หรือ PlatformIO (VS Code)
- บัญชี CynoIoT (ฟรี)
- Library ที่จำเป็น (ติดตั้งผ่าน Library Manager)
การเชื่อมต่อวงจร
เซ็นเซอร์ทั้งหมดใช้บัส I2C ซึ่งหมายความว่าเราสามารถเชื่อมต่อได้หลายตัวบนสายเดียว! นี่คือตารางการเชื่อมต่อ:
| Sensor | VCC | GND | SDA | SCL | อื่นๆ |
|---|---|---|---|---|---|
| DHT22 | 3.3V | GND | - | - | DATA → GPIO 4 (พร้อม Resistor 10KΩ) |
| BMP280 | 3.3V | GND | GPIO 21 (SDA) | GPIO 22 (SCL) | - |
| BH1750 | 3.3V | GND | GPIO 21 (SDA) | GPIO 22 (SCL) | ADDR → GND (Address 0x23) |
| MQ135 | 5V (VCC) | GND | - | - | AOUT → GPIO 34 (ADC) |
⚠️ ข้อควรระวัง:
- MQ135 ต้องการ 5V สำหรับ Heater แต่ ESP32 ใช้ 3.3V ใช้ VIN จาก ESP32 (ซึ่งได้ 5V จาก USB)
- อย่าใช้ GPIO 34-35 สำหรับ Input เท่านั้น (ไม่มี Internal Pull-up)
- DHT22 ต้องการ Resistor 10KΩ ระหว่าง VCC และ DATA
การต่อ I2C (แบ่งใช้ Bus เดียวกัน)
BMP280 และ BH1750 ใช้ I2C Bus เดียวกันได้ เพราะมี Address ต่างกัน:
- BMP280: 0x76 หรือ 0x77
- BH1750: 0x23 (เมื่อ ADDR ต่อ GND)
การตั้งค่า CynoIoT Platform
ก่อนเขียนโค้ด เราต้องตั้งค่า CynoIoT Platform ก่อนเพื่อรองรับข้อมูลจากเซ็นเซอร์ของเรา:
Step 1: สร้าง Device
- เข้าสู่ระบบ CynoIoT Dashboard
- ไปที่ Devices → Add New Device
- ตั้งชื่อ: "Environment Monitor"
- เลือก Type: ESP32
- กด Create
เก็บ Device ID และ API Key ไว้ใช้ในโค้ด
Step 2: สร้าง Data Points
ไปที่ Device → Data Points → Add New Data Point:
| ชื่อ Data Point | ชนิด | หน่วย |
|---|---|---|
| temperature | Number | °C |
| humidity | Number | % |
| pressure | Number | hPa |
| light | Number | Lux |
| air_quality | Number | PPM |
โค้ด Arduino/PlatformIO
ติดตั้ง Library ที่จำเป็นผ่าน Library Manager:
- DHT sensor library by Adafruit
- Adafruit BMP280 Library
- Adafruit Unified Sensor
- BH1750 by Christopher Laws
โค้ดหลัก (ESP32 Multi-Sensor Monitor)
/*
* ESP32 Multi-Sensor Environment Monitor
* เชื่อมต่อ CynoIoT Platform
*
* Components:
* - DHT22: Temperature & Humidity
* - BMP280: Pressure
* - BH1750: Light
* - MQ135: Air Quality
*/
#include <WiFi.h>
#include <HTTPClient.h>
#include <DHT.h>
#include <Adafruit_BMP280.h>
#include <Wire.h>
#include <BH1750.h>
// ===== WiFi Credentials =====
const char* ssid = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";
// ===== CynoIoT Configuration =====
const char* cynoiot_server = "api.cynoiot.com"; // แก้ไขตาม server จริง
const String device_id = "YOUR_DEVICE_ID";
const String api_key = "YOUR_API_KEY";
// ===== Sensor Configuration =====
// DHT22 - Temperature & Humidity
#define DHTPIN 4
#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);
// BMP280 - Pressure (I2C)
Adafruit_BMP280 bmp;
// I2C: SDA = GPIO 21, SCL = GPIO 22
// BH1750 - Light (I2C)
BH1750 lightMeter;
// MQ135 - Air Quality (Analog)
#define MQ135_PIN 34
// ===== Timing Configuration =====
unsigned long lastReadTime = 0;
const long readInterval = 5000; // อ่านทุก 5 วินาที
unsigned long lastUploadTime = 0;
const long uploadInterval = 30000; // อัปโหลดทุก 30 วินาที
// ===== Data Storage =====
struct SensorData {
float temperature;
float humidity;
float pressure;
float light;
int airQuality;
bool valid;
};
SensorData currentData;
void setup() {
Serial.begin(115200);
Serial.println("\n=== ESP32 Environment Monitor ===");
// Initialize WiFi
setupWiFi();
// Initialize Sensors
setupSensors();
Serial.println("Setup complete! Starting monitoring...");
}
void loop() {
unsigned long currentTime = millis();
// อ่านเซ็นเซอร์ทุก 5 วินาที
if (currentTime - lastReadTime >= readInterval) {
lastReadTime = currentTime;
readSensors();
printData();
}
// อัปโหลดไป CynoIoT ทุก 30 วินาที
if (currentTime - lastUploadTime >= uploadInterval) {
lastUploadTime = currentTime;
if (currentData.valid) {
uploadData();
} else {
Serial.println("Skipping upload - invalid data");
}
}
}
// ===== WiFi Setup =====
void setupWiFi() {
Serial.print("Connecting to WiFi");
WiFi.begin(ssid, password);
int attempts = 0;
while (WiFi.status() != WL_CONNECTED && attempts < 20) {
delay(500);
Serial.print(".");
attempts++;
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println("\nWiFi connected!");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
} else {
Serial.println("\nFailed to connect to WiFi");
}
}
// ===== Sensor Setup =====
void setupSensors() {
Serial.println("Initializing sensors...");
// DHT22
dht.begin();
Serial.println("✓ DHT22 initialized");
// BMP280
if (bmp.begin(0x76)) {
Serial.println("✓ BMP280 initialized (0x76)");
} else if (bmp.begin(0x77)) {
Serial.println("✓ BMP280 initialized (0x77)");
} else {
Serial.println("✗ BMP280 not found!");
}
// BH1750
Wire.begin();
if (lightMeter.begin(BH1750::ONE_TIME_HIGH_RES_MODE)) {
Serial.println("✓ BH1750 initialized");
} else {
Serial.println("✗ BH1750 not found!");
}
// MQ135 (Analog - no init needed)
pinMode(MQ135_PIN, INPUT);
Serial.println("✓ MQ135 initialized");
Serial.println("All sensors initialized!");
}
// ===== Read All Sensors =====
void readSensors() {
// DHT22 - Temperature & Humidity
float temp = dht.readTemperature();
float hum = dht.readHumidity();
// BMP280 - Pressure
float press = bmp.readPressure() / 100.0F; // hPa
// BH1750 - Light
float lux = lightMeter.readLightLevel();
// MQ135 - Air Quality
int airQ = analogRead(MQ135_PIN);
// Validate data
currentData.temperature = temp;
currentData.humidity = hum;
currentData.pressure = press;
currentData.light = lux;
currentData.airQuality = airQ;
// Check if valid
currentData.valid = !isnan(temp) && !isnan(hum) && !isnan(press) && !isnan(lux);
if (!currentData.valid) {
Serial.println("Warning: Some sensor readings are invalid!");
}
}
// ===== Print Data to Serial =====
void printData() {
Serial.println("\n=== Sensor Readings ===");
Serial.printf("Temperature: %.2f °C\n", currentData.temperature);
Serial.printf("Humidity: %.2f %%\n", currentData.humidity);
Serial.printf("Pressure: %.2f hPa\n", currentData.pressure);
Serial.printf("Light: %.2f Lux\n", currentData.light);
Serial.printf("Air Quality: %d PPM\n", currentData.airQuality);
// Air quality status
if (currentData.airQuality < 200) {
Serial.println("Air Quality: GOOD");
} else if (currentData.airQuality < 400) {
Serial.println("Air Quality: MODERATE");
} else {
Serial.println("Air Quality: POOR");
}
Serial.println("========================\n");
}
// ===== Upload to CynoIoT =====
void uploadData() {
if (WiFi.status() != WL_CONNECTED) {
Serial.println("WiFi not connected - skipping upload");
return;
}
HTTPClient http;
// Build JSON payload
String jsonPayload = "{";
jsonPayload += "\"temperature\":" + String(currentData.temperature) + ",";
jsonPayload += "\"humidity\":" + String(currentData.humidity) + ",";
jsonPayload += "\"pressure\":" + String(currentData.pressure) + ",";
jsonPayload += "\"light\":" + String(currentData.light) + ",";
jsonPayload += "\"air_quality\":" + String(currentData.airQuality);
jsonPayload += "}";
// Send HTTP POST
String url = "http://" + String(cynoiot_server) + "/api/devices/" + device_id + "/data";
http.begin(url);
http.addHeader("Content-Type", "application/json");
http.addHeader("X-API-Key", api_key);
int httpResponseCode = http.POST(jsonPayload);
if (httpResponseCode > 0) {
Serial.print("Upload successful. Response code: ");
Serial.println(httpResponseCode);
} else {
Serial.print("Upload failed. Error code: ");
Serial.println(httpResponseCode);
}
http.end();
}💡 จุดสำคัญในโค้ด:
- I2C Address ของ BMP280 อาจเป็น 0x76 หรือ 0x77 ให้ลองทั้งสองค่า
- MQ135 ให้ค่าเป็น PPM แต่ต้องสอบเทียบกับค่าจริง (ดูในส่วง Calibration)
- ใช้ FreeRTOS หรือ non-blocking delays เพื่อปรับปรุงประสิทธิภาพ
การสอบเทียบเซ็นเซอร์
MQ135 Air Quality Sensor
MQ135 ต้องการการสอบเทียบเพื่อให้ได้ค่าที่ถูกต้อง:
// ฟังก์ชันสอบเทียบ MQ135 (เพิ่มลงในโค้ดหลัก)
void calibrateMQ135() {
Serial.println("Calibrating MQ135...");
Serial.println("Please ensure the sensor is in fresh air");
int sensorValue = 0;
int samples = 100;
// อ่านค่าเฉลี่ย 100 ครั้ง
for (int i = 0; i < samples; i++) {
sensorValue += analogRead(MQ135_PIN);
delay(10);
}
sensorValue = sensorValue / samples;
Serial.print("Baseline value: ");
Serial.println(sensorValue);
// ค่า R0 = Rs ในอากาศสะอาด (จะใช้ในการคำนวณ)
float r0 = (float)sensorValue;
// ... เก็บ r0 ไว้ใช้ในการคำนวณค่า PPM จริง
Serial.println("Calibration complete!");
}📊 การตีความค่า MQ135:
- <200 PPM: อากาศดี (Good)
- 200-400 PPM: ปานกลาง (Moderate)
- >400 PPM: แย่ (Poor) - อาจมีก๊าซเป็นพิษหรือควัน
DHT22 Temperature Calibration
เปรียบเทียบกับเทอร์โมมิเตอร์ที่รู้จักว่าถูกต้อง แล้วแก้ไข Offset:
// แก้ไขใน readSensors()
float tempOffset = 0.0; // ปรับค่านี้หากค่าที่ได้สูง/ต่ำไป
float temp = dht.readTemperature() + tempOffset;การสร้าง Dashboard บน CynoIoT
หลังจากอัปโหลดข้อมูลได้แล้ว ไปสร้าง Dashboard เพื่อแสดงผล:
- ไปที่ Dashboards → Create New Dashboard
- ตั้งชื่อ: "Environment Monitor"
- เพิ่ม Widgets:
- Gauge Chart: Temperature
- Line Chart: Temperature History (24h)
- Gauge Chart: Humidity
- Line Chart: Air Quality
- Number Display: Light Level
- Status Indicator: Air Quality (Good/Moderate/Poor)
- ตั้งค่า Refresh Interval: 30 วินาที
- บันทึกและดูผลลัพธ์!
การตั้งค่าการแจ้งเตือน
ไปที่ CynoIoT → Alerts → Create New Alert:
| ชื่อ Alert | เงื่อนไข | การแจ้งเตือน |
|---|---|---|
| Temperature High | temperature > 35°C | Email + Push Notification |
| Humidity High | humidity > 70% | |
| Air Quality Poor | air_quality > 400 PPM | SMS + Push Notification |
| Light Low | light < 50 Lux |
การประหยัดพลังงานด้วย Deep Sleep
หากต้องการใช้แบตเตอรี่ สามารถปรับโค้ดให้ใช้ Deep Sleep Mode:
// เพิ่มบรรทัดนี้ใน setup()
// ตั้งเวลาให้ตื่นทุก 5 นาที (300 วินาที)
esp_sleep_enable_timer_wakeup(300 * 1000000);
// ใน loop() - อ่านเซ็นเซอร์และอัปโหลด แล้วเข้า Deep Sleep
void loop() {
readSensors();
if (WiFi.status() != WL_CONNECTED) {
WiFi.begin(ssid, password);
delay(2000); // รอให้เชื่อมต่อ
}
if (currentData.valid) {
uploadData();
}
Serial.println("Entering deep sleep...");
Serial.flush();
esp_deep_sleep_start(); // จะตื่นเมื่อครบเวลาที่ตั้งไว้
}🔋 ประหยัดพลังงานเพิ่ม:
- ใช้แบตเตอรี่ Li-ion 18650 (3000-3500 mAh)
- Deep Sleep Mode ลดการใช้พลังงานลง ~90%
- ปิด WiFi เมื่อไม่ได้ใช้: WiFi.off()
- ลดความถี่ในการอ่านเซ็นเซอร์ (เช่น ทุก 5 นาที)
การแก้ปัญหา
ปัญหา: DHT22 อ่านค่าไม่ได้ (NaN)
วิธีแก้:
- ตรวจสอบการเชื่อมต่อ DATA Pin กับ GPIO
- ตรวจสอบ Resistor 10KΩ ระหว่าง VCC และ DATA
- เพิ่ม delay(2000) หลังจากเริ่มใช้งาน DHT
- ลองใช้ GPIO 4 หรือ GPIO 5 แทน
ปัญหา: BMP280 ไม่พบ
วิธีแก้:
- ตรวจสอบ Address (0x76 หรือ 0x77)
- ตรวจสอบการเชื่อมต่อ SDA/SCL (GPIO 21/22)
- ตรวจสอบ VCC (ใช้ 3.3V เท่านั้น!)
- ลองใช้ I2C Scanner เพื่อหา Address
ปัญหา: WiFi เชื่อมต่อไม่ได้
วิธีแก้:
- ตรวจสอบ SSID และ Password ให้ถูกต้อง
- ลองเพิ่ม delay(2000) หลังจาก WiFi.begin()
- ตรวจสอบว่า ESP32 อยู่ในระยะที่สัญญาณ WiFi เข้าถึง
- ลอง restart ESP32: ESP.restart()
ปัญหา: อัปโหลดไป CynoIoT ไม่ได้
วิธีแก้:
- ตรวจสอบ Device ID และ API Key
- ตรวจสอบ server URL (api.cynoiot.com)
- ดู Serial Monitor สำหรับ HTTP Response Code
- ลองใช้ Postman หรือ curl เพื่อทดสอบ API
สรุปและไอเดียต่อยอด
ในบทความนี้ เราได้เรียนรู้วิธีสร้างระบบตรวจสอบสิ่งแวดล้อมแบบครบวงจรด้วย ESP32 และ CynoIoT Platform:
- ✅ เชื่อมต่อเซ็นเซอร์ได้ถึง 4 ประเภท (อุณหภูมิ, ความชื้น, ความดัน, แสง, คุณภาพอากาศ)
- ✅ อัปโหลดข้อมูลไปยัง Cloud Platform แบบ Real-time
- ✅ สร้าง Dashboard เพื่อแสดงผลและวิเคราะห์ข้อมูล
- ✅ ตั้งค่าการแจ้งเตือนอัตโนมัติ
- ✅ ปรับปรุงประสิทธิภาพพลังงานด้วย Deep Sleep
🚀 ไอเดียต่อยอดโปรเจกต์
Smart Greenhouse
เพิ่มเซ็นเซอร์วัดความชื้นในดิน (Soil Moisture) และควบคุมระบบรดน้ำอัตโนมัติ
Warehouse Monitor
ใช้หลาย Device ในพื้นที่ขนาดใหญ่ ตรวจสอบความชื้นและอุณหภูมิในแต่ละจุด
Air Quality Map
สร้างแผนที่คุณภาพอากาศแบบ Real-time โดยใช้ข้อมูลจากหลายจุดในเมือง
Predictive Maintenance
วิเคราะห์ข้อมูลเพื่อทำนายปัญหาก่อนเกิด เช่น อุณหภูมิสูงเกินไป
🎓 บทความที่เกี่ยวข้อง