กลับไปหน้ารวมไฟล์
my-not-so-stupid-home-8ccd5b.md

ชื่อโปรเจกต์: บ้านที่ (ไม่ค่อย) เซ่อซ่าของพี่ (My not so stupid home)

สรุปสั้นๆ ให้ฟัง

พี่เพิ่งถอย NodeMcu Lua ESP8266 มาใหม่ เลยกะว่าจะทำโปรเจกต์ Home Automation (หรือระบบจัดการบ้าน) สักหน่อย เอาไว้ช่วยให้ชีวิตประจำวันมันง่ายขึ้นนิดนึง งานนี้พี่จัดเต็มทั้ง Relay ไว้คุมไฟในห้อง, DHT11 ไว้เช็คอุณหภูมิและความชื้น แล้วก็มีมอเตอร์ 2 ตัว คือปั๊มน้ำ DC (DC water pump) กับ Servo motor เอาไว้ทำเครื่องให้อาหารแมวสุดเลิฟ ส่วน Software บน NodeMcu พี่เขียนด้วย Arduino Studio ใน Sketch จะมี Http server ตัวหนึ่งคอยรับ Request จาก Http client (ซึ่งพี่เขียนแอป Android ขึ้นมาคุมเองแหละ) แถมยังมี UDP client เอาไว้ดึงเวลาจาก NTP servers ด้วย พี่เลือกใช้ HTTP เพราะมันง่ายดีและ Schematic พี่ก็ไม่ได้ซับซ้อนอะไร แค่ต้องการการสื่อสารแบบ Request-Response ง่ายๆ ล่าสุดพี่เพิ่งเพิ่ม SD card module เข้าไปเอาไว้เก็บไฟล์ตั้งค่า (Configuration) สำหรับระบบให้อาหารและรดน้ำอัตโนมัติด้วยนะ

อธิบายโค้ดหน่อย

ในส่วนนี้พี่จะอธิบายว่าพี่จัดการแต่ละส่วนยังไง ส่วนโค้ด Sketch และแอป Android ตัวเต็ม เดี๋ยวพี่แปะไว้ให้ช่วงท้ายนะวัยรุ่น

อย่างแรกเลย เราต้อง Include บรรดา Library ที่จะใช้ก่อน

// สำหรับ HTTP server
#include
#include
// สำหรับ DHT11 sensor
#include
// สำหรับเรื่องเวลา
#include
#include
// สำหรับ Servo
#include

สร้าง HTTP server

สร้าง Object ของ Server ขึ้นมาซะ

ESP8266WebServer server(80);

ก่อนจะ Start server ได้ NodeMcu ของเราต้องต่อ WiFi ให้ติดก่อนนะน้อง

// ต่อ Wi-Fi ด้วย SSID และ Password
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
}

พอต่อติดแล้ว พี่ก็ประกาศ Method ของ Server ทั้งแบบ POST และ GET (พวก Callback นั่นแหละ) โดย Argument แรกคือ Route และตัวที่สองคือฟังก์ชันที่จะทำงานตอน Client เรียกใช้งาน ข้างในฟังก์ชันพวกนี้ก็จะมี Logic ไว้คอยอ่านค่า Sensor หรือสั่งงาน Actuator ต่างๆ

 // กำหนดเส้นทาง GET และ POST ให้ Server
server.on("/", handleRoot);
server.on("/getTemperatureAndHumidity", handleTemperatureAndHumidity);
server.on("/setLightState/",handleLightState);
server.on("/startWatering",handlePump);
server.on("/startFeeding",handleFeeder);
server.onNotFound(handleNotFound);

สุดท้าย สั่ง Start HTTP server ด้วยคำสั่งนี้:

server.begin();

ปั๊มน้ำ (Water pump)

สำหรับการเติมน้ำให้เจ้าเหมียว พี่ใช้ปั๊มน้ำ 5V DC ต่อเข้ากับ L298N motor driver และพี่ใช้ไฟแยก 12V จ่ายให้ Driver นะ (ห้ามลืมล่ะ เดี๋ยวบอร์ดพัง)

ตัว Driver จะใช้ Pins ของ Arduino 3 ขา บวกกับ GND อีกหนึ่ง

// ขาสำหรับปั๊มน้ำ
#define ENBPin 2 // D4
#define IN3Pin 4 // D2
#define IN4Pin 0 // D3

กำหนดค่า Pins ให้เป็น OUTPUT ในฟังก์ชัน setup() ซะ

 pinMode(IN3Pin, OUTPUT);
pinMode(IN4Pin, OUTPUT);
pinMode(ENBPin, OUTPUT);

แล้วก็ใส่ Logic การทำงานของปั๊มไว้ในฟังก์ชัน handlePump ที่เราประกาศไว้ก่อนหน้านี้

