Controlling a Servo Motor with Gmail and Raspberry Pi
You covered part one and part two (good work!), or maybe you didn’t and you found yourself here anyway (welcome). Part three is focused on the servo motor, primarily making it spin. Later in part four we’ll wrap things up by connecting it to the dispenser.
As always, let’s start with what it is we’re trying to accomplish here:
- Connect the servo motor to the Raspberry Pi’s GPIO header pins
- Write some code to make that motor functional
- Tie in the GmailWrapper.py script we wrote in part two so the motor spins when the right email lands in your inbox
Here we go!
Connecting the Servo Motor to the Raspberry Pi
If you’re using the same servo motor as me, it has power, ground and control wires. If you decided to use the standard non-continuous servo motor (which is a viable alternative since we won’t be performing a full revolution), then: power is red, ground is black and white is control.
Connect your male-to-female jumper cables to the servo. Now, using the GPIO diagram below as reference, connect the wires to the Pi’s GPIO headers as follows:
Note: this diagram is for Raspberry Pi’s with 40 pin GPIO, but the below works with 26 pin GPIO as well.
- power connects to header pin 2 (5v power)
- ground (black wire for standard servo) connects to header pin 6 (Ground)
- control (white for standard servo) connects to header pin 12 (GPIO18)
Here’s what mine looks like (don’t mind the zip tie):
Writing Code to Make the Servo Motor Spin
Before getting started, let’s update our Pi to the latest packages and install the GPIO library. Run this in the terminal:
sudo apt-get update
sudo apt-get upgrade
sudo apt-get install rpi.gpio
We’re all set. In the terminal, create a new python script using nano: sudo nano CatFeeder.py
Within the CatFeeder.py script, add the following code:
#!/usr/bin/env python import RPi.GPIO as GPIO import time def feed(): # let the GPIO library know where we've connected our servo to the Pi GPIO.setmode(GPIO.BCM) GPIO.setup(18, GPIO.OUT) try: servo = GPIO.PWM(18, 50) servo.start(12.5) # spin left, right, then left again rather than in a continuous circle # to prevent the food from jamming the servo for index in range(0, 3): dutyCycle = 2.5 if (index % 2 == 0) else 12.5 servo.ChangeDutyCycle(dutyCycle) # adjust the sleep time to have the servo spin longer or shorter in that direction time.sleep(0.8) finally: # always cleanup after ourselves servo.stop() GPIO.cleanup() if __name__ == '__main__': # kick off the feeding process (move the servo) feed()
Save and exit the nano editor: CTRL+X
, then Y
, then Enter
. Moment of truth, time to make that servo move! Let’s drop back into the Python interpreter, in the terminal:
# remember, hit enter after each line to have the interpreter... interpret python import CatFeeder CatFeeder.feed()
I hope that went smooth for you, because I just got really excited. Welcome to physical computing! You moved a physical object with code, hell yeah.
GmailWrapper, Meet CatFeeder: Putting it all Together
Alright, so we’ve created a way to read emails, and we’ve created a way to move a servo motor. Now we need to combine the two so the servo motor moves when we read emails.
Open CatFeeder.py in nano and add the highlighted lines of code: sudo nano CatFeeder.py
#!/usr/bin/env python from GmailWrapper import GmailWrapper import RPi.GPIO as GPIO import time HOSTNAME = 'imap.gmail.com' USERNAME = '<your gmail username>' PASSWORD = '<your app password or regular gmail password>' def feedByGmail(): gmailWrapper = GmailWrapper(HOSTNAME, USERNAME, PASSWORD) ids = gmailWrapper.getIdsBySubject('feed cats') if(len(ids) > 0): try: feed() gmailWrapper.markAsRead(ids) except: print("Failed to feed cats, they're starvingggg") def feed(): # let the library know where we've connected our servo to the Pi GPIO.setmode(GPIO.BCM) GPIO.setup(18, GPIO.OUT) try: servo = GPIO.PWM(18, 50) servo.start(12.5) # spin left, right, then left again rather than in a continuous circle # to prevent the food from jamming the servo for index in range(0, 3): dutyCycle = 2.5 if (index % 2 == 0) else 12.5 servo.ChangeDutyCycle(dutyCycle) time.sleep(0.8) finally: # always cleanup after ourselves servo.stop() GPIO.cleanup() if __name__ == '__main__': # kick off the feeding process (move the servo) # we now use our new feedByGmail method to handle the feeding feedByGmail()
Save and exit the nano editor: CTRL+X
, then Y
, then Enter
. As always, let’s give it a test. Send yourself an email with the subject feed cats. Drop into the python interpreter once the email arrives:
python import CatFeeder CatFeeder.feedByGmail()
Did it work? It did? Excellent! If it didn’t, don’t be discouraged! Python’s support community is huge, you’ll find the answer. Plus, you can always leave a comment below.
Scheduling a Cron Job to Run Our Script Regularly
We have code to move our servo motor when the correct email lands in our inbox, but it currently only runs when we physically tell it to. Let’s fix that by setting up a cron job to run our script every 60 seconds.
In order for the cron job to execute our script, we need to make our script executable. This is done by issuing the following command in the terminal: sudo chmod +x CatFeeder.py
Now we’ll add the cron job. In the terminal: crontab -e
. Add the following after the very last commented line (comments start with #
):
* * * * * python /path/to/your/script/CatFeeder.py
As Gabe called out in the comments, adding python
after the last asterisk (*) in the line above will force the python interpreter to be used while running our script. We’re in the nano editor, so save and exit: CTRL+X
, Y
, Enter
. Easy as Pi (sorry). A job is now running every 60 seconds with the sole task of looking for emails and feeding cats. Don’t believe me? Give it a shot, send yourself an email with the subject feed cats.
A nicety of cron jobs is they’ll continue running automatically if your Pi ever restarts.
Scheduling an Email to be Sent Regularly
We have the code, we have the cron job. Sending an email on demand will cause the servo motor to spin, but what about spinning it on a schedule? That’s where we’ll make use of IFTTT. IFTTT stands for if this then that and allows you to connect two disparate systems based on what they call a “recipe” (trigger and action). For our purpose, we need the clock to trigger an email to be sent (action). Here’s what to do:
- Setup an account if you haven’t already
- Use IFTTT website or download the app to your phone and log in
- In the My Applets section, add a new applet
- You’ll see a screen saying if +this then that: click the +this button, then select the Date & Time service.
- Select the Every day at trigger, and select the time you’d like the cat feeder to activate, then hit next
- Now you’ll see +that, click it and find the Gmail service. You’ll need to connect the service to your Gmail account. Once finished, set the action to Send yourself an email with the subject Feed cats.
- Hit next, then Finish
There you have it, every day at the time you specified an email will be sent to your account. If you had any issues setting up the IFTTT recipe, check out this post for a really nice and in-depth walk-through.
Having fun? Here’s Other Ways to Send an Email
Alexa
Alexa integrates nicely with IFTTT. In the IFTTT app, create a new recipe with the trigger (+this) connecting to Alexa. You’ll need to connect the service like you did for Gmail. Once connected, set the action (+that) to send an email, like we did in the previous section.
Hands free feeding at your ready: Alexa, trigger the cat feeder.
The DO Button App
Created by the IFTTT team, the DO Button app and accompanying widget provides a straightforward way to trigger the action. You! You’re the trigger. You setup a recipe, same as before, except you’ll notice there’s no +this. You are the +this. You open the app and click the button, it then triggers an email. This app can also be configured to show on your iPhone or Androids home screen, so triggering the email is even easier.
Conclusion and Next Steps
Part one has goals and items covered, part two has Gmail automation down, and part three provided the spinning of the motor. A lot has been done so far, we’re nearly there. Up for part four? That’s where I’m headed to connect the Pi and motor to the dispenser.
Hi, I’m Sam.
I’m a programmer and a DIYer. When I’m not finding things to build I enjoy cooking, hiking, camping and traveling the world with my best friend. Say Hello!
52 Replies to “Controlling a Servo Motor with Gmail and Raspberry Pi”
Everything is working great expect for the pesky cron job. It just won’t run. I’ve always had issues getting python scripts to run using cron. Any ideas?
Awesome, glad you’re making your way through the posts without much difficulty.
I had trouble running a cronjob in the past and wrote a post about the findings. Check it out and let me know if it helps!
https://storiknow.com/linux-cronjob-doesnt-execute-valid-python-script/
I must say this is one of the best tutorials I have seen. I particularly like the way you got me to check every step of the coding before proceeding to the next. I managed to troubleshoot every thing up to the Cron part Just Like Mike. I read the post you linked to on your blog and that line is in the script already. Im not sue what I have missed.
That’s excellent to hear, I appreciate the feedback, Carl! I won’t pretend to be an expect in Cron jobs, but would start by ensuring you can run the script manually without issue. If you can then we can safely point the blame at the cronjob step. Here’s how:
In the terminal:
1) make the the script executable (chmod +x script.py) if you haven’t already
2) navigate to the directory of the script and type: ./script.py
If the script runs fine it all comes down to cron. Unfortunately, there could be any number of small issues here, ranging from security to simply adding a new line at the very end of the cron job. This [askubuntu](https://askubuntu.com/questions/23009/why-crontab-scripts-are-not-working) post really outlines a number of common gotcha’s.
Hope that helps!
For many of us I think the issue may be we don’t know where the scripts we created are stored.
Thanks for the reply Sam
It works perfectly when using python
I have run the chmod +x script.py command but it said ‘not possible’ until I put sudo in front (maybe this is the issue). I am logged in as pi
When I run the script using ./ I get the following
from: cant read /var/mail/GmailWrapper…
./CatFeeder.py: line6: import:command not found and several other lines up to line13
Itried making GmailWrapper.py executable as well and checked the shebang no joy
If I run ./GmailWrapper.py i get the same errors but listing imap client.
Searching the error brings me back to the shebang error any Ideas gratefully received
Carl
I got it to work by calling calling python in my cronjob
**** python CatFeeder.py
Hope this helps,
Gabe
That’s great, thanks for sharing Gabe. I believe Carl was able to resolve his issue by ensuring the SheBang tag was first and foremost in the script. If anything is before it, the python interpreter won’t be used by default.
Hey Sam, Carl and Gabe,
I had the same errors. Gabe’s solution really did help.
Thanks a ton!
Do you know how to implement a camera into this?
Hi Liam, I haven’t hooked one up before but have thought about it using the Pi Camera (https://www.raspberrypi.org/products/camera-module-v2/).
Hi Sam,
Great project. Gotten down to making.
I’m using a 12V dc motor 3.5 rpm instead of the servo. I’ve used a driver circuit for my motor.
I’m implementing the same code, but the motor won’t run.
How do I fix this?
Please Revert asap as I’m on a time crunch.
Thanks.
I’ve never wired up/ controlled a DC motor before, so I can’t really give much advice here. There are a lot of tutorials online that should be able to help out.
https://medium.com/@Keithweaver_/controlling-dc-motors-using-python-with-a-raspberry-pi-40-pin-f6fa891dc3d
Alright, I’ll check them out.
Thanks.
Hey Sam,
So I changed my motor to servo motor as it was a more efficient option considering I’m on a time crunch.
Now my entire system is up and running with the email.
I have two issues-
1. How do I get the motor to run a complete circle in one direction, instead of left and right?
2. When I schedule a cron job, it shows in the status that it’s running, but it actually isn’t. The email is not getting read automatically. Can you tell me why this could be happening and what can I do to fix this?
Thanks.
I’m glad to hear you’re making some good progress. Assuming you purchased the continuous rotation servo, you can have it rotate one direction by replacing lines `33` through `36` in the “GmailWrapper, Meet CatFeeder: Putting it all Together” section above with:
servo.ChangeDutyCycle(2.5)
time.sleep(3) # this is how long to rotate for
As for the cronjob, if setup correctly it should just be making a call to execute your `CatFeeder.py` script. It’s possible you have errors in that script. You can check that by running it manually. In your terminal, navigate to the directory where your `CatFeeder.py` script lives, and type:
./CatFeeder.py
If there’s an issue, the terminal should show an error. You’ll then need to investigate what went wrong. If it runs fine but the email doesn’t get read, there may be a logical error with the code itself, at that point you’ll just have to look in the code above and ensure you didn’t miss anything.
Hi,
Alright I’ll check through this. Will revert back.
Thank you so much!
Hey Sam,
So I followed your instruction.
The code for the motor didnt make my motor run according to it. I guess it started drawing more current from the Pi, cause it restarted it in the middle of it.
As for the cronjob, I ran it in the terminal and got something as below. I’m a little confused because the code works just fine when I run it in the terminal. How do I resolve this issue.
Please do help.
pi@raspberrypi:~ $ ./PetFeeder.py
from: can’t read /var/mail/GmailWrapper
./PetFeeder.py: line 3: import: command not found
./PetFeeder.py: line 4: import: command not found
./PetFeeder.py: line 6: HOSTNAME: command not found
./PetFeeder.py: line 7: USERNAME: command not found
./PetFeeder.py: line 8: PASSWORD: command not found
./PetFeeder.py: line 10: syntax error near unexpected token `(‘
./PetFeeder.py: line 10: `def feedByGmail():’
Thanks.
Hey,
Okay, Gabe’s solution above helped resolve my issues.
Just the motor issue is left now. Help!
Thanks.
just had to comment how amazing this project is! me and my son built one together and its running great!! Only time i had trouble was for some reason google wouldnt auth (had spaces in app key).
I’m glad you two enjoyed the project! That’s really great. Thank you for the feedback Jake.
I’m working on a new tutorial for a cat laser toy, which should be up in the next few days. Stay tuned.
Hey Sam,
I’m loving this tutorial but I’ve hit a stumbling block. I’m not able to make the servo rotate. I’ve followed your steps to the letter but id doesn’t move. When I run CatFeeder.py the servo makes a noise but doesn’t move.
I’m using a different servo to the one you recommended. It’s a carson reflex cs3. Will this make any difference?
Thanks in advance,
Rob
Ignore this, I did something stupid…haha
Ha! I can certainly say I know the feeling. I’m glad you were able to get it figured out, Rob. Thanks for the feedback in your other comment, it’s nice to hear the tutorial is working out for you. Feel free to reach out if you’re stuck in the future. Enjoy!
Got it to work by using Gabe’s suggestion for the cronjob script. Very cool project and the steps were straight forward. Thanks!
Thanks for the feedback, G!
Hi Sam,
Awesome project, thanks for sharing and answering questions.
I also have a question on the servo script, for some reason I can’t get my servo to start turning.
The servo is the FS5113R and I’ve followed your steps above. It does not provide any errors, but there is simply no movement.
Is there any way to troubleshoot this?
Also, when I enable pigpiod and use “set_servo_pulsewidth(18, 1600)” it does start spinning, however it also spins randomly afterwards.
Are there any additional librabries or deamons required?
Thanks in advance!
Regards,
Kevin
Hi Kevin,
If you’re able to make it move using the `pigpio` module, it may be worth continuing that route. It sounds like you’ve started the necessary daemon, and were able to at least get the motor to make an initial move. I don’t have experience with the `pigpio` library, but according to their documentation it looks like you may be able to do something like this:
set_servo_pulsewidth(18, 1000) # rotate anti-clockwise safe
time.sleep(1)
set_servo_pulsewidth(18, 1500) # rotate center safe
time.sleep(1)
set_servo_pulsewidth(18, 2000) # rotate clockwise safe
time.sleep(1)
Be sure you’re performing a
sleep
between each change to allow the motor a chance to set itself.Here’s a forum post related to pigpio which may be helpful.
Hi Sam,
Thank you for your reply.
This is indeed exactly what I had and it works, however the issue I had is that if I leave it at 1500 at the end of the script it doesn’t fully stop (the stop() function does nothing) and it spins randomly after the script closes.
Reading your link, I should change it to 0. Will definitely give it a try this evening!
Again cool post(s) and thanks for sharing.
Regards,
Kevin
Hi, so far this guide is great! I am having problem with running the servo though. I bought the one you recommended but everytime I attempt to start it, power cycles on my pi. I have confirmed that I am wired up like you said and I don’t see any visible short in the cables. How did you determine which lead on the motor was what? I can’t seem to find a wire diagram for the motor.
I’m glad you’re enjoying the guide! This sounds like a power issue. The motor draws a decent amount, so double check you’re not skimping out on the power supply. Check out the “parts” section of part one and compare your specs with the one I’ve posted there.
I can’t recall exactly where I obtained the wiring information, but a quick search revealed this page which is pretty useful.
https://www.pololu.com/product/2820/faqs
Hi Sam,
Fantastic job on detailing this! It works perfect with a pizero. I want to keep the rotations of the servo but want to reduce the food output by half. I suspect this has to do with line 35 but I haven’t figured out how to reduce the rotation of the servo. I keep putting too much food out.
Sam,
Just figured out to just reduce the time.sleep(X). I’d still love to know how the LRL is coded. Would that be in the servo GPIO documentation or in python?
Hey Carl. Apologies for the delay. I’m glad you’re experimenting with the code a bit, it’s interesting to see what happens when you start modifying things.
The code responsible for rotating the motor left, right, then left again is in the first block of code above, line 18:
`dutyCycle = 2.5 if (index % 2 == 0) else 12.5`
2.5 translated to 0 degrees, while 12.5 is 180 degrees (7.5 is 90 degrees etc…). By modifying the sleep time you’ve given the motor less time to get to its destination degree, which is why it reduced the output. It’s still trying to hit 0 and 180, but only making it a portion of the way, and that’s ok!
I hope that helps. For further reading you can look up Pulse Width Modulation and Duty Cycle.
Sam,
For some reason after I did the cronjob the servo will turn every 60 seconds. I haven’t sent an email in the last 30 minutes but it keeps turning every 60 seconds?? Also, you added a link with the Monitoring script. Where do we get the code for the monitoring script?
Thanks
Richard
Hey Richard. It sounds like your email isn’t successfully being marked as read after the script runs. Can you verify this? Make sure you don’t have any unread emails, then send yourself one with the correct subject (e.g. Feed cats). Once the servo spins, check that your email is marked read. If for some reason it isn’t, or it becomes unread (or another email is sent to that email box), you’ll see the motor continue to spin.
As for the monitoring script, I couldn’t find a reference to that in the code above. Would you mind pointing me to it? Regardless, that script isn’t necessary for this project.
Sam,
Thanks for getting back to me so quickly. I am actually trying to help a fifth grader finish this project for Maker Faire Seoul this weekend!!
When I send a feed cats email it take a pretty long time to go through and then nothing.
Thanks
Richard
That’s fantastic! I just took a look at your linked website, you’re doing some very incredible work. If you’re still stuck after reading my response to your latest comment, send me a direct email to “[email protected]” with all the files you’re using attached. I can take a deeper looked to see if any of the code seems out of sorts.
Ok, so I found a mistake in the crontab. The command I entered had Catfeeder.py (Now CatFeeder.py) So now I am back to every 60 seconds the servo turns. I sent two emails, only one came through so far and it was read. But now I don’t have any unread emails in my inbox but the servo still turns every 60 seconds???
Thanks
Richard
Interesting. It sounds like you have cron figured out at this point, and marking an email as read doesn’t seem to be an issue. All signs are pointing to the steps before that responsible for determining if a valid email is found/ should the feeder be triggered. If you just run the script normally, without the help of cron, does that trigger the feeder?
Yes, when I run the python
import CatFeeder
CatFeeder.feedByGmail()
It will turn the servo and read the email.
Right, but when you don’t have any “unread” emails in your account and you run the script, does it still turn the servo?
To avoid bloating the comments, shoot me an email ([email protected]) with the response and the code files attached, I’ll take a look and see if anything is out of sorts.
Sam,
I was able to find the problem. In the last line of the CatFeeder.py code I had an extra line from the original code feed() Once I took that out everything is working fine!!
Now on to the scheduler!!
Thanks for your help!
Richard
Excellent work. Best of luck at the maker faire!
Hi there Sam. I am another one of your very grateful readers. I am using the your concept to override a simple borehole pump controller which pumps water for over 800 people.
Sadly I cannot get the system working as a python script. If I run the commands individually at the python interpreter it all work just fine.
If I have an email unread in the inbox which has a matching subject line (“Stop-Borehole-A”), the code will detect this (as evidenced by a ids number returned) but a fault occurs when trying to mark this email as read.
Running the code below results in the following:
pi@raspberrypi:~ $ sudo python timer_by_email.py
Logging in as thosethreetowers
[56]
Low
Failed to stop pump
The code is pasted below — I hope you will be able to assist me in resolving this:
#!/usr/bin/env python
from GmailWrapper import GmailWrapper
import RPi.GPIO as GPIO
import time
HOSTNAME = ‘imap.gmail.com’
USERNAME = ‘thosethreetowers’
PASSWORD = ‘oret pyzj czml xowv’
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(18, GPIO.OUT)
gmailWrapper = GmailWrapper(HOSTNAME, USERNAME, PASSWORD)
def StartByGmail():
ids = gmailWrapper.getIdsBySubject(‘Start-Borehole-A’)
if(len(ids) > 0):
print(ids)
try:
print “High”
gpio.output(18, gpio.HIGH)
sleep (5)
gmailWrapper.markAsRead(ids)
except:
print(“Failed to start pump”)
quit()
def StopByGmail():
ids = gmailWrapper.getIdsBySubject(‘Stop-Borehole-A’)
if(len(ids) > 0):
print(ids)
try:
print “Low”
gpio.output(18, gpio.Low)
sleep (5)
gmailWrapper.markAsRead(ids)
except:
print(“Failed to stop pump”)
quit()
if __name__ == ‘__main__’:
# kick off the feeding process (move the servo)
# we now use our new feedByGmail method to handle the feeding
StartByGmail()
StopByGmail()
I am not concerned about the password details shared. These will be modified following resolution of this problem. 🙂
I also do believe that my white space in code is all correct.
I wish to thank Sam for contacting me directly with the solution 🙂 — A simple case of case error 😉
Many many thanks for this well written tutorial — Your system is now used also as a means of controlling a large pump and set of solenoid valves which direct water to various places 🙂
Great tutorial, it works very well.
Is it possible to go from 60 seconds to 10 seconds for the cronjab?
Thanks for the feedback, it’s good to hear the tutorial was easy enough to follow.
As far as I’m aware, cron only allows minimum 1 minute intervals.
If you need to run this more frequently, you’ll need to take a different approach to the problem. I just so happen to have taken an alternative approach in my Automatic Laser Pointer series. There, I run the script when the Raspberry Pi boots up, and have it running continuously, rather than every 60 seconds via cron. I log into Gmail initially at launch of the script, then every 30 seconds it scans for new emails.
Technically you could alter this to check every 10 seconds. However, too much activity to a single Gmail account could look suspicious from Google’s perspective, and may result in an account lock. With that said, I haven’t tested the thresholds as I haven’t had a need for instantaneous feedback.
Hope that helps!
i have error on ImportError: No module named ‘GmailWrapper, can you help?
Hi Navi,
I can give it a shot. To avoid bloating the comments, shoot me a message through the “Contact” page with the code you’re trying to run. This way I can take a look.