Getting weather reports using Google and Python

E

ehansen

Guest
My fiance is always asking me to give her a basic weather report on days that she works, and while I have no problem doing such things I knew there were ways to automate the process. I previously wrote a script for my business that parsed text messages being sent to a Google Voice number using PHP, and so I took that knowledge and used it in Python to make the job a lot easier.

Requirements

This guide is going to figure the following statements are true before you continue on:

  • You are using Python 2.7.3 (this hasn't been tested with anything earlier or later)
  • You have a Google Voice number
  • Said Google Voice number forwards text messages to a specific e-mail address (GMail is assumed here, no others have been tested)
  • You have a Weather Underground API key (the free service works just fine for what we are going to do here)
  • You have the Requests Python module installed (pip install requests). You can use httplib or urllib, but I always prefer Requests
The Start

Before we do anything, we'll specify some modules we'll need for this to work:


Code:
import imaplib
import smtplib
import email
import json
import requests

To make things easier for myself, I have a file in my "everything not related to work" GitHub repo that has configurations for various aspects (e-mail accounts, API keys, etc...). It's got nothing in it but a JSON object so I use this function to deal with it:

Code:
f = open('balance.sfu', 'r') data = json.loads(f.read()) f.close()
Since we're going to be using GMail, we need to get the variables from the configure script:

Code:
wu_api = data['weather_api'] gu = "%[email protected]" % (data['gmail']['user']) gp = data['gmail']['pass']
The Weather Function

Now, we will have a function to call the API each time a text message is found. If you want to take this one step further you could either cache the JSON in something like Redis or in a class structure instead of making a new API call each time. However, that will be homework.

Code:
def get_msg(state="MI", zc=48122):    import requests    import json          req = "http://api.wunderground.com/api/"+wu_api+"/conditions/q/%s/%d.json" % (state, zc)          resp = requests.get(req)          resp = resp.json['current_observation']          msg = "It is %s at %s with humidity of %s (dewpoint: %s)." % (resp['icon'], resp['temperature_string'], resp['relative_humidity'], resp['dewpoint_string'])    msg = "%s  %s are the winds," % (msg, resp['wind_string'])    msg = "%s providing a wind chill of %s and heat index of %s." % (msg, resp['windchill_string'], resp['feelslike_string'])    msg = "%s  Visibility is %s miles." % (msg, resp['visibility_mi'])    msg = "%s  Data was fetched from %s at %s." % (msg, resp['observation_location']['full'], resp['local_time_rfc822'])          return msg
Overall this is pretty simple to deal with. You can pass a zip code and/or state to Weather Underground (WU) and it will return the following JSON object:

Code:
{    "current_observation": {        "UV": "1",          "dewpoint_c": 14,          "dewpoint_f": 57,          "dewpoint_string": "57 F (14 C)",          "display_location": {            "city": "Melvindale",              "country": "US",              "country_iso3166": "US",              "elevation": "176.00000000",              "full": "Melvindale, MI",              "latitude": "42.27963257",              "longitude": "-83.18051147",              "state": "MI",              "state_name": "Michigan",              "zip": "48122"        },          "estimated": {},          "feelslike_c": "18.4",          "feelslike_f": "65.1",          "feelslike_string": "65.1 F (18.4 C)",          "forecast_url": "http://www.wunderground.com/US/MI/Melvindale.html",          "heat_index_c": "NA",          "heat_index_f": "NA",          "heat_index_string": "NA",          "history_url": "http://www.wunderground.com/weatherstation/WXDailyHistory.asp?ID=KMILINCO9",          "icon": "cloudy",          "icon_url": "http://icons-ak.wxug.com/i/c/k/cloudy.gif",          "image": {            "link": "http://www.wunderground.com",              "title": "Weather Underground",              "url": "http://icons-ak.wxug.com/graphics/wu2/logo_130x80.png"        },          "local_epoch": "1351012005",          "local_time_rfc822": "Tue, 23 Oct 2012 13:06:45 -0400",          "local_tz_long": "America/New_York",          "local_tz_offset": "-0400",          "local_tz_short": "EDT",          "ob_url": "http://www.wunderground.com/cgi-bin/findweather/getForecast?query=42.241833,-83.183273",          "observation_epoch": "1351010968",          "observation_location": {            "city": "Detroit Ave. & Fort Park, Lincoln Park",              "country": "US",              "country_iso3166": "US",              "elevation": "587 ft",              "full": "Detroit Ave. & Fort Park, Lincoln Park, Michigan",              "latitude": "42.241833",              "longitude": "-83.183273",              "state": "Michigan"        },          "observation_time": "Last Updated on October 23, 12:49 PM EDT",          "observation_time_rfc822": "Tue, 23 Oct 2012 12:49:28 -0400",          "precip_1hr_in": "-999.00",          "precip_1hr_metric": " 0",          "precip_1hr_string": "-999.00 in ( 0 mm)",          "precip_today_in": "0.27",          "precip_today_metric": "7",          "precip_today_string": "0.27 in (7 mm)",          "pressure_in": "29.99",          "pressure_mb": "1016",          "pressure_trend": "+",          "relative_humidity": "75%",          "solarradiation": "0",          "station_id": "KMILINCO9",          "temp_c": 18.4,          "temp_f": 65.1,          "temperature_string": "65.1 F (18.4 C)",          "visibility_km": "16.1",          "visibility_mi": "10.0",          "weather": "Overcast",          "wind_degrees": 180,          "wind_dir": "South",          "wind_gust_kph": 0,          "wind_gust_mph": 0,          "wind_kph": 4.3,          "wind_mph": 2.7,          "wind_string": "From the South at 2.7 MPH",          "windchill_c": "NA",          "windchill_f": "NA",          "windchill_string": "NA"    },      "response": {        "features": {            "conditions": 1        },          "termsofService": "http://www.wunderground.com/weather/api/d/terms.html",          "version": "0.1"    } }

The object out of this we are most interested in is the "current_observation", which we get by simply doing

Code:
resp = resp.json['current_observation']
Then we just pull a bunch of different variables and store it into 'msg', then ship it off.

The real fun comes in checking the texts and shipping it off:

Code:
imap = imaplib.IMAP4_SSL('imap.gmail.com') imap.login(gu, gp) imap.select('inbox')  smtp = smtplib.SMTP_SSL('smtp.gmail.com',465) smtp.login(gu, gp)
First we start off by connecting to the IMAP server via SSL and selecting the inbox. Then we establish a connection to GMail's SMTP server via SSL.

Code:
res, data = imap.uid('search', None, '((FROM "*.txt.voice.google.com") (UNSEEN))')
At the time of writing, Google Voice forwards text messages from "@txt.voice.google.com", so here we search for any e-mails that are unread ("UNSEEEN") from Google Voice that are text messages, and return the unique message ID's (instead of message numbers) in data (res is the result of the call).

Now we cycle through each unique ID like so:

Code:
for msg_id in data:    body = imap.uid('fetch', msg_id, '(RFC822)')[1][0][1]    em = email.message_from_string(body)          users, domain = email.utils.parseaddr(em['From'])[1].split('@')    parts = users.split(".")          to = em['From']
We then fetch the RFC 822 formatted e-mail body (which you typically see when you are viewing the original body in GMail), and then use Python's email library to parse the e-mail into a dictionary, making it easier to work with the headers. The recipient address will be the same as the from address so we just need to get the 'From' field of the message.

Next, we're done with the message so we'll mark it for deletion on GMail's server and then tell GMail to delete any e-mails marked as such:

Code:
    imap.uid('STORE', msg_id, '+FLAGS', '(Deleted)')    imap.expunge()
Next we set some headers to send to GMail's servers so they at least know what's going on, then send the e-mail via SMTP:

Code:
    headers = ["From: " + em['From'], "Subject: SMS from %s" % (email.utils.parseaddr(em['From'])[0]),                "To: "+to, "MIME-Version: 1.0", "Content-Type: text/html"]    headers = "rn".join(headers)          smtp.sendmail(em['From'], to, headers + "rnrn" + body_msg)
After all the e-mails are sent, we will close any open connections because we're nice programmers :)

Code:
smtp.quit() imap.close()
The ending text message will look something like this:

It is partlycloudy at 39.6 F (4.2 C) with humidity of 99% (dewpoint: 39 F (4 C)). Calm are the winds, providing a wind chill of 40 F (4 C) and heat index of 40 F (4 C). Visibility is 4.0 miles. Data was fetched from Detroit & Fort Park, Lincoln Park, Michigan at Sun, 21 Oct 2012 08:50:11 -0400.
 



Members online


Top