Automated cat feeder

Tell us the story of your project: 

Our middle-aged tom cat had over-eaten his way to an unhealthy size.  In response to this, we began to regulate his caloric intake. Unfortunately, as he became hangrier, his tactics for waking me to feed him in the morning became more aggressive.  The situation culminated one morning when, at 5AM, he knocked my glasses, phone, and a half-full can of seltzer off my night stand and onto the floor.  

My wife, knowing I never shy away from an opportunity to build stuff, asked me if I couldn’t devise a gadget to feed him so we no longer needed to get out of bed.  That sounded like good fun.

The final design for the automated cat feeder The hardware

To get the creative juices flowing, I began by rummaging around in the box in my office where I throw unused parts from past projects.  The “ah-ha moment” arrived as I pulled out an old Lynxmotion 3DOF robotic arm (an extra part purchased while building a quadruped kit with my boys).  

 

Lynxmotion 3DOF  

I also found a Raspberry Pi computer that was not currently in use.  All that was left to do was to buy some servos, somehow mount the arm over a cat food dish, program the Raspberry Pi GPIO pins to open and close a lid over the dish using servos, and maybe ring a bell to evoke a Pavlovian response from the cat.  Presumably if I could get all that done, it would be no problem to then go further and create a smartphone-friendly web app so I could open the cat dish without leaving my bed. Maybe I would even try to build a scheduler it so that the cat could be fed while I sleep.

Final ingredients:  
  • 1 Lynxmotion 3DOF arm assembly

  • 1 extra Lynxmotion aluminum tubing connector hub

  • 1 Lynxmotion 7” aluminum channel

  • 2 Lynxmotion L connector brackets (to connect channel to base)

  • 3 Hitec HS422 servos

  • 1 Raspberry Pi with wifi and power supply

  • 1 PN2222 transistor

  • 1 1k resistor

  • 1 5V electromagnet

  • 1 5” square wooden desktop storage box

  • A wood board for base and steel L bracket (found in garage)

  • A bell stolen from the Christmas stuff in the attic

  • Part of 1 coat hanger (from which to dangle the bell)

Once I had located a piece of wood in the garage suitable for a base, and mounted the Lynxmotion arm to the channel (and the channel to the base using the L connector brackets), I attached a plastic cup to the base and a plastic lid to the device and programmed the basic arm-up and arm-down routines in Python (more on that later).  

Animated GIF of the working prototype  

The prototype looked and worked great in the lab, but field testing did not go as well.  It took the cat exactly one night to work out the brute-force hack that ended this iteration. Teeth marks left at the scene of the crime led to the hypothesis that he simply got ahold of the plastic cap and ripped it clear.

Version 2, complete with electromagnet, wooden box, and Pavlovian bell  

Version 2 had some significant enhancements, notably the addition of a wooden box (tastefully stained black) held in place by an electromagnet, and a bell  to call him at feeding time. The box I found online was a nicely machined cube 12.5cm on a side with a sliding lid. Since I only had about 12cm of clearance between the arm’s highest and lowest points, I needed to cut down the box height as well as the length of the aluminum tube connecting the box to the arm.  After a bit of trial and error, I arrived at a 6cm box height. I was also able to shave down and repurpose the sliding lid that came with the box as a base to set the cat food dish on, providing a bit of additional protection from curious animals during the night.  

The electromagnet presented me with my next challenge, as I needed to create a circuit that could toggle a 5V power source using a 3.3V GPIO pin.  I’m not an electrical engineer, so this step required a fair amount of Googling around for me, an exercise which was really useful from a general education point of view.  The exercise was also a bit frustrating, though, as I was never able to locate an exact solution to my problem on the web. However I got close enough that I could experiment and ultimately built a working circuit.

 

Electromagnet control circuit  

