Andrea S.
Published © CC BY-NC

E-mail watch

Check your (Gmail) mailbox (with labels) in a glance. Now works with any IMAP mail server!

IntermediateFull instructions provided8,768
E-mail watch

Things used in this project

Hardware components

Spark/Particle Core or Photon or Electron
39$ or 19$ or 29$
×1
3d printed cover
Make yours or buy one
×1
Mini-breadboard
×1
LEDs + adequate resistors
One RGB + one Yellow
×1

Story

Read more

Schematics

Breadboard circuit

It's very simple, no real need for Fritzing!

Code

Particle firmware (copy and paste in Particle IDE)

C/C++
Particle firmware (Core or Photon)
// ----------------------------------
// E-Mail Watch - Set email LEDs
// ----------------------------------

const int led_yell = D0;
const int led_blue = D7;
// Pin numbers (Core)
const int rgbPin[3] = {A7, A5, A4};     // RGB
// Pin numbers (Photon)
// const int rgbPin[3] = {WKP, A5, A4}; // RGB

// Variables
int starVal = 0;                    // Special / cloud connection status
int bankVal = 0;                    // Bank label
int persVal = 0;                    // Personal label
int servVal = 0;                    // Service/social label
int notiVal = 0;                    // Notifications label
int mailErr = 0;                    // Gmail error
const int colorDelay = 1000;        // Delay between colors
int watchdog = 0;                   // Watchdog counter
const int resetdog = 350;           // + ~10s
unsigned long uptime = 0;           // Uptime value
int rVal = 0;                       // Current red value
int gVal = 0;                       // Current green value
int bVal = 0;                       // Current blue value

// This routine runs only once upon reset
void setup()
{
   //Register our Particle functions (and variable) here
   Particle.function("mail", mailControl);
   Particle.function("label", labelControl);
   Particle.variable("uptime", &uptime, INT);

   // Configure the pins to be outputs
   pinMode(led_yell, OUTPUT);
   pinMode(led_blue, OUTPUT);
   for (int i=0; i<3; i++) {
        pinMode(rgbPin[i], OUTPUT);
   }

   // Do a LED test: all ON for 3 secs
   digitalWrite(led_yell, HIGH);
   delay(200);
   digitalWrite(led_blue, HIGH);
   delay(200);
   for (int i=0; i<3; i++) {
        analogWrite(rgbPin[i], 255);
        delay(200);
   }
   delay(3000);
   
   // Initialize all LEDs to be OFF
   digitalWrite(led_yell, LOW);
   delay(200);
   digitalWrite(led_blue, LOW);
   delay(200);
   for (int i=0; i<3; i++) {
        analogWrite(rgbPin[i], LOW);
        delay(200);
   }
   delay(500);
}


// This routine loops forever 
void loop()
{
   // Update uptime
   uptime = millis();
   
    if (--watchdog < 0) {                                       // Decrease watchdog and enter in warning state if timed out
        watchdog = -1;
        //blueState2 = LOW;
        digitalWrite(led_blue, LOW);                            // Blue status LED off
        digitalWrite(led_yell, LOW);                            // Yellow star off
        ledColors(255, 128, 0, 5);                              // Yellow warning
        }
    else {
        if (mailErr) {                                          // In case of Gmail error
            digitalWrite(led_yell, LOW);                        // Yellow star off
            ledColors(255, 0, 0, 5);                            // Gmail error (red)
        }
        else {
            for (int i=0; i<3; i++) {
                //digitalWrite(led_yell, LOW);                  // Blink star LED
                //delay(125);
                //digitalWrite(led_yell, starVal);
                //delay(125);
                //digitalWrite(led_yell, LOW);
                //delay(125);
                digitalWrite(led_yell, starVal);                // Write Special (star) LED

                if (notiVal) {                                  // Notifications
                    ledColors(notiVal*16, notiVal*8, notiVal*5, 1);
                }
                if (servVal) {                                  // Service/Social
                    ledColors(servVal*24, 0, servVal*20, 1);
                }
                if (persVal) {                                  // Personal
                    ledColors(0, persVal*28+3, 0, 1);
                }
                if (bankVal) {                                  // Bank (0-3)
                    ledColors(0, 0, bankVal*85, 1);
                }
                if (servVal+persVal+bankVal+notiVal == 0) {     // None
                    ledColors(0, 0, 0, 2);
                }
                
                //delay(2000-starVal*100);
            }
        }
    }
}
// End of loop


