Introduction: Cat Food Access Control (ESP8266 + Servo Motor + 3D Printing)
This project goes over the process I used to create an automated cat food bowl, for my elderly diabetic cat Chaz. See, he needs to eat breakfast before he can get his insulin, but I often forget to pick up his food dish before I go to bed, which spoils his appetite and throws off his insulin schedule. This dish uses a servo motor to close a lid over the food between the hours of midnight and 7:30am. The NodeMCU ESP8266 microcontroller's Arduino sketch uses Network Time Protocol (NTP) to control the schedule.
This project may not be suitable for younger, more active cats. Chaz is so old and frail, he isn't inclined to try to pry the bowl open, but it is possible.
If you're new to the Arduino or the ESP8266, you may enjoy the following prerequisite guides:
Supplies
- 3D printer (I use a Creality CR-10s Pro)
- 3D printer filament (I'm using gold PLA)
- NodeMCU ESP8266 wifi microcontroller
- USB cable (A to microB)
- USB power adapter
- Micro servo motor
- Small screwdriver and screws
- Hookup wire
- Header pins
- Perma-proto board
To keep up with what I'm working on, follow me on YouTube, Instagram, Twitter, Pinterest, and subscribe to my newsletter. As an Amazon Associate I earn from qualifying purchases you make using my affiliate links.
Step 1: 3D Printed Parts
The cat food bowl holder is based on Ardy Lai's design on Thingiverse. I made it bigger to accommodate my cat's bowl, and also made it shorter since scaling it up had made it too tall. I added a holder for a micro servo motor, and a couple of holes for cables to route to the inside.
I modeled a simple lid using Tinkercad, designed to attach to the horn of the micro servo. You can grab my design directly from Tinkercad, and/or download the STLs attached to this step.
I printed the parts on my Creality CR-10s Pro printer with gold PLA filament.
Disclosure: at the time of this writing, I'm an employee of Autodesk, which makes Tinkercad.
Step 2: Attach Lid to Servo Motor
I used a small drill bit to increase the size of the holes on the servo horn, then used screws to attach the servo to the 3D printed lid.
Step 3: Build NodeMCU ESP8266 Circuit
The circuit is controlled by a NodeMCU ESP8266 wifi microcontroller. I used header pins on a perma-proto board to make the micro servo motor easily detachable.The servo headers are connected to the NodeMCU as follows:
Yellow servo wire: NodeMCU D1
Red servo wire: NodeMCU power (3V3 or VIN)
Black servo wire: NodeMCU ground (GND)
Step 4: Upload Arduino Code and Test
Install your motor/lid assembly into the motor-shaped cutout on the bowl holder 3D printed part. Plug the motor header into the microcontroller board's header pins, and plug the circuit into your computer with a USB cable.
The Arduino sketch uses Network Time Protocol to fetch the current time and then opens or closes the lid according to a hard-coded schedule. Copy the following code, update your wifi credentials and UTC time offset, and upload it to your NodeMCU board using the Arduino IDE.
#include <Servo.h> #include <ESP8266WiFi.h> #include <ESP8266WiFiMulti.h> #include <WiFiUdp.h> ESP8266WiFiMulti wifiMulti; // Create an instance of the ESP8266WiFiMulti class, called 'wifiMulti' WiFiUDP UDP; // Create an instance of the WiFiUDP class to send and receive IPAddress timeServerIP; // time.nist.gov NTP server address const char* NTPServerName = "time.nist.gov"; const int NTP_PACKET_SIZE = 48; // NTP time stamp is in the first 48 bytes of the message byte NTPBuffer[NTP_PACKET_SIZE]; // buffer to hold incoming and outgoing packets Servo myservo; // create servo object to control a servo // twelve servo objects can be created on most boards int pos = 0; // variable to store the servo position void setup() { myservo.attach(5); // attaches the servo on pin 5 aka D1 to the servo object //open the lid by default Serial.println("opening the lid"); for (pos = 95; pos >= 0; pos -= 1) { // goes from 95 degrees to 0 degrees myservo.write(pos); // tell servo to go to position in variable 'pos' delay(15); // waits 15ms for the servo to reach the position } Serial.begin(115200); // Start the Serial communication to send messages to the computer delay(10); Serial.println("\r\n"); startWiFi(); // Try to connect to some given access points. Then wait for a connection startUDP(); if(!WiFi.hostByName(NTPServerName, timeServerIP)) { // Get the IP address of the NTP server Serial.println("DNS lookup failed. Rebooting."); Serial.flush(); ESP.reset(); } Serial.print("Time server IP:\t"); Serial.println(timeServerIP); Serial.println("\r\nSending NTP request ..."); sendNTPpacket(timeServerIP); } unsigned long intervalNTP = 60000; // Request NTP time every minute 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) { // If a minute has passed since last NTP request prevNTP = currentMillis; Serial.println("\r\nSending NTP request ..."); sendNTPpacket(timeServerIP); // Send an NTP request } uint32_t time = getTime(); // Check if an NTP response has arrived and get the (UNIX) time if (time) { // If a new timestamp has been received timeUNIX = time; Serial.print("NTP response:\t"); Serial.println(timeUNIX); lastNTPResponse = currentMillis; } else if ((currentMillis - lastNTPResponse) > 3600000) { Serial.println("More than 1 hour since last NTP response. Rebooting."); Serial.flush(); ESP.reset(); } uint32_t actualTime = timeUNIX + (currentMillis - lastNTPResponse)/1000; uint32_t easternTime = timeUNIX - 18000 + (currentMillis - lastNTPResponse)/1000; if (actualTime != prevActualTime && timeUNIX != 0) { // If a second has passed since last print prevActualTime = actualTime; Serial.printf("\rUTC time:\t%d:%d:%d ", getHours(actualTime), getMinutes(actualTime), getSeconds(actualTime)); Serial.printf("\rEST (-5):\t%d:%d:%d ", getHours(easternTime), getMinutes(easternTime), getSeconds(easternTime)); Serial.println(); } // 7:30am if(getHours(easternTime) == 7 && getMinutes(easternTime) == 30 && getSeconds(easternTime) == 0){ //open the lid Serial.println("opening the lid"); for (pos = 95; pos >= 0; pos -= 1) { // goes from 95 degrees to 0 degrees myservo.write(pos); // tell servo to go to position in variable 'pos' delay(15); // waits 15ms for the servo to reach the position } } // midnight if(getHours(easternTime) == 0 && getMinutes(easternTime) == 0 && getSeconds(easternTime) == 0){ //close the lid Serial.println("closing the lid"); for (pos = 0; pos <= 95; pos += 1) { // goes from 0 degrees to 95 degrees // in steps of 1 degree myservo.write(pos); // tell servo to go to position in variable 'pos' delay(15); // waits 15ms for the servo to reach the position } } /* // testing if(getHours(easternTime) == 12 && getMinutes(easternTime) == 45 && getSeconds(easternTime) == 0){ //close the lid Serial.println("closing the lid"); for (pos = 0; pos <= 95; pos += 1) { // goes from 0 degrees to 95 degrees // in steps of 1 degree myservo.write(pos); // tell servo to go to position in variable 'pos' delay(15); // waits 15ms for the servo to reach the position } //open the lid Serial.println("opening the lid"); for (pos = 95; pos >= 0; pos -= 1) { // goes from 95 degrees to 0 degrees myservo.write(pos); // tell servo to go to position in variable 'pos' delay(15); // waits 15ms for the servo to reach the position } } */ } void startWiFi() { // Try to connect to some given access points. Then wait for a connection wifiMulti.addAP("ssid_from_AP_1", "your_password_for_AP_1"); // add Wi-Fi networks you want to connect to //wifiMulti.addAP("ssid_from_AP_2", "your_password_for_AP_2"); //wifiMulti.addAP("ssid_from_AP_3", "your_password_for_AP_3"); Serial.println("Connecting"); while (wifiMulti.run() != WL_CONNECTED) { // Wait for the Wi-Fi to connect delay(250); Serial.print('.'); } Serial.println("\r\n"); Serial.print("Connected to "); Serial.println(WiFi.SSID()); // Tell us what network we're connected to Serial.print("IP address:\t"); Serial.print(WiFi.localIP()); // Send the IP address of the ESP8266 to the computer Serial.println("\r\n"); } void startUDP() { Serial.println("Starting UDP"); UDP.begin(123); // Start listening for UDP messages on port 123 Serial.print("Local port:\t"); Serial.println(UDP.localPort()); Serial.println(); } uint32_t getTime() { if (UDP.parsePacket() == 0) { // If there's no response (yet) return 0; } UDP.read(NTPBuffer, NTP_PACKET_SIZE); // read the packet into the buffer // Combine the 4 timestamp bytes into one 32-bit number uint32_t NTPTime = (NTPBuffer[40] << 24) | (NTPBuffer[41] << 16) | (NTPBuffer[42] << 8) | NTPBuffer[43]; // Convert NTP time to a UNIX timestamp: // Unix time starts on Jan 1 1970. That's 2208988800 seconds in NTP time: const uint32_t seventyYears = 2208988800UL; // subtract seventy years: uint32_t UNIXTime = NTPTime - seventyYears; return UNIXTime; } void sendNTPpacket(IPAddress& address) { memset(NTPBuffer, 0, NTP_PACKET_SIZE); // set all bytes in the buffer to 0 // Initialize values needed to form NTP request NTPBuffer[0] = 0b11100011; // LI, Version, Mode // send a packet requesting a timestamp: UDP.beginPacket(address, 123); // NTP requests are to port 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; }
Step 5: Use It!
Route your wires to the inside of the bowl holder, and plug your cat feeder into an outlet using a USB AC adapter. The way the simple code is written, it is meant to be booted up in the "open" state, and will only change its lid position at the time thresholds specified in the Arduino sketch.
Thanks for following along! If you make your own version, I'd love to see it in the I Made It section below!
If you like this project, you may be interested in some of my others:
- Prism Holder for Rainbow Portraits
- Plywood Storage Wall with Cat Tower
- LED Mason Jar Lanterns (3D printed lid)
- 3D Printer Filament Dry Box
- Emergency USB Power Source (3D Printed)
- Glowing LED Gummy Candy
- 3D Printed Geometric Planter With Drainage
- Glowing 3D Printed Flowers
- How to Install LEDs Under a Scooter (with Bluetooth)
To keep up with what I'm working on, follow me on YouTube, Instagram, Twitter, and Pinterest.