Email to SMS gateway solution for Google Voice users

I’m a loyal Sprint PCS cell phone subscriber. Not only is their price/feature ratio right, but they offer outstanding integration with Google Voice that is unmatched by any other national cell phone carrier.

With almost all cell phone carriers, they offer an email to SMS gateway solution, wherein anyone can send an email with a short message in it to a specific address and it will be converted by the carrier into a text message and sent to your phone. For Sprint, the form is:

10digitnumber@messaging.sprintpcs.com

This is all fine and dandy, except that if you opt in for the enhanced Google Voice integration, that email to SMS gateway no longer will function for your number. There is a common solution posted on the Internet for this problem:

Have Google Voice forward text messages to your GMail account. From Google Voice, send a text message to yourself and wait for it to show up in your inbox. Look at that email and use the from address there as the address to send email to to send SMS to your phone. The problem with this is that Google generates a unique from address for each email it sends out as a text, which ties that from address to a specific recipient, so that threaded conversation can be maintained in Google Voice. Aka, you can save that specific from address and use it from your local GMail account to email texts to yourself, but if you give that email address to someone else, the emails from them will not go through to your phone.

I required a more robust solution, where I could provide a single email address to multiple outside people/services that would act as a gateway into a text message sent to my phone.  Here is my solution:

Requirements:

  • a machine that continually is up and runs python
  • a dummy GMail account
  • a dummy Google Voice account (optional, may be hard to get if you don’t have an extra phone number to tie it to)
  • pygooglevoice installed on the server machine (either in a virtualenv or system wide), patched as shown on https://code.google.com/p/pygooglevoice/issues/detail?id=58#c24
  • python-daemon installed on the server machine (either in a virtualenv or system wide)

Here is the setup:

Run a python daemon on a machine that monitors the dummy GMail inbox periodically for new mail. When it finds on, it downloads the message, parses out the body text, logs into a Google Voice account, and texts the body of the message to your cell phone. Seem simple enough?

Here is the code for the daemon that I wrote:

'''
Created on Dec 4, 2012

Simple Python daemon that acts as a proxy to forward
emails to a cell phone (for me, my google voice number)

@author: Charlie Meyer <charlie@charliemeyer.net>
'''

import imaplib
import email
from googlevoice import Voice
import logging
import time
from daemon import runner

logger = logging.getLogger("SMSDaemon")
logger.setLevel(logging.INFO)
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
handler = logging.FileHandler("/var/log/SMSDaemon.log")
handler.setFormatter(formatter)
logger.addHandler(handler)

class EmailReceiver:

    def __init__(self):
        logger.debug("Email Init")
        self.username = "yoursmsdummyaccount@gmail.com" #edit this
        self.password = "smsdummyaccountpassword" #edit this
        self.server = "imap.gmail.com"
        self.port = 993
        self.M = None
        self.response = None

    def login(self):
        logger.debug("IMAP Login")
        self.M = imaplib.IMAP4_SSL(self.server, self.port)
        rc, self.response = self.M.login(self.username, self.password)
        logger.info(self.response)
        logger.debug("IMAP Login complete")
        return rc

    def logout(self):
        logger.debug("IMAP logout")
        self.M.logout()
        logger.debug("Logged out")

    def get_messages(self):
        logger.debug("Getting unread mail")
        self.M.select("INBOX")
        logger.debug("Getting unread count")
        status, response = self.M.status('INBOX', "(UNSEEN)")
        logger.debug("rc="+str(status))
        unreadcount = int(response[0].split()[2].strip(').,]'))
        if unreadcount > 0:
            logger.info("There are "+str(unreadcount)+" unread messages")
        logger.debug("Getting unread mail")
        status, email_ids = self.M.search(None, '(UNSEEN)')
        logger.debug("rc="+str(status))
        messages = []
        for e_id in email_ids[0].split():
            logger.info("Fetching message id="+str(e_id))
            rc, data = self.M.FETCH(e_id, '(RFC822)')
            logger.debug("Fetch complete, rc="+str(rc))
            mail = email.message_from_string(data[0][1])
            for part in mail.walk():
                if part.get_content_maintype() == 'multipart':
                    continue
                if part.get_content_subtype() != 'plain':
                    continue
                payload = part.get_payload()
                logger.info("message: "+str(payload))
                messages.append(payload)
        logger.debug("Mail fetch complete")
        return messages

