ใครเป็นทาสแมวแล้วเจอปัญหาแบบนี้บ้าง? ลืมเก็บชามอาหารแมวก่อนนอน! โปรเจกต์นี้เกิดมาจากคุณ Becky Stern ที่ต้องการควบคุมเวลาการกินของ "Chaz" แมวแก่ที่ป่วยเป็นเบาหวาน น้องจำเป็นต้องกินอาหารเช้าให้ตรงเวลาก่อนฉีดอินซูลิน แต่ถ้าปล่อยอาหารทิ้งไว้ข้ามคืน น้องจะกินจนอิ่มและไม่ยอมกินมื้อเช้า
ไอเดียนี้เลยเกิดขึ้นมาเพื่อตอบโจทย์สถานการณ์นี้ครับ เราจะมาทำ "ฝาครอบชามอาหารแมวอัตโนมัติ" โดยใช้บอร์ด NodeMCU ESP8266 และ Servo Motor ควบคุมการเปิด-ปิดฝาในช่วงเที่ยงคืนถึง 7:30 น. โดยอิงเวลาจริงจากเซิร์ฟเวอร์ (NTP) บอกเลยว่าสาย DIY ห้ามพลาด!
📌 ข้อแนะนำ: โปรเจกต์นี้เหมาะกับแมวแก่หรือแมวที่ไม่ค่อยซนนะครับ ถ้าเป็นแมววัยรุ่นพลังล้นเหลือ น้องอาจจะพยายามงัดฝาเปิดเองได้
🛠️ อุปกรณ์ที่ต้องใช้ (Supplies)
- เครื่องพิมพ์ 3 มิติ (3D printer) และเส้นพลาสติก (ผู้เขียนใช้สีทอง PLA)
- บอร์ดไมโครคอนโทรลเลอร์ NodeMCU ESP8266
- สาย USB (A to microB) และ อะแดปเตอร์จ่ายไฟ USB
- Micro Servo Motor
- ไขควงขนาดเล็ก และ น็อต
- สายไฟ Hookup wire และ Header pins
- แผงวงจร Perma-proto board
🚀 ขั้นตอนการทำ (Step-by-Step)
ขั้นตอนที่ 1: พิมพ์ชิ้นส่วน 3D (3D Printed Parts)
ฐานใส่ชามอาหารดัดแปลงมาจากดีไซน์ของ Ardy Lai บน Thingiverse โดยปรับขนาดให้พอดีกับชามแมว และเพิ่มช่องสำหรับยึด Micro Servo Motor พร้อมรูร้อยสายไฟ ส่วนตัวฝาปิด (Lid) ออกแบบง่ายๆ ผ่าน Tinkercad เพื่อให้ยึดกับแกนหมุนของ Servo ได้พอดี
ดาวน์โหลดไฟล์ 3D ได้ที่นี่:
- Cat_Food_Bowl_Holder.stl (Shopify CDN)
- เอกสาร PDF เพิ่มเติม
ขั้นตอนที่ 2: ประกอบฝาเข้ากับมอเตอร์ (Attach Lid to Servo)
ใช้สว่านดอกเล็กขยายรูบนหัวแกน Servo (Servo Horn) เล็กน้อย จากนั้นใช้น็อตยึดเข้ากับฝาปิดที่พิมพ์ 3D ไว้ให้แน่นหนา
ขั้นตอนที่ 3: ต่อวงจร NodeMCU ESP8266
เพื่อให้ถอดประกอบง่าย แนะนำให้บัดกรี Header pins ลงบน Perma-proto board การต่อสายไฟมีแค่ 3 เส้นหลักๆ ดังนี้ครับ:
- 🟡 สายสีเหลือง (Signal): ต่อเข้าขา D1 ของ NodeMCU
- 🔴 สายสีแดง (Power): ต่อเข้าขา 3V3 หรือ VIN
- ⚫ สายสีดำ (Ground): ต่อเข้าขา GND
ขั้นตอนที่ 4: อัปโหลดโค้ด Arduino
ประกอบมอเตอร์เข้ากับฐาน เสียบสายเชื่อมต่อเข้าบอร์ด แล้วต่อ USB เข้าคอมพิวเตอร์ โค้ดนี้จะใช้ NTP ดึงเวลาปัจจุบันจากอินเทอร์เน็ต เพื่อสั่งเปิด-ปิดฝาตามตารางที่ตั้งไว้ (อย่าลืมแก้ชื่อ WiFi กับ Password ในโค้ดด้วยนะ)
#include <Servo.h>
#include <ESP8266WiFi.h>
#include <ESP8266WiFiMulti.h>
#include <WiFiUdp.h>
ESP8266WiFiMulti wifiMulti;
WiFiUDP UDP;
IPAddress timeServerIP;
const char* NTPServerName = "time.nist.gov";
const int NTP_PACKET_SIZE = 48;
byte NTPBuffer[NTP_PACKET_SIZE];
Servo myservo;
int pos = 0;
void setup() {
myservo.attach(5); // D1
Serial.println("opening the lid");
for (pos = 95; pos >= 0; pos -= 1) {
myservo.write(pos);
delay(15);
}
Serial.begin(115200);
delay(10);
Serial.println("\r\n");
startWiFi();
startUDP();
if(!WiFi.hostByName(NTPServerName, timeServerIP)) {
Serial.println("DNS lookup failed. Rebooting.");
Serial.flush();
ESP.reset();
}
sendNTPpacket(timeServerIP);
}
unsigned long intervalNTP = 60000;
unsigned long prevNTP = 0;
unsigned long lastNTPResponse = millis();
uint32_t timeUNIX = 0;
unsigned long prevActualTime = 0;
void loop() {
unsigned long currentMillis = millis();
if (currentMillis - prevNTP > intervalNTP) {
prevNTP = currentMillis;
sendNTPpacket(timeServerIP);
}
uint32_t time = getTime();
if (time) {
timeUNIX = time;
lastNTPResponse = currentMillis;
} else if ((currentMillis - lastNTPResponse) > 3600000) {
ESP.reset();
}
uint32_t actualTime = timeUNIX + (currentMillis - lastNTPResponse)/1000;
uint32_t easternTime = timeUNIX - 18000 + (currentMillis - lastNTPResponse)/1000;
if (actualTime != prevActualTime && timeUNIX != 0) {
prevActualTime = actualTime;
}
// 7:30am Open
if(getHours(easternTime) == 7 && getMinutes(easternTime) == 30 && getSeconds(easternTime) == 0){
for (pos = 95; pos >= 0; pos -= 1) {
myservo.write(pos);
delay(15);
}
}
// Midnight Close
if(getHours(easternTime) == 0 && getMinutes(easternTime) == 0 && getSeconds(easternTime) == 0){
for (pos = 0; pos <= 95; pos += 1) {
myservo.write(pos);
delay(15);
}
}
}
void startWiFi() {
// แก้ไข SSID และ Password ตรงนี้
wifiMulti.addAP("ssid_from_AP_1", "your_password_for_AP_1");
Serial.println("Connecting");
while (wifiMulti.run() != WL_CONNECTED) {
delay(250);
Serial.print('.');
}
}
void startUDP() {
UDP.begin(123);
}
uint32_t getTime() {
if (UDP.parsePacket() == 0) { return 0; }
UDP.read(NTPBuffer, NTP_PACKET_SIZE);
uint32_t NTPTime = (NTPBuffer[40] << 24) | (NTPBuffer[41] << 16) | (NTPBuffer[42] << 8) | NTPBuffer[43];
const uint32_t seventyYears = 2208988800UL;
uint32_t UNIXTime = NTPTime - seventyYears;
return UNIXTime;
}
void sendNTPpacket(IPAddress& address) {
memset(NTPBuffer, 0, NTP_PACKET_SIZE);
NTPBuffer[0] = 0b11100011;
UDP.beginPacket(address, 123);
UDP.write(NTPBuffer, NTP_PACKET_SIZE);
UDP.endPacket();
}
inline int getSeconds(uint32_t UNIXTime) { return UNIXTime % 60; }
inline int getMinutes(uint32_t UNIXTime) { return UNIXTime / 60 % 60; }
inline int getHours(uint32_t UNIXTime) { return UNIXTime / 3600 % 24; }
ขั้นตอนที่ 5: เริ่มใช้งานจริงได้เลย!
จัดสายไฟเก็บเข้าในช่องฐานให้เรียบร้อย เสียบปลั๊กจ่ายไฟให้ระบบ โปรแกรมถูกเขียนมาให้เริ่มต้นด้วยสถานะ "เปิดฝา" ก่อนเสมอ และจะเปลี่ยนสถานะก็ต่อเมื่อถึงเวลาที่ตั้งไว้ในโค้ด (เที่ยงคืนปิด, 7 โมงครึ่งเปิด)
สนใจอุปกรณ์ IoT หรือพูดคุยแลกเปลี่ยนไอเดีย?
คำเตือน: เนื้อหานี้เป็นการสรุปและเรียบเรียงจากบทความต้นฉบับภาษาอังกฤษ ข้อมูลฉบับภาษาไทยอาจมีความคลาดเคลื่อนบางประการจากการตีความหรือย่อเนื้อหา