โปรเจกต์ปานกลาง20 นาที

ESP32 Multi-Sensor Environment Monitoring: ระบบตรวจสอบสิ่งแวดล้อมครบวงจร

สร้างระบบตรวจสอบสิ่งแวดล้อมแบบหลายเซ็นเซอร์ด้วย ESP32 เชื่อมต่อ CynoIoT ตรวจจับอุณหภูมิ ความชื้น แสง และคุณภาพอากาศ พร้อม Dashboard และแจ้งเตือนอัตโนมัติ

Cyno IoT Team
19 มีนาคม 2569
ESP32 Multi-Sensor Environment Monitoring System

ทำไมต้องระบบตรวจสอบสิ่งแวดล้อมแบบ Multi-Sensor?

ในยุค IoT (Internet of Things) ที่เชื่อมต่อทุกอย่างเข้าด้วยกัน การตรวจสอบสิ่งแวดล้อม (Environment Monitoring) ไม่ใช่แค่เรื่องของโรงงานหรือสถานที่เท่านั้น แต่เป็นสิ่งสำคัญสำหรับบ้าน สำนักงาน และโกดังเก็บของด้วย เพราะคุณภาพของอากาศ อุณหภูมิ และความชื้นส่งผลต่อ:

  • สุขภาพของผู้อยู่อาศัย - ลดโรคภูมิแพ้ ปัญหาทางหายใจ
  • การเก็บรักษาสินค้า - อาหาร ยา และอุปกรณ์ไว้ไฟฟ้า
  • ประสิทธิภาพการทำงาน - อุณหภูมิที่เหมาะสมช่วยเพิ่มประสิทธิภาพ
  • การประหยัดพลังงาน - ควบคุมเครื่องใช้ไฟฟ้าอัจฉริยะ

💡 ทำไมต้อง Multi-Sensor?
เซ็นเซอร์เดียวไม่เพียงพอ! อุณหภูมิอาจโอเคแต่ความชื้นสูงเกินไป หรืออากาศดูสะอาดแต่มีก๊าซเป็นพิษ การใช้หลายเซ็นเซอร์ช่วยให้เห็นภาพรวมและตัดสินใจได้ถูกต้อง

อุปกรณ์ที่ต้องใช้

Hardware

อุปกรณ์รายละเอียดราคาโดยประมาณ
ESP32 Dev BoardNodeMCU, 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 & PowerMicro USB สำหรับ ESP32฿30-50

💡 งบประมาณรวม: โดยประมาณ ฿450-750 (ขึ้นอยู่กับคุณภาพและที่ซื้อ)
⏱️ เวลาที่ใช้: 2-3 ชั่วโมงสำหรับการประกอบและตั้งค่า

Software

  • Arduino IDE หรือ PlatformIO (VS Code)
  • บัญชี CynoIoT (ฟรี)
  • Library ที่จำเป็น (ติดตั้งผ่าน Library Manager)

การเชื่อมต่อวงจร

เซ็นเซอร์ทั้งหมดใช้บัส I2C ซึ่งหมายความว่าเราสามารถเชื่อมต่อได้หลายตัวบนสายเดียว! นี่คือตารางการเชื่อมต่อ:

SensorVCCGNDSDASCLอื่นๆ
DHT223.3VGND--DATA → GPIO 4 (พร้อม Resistor 10KΩ)
BMP2803.3VGNDGPIO 21 (SDA)GPIO 22 (SCL)-
BH17503.3VGNDGPIO 21 (SDA)GPIO 22 (SCL)ADDR → GND (Address 0x23)
MQ1355V (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

  1. เข้าสู่ระบบ CynoIoT Dashboard
  2. ไปที่ Devices → Add New Device
  3. ตั้งชื่อ: "Environment Monitor"
  4. เลือก Type: ESP32
  5. กด Create

เก็บ Device ID และ API Key ไว้ใช้ในโค้ด

Step 2: สร้าง Data Points

ไปที่ Device → Data Points → Add New Data Point:

ชื่อ Data Pointชนิดหน่วย
temperatureNumber°C
humidityNumber%
pressureNumberhPa
lightNumberLux
air_qualityNumberPPM

โค้ด 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 เพื่อแสดงผล:

  1. ไปที่ Dashboards → Create New Dashboard
  2. ตั้งชื่อ: "Environment Monitor"
  3. เพิ่ม 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)
  4. ตั้งค่า Refresh Interval: 30 วินาที
  5. บันทึกและดูผลลัพธ์!

การตั้งค่าการแจ้งเตือน

ไปที่ CynoIoT → Alerts → Create New Alert:

ชื่อ Alertเงื่อนไขการแจ้งเตือน
Temperature Hightemperature > 35°CEmail + Push Notification
Humidity Highhumidity > 70%Email
Air Quality Poorair_quality > 400 PPMSMS + Push Notification
Light Lowlight < 50 LuxEmail

การประหยัดพลังงานด้วย 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

วิเคราะห์ข้อมูลเพื่อทำนายปัญหาก่อนเกิด เช่น อุณหภูมิสูงเกินไป

🎓 บทความที่เกี่ยวข้อง

บทความที่เกี่ยวข้อง