class SMSSender:

    def __init__(self):
        logger.debug("SMS init")
        self.username = "dummygvoice@gmail.com" #edit this to be either your gvoice account or your dummy one if you have one
        self.password = "password" #edit this
        self.to = "your10digitphonenumber" #edit this
        self.voice = None

    def login(self):
        logger.info("SMS Login")
        self.voice = Voice()
        self.voice.login(self.username, self.password)
        logger.info("SMS Login complete")

    def logout(self):
        logger.info("SMS Logout")
        self.voice.logout()
        logger.info("SMS Logout complete")

    def send_sms(self, message):
        logger.info("Sending message: "+str(message))
        self.voice.send_sms(self.to, message)
        logger.info("Message sent")

class SMSDaemon():

    def __init__(self):
        self.stdin_path = '/dev/null'
        self.stdout_path = '/dev/tty'
        self.stderr_path = '/dev/tty'
        self.pidfile_path =  '/var/run/smsdaemon.pid'
        self.pidfile_timeout = 5

    def run(self):
        while True:
            logger.info("Daemon looping")
            g = EmailReceiver()
            g.login()
            messages = g.get_messages()
            if len(messages) > 0:
                sms = SMSSender()
                sms.login()
                for message in messages:
                    sms.send_sms(message)
                sms.logout()
            g.logout()
            logger.debug("Daemon Sleeping")
            time.sleep(60)

daemon = SMSDaemon()
daemon_runner = runner.DaemonRunner(daemon)
daemon_runner.daemon_context.files_preserve=[handler.stream]
daemon_runner.do_action()

I saved that file in /root/smsdaemon/SMSDaemon.py

I then tested it using:

python SMSDaemon.py start

and sent an email to my dummy gmail account and ensured it arrived as a text message on my phone. The program will log to /var/log/SMSDaemon.log, so you can look there to ensure it is working. Once that works, time to write the init script so it loads on boot.

First, ensure the daemon is stopped:

python SMSDaemon.py stop

Then, add the following to /etc/init.d/smsdaemon

#!/bin/bash
#
# /etc/init.d/smsdaemon
#
### BEGIN INIT INFO
# Provides: smsdaemon
# Required-Start:
# Should-Start:
# Required-Stop:
# Should-Stop:
# Default-Start:  3 5
# Default-Stop:   0 1 2 6
# Short-Description: SMSDaemon process
# Description:    Runs up the SMSDaemon process
### END INIT INFO

# modified from http://www.gavinj.net/2012/06/building-python-daemon-process.html

# uncomment and edit the following line if needed to activate the python virtual environment
# . /path_to_virtualenv/activate

case "$1" in
  start)
    echo "Starting"
    python /root/smsdaemon/SMSDaemon.py start
    ;;
  stop)
    echo "Stopping"
    python /root/smsdaemon/SMSDaemon.py stop
    ;;
  restart)
    echo "Restarting"
    python /root/smsdaemon/SMSDaemon.py restart
    ;;
  *)
    echo "Usage: /etc/init.d/smsdaemon {start|stop|restart}"
    exit 1
    ;;
esac

exit 0

Now:

sudo chmod +x /etc/init.d/smsdaemon
sudo update-rc.d smsdaemon defaults
sudo service smsdaemon start

There you go, all done!

Let me know if this works for you or if you have any comments, I might be looking to port this solution to a compute cloud like the Google App Engine or similar in the future.

ymmv

Automapping channel numbers to TiVo commands from the Ubuntu command line

In a previous post, I described how to control your TiVo from the Linux command line. I wanted to take it a bit further and make it easier to enter numeric IR codes to my TiVo. With some pointers from Alex Lambert, I came up with a solution that works on my Ubuntu 12.04 laptop.

I am assuming you followed the instructions in the previous post and have created the following symlinks:

lrwxrwxrwx 1 chuck chuck   10 Dec  4 23:24 NUM0 -> tivoircode
lrwxrwxrwx 1 chuck chuck   10 Dec  4 23:24 NUM1 -> tivoircode
lrwxrwxrwx 1 chuck chuck   10 Dec  4 23:24 NUM2 -> tivoircode
lrwxrwxrwx 1 chuck chuck   10 Dec  4 23:24 NUM3 -> tivoircode
lrwxrwxrwx 1 chuck chuck   10 Dec  4 23:24 NUM4 -> tivoircode
lrwxrwxrwx 1 chuck chuck   10 Dec  4 23:24 NUM5 -> tivoircode
lrwxrwxrwx 1 chuck chuck   10 Dec  4 23:24 NUM6 -> tivoircode
lrwxrwxrwx 1 chuck chuck   10 Dec  4 23:24 NUM7 -> tivoircode
lrwxrwxrwx 1 chuck chuck   10 Dec  4 23:25 NUM8 -> tivoircode
lrwxrwxrwx 1 chuck chuck   10 Dec  4 23:25 NUM9 -> tivoircode
lrwxrwxrwx 1 chuck chuck   10 Dec  4 23:32 ENTER -> tivoircode

