Friday, February 29, 2008

Ambient Email Notifier (some code)

Will asked about the code I was using in my ambient email notifier. The full code is a bit difficult to figure out because I've got it tied to a system tray icon thingi, which can go in another post another day, but here are some relevant bits.

First, you can get the number of emails with a particular label in gmail with the following Python code:

import feedparser
def msgCount(uid, pwd, filter):
    inbox = feedparser.parse("https://%s:%s@gmail.google.com/gmail/feed/atom%s" % (uid, pwd, filter))
    return len(inbox["entries"])

uid is your gmail address without the @gmail.com bit, filter is "" for the inbox, and "/label/" to get messages tagged with a particular label.

So, after calling msgCount a few times, for different labels, I compute the colour of the RGB LED:

colour = (inbox > 0 and 1 or 0) + (news > 0 and 2 or 0) + (work > 0 and 4 or 0)

This is sent over the serial port to the picaxe which decodes bit 0 for blue, 1 for green and 2 for red.

def triggerAmbient(colour):
    com = serial.Serial("COM3", 2400, timeout=0.25)
    for attempt in range(0,10):
        com.write("%c" % (colour) )
    com.close()

It tries to send a few times in case the picaxe doesn't get it the first time. The code on the picaxe just listens for a byte on the serial input and outputs the lowest 4 bits to the output pins:

main:
 serout 0, n2400, ("Ok")
 serin 3, n2400, b0
 gosub nibble3
 b0 = b0 & 7
 serout 0, n2400, (#b0)
 goto main
 
nibble3:
 if bit2 = 1 then
  high 1
 else
  low 1
 endif
 if bit1 = 1 then
  high 2
 else
  low 2
 endif
 if bit0 = 1 then
  high 4
 else
  low 4
 endif
 return 

This means the python script has control over the colour and you can test that it's working by simply opening up a terminal on COM3 and typing away (A is 01000001 in ASCII, meaning pin 1 is switched on, B is 01000010 so pin 2 is on, etc.)

Update: I've detailed the changes I made to get this working under Linux.

17 comments:

Will said...

thanks i will try to start this project in a week or 2 i am interested in making a little mailbox to mount onto of my flat screen monitor when there is mail the little arm swings up when ii have mail and after i have read the mail it flips down again im sure this will require harder coding but im sure one of my teachers could help me if im stuck could you possibly help me?

thanks again will

Jim said...

Will the picaxe has some really neat servo controls built into it. I bet you could do this very simply with a cheap servo.

Nermal said...

I've just used this blog to make something similar :)

It will display the build status of our subversion repo in the end but for now it's responding to a python dbus script which watches dbus for events from Pidgin and flashes an LED red for a new IM from an employee or green for an IM from anyone else :)

Handy when you've got 12 virtual desktops on the go :)

Dennis said...

Hi all,

I'd like to build something like this myself, but I'm not familiar with python at all..

Nerval (or anyone), can I ask you to help me a bit with this maybe? The circuitry and picaxe progging should be no problem for me but I don't know where to start for a python script. I reckon Tom has posted some code snippets here on this blog for use as a gmail notifier with windows, but I'd rather like to have the pidgin notifier you mention. I further plan to build some nice email notification LED into my eeepc netbook at a later time, but first need to get things working with some kind of prototype before I open up my eeepc (obvious).

I'd even try to learn python language for this, but currently my free time is spare due to having to mess with Softgrid and SQL server at work so learning a language would take forever until I'd arrive at something useful I'm afraid...

If anyone can help me w/coding or preferably supply me with a working standalone python script to check for mail annd send data via serial port I'd be very grateful.

I promise I'll build & donate a free email LED notifier (built in SMD form factor on tiny custom PCB) to the person helping me with this project.

Now thats an offer isn't it!?


If you'd like to help out, please contact me at

emailfuerdennis at web dot de


Thanks

Tom said...

Dennis, I posted an update regarding running this in Linux. It works fine, it's not really Windows specific.

Dennis said...

Hi,
Thanks for your quick reply. So if I just copy & paste the three given code blocks the script will work? I ask since you mention calling some kind of function and sending serial data several times, I bet additional code is needed for this?

The USB virtual serial port, pySerial and linux specific details should be no problem, just the python script is a miracle to me.

Tom said...

Hi Dennis,

To clarify, the code in the post is cut down from the real script as the other bits are/were particular to my situation and not going to help anyone in general. I.e. it's setup for my GMail labels and did have stuff in there for the Windows system tray.

To answer you question, cutting and pasting won't work as is, you'll need to "glue" it together into something that suits your circumstances. And the multiple calls I refered to are to GMail to get unread message counts in multiple labels, there is only one call to update the serial port.

I'd suggest something like the following as a starting point:

#!/usr/bin/python
# check gmail inbox and send signal via COM port
import feedparser, serial

def msgCount(uid, pwd, filter):
   inbox = feedparser.parse("https://%s:%s@gmail.google.com/gmail/feed/atom%s" % (uid, pwd, filter))
   return (len(inbox["entries"]), ["%s: %s"%(e["author_detail"]["name"], e["title"]) for e in inbox["entries"]])

def triggerAmbient(colour):
   com = serial.Serial("/dev/ttyUSB0", 2400, timeout=0.25)
   for attempt in range(0,10):
      com.write("%c" % (colour) )
   com.close()

if __name__ == "__main__":
   inbox = msgCount("user name", "password", "") # unread messages in inbox
   colour = 7 if inbox > 0 else 0 # low three bits specify RGB leds
   triggerAmbient(colour)

Dennis said...

:D milllion thanks!

Now I've installed python-feedparser and pyserial, slightly modified the gmail feed URL line to fit my locale needs, changed the virtual serial port to the one I'm using ("/dev/ttyUSB3") and and replaced the "username" and "password" in the "msgCount" call with my own ones.

When I call my "emailcheck.py" script nothing happens, console window hangs.

Guess its time to start building the hardware now, right? :)