// Function to activate RGB LED colors
int ledColors(int r, int g, int b, int delMult)
{
    if ((r != rVal) || (g != gVal) || (b != bVal)) {
        // Fade off
        while (rVal+gVal+bVal > 0) {
            if (rVal > 0) rVal--;
            if (gVal > 0) gVal--;
            if (bVal > 0) bVal--;
            analogWrite(rgbPin[0], rVal);        // old R val
            analogWrite(rgbPin[1], gVal);        // old G val
            analogWrite(rgbPin[2], bVal);        // old B val
            delay(10);
        }
        delay (10);
        // Fade to new RGB color
        while (rVal+gVal+bVal < r+g+b) {
            if (rVal < r) rVal++;
            if (gVal < g) gVal++;
            if (bVal < b) bVal++;
            analogWrite(rgbPin[0], rVal);        // new R val
            analogWrite(rgbPin[1], gVal);        // new G val
            analogWrite(rgbPin[2], bVal);        // new B val
            delay(10);
        }
        // Save new components
        rVal = r;
        gVal = g;
        bVal = b;
    }
    delay(delMult*colorDelay);
    watchdog = watchdog - delMult;

    return 0;
}


// Read command string: E_B_P_S_N_T_
int mailControl(String command)
{
   char readLabel = command.charAt(0);                      // Read label char
   if (readLabel != 'E') {
       int letto = readLabel;
       return letto;
    }   // "E"rror
   int readVal = command.charAt(1) - '0';                   // get E value (0-1)
   if (readVal < 0 || readVal > 1) return -2;               // sanity check
   else mailErr = readVal;
   
   readLabel = command.charAt(2);                           // Read label char
   if (readLabel != 'B') return -9;                         // "B"ank
   readVal = command.charAt(3) - '0';                       // get B value (0-3)* Special range
   if (readVal < 0 || readVal > 3) return -3;               // sanity check
   else bankVal = readVal;

   readLabel = command.charAt(4);                           // Read label char
   if (readLabel != 'P') return -9;                         // "P"ersonal
   readVal = command.charAt(5) - '0';                       // get P value (0-9)
   if (readVal < 0 || readVal > 9) return -4;               // sanity check
   else persVal = readVal;

   readLabel = command.charAt(6);                           // Read label char
   if (readLabel != 'S') return -9;                         // "S"ervice / "S"ocial
   readVal = command.charAt(7) - '0';                       // get S value (0-9)
   if (readVal < 0 || readVal > 9) return -5;               // sanity check
   else servVal = readVal;

   readLabel = command.charAt(8);                           // Read label char
   if (readLabel != 'N') return -9;                         // "N"otifications
   readVal = command.charAt(9) - '0';                       // get N value (0-9)
   if (readVal < 0 || readVal > 9) return -6;               // sanity check
   else notiVal = readVal;

   readLabel = command.charAt(10);                          // Read label char
   if (readLabel != 'T') return -9;                         // s"T"ar
   readVal = command.charAt(11) - '0';                      // get N value (0-9)
   if (readVal < 0 || readVal > 9) return -7;               // sanity check
   else starVal = readVal;

   watchdog = resetdog;                                     // Reset watchdog
   digitalWrite(led_blue, LOW);
   delay(100);
   digitalWrite(led_blue, HIGH);
   delay(100);
   digitalWrite(led_blue, LOW);
   delay(100);
   digitalWrite(led_blue, HIGH);

   return mailErr;                                          // Return "E" value as status
}