void handlePump(){
// สั่งปั๊มทำงาน
digitalWrite(IN3Pin, LOW);
digitalWrite(IN4Pin, HIGH);
digitalWrite(ENBPin, HIGH);
// ให้ปั๊มทำงานสัก 6 วินาที
Serial.println("Watering..");
delay(6000);
// สั่งหยุดปั๊ม
digitalWrite(IN3Pin, LOW);
digitalWrite(IN4Pin, LOW);
digitalWrite(ENBPin, LOW);
Serial.println("Watering done.");
server.send(200,"text/plain","Watering done");
}

บรรทัด server.send(200,"text/plain","Watering done"); คือการส่ง Http response กลับไปบอก Client ว่า "เออ รดน้ำเสร็จแล้วนะ"

เครื่องให้อาหารแมว (Cat feeder)

พี่ใช้ mini servo motor หนึ่งตัวทำเครื่องให้อาหาร ส่วนไอเดียดีไซน์พี่ก็ได้มาจากแถวๆ นี้แหละ

Servo ใช้ Pins ของ Arduino 3 ขา คือ:

ul >
  • ขา 5V
    • GND
    • ขา Signal - ในที่นี้พี่ใช้ GPIO15

    ประกาศตัวแปร Servo:

    Servo servo1;

    จับคู่ Servo กับ Pins ในฟังก์ชัน setup()

    servo1.attach(SERVOPin);

    ใส่ Logic การให้อาหารไว้ในฟังก์ชัน handleFeeder

    void handleFeeder(){
    servo1.write(0);
    delay(500);
    servo1.write(90);
    delay(500);
    servo1.write(0);
    delay(500);\t
    server.send(200,"text/plain","Feeding done");
    }

    ระบบไฟ (Light)

    อันนี้พี่ใช้ Relay module 5V ง่ายๆ เลย

    Set ขาให้เป็น OUTPUT เหมือนเดิม

    pinMode(LIGHTPin, OUTPUT);

    หลักการคือให้ Client ส่งค่าสถานะไฟมาที่ Server แล้ว Server ก็จะอ่านจาก HttpRequest เพื่อสั่งงาน

    void handleLightState(){
    String message = "";
    if (server.method() != HTTP_POST) {
    Serial.println("Not allowed.");
    server.send(405, "text/plain", "Method Not Allowed");
    }
    else
    {
    String state = server.arg("plain");
    SetLight(state.toInt());
    server.send(200, "application/json", "{\"lightState\":" + state +"}");
    }
    }

    String state = server.arg("plain"); คือการอ่าน Parameter จาก Client แล้วส่งต่อให้ฟังก์ชัน SetLight(int state) อีกที

    void SetLight(int state){
    digitalWrite(LIGHTPin, state);
    lightState = state;
    }

    DHT11 sensor

    เจ้าตัว DHT11 จะมี 4 ขา แต่เราใช้แค่ 3 ขา คือ VCC, Data และ GND โดย VCC กับ GND ก็ต่อเข้า 3V และ Ground ของ NodeMCU ไปเลย อ้อ! อย่าลืมคร่อมตัวต้านทาน 10K ohm ระหว่าง VCC กับขา Data ด้วยนะ เพื่อทำเป็น Pull up ให้สัญญาณมันนิ่งๆ พี่ใช้แผ่น Protoboard บัดกรีเอาเลยเพื่อความแน่นหนา

    Define ทุกอย่างให้ DHT11 ซะ

    #define DHTTYPE DHT11 
    // ขาของ DHT11
    #define DHTPin 3 // ใช้ขา RX
    DHT dht(DHTPin, DHTTYPE);

    Start การทำงานใน setup() ด้วยคำสั่ง dht.begin();

    พอลูกค้าส่ง Request มา เราก็ค่อยอ่านค่าจาก Sensor

    void handleTemperatureAndHumidity(){
    if(ReadDHT11())
    {
    server.send(200,"application/json","{\"temperature\":"+ String(celsiusTemp) +",\"humidity\":"+ String(humidityTemp) +"}");
    }
    else
    {
    server.send(400,"text/plain","Unable to read from sensor");
    }

    ฟังก์ชัน ReadDHT11 เอาไว้อ่านค่าจริงๆ

    bool ReadDHT11(){
    float h = dht.readHumidity();
    float t = dht.readTemperature();
    if (isnan(h) || isnan(t)) {
    Serial.println("Failed to read from DHT sensor!");
    celsiusTemp = 0;
    humidityTemp = 0;
    return false;
    }
    else{
    celsiusTemp = t;
    humidityTemp = h;
    return true;
    }
    }

    เรื่องของเวลา (Time)

    ใส่ Library ให้ครบ

    #include 
    #include

    ประกาศ Client ที่จำเป็น

    WiFiUDP ntpUDP;
    NTPClient timeClient(ntpUDP, "europe.pool.ntp.org", utcOffsetInSeconds);

    ไอเดียคือ พี่จะอ่านเวลาจาก NTP server แล้วเช็คว่าตรงกับเวลาที่ตั้งไว้ให้ข้าวหรือให้น้ำแมวหรือยัง ถ้าตรงปุ๊บก็สั่งลุย!

    void getTime() {
    timeClient.update();
    int hours = timeClient.getHours();
    int minutes = timeClient.getMinutes();
    int seconds = timeClient.getSeconds();
    // เรียกใช้ฟังก์ชันจัดการเวลา
    handleTime(Time(hours,minutes,seconds);
    delay(1000);
    }

    ฟังก์ชันนี้พี่เรียกใช้งานใน loop() นะ

    ส่วน Class Time เดี๋ยวไปดูใน Code section ท้ายบทความ ฟังก์ชัน handleTime() จะเช็คว่าเวลาปัจจุบันมันเท่ากับเวลาที่นัดแนะไว้หรือยัง

    void handleTime(Time t){
    if(t.isEqual(feedingTime))
    {
    handleFeeder();
    }
    if(t.isEqual(wateringTime))
    {
    handlePump();
    }
    }

    SD CARD

    พี่อยากใส่ SD card module เพิ่มเข้าไปเพื่อเก็บไฟล์ Config ของ ESP8226 ไฟล์จะเป็นแบบ .json เก็บพวกเวลาให้อาหารและรดน้ำ เผื่อวันไหนไฟดับ พอมันติดขึ้นมาใหม่ ESP8266 จะได้อ่าน Config กลับมาใช้ต่อได้เลย พี่สามารถแก้เวลาพวกนี้ผ่านแอป Android ได้ด้วยนะ แอปจะส่ง JSONobject มาให้ แล้วโมดูลก็จะทำการ Parse JSON เพื่ออัปเดตข้อมูล

    การต่อสายระหว่าง SD card module กับ NodeMcu
    CS - D8
    MISO - D7
    MOSI - D6
    SCK - D5

    โค้ดสำหรับอ่าน SD card:

    void readConfig() {
    if (!SD.begin(chipSelect)) {
    Serial.println("Initialization failed!");
    while (1);
    }
    String data ="";
    File dataFile = SD.open("config.json",FILE_READ);
    if (dataFile) {
    while (dataFile.available())
    {
    data += (char)dataFile.read();
    }
    StaticJsonDocument<200> doc;
    deserializeJson(doc, data);
    // ตั้งเวลารดน้ำ
    wateringTime.setHours((int)doc["wateringTime"]["hours"]);
    wateringTime.setMinutes((int)doc["wateringTime"]["minnutes"]);
    wateringTime.setSeconds((int)doc["wateringTime"]["seconds"]);
    // ตั้งเวลาให้อาหาร
    feedingTime.setHours((int)doc["feedingTime"]["hours"]);
    feedingTime.setMinutes((int)doc["feedingTime"]["minnutes"]);
    feedingTime.setSeconds((int)doc["feedingTime"]["seconds"]);
    }
    }

    พี่ใช้ Library ArduinoJson เพราะไฟล์พี่เป็น JSON ไงล่ะ

    StaticJsonDocument<200> doc;

    deserializeJson(doc, data);

    พอ Deserialize เสร็จ พี่ก็เอาผลลัพธ์มาเซ็ตเวลาให้อาหารกับรดน้ำ โค้ดสำหรับ Class Time พี่แปะไว้ท้ายบทเรียนนี้นะ

    ฟังก์ชันอื่นๆ (Other methods)

    พี่สร้าง Method หลัก (root) เอาไว้ เพื่อให้ Client เรียกตอนเปิดแอป Android ครั้งแรก

    void handleRoot(){
    ReadDHT11();
    server.send(200,"application/json","{\"temperature\":"+ String(celsiusTemp) +",\"humidity\":"+ String(humidityTemp) +",\"lightState\":"+ String(lightState) +"}");
    }

    มันจะส่ง Http response เป็น Json object ที่บอกสถานะของ Sensor และ Actuator ทั้งหมดที่มี

    แอปพลิเคชัน Android

    จริงๆ ใน Google play มีแอปแนวนี้เยอะนะ หรือจะทำหน้าเว็บ Html บน Server ก็ได้ แต่ในฐานะที่พี่เป็น Software Developer พี่เลยจัดแอป Android ง่ายๆ ขึ้นมาเองเลย พี่ใช้ Visual Studio 2019 กับภาษา C# ทำเป็นแอป Xamarin.Android ครับ

    พี่จะไม่ขออธิบายขั้นตอนการทำแอปนี้ละเอียดนะ แต่ไปส่องโค้ดใน GitHub ของพี่ได้ตามนี้ เผื่อน้องจะเอาไปทำแอปคูลๆ ของตัวเองบ้าง

    โปรเจกต์ตอนทำเสร็จแล้ว

    ตอนนี้พี่จับทุกอย่างยัดใส่กล่องพลาสติกไปก่อน แต่แพลนไว้ว่าจะทำกล่องไม้เท่ๆ ที่มีปลั๊กตัวเมียต่อเข้ากับ Relay แล้วก็มีขั้วต่อสำหรับ Sensor ปั๊มน้ำ และเครื่องให้อาหารให้เรียบร้อยกว่านี้

    อ้อ พี่ใช้ที่ชาร์จมือถือเก่าๆ มาแปลงไฟจาก 220v เป็น 5v เพื่อเลี้ยง NodeMCU ด้วยนะน้อง ประหยัดงบสุดๆ

    รายละเอียดเทคนิคแบบจัดเต็ม (EXPANDED TECHNICAL DETAILS)

    Integrated Smart Environment Lifecycle

    โปรเจกต์นี้คือระบบบ้านอัจฉริยะแบบครบวงจรที่รวมทั้งการเฝ้าระวังสิ่งแวดล้อม, ระบบรักษาความปลอดภัย และการควบคุมจากส่วนกลางผ่าน Desktop

    • Visual Studio command center: พี่ใช้โปรแกรม Windows Forms (เขียนด้วย VS 2017) เป็นหน้า Dashboard หลัก โดยคอมพิวเตอร์จะคุยกับ "Master Arduino" ผ่าน Serial ความเร็วสูง
    • Multi-Sensor Polling Matrix: บอร์ด Arduino จะจัดการเครือข่ายเซนเซอร์ที่กระจายตัวอยู่ ทั้ง DHT22, LDR และ PIR จากนั้น Firmware จะรวมข้อมูลทั้งหมดแล้วส่งเป็น "System Health" packet ไปที่ Dashboard บน Windows ทุกๆ 5 วินาที

    ประสิทธิภาพ (Efficiency)

    • Priority-Event Handling: พี่ใส่ "Interrupt" สำหรับกรณีที่มีการบุกรุกด้วยนะ ถ้า PIR sensor ตรวจเจออะไรบางอย่าง Arduino จะหยุด Loop การอ่านค่าปกติทันที แล้วส่งสัญญาณเตือนไปที่ Dashboard บนคอมพิวเตอร์แบบ Real-time เลยล่ะน้อง!

    ข้อมูล Frontmatter ดั้งเดิม

    title: "My not so stupid home"
    description: "Control and read light, temperature, humidity, feeder and water pump for cat with SD card, HTTP server on NodeMcu and Android client app."
    author: "sadreactonly"
    category: ""
    tags:
      - "wifi"
      - "iot"
      - "motor"
      - "home automation"
      - "httpserver"
      - "animals"
      - "android"
      - "servo"
    views: 1474
    likes: 1
    price: 1499
    difficulty: "Intermediate"
    components:
      - "1x SG90 Micro-servo motor"
      - "1x Android phone"
      - "1x Jumper wires (generic)"
      - "1x SD Card module"
      - "1x Dual H-Bridge motor drivers L298"
      - "1x DHT11 Temperature & Humidity Sensor (4 pins)"
      - "1x Arduino UNO"
      - "1x NodeMCU ESP8266 Breakout Board"
      - "1x DC motor (generic)"
      - "1x Relay Module (Generic)"
      - "1x LED Light Bulb, Frosted GLS"
      - "1x 60W PCIe 12V 5A Power Supply"
    tools: []
    apps:
      - "1x Arduino IDE"
      - "1x Visual Studio 2017"
    downloadableFiles:
      - "https://github.com/sadreactonly/MyNotSoStupidHome"
      - "https://github.com/sadreactonly/MyNotSoStupidHomeHttp"
      - "https://github.com/sadreactonly/MyNotSoStupidHomeHttp"
      - "https://github.com/sadreactonly/MyNotSoStupidHome"
    documentationLinks: []
    passwordHash: "e4490a960607edf613701063f6dbde70a6da0fdc49c9186b8183d6f523bf0f75"
    encryptedPayload: "U2FsdGVkX19RMil/PLxZoSJwIE0Ms8PDz0exqFB+YWsnA64rOhY4uMyOB2wH1wgYFpf4gmZwOmnW8wnbhzOonjSdo42wYFOmTbpwp6pCYgLN7MJNyShoP6Sd6GZ5Aedv"
    seoDescription: "Control light, temperature, humidity, and cat feeder using NodeMcu and Android app with SD card storage."
    videoLinks: []
    heroImage: "https://cdn.jsdelivr.net/gh/bigboxthailand/arduino-assets@main/images/projects/my-not-so-stupid-home-8ccd5b_cover.jpg"
    lang: "th"