At the end of the day, I put a 3.3V GPIO pin through a 1k resistor and then on the base pin of a PN2222 transistor.  Then I wired the 5V power supply to the electromagnet via the remaining two transistor pins. This circuit did the trick: the magnet very satisfyingly grabbed the steel L bracket when I wrote a 1 to the GPIO pin and released it when I wrote a 0 to the pin.  

 

Electrical diagram  

I flush-mounted the magnet into the wooden base, and added the steel L bracket to the side of the box so that it covered the magnet.  And, since all this revision had required me to take the prototype completely apart anyway, I touched it up a bit and added a few coats of polyurethane to help with cleanup going forward.

 

The software  

Once or twice in the recent past I had some experience with GPIO programming on a Raspberry Pi in Python, and so I was able fairly quickly to follow some online recipes and get the servos to move.  However, in this instance I experienced a fair amount of jitter whenever the servos were held in place. I did some reading about this problem and tried a few different pins and approaches, ultimately settling on two enhancements.  

First, I installed the very useful pigpio Python library which runs as a daemon on the Raspberry Pi and generates a lot less jitter than other GPIO libraries I experimented with.  Once you have the library and daemon properly installed, moving servos with pigpio comes down to six lines of code:

import pigpio PI = pigpio.pi() PI.set_mode(servo_number, pigpio.OUTPUT) PI.set_PWM_frequency(servo_number, 50) PI.set_servo_pulsewidth(servo_number, arg)

In the lines above, servo_number will be an integer representing the GPIO pin controlling the servo and arg will be another integer, this one ranging from 500 to 2500 depending upon the angle you want the servo to move to.  

Second, I wrote my Python program that controls the arm in such a way that I sent power to the servos only when absolutely necessary.  The upside of this was less power consumption, and less annoying buzzing and humming. The downside was, with the servos usually dead, any person (or animal) could freely reposition the arm most of the time.  This was not a good situation if your goal it to keep a fat cat away from food hidden inside a wooden box for six or seven hours. That is why in version 2 of the design I had to add the electromagnet to hold the lid closed.  The magnet, unlike the servos, could be left powered all night without any hum or jitter.

The full arm_controller.pi source file is available here, and I’ll freely admit that this could have been done with a lot fewer lines of Python.  However, a few design criteria evolved out of the iterative process that ultimately resulted in several additional useful routines.  I’ll illustrate them below as I walk you through the script.

The main function is simple enough:

init_servos() parser = OptionParser() parser.add_option("-o", "--open", action="callback", callback=open_lid) parser.add_option("-c", "--close", action="callback", callback=close_lid) parser.add_option("-d", "--debug", action="callback", callback=debug_mode) (options, args) = parser.parse_args()

The arm_controller.py script can be called from the command line (or imported into a Flask web server, which we will discuss later) with one of three command line arguments.  The --open argument raises the wooden box. The --close argument lowers the wooden box. The --debug argument runs the program in an interactive mode. It was the --debug mode that took up the majority of the “extra” code in the script.  But it was worth it. Aside from being useful during software development, the --debug argument served as a physical calibration function, allowing me to make small incremental changes to the arm position and save them to a json-formatted configuration file in the pi user’s home directory.

catfood$ ./arm_controller.py --debug Cat food commands: open : open the lid (move to one) close : close the lid (move to zero) jiggle : move side to side to ring the bell magnet X : toggle magnet 1 or 0 we X : move elbow and wrist together to X sh X : move shoulder to X el X : move elbow to X wr X : move wrist to X kill X : kill servo 13, 18, 19, or ALL save close : save as new close save open : save as new open schedule 13:30 0,2 : schedules lid open for 1:30 PM on Sunday and Tuesday print : print all values exit : leave this script Command: open Moving to OPEN Command: wr 2400 Moving wrist to 2400 Command: save open Saving to config file Command: exit

In the above example, I use --debug mode to move the arm to the open position, make a slight change to the angle of the wrist servo, then write the resulting arm position to the config file as the new open position.