// Read (one label) command string: X_ (possible labels: E_B_P_S_N_T_)
int labelControl(String command)
{
    // Read char
    char readLabel = command.charAt(0);
    char readVal = command.charAt(1) - '0';
    switch (readLabel) {
        case 'E':                                               // "E"rror
            if (readVal < 0 || readVal > 1) return -2;          // sanity check
            else mailErr = readVal;
            break;
        case 'B':                                               // "B"ank
            if (readVal < 0 || readVal > 3) return -3;          // sanity check - special range
            else bankVal = readVal;
            break;
        case 'P':                                               // "Personal"
            if (readVal < 0 || readVal > 9) return -4;          // sanity check
            else persVal = readVal;
            break;
        case 'S':                                               // "S"ervice / "S"ocial
            if (readVal < 0 || readVal > 9) return -5;          // sanity check
            else servVal = readVal;
            break;
        case 'N':                                               // "N"otifications
            if (readVal < 0 || readVal > 9) return -6;          // sanity check
            else notiVal = readVal;
            break;
        case 'T':                                               // s"T"ar
            if (readVal < 0 || readVal > 9) return -7;          // sanity check
            else starVal = readVal;
            break;
    default: 
        return readLabel;
    }

   watchdog = resetdog;                                        // Reset watchdog
   digitalWrite(led_blue, LOW);
   delay(100);
   digitalWrite(led_blue, HIGH);
   delay(100);
   digitalWrite(led_blue, LOW);
   delay(100);
   digitalWrite(led_blue, HIGH);
   return readVal;
}

Python update script

Python
It may run on a RaspberryPi or PC or any online server
# Update email watch:
# Open a connection in IDLE mode and wait for notifications from the IMAP mail server.
# Send updates to Particle

# Imports
from imapclient import IMAPClient
from time import sleep
from simplecrypt import decrypt
from base64 import b64decode
from getpass import getpass
import requests
import sys

# Gmail
HOST = 'imap.gmail.com'                                         # IMAP server address
PORT = '993'                                                    # IMAP server port
USERNAME = 'your.user.name'                                     # (G)mail user name
DEC_PASSWORD = 'decoding_password'                              # Decrypt password (NOT your (G)mail password)
TIMEOUT = 300                                                   # Loop delay (5 minutes)
# Gmail labels (or folder names)
BANK = 'Banca'                                                  # Blue led
PERSONAL = 'Personali'                                          # Green led
SOCIAL = 'Servizio'                                             # Purple led
NOTICE = 'Notifiche'                                            # Yellow led
STAR = 'Webcam'                                                 # Star led

# Particle
PARTICLE_URL = "https://api.particle.io/v1/devices/"
URL_MAIL = "/mail"                                              # Particle function name
TOKEN = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"              # Particle Access Token
DEVICE1 = "111111111111111111111111"                            # Particle device (1) ID
PART_TIMEOUT = 20                                               # Timeout when connecting to Particle (20s)

# ######### #
# Functions #
# ######### #

# Send Particle updates
def update_particle(device, url_end, data_sent):
    try:
        r = requests.post(url = PARTICLE_URL + device + url_end, data = data_sent, timeout = 30)
    except requests.exceptions.Timeout:
        print("Timeout connecting to Particle, device: " + device + ", type: " + url_end)
        s.close()
        return
    except requests.exceptions.ConnectionError:
        print("ConnectionError connecting to Particle, device: " + device + ", type: " + url_end)
        s.close()
        return
    # Debug
    response = r.text
    print("Particle server responded:\n %s" %response)

# Connect to IMAP server
def connect_gmail():
    delay = TIMEOUT
    while True:
        try:
            server = IMAPClient(HOST, PORT)
            server.login(USERNAME, password)
            server.select_folder('INBOX', readonly=True)
        except IMAPClient.Error as e:
            print("Error connecting to Gmail server: ", e)
            maildata = {'access_token':TOKEN, 'params':'E1B0P0S0N0T0'}
            update_particle('all', URL_MAIL, maildata)
        else:
            print("Connected to Gmail server")
            delay = 0
            break
        finally:
            sleep(delay)
    return server

# Start IDLE mode
def goto_idle(server):
    try:
        server.idle()
    except:
        print("Connection is NOT in IDLE mode! Quit with ^c")
        maildata = {'access_token':TOKEN, 'params':'E1B0P0S0N0T0'}
        update_particle('all', URL_MAIL, maildata)
    else:
        print("Connection is now in IDLE mode, quit with ^c")