Tom said...

Nice work. Though I wouldn't expect it to hang...

In case you haven't already, a quick check would be to put a couple of print statements in there so you can see how it's doing, like:

print "message count: ", inbox

Dennis said...

Ok figured it takes approx five seconds then the script terminates properly.

I also noticed there are five TX packets sent via pppd when running the script. Should there be more traffic or is this ok as is? can I monitor the serial port somehow to see if the byte is sent?

thanks

Dennis said...

Hmm. "print "message count: ", inbox"
gives "message count: (0, [])" as output.

Guess it doesn't work properly since there are three unread mails in my inbox?

This is how I modified the feed URL:
inbox = feedparser.parse("https://%s:%s@mail.google.com/mail/feed/atom%s" % (uid, pwd, filter))
return (len(inbox["entries"]), ["%s: %s"%(e["author_detail"]["name"], e["title"]) for e in inbox["entries"]])

if I paste the url in firefox and replace the %s with my username I can see the feed!

Tom said...

I've no idea how to monitor the serial port in Linux, but I'm guessing you can redirect it or open a terminal to it or something...

The notifier is essentially a hardware serial port "sniffer", but it has no buffering and shows only the lower 3 bits of the byte transmitted.

I did all the development of this in Windows, and tested that the "receiver" side was working by sending data to the email notifier with HyperTerminal.

Sending a single byte was never reliable, so that's why the loop is there to just send it 10 times. I'm sure there's a better way, changing buffer sizes or something like that, but it worked fine like that as there is only one byte in each message.

Tom said...

First, not sure why you're getting a count of 0, when you have 3 unread messages. Occassionally I have noticed I have email but the feed is empty, so maybe that's happening in this case for you too. It's never lasted long enough to force me to debug it.

Second, there's a bug in that code I gave you, the easiest fix is to change the last line of the msgCount function to just:

return len(inbox["entries"])

Sorry about that, some stuff still left around from when I had the system tray notifier going too.

Dennis said...

hm, changing msgCount to the simpler variant didn't do it. I get back
"message count: 0" when trying to run the script.

when I open "https://username:password@mail.google.com/mail/feed/atom/" with firefox, the feed is there, now with four unread messages. If I append something like "/inbox" to above url I still see the feed page but with no messages.

I suspect that in my case, feedparser browses either wrong or nonexistent email folders to retrieve its "inbox" value, and thus contains this "0" value..

The sourcecode of my rss mail feed has a tag "fullcount" that is incremented with each new incoming mail so it should be possible for feedparser to read this tag?


Any ideas how to make feedparser read the amnt of mails properly?

Dennis said...

I'm sorry for having bothered you, as it seems the python script runs fantastically whereas my gmail atom feed does not - at least not in the way/syntax I have named it.

using print "debug ", inbox
inside the msgCount function, I get the following output:

debug {'feed': {}, 'encoding': 'utf-8', 'bozo': 1, 'version': None, 'entries': [], 'bozo_exception': URLError(gaierror(-2, 'Name or service not known'),)}

Dennis said...

Got it to work now, but only by burning another feed containing the gmail feed with "feedburner". This new feed now can be read by "feedparser.parse" without problems, even SSH authorization is no problem (luckily).
It seems that my original "gmail" atom feed is sub-standard..

Now I'll try to set up email forwarding and build a circuit to test the com.write part

Do you have a paypal acc Tom? Then I'd like to pay you a beer or something.

Tom said...

Good to hear you got the RSS stuff straightened out. Good luck with the circuit, that's the fun bit :)

As for the beer money, I appreciate the thought but don't worry about it, it is my pleasure to help out. If you insist on spending some money, find a charity or open source project, they'll have a better use for it than me.