Once I had the ability to open and close the lid, calibrate the arm, and turn the magnet on and off, I started looking into scheduling the early morning feedings.  I came across the python-crontab library, which gave me a convenient way to create and modify jobs that could be run by the built-in Raspbian cron scheduler.  Below I show the relevant lines needed to set up a job to raise the arm at 1:30PM on Sunday and Tuesday.

from crontab import CronTab pi_cron = CronTab(user='pi') pi_cron.remove_all() #assume there are no other jobs for pi cmd = '/home/pi/arm_controller.py --open >/home/pi/cron.log 2>&1' job = pi_cron.new(command=cmd) job.setall(30, 13, None, None, “0,2”, None) pi_cron.write_to_user(user='pi')

That’s about it.  The only other bit of discretionary logic I coded into the script was the ability to control the built-in red LED on the Raspberry Pi board.  Instead of the default behavior of glowing red whenever the board is powered up, I wanted the LED to be dark unless some activity is underway, such as engaging the magnet or moving the arm.  To do this, I learned from the web, you need to write a binary value to a system file. So I created the function shown below that gets called with either a 0 or a 1 from the appropriate places in code where the LED should be turned off and on.

def toggle_red_LED(val): file = '/sys/class/leds/led1/brightness' with open(file, "w") as f: f.write(str(val))

The only thing left to do was to create a user interface that would allow me to manage the schedule and control the arm from my bed, ideally delivered as an app on my phone.  

The web server

I knew that Flask was, among other things, a lightweight Python web server that might serve my purposes.  Problem was, I knew very little more about it than that. Now, having completed this project I know much more about this technology.  I know, for instance, that it’s amazing! Pasted below is the complete 8-line script required to start a fully functioning web server on your Raspberry Pi.

#!/usr/bin/python from flask import Flask app = Flask(__name__) @app.route('/', methods=['GET', 'POST']) def index(): return "<html><body><h1>Hello World!</h1></body></html>" if __name__ == '__main__': app.run(host="0.0.0.0", port=80)

Of course, a Hello World! message won’t feed the cat, and there was much more reading and writing to be done before I was able to complete a useable web app (full webserver script and html template files are linked here), such as rendering an HTML form, and using an HTML template file to separate page design from programming logic.   You can read the files for yourself, but I will point out the most important aspects in the text that follows, starting with webserver.py.

 

The web application rendered on an iPhone

For HTML form rendering, I used the wtforms library.  I won’t go into exhaustive detail as to how to use this library, but you’ll see from looking at the webserver.py file that I create an instance of the FlaskForm object that defines all my form fields.

class CatFoodForm(FlaskForm): open = SubmitField(label='Open Lid') lock = SubmitField(label='Lock Lid') save = SubmitField(label='Save Schedule') time_picker = TimeField(validators=(validators.Optional(),)) form = CatFoodForm()

The form is then passed to my index.html template for rendering, along with several other variables needed to properly set up the page, by the line below.

return render_template('index.html', title='Cat Food', form=form, days=days, lock_label=lock_label, sched_time=sched_time.strftime("%H:%M"))

When the user (in this case me) presses the “Open Lid” button on the web application, it sets the data attribute of that button to True, allowing the webserver.py script to take whatever action is appropriate.  I needed to the web server to interact with the device control script I had written, so I imported it in.

import arm_controller

The above line takes advantage of the Python language import feature that can be used to bring all the code from one script (arm_controller.py) into another script (webserver.py).  In my case, this gave me the ability to build and maintain my hardware device controlling logic in a single file that could then be called from the command line for calibration and testing, from the cron daemon for scheduled feedings, or from the Flask web application for remote control.  Once arm_controller.py was imported into webserver.py as shown above, I was able to call its methods directly, such as happens in the “Open Lid” form submit code replicated below.

if (form.open.data): arm_controller.init_servos() arm_controller.toggle_magnet(0) arm_controller.open_lid() arm_controller.kill_servos() flash("Opened the dish.")