Now, the goal here is that simply entering the command such as “1695” will send the sequence NUM1;NUM6;NUM9;NUM5 to the TiVo. This can be accomplished using the same facility that Ubuntu uses to suggest packages to install when you issue a command from a package that is not currently installed. To do this, add the following to your ~/.bashrc:

function is_integer() {
    s=$(echo $1 | tr -d 0-9)
    if [ -z "$s" ]; then
        return 0
    else
        return 1
    fi
}

command_not_found_handle() {
        if is_integer $1; then
                for NUM in `echo $1 | fold -w1`
                do
                        NUM$NUM
                done
#               ENTER
        else
                if [ -x /usr/lib/command-not-found -o -x /usr/share/command-not-found/command-not-found ]; then
                if [ -x /usr/lib/command-not-found ]; then
                   /usr/bin/python /usr/lib/command-not-found -- "$1"
                   return $?
                elif [ -x /usr/share/command-not-found/command-not-found ]; then
                   /usr/bin/python /usr/share/command-not-found/command-not-found -- "$1"
                   return $?
                else
                   printf "%s: command not found\n" "$1" >&2
                   return 127
                fi
                fi

        fi
}

Now, when you enter an numeric integer value on your bash session, it will send it to your TiVo as a series of IR codes for each digit in the number. You can uncomment the final ENTER command if you wish for the enter button to be pressed when a number is finished being entered on the TiVo, but that is up to you. This function copies the body of the command_not_found_handle default function from /etc/bash.bashrc that ships with Ubuntu, so that non-integer commands are sent through the old mechanism and no functionality is lost.

Happy TiVo’ing

Remote control of a TiVo from the Linux command line

I’m a huge fan of TiVo brand DVRs, so much so that I own two of them for myself. That being said,  most of the time that I am watching TV, I also have my laptop open with me. Since I run Linux on my laptops, I knew there had to be a way to write a set of shell scripts to allow me to use my always open terminal to easily manipulate my TiVo.

This is the solution that I came up with:

Assume that ~/bin is on your PATH

Create ~/bin/tivoircode with the following contents:

#!/usr/bin/env python

#simple python script to send remote IR commands to a TiVo DVR
#this script should never be called directly, but rather via
#symbolic links to it
#
#Written by Charlie Meyer <charlie@charliemeyer.net>
#December 2012

import socket
import sys
import time
import os.path

#set this to the ip address of the tivo to control
tivo_address = "192.168.1.101"

sock = None

def connect():
    global sock
    global tivo_address
    try:
        sock = socket.socket()
        sock.settimeout(5)
        sock.connect((tivo_address, 31339))
        sock.settimeout(None)
    except Exception, msg:
        print msg

def disconnect():
    global sock
    sock.close()

def send_code(code):
    if not sock:
        connect()
    try:
        sock.sendall("IRCODE "+code+"\r")
        time.sleep(0.1)
    except Exception, msg:
        print msg

times = 1;
if(len(sys.argv) > 1):
    times = int(sys.argv[1])

for i in range(0,times):
    send_code(os.path.basename(sys.argv[0]))

disconnect()

This is the base script from which all others will inherit. Obviously you will need to change the variable at the top of the file to point to the IP address of the TiVo which you want to control.

Now, here comes the fun part. There are a series of symbolic links that need to be made. The name of the link is the raw IR code that is sent to the TiVo, and they all link back to the master script we just created. I have not added all of the remote functions to my environment, but rather only the ones that I regularly use:

lrwxrwxrwx 1 chuck chuck   10 Dec  3 21:48 CLEAR -> tivoircode
lrwxrwxrwx 1 chuck chuck   10 Dec  3 21:50 PAUSE -> tivoircode
lrwxrwxrwx 1 chuck chuck   10 Dec  3 21:51 REPLAY -> tivoircode
lrwxrwxrwx 1 chuck chuck   10 Dec  3 21:51 RIGHT -> tivoircode
lrwxrwxrwx 1 chuck chuck   10 Dec  3 21:52 LEFT -> tivoircode
lrwxrwxrwx 1 chuck chuck   10 Dec  3 21:52 UP -> tivoircode
lrwxrwxrwx 1 chuck chuck   10 Dec  3 21:52 DOWN -> tivoircode
lrwxrwxrwx 1 chuck chuck   10 Dec  3 21:54 PLAY -> tivoircode
lrwxrwxrwx 1 chuck chuck   10 Dec  3 21:54 INFO -> tivoircode
lrwxrwxrwx 1 chuck chuck   10 Dec  3 21:55 SELECT -> tivoircode
lrwxrwxrwx 1 chuck chuck   10 Dec  3 21:56 ADVANCE -> tivoircode
lrwxrwxrwx 1 chuck chuck   10 Dec  3 22:02 TIVO -> tivoircode
lrwxrwxrwx 1 chuck chuck   10 Dec  3 22:03 WINDOW -> tivoircode
lrwxrwxrwx 1 chuck chuck   10 Dec  3 22:04 CHANNELUP -> tivoircode
lrwxrwxrwx 1 chuck chuck   10 Dec  3 22:04 CHANNELDOWN -> tivoircode
lrwxrwxrwx 1 chuck chuck   10 Dec  3 22:04 THUMBSDOWN -> tivoircode
lrwxrwxrwx 1 chuck chuck   10 Dec  3 22:04 THUMBSUP -> tivoircode
lrwxrwxrwx 1 chuck chuck   10 Dec  4 18:04 GUIDE -> tivoircode

To create each one of these, use the ln command, such as:

ln -s tivoircode PLAY

to link PLAY to tivoircode

You should now be able to use the command PLAY in your terminal to cause the TiVo to emulate a regular infrared remote control pressing the play button.

Based on research done online, the following IRCODE commands can be symlinked to my tivoricode script and work properly (should be self-explanatory):

UP
DOWN
LEFT
RIGHT
SELECT
TIVO
LIVETV
THUMBSUP
THUMBSDOWN
CHANNELUP
CHANNELDOWN
RECORD
DISPLAY
DIRECTV
NUM0
NUM1
NUM2
NUM3
NUM4
NUM5
NUM6
NUM7
NUM8
NUM9
ENTER
CLEAR
PLAY
PAUSE
SLOW
FORWARD
REVERSE
STANDBY
NOWSHOWING
REPLAY
ADVANCE
DELIMITER
GUIDE

I wanted to make it a little easier for me, since typing PLAY is probably harder than actually just pressing the play button on the physical remote. To solve this, I created a set of bash aliases that map shorthand commands to the symbolic links we just created. Here is what I added to my ~/.bashrc at the bottom:

#TiVO commands
alias info="INFO"
alias cl="CLEAR"
alias pa="PAUSE"
alias pu="PAUSE"
alias replay="REPLAY"
alias rw="REPLAY"
alias re="REPLAY"
alias down="DOWN"
alias d="DOWN"
alias up="UP"
alias u="UP"
alias left="LEFT"
alias l="LEFT"
alias right="RIGHT"
alias r="RIGHT"
alias play="PLAY"
alias pl="PLAY"
alias select="SELECT"
alias sel="SELECT"
alias ff="ADVANCE"
alias skip="ADVANCE"
alias tivo="TIVO"
alias zoom="WINDOW"
alias zm="WINDOW"
alias chup="CHANNELUP"
alias chu="CHANNELUP"
alias pgu="CHANNELUP"
alias pgup="CHANNELUP"
alias chdn="CHANNELDOWN"
alias chd="CHANNELDOWN"
alias pgdn="CHANNELDOWN"
alias pgd="CHANNELDOWN"
alias thumbsup="THUMBSUP"
alias thup="THUMBSUP"
alias thu="THUMBSUP"
alias thumbsdown="THUMBSDOWN"
alias thdn="THUMBSDOWN"
alias thd="THUMBSDOWN"
alias guide="GUIDE"
alias gu="GUIDE"

Now, i can use my TiVo from the konsole (via yakuake) that I always have running using commands such as `ff 6` to fast forward 6×30 seconds, `rw 3` to rewind 3×8 seconds, `pu` to pause, `pl` to play, etc etc etc

shoutout to Alex Lambert for his help pointing out some of the obvious to me