# Retrieve maildata from IMAP server
def get_maildata(server):
    folder_status = server.folder_status(BANK, 'UNSEEN')
    bank = min(3, int(folder_status[b'UNSEEN']))
    folder_status = server.folder_status(PERSONAL, 'UNSEEN')
    personal = min(9, int(folder_status[b'UNSEEN']))
    folder_status = server.folder_status(SOCIAL, 'UNSEEN')
    social = min(9, int(folder_status[b'UNSEEN']))
    folder_status = server.folder_status(NOTICE, 'UNSEEN')
    notify = min (9, int(folder_status[b'UNSEEN']))
    folder_status = server.folder_status(STAR, 'UNSEEN')
    star = min(9, int(folder_status[b'UNSEEN']))
    # Debug
    print("New mails (B,P,S,N,T):", str(bank), str(personal), str(social), str(notify), str(star))
    # Data to be sent to Particle
    maildata = {'access_token':TOKEN,'params':'E0'+'B'+str(bank)+'P'+str(personal)+'S'+str(social)+'N'+str(notify)+'T'+str(star)}
    return maildata

# ##### #
# BEGIN #
# ##### #

# Receive encrypted Gmail password or ask user
if len(sys.argv) != 2:
    print("Enter (G)mail password:")
    password = getpass()
else:
    enc_password = sys.argv[1]
    cipher = b64decode(enc_password)
    password = decrypt(DEC_PASSWORD, cipher)

# Start IMAP connection
server = connect_gmail()

# Retrieve initial maildata
maildata = get_maildata(server)

# Initial Particle update
update_particle('all', URL_MAIL, maildata)

# Go to IDLE mode
goto_idle(server)

# Loop starts here
while True:
    try:
        # Wait for up to TIMEOUT seconds for an IDLE response
        responses = server.idle_check(timeout = TIMEOUT)
        if responses:
            try:
                server.idle_done()
                maildata = get_maildata(server)
                server.idle()
            except IMAPClient.Error as e:
                print("Error exiting idle mode (responses), retrying server connection: ", e)
                server = connect_gmail()
                maildata = get_maildata(server)
                goto_idle(server)
            finally:
                # Debug
                #print(maildata)
                update_particle('all', URL_MAIL, maildata)
        else:
            # Keepalive IMAP server connection
            try:
                server.idle_done()
                responses = server.noop()
                # Check wether we lost anything...
                if responses != (b'Success', []):
                    print("Responses: ", responses)
                    maildata = get_maildata(server)
                # Debug
                else:
                    print("Noop... ", responses)
                server.idle()
            except IMAPClient.Error as e:
                print("Error exiting idle mode (timeout), retrying server connection: ", e)
                server = connect_gmail()
                maildata = get_maildata(server)
                goto_idle(server)
            finally:
                # Keepalive watchdog
                update_particle(DEVICE1, URL_MAIL, maildata)
    except KeyboardInterrupt:
        break

server.idle_done()
print("\nIDLE mode done")
server.logout()
print("\nLogged out")

Python password encrypt script

Python
Use this script to encode your (G)mail password (only done once)
# Encrypt and b64encode a password

from simplecrypt import encrypt, decrypt
from base64 import b64encode, b64decode

enc_pass = 'your_encoding_password'     # Same as "decoding_password" in update script
gmail_pwd = 'XXXXXXXXXX'                # Your (G)mail password to be encoded

cipher = encrypt(enc_pass, gmail_pwd)
encoded_cipher = b64encode(cipher)
print(encoded_cipher)

Linux bash script (to be run on an 'always on' PC)

SH
Bash script
No preview (download only).

Command line operation

SH
Encode your Gmail password
   pi@raspberry ~ $ echo your_gmail_password | openssl enc -aes-128-cbc -a -salt -pass pass:local_encoding_password

U2FsdGVkX1+VaAnuBaYJOmqJVWn6l9HOD/2pBbShJrs+xfCN50oHaxJVdOLU32/v

Command line operation

SH
Verify your password encoding is correct
   pi@raspberry ~ $ echo U2FsdGVkX1/YCybSOE77JRZR377kYl0MnMy9zVru8q9oljUyGrzMM4vD/cOzLeqv | openssl enc -aes-128-cbc -a -d -salt -pass pass:local_encoding_password

your_gmail_password

Command line operation

SH
I recommend to launch the script at startup (with cron, for example)
   pi@raspberry ~ $ crontab -l

@reboot /home/pi/updatesparkmon.sh U2FsdGVkX1+VaAnuBaYJOmqJVWn6l9HOD/2pBbShJrs+xfCN50oHaxJVdOLU32/v  #Update Spark monitor

Credits

Andrea S.

Andrea S.

5 projects • 19 followers
Technology enthusiast, problem solver, creative thinker!

Comments