The code above initializes the servos, turns off the electromagnet, raises the arm to the open position, and cuts the power to the servos.  The flash command is a convenient Flask mechanism to show status and alerts in your web application. To illustrate how the form is rendered within my index.html template file, I replicated the relevant lines below.

<form method="POST" action="{{ url_for('index') }}"> <p>{{ form.open (class="button red") }}</p> <p>{{ form.lock (class="button red") }}</p> <p>Time<span><input id="time_picker" name="time_picker" type="time" value="{{sched_time}}"></span></p> {% for day in days %} <p>{{ day.name }}<span> <input type="checkbox" id="day_{{day.id}}" name="days" value="{{day.id}}" {{"checked=checked" if day.checked else ""}}></span></p> {% endfor %} <p>{{ form.save (class="button red") }}</p> </form>

You’ll notice that Flask renders variables and other logical statements, such as my red submit buttons or my checkboxes for each day of the week, within double squiggly brackets.  This is because Flask leverages another open source technology called Jinja2, and we’re only scratching the surface on that here. I got what I needed out of these technologies for this project, but they are certainly topics I plan to dig into further at some point in the future.

There are only a couple other things worthty of summarizing in the webserver.py file.  The first one I have to admit it is a bit of a hack. Remember my desire to control the Raspberry Pi’s onboard red LED?  Well, it seems that that file gets reset to read-only every time the operating system is restarted. Since my Flask application runs as root (it doesn’t have to do that, but here is a side benefit), I just take the opportunity at every web server startup to reset the LED control file to read/write.

os.chmod('/sys/class/leds/led1/brightness',0o666)

And the other thing to point out in my index.html template is the 68% of the code that comes before the <form> tag.  All of that code is css stylings suitable to my target device, the iPhone, that I modified from an iPad css template designed by Xavi Esteve.  Thanks Xavi.

Production deployment

Arm that can open and close a lid over a dish of cat food?  Check. Electromagnet strong enough to keep the cat out? Check.  Small computer capable of scheduling a regular morning feeding? Check.  Now I was only one step away from blissfully sleeping past 5AM (while the gentle jingle of a stolen Christmas bell intrudes on my dreams).  That was to make sure that the Flask web server was started automatically every time the Raspberry Pi started up.

I fiddled around with this a bit before I got it to work, and my notes are not comprehensive, so I apologize in advance if I missed a step.  But I think I captured the basic components below. The first thing to do is to create a service that can be called by Raspbian’s built-in systemctl daemon.  My service is called ‘flask’ by virtue that I created a file called /lib/systemd/system/flask.service containing the following lines.

[Unit] Description=Catfood web server After=network.target StartLimitIntervalSec=0 [Service] User=root WorkingDirectory=/home/pi ExecStart=/home/pi/webserver.py RestartSec=1 [Install] WantedBy=multi-user.target

Once that file was in place, I called the commands below to register and start my service.

sudo systemctl daemon-reload sudo systemctl start flask

And that’s the whole story!  I have been loading the device with cat food every evening for about two weeks now and everything is going fine.  For me, at least. I don’t get the sense that the cat is a huge fan of automation; indeed I believe he reflects fondly on a simpler era where all he had to do was walk on somebody’s face to get fed.

 



 

Collaborators: 
willipe
Number of Forks: 
0
Choose at least one category.: 
Team Members: 
Team member name: 
willipe
What role did this person play on the project?: 
Project Lead
Public
Sort Order: 
0
Teaser: 
My wife, knowing I never shy away from an opportunity to build stuff, asked me if I couldn’t devise a gadget to feed the cat so we no longer needed to get out of bed. That sounded like good fun.
Aha! moment: 
Finding the Lynxmotion arm assembly in the workshop.
Uh-oh! moment: 
Realizing I didn't actually know how to wire up a transistor.
Show & Tell video as default: 
Creation Date: 
Monday, November 18, 2019 - 11:16