SparkFun Inventor's Kit for Edison Experiment Guide
Experiment 4: Email Notifier
Introduction
While flashing an LED is not the most interesting activity, we could use the LED as a way to notify us if something is happening. For example, we could turn on an LED if we have a new email. That way, we would know that we needed to check our inbox!
To do this, we will have the Edison log in to an email account, read the number of unread emails, and turn on an LED if it is over 0.
Parts Needed
We'll be using the same circuit as in the previous example, so you don't need to build anything new! In addition to the Edison and Block Stack, you will need the following parts:
- 1x Breadboard
- 1x RGB LED
- 1x NPN Transistor
- 1x 1kΩ Resistor
- 1x 100Ω Resistor
- 5x Jumper Wires
Suggested Reading
- How Email Works -- What actually happens when you send an email
- Callbacks -- What is a callback function?
Concepts
External Modules
In the previous few examples, we have been relying on the MRAA module to control various hardware from the Edison. MRAA comes pre-installed on the Edison, so we did not have to perform any extra steps to install it. However, in this example, we will use node-imap, which contains several useful functions for connecting to Internet Message Access Protocol (IMAP) servers and retrieving email.
If we were to log in to the Edison via SSH or serial terminal, we could install Node.js packages (e.g. modules) using the npm
command (e.g. npm install imap
). However, with the XDK, we can tell the IDE to install necessary modules automatically whenever we upload code. This is accomplished by adding the library name to a list of dependencies in the project. The specifics on how to do this can be found in "The Code" section.
Callbacks
A callback is a piece of code (often a function) that is expected to be called by another piece of code at a convenient time. A popular way to use callbacks in web programming is to assign a function to a mouse click event. For example:
language:JavaScript
element.on('click', myFunction);
myFunction() {
console.log("Hi!");
}
Whenever a user clicks on the web page element (called element
in this example), myFunction()
gets called. In this instance, myFunction()
is the callback, and our assignment, element.on('click', myFunction);
, is known as creating an event handler. See here to learn more about the .on() event handler.
Hardware Hookup
The circuit is the same as in the previous experiment.
The Code
Create a new project in the XDK. Click on package.json in the files browser on the left pane. Add the line
language:JavaScript
"imap": "0.8.16"
under "dependencies"
. This will tell the XDK to download and install the node-imap v0.8.16 module before running the program.
Copy the following code into main.js. If you are not using Gmail, you can comment out (or delete) the part labeled "Set email credentials (Gmail)". If you are using Yahoo, you will want to uncomment the part labeled "Set email credentials (Yahoo)".
Finally, change the <username>
and <password>
parts to your actual email username and password.
language:JavaScript
/*jslint node:true, vars:true, bitwise:true, unparam:true */
/*jshint unused:true */
// Leave the above lines for propper jshinting
/**
* SparkFun Inventor's Kit for Edison
* Experiment 4: Email Notifier
* This sketch was written by SparkFun Electronics
* October 29, 2015
* https://github.com/sparkfun/Inventors_Kit_For_Edison_Experiments
*
* Connect to an email service and turn on an LED if there are any unread
* emails.
*
* Released under the MIT License(http://opensource.org/licenses/MIT)
*/
// Import the MRAA and IMAP modules
var mraa = require('mraa');
var Imap = require('imap');
// Set up a digital output on MRAA pin 20 (GP12)
var ledPin = new mraa.Gpio(20);
ledPin.dir(mraa.DIR_OUT);
// It's usually a good idea to set the LED to an initial state
ledPin.write(0);
// Global LED variable to know if the LED should be on or off
var led = 0;
// Set email credentials (Gmail)
// Turn on "Acess for less secure apps" in Google account settings
// https://www.google.com/settings/security/lesssecureapps
var imap = new Imap({
user: "<username>@gmail.com",
password: "<password>",
host: "imap.gmail.com",
port: 993,
tls: true
});
// Set email credentials (Yahoo)
/*var imap = new Imap({
user: "<username>@yahoo.com",
password: "<password>",
host: "imap.mail.yahoo.com",
port: 993,
tls: true
});*/
// Open the mail box with the name "INBOX"
function openInbox(cb) {
imap.openBox("INBOX", true, cb);
}
// This is called when a connection is successfully made to the IMAP server.
// In this case, we open the Inbox and look for all unread ("unseen")
// emails. If there are any unread emails, turn on a LED.
imap.on('ready', function() {
openInbox(function(err, box) {
if (err) throw err;
// Search for unread emails in the Inbox
imap.search(["UNSEEN"], function(err, results) {
if (err) throw err;
// Print the number of unread emails
console.log("Unread emails: " + results.length);
// If there are unread emails, turn on an LED
if (results.length > 0) {
ledPin.write(1);
} else {
ledPin.write(0);
}
// Close the connection
imap.end();
});
});
});
// If we get an error (e.g. failed to connect), print that error
imap.on('error', function(err) {
console.log(err);
});
// When we close the connection, print it to the console
imap.on('end', function() {
console.log("Connection closed.");
});
// Call this function over and over again
periodicActivity();
function periodicActivity() //
{
// Perform a quick connection to the IMAP server and look for unread emails
imap.connect();
// Wait for 10 seconds before checking for emails again
setTimeout(periodicActivity, 10000);
}
What You Should See
When uploading the code, you might see some notifications that the XDK is downloading and installing npm packages. Just be patient while the packages are installed.
When you run the code, it should connect to your email account and print to the console the number of unread emails every 10 seconds.
If you have one or more unread emails, the red LED should come on.
Code to Note
Callbacks
I know that we talked about callbacks in the Concepts section, but now we get to see them in action. The node-imap package heavily relies on them, so it is important to get used to them if you plan to use that package in the future.
imap.on
is used several times. This function gets called when something particular happens in our program. These are known as "Connection Events" according to the Application Program Interface (API) documentation. We can define particular connection events using strings.
For example, imap.on('ready', function() {...});
allows us to define a function (as noted by the function()
keyword) that is called whenever a successful connection is made with the IMAP server.
imap.on('error', function() {...});
is called if there is a problem making a connection.
imap.on('end', function() {...});
is called whenever the connection with the server is closed.
There is another seemingly odd callback. In our imap.on('ready',...)
code, we call openInbox(function(err, box) {...});
. A few lines above, we define the openInbox()
function, which tells the imap
object to open our inbox and then call another function (as denoted by cb
for "callback"). This callback function (cb) is the function we defined inside openInbox(function(err, box) {...});
. So yes, we call a function that accepts another function as a parameter, which then calls that function as a callback. Don't worry if this was confusing; it is a bit hard to follow. Knowing how to use the .on()
callback functions is the most important part of this exercise.
Error Handling
Being able to appropriately deal with errors is a very useful ability in a program. The example relies on if (err) throw err;
to determine if an error has occurred (e.g. could not connect to the IMAP server).
If we dig into the node-imap code, we would likely find a few catch
statements. if (err)
only executes the second part (throw err
) if an err actually exists (not null or undefined). throw
stops execution within that loop, function, etc., and program control is passed to the first catch
statement it finds within the calling stack (e.g. look for a function that called the callback withing a try/catch statement).
If an error occurs, the node-imap module calls our callback function imap.on('error', function() {...});
, which lets us handle the error. In this case, all we do is print the error to the console.
Troubleshooting
- It won't connect to the IMAP server -- This could be caused by several reasons:
- Make sure your Edison has Internet access
- Ensure you are using the correct IMAP server settings (Gmail and Yahoo examples are given in the code)
- Check that your email and password are correct in the code
- The LED won't come on -- As always, double-check the wiring. Make sure you are seeing
Unread emails:
is appearing in the console, and that number is at least 1. Be patient, as it can take a few seconds for the program to make a connection to the IMAP server.
Going Further
Challenges
- Have the LED flash rapidly when there are unread emails.
- Have the LED turn on only when there are unread emails from a particular sender (e.g. a friend or your boss). You will need to carefully read the examples in the node-imap documentation.
- When you receive a new email, print its contents to the console and flash the LED. Hint: Take a look at the
mail(...)
callback in Connection Events in the node-imap documentation.
Digging Deeper
- How IMAP works
- Handling errors with try, catch, and throw in JavaScript
- How the throw statement works
- node-imap GitHub repository and documentation