Do I Need An Umbrella Today?

Posted on

A few years ago I came across Umbrella Today. The site had a simple function, providing a weather forecast with only one variable: is it going to rain today? Enter your zip code and you get a Yes-No answer to the question. The simplicity of this appealed to me. Although it was featured on LifeHacker in 2008 the site has since gone offline. Anyway, I don’t live in the USA and therefore didn’t have a zip code.

It thought it would be fun to set up a similar system using a Raspberry Pi, an RGB LED, and the Weather Underground API. When rain is forecast the LED is blue, otherwise it’s green. The final product is shown below.

Schematic

To built your own Umbrella Advisement Notification Device, you will need:

  • Raspberry Pi (with network connection)
  • RGB LED (either common anode or cathode)
  • 3x 220Ω resistors
  • Breadboard
  • Breadboard jumper wires

The components should be assembled following the schematic below. 3 of the pins on the LED correspond to the red, green and blue LEDs inside. The longest pin on the LED should be connected to 3v3 (if common anode) or ground (if common cathode). The value of the resistors can be adjusted to preference, with larger values resulting in less brightness. For more information, see the Adafruit tutorial on RGB LEDs.

Schematic

Now that the hardware is set up we need to write some software to control the LED.

All of the code in this post is available on GitHub: https://github.com/snorfalorpagus/umbrella_today_rpi

If you haven’t already, install the RPi.GPIO and requests modules:

sudo pip install RPi.GPIO
sudo pip install requests

You will need to update the APIKEY and LOCATION variables. An API key can be requested from the Weather Underground developer site. At the time of writing, free access allows 500 queries per day. That’s enough for us! You can get the location code by copy and pasting the URL from the standard website. The example below is for Oxford, England.

The module below uses the requests module to query the Weather Underground API. The forecast for the current date is retrieved in JSON (or tomorrow, if it’s after 5pm). The umbrella_today function returns True if it forecasts greater than or equal to 1 millimetre of rainfall during the day.

"""
umbrella.py

Is it going to rain today (or tomorrow)?

Requires an API key for Weather Underground:
https://www.wunderground.com/weather/api/
"""

import requests
from datetime import datetime, timedelta

APIKEY = "YOUR API KEY HERE"
LOCATION = "gb/benson/zmw:00000.19.03658"

URL_TEMPLATE = "http://api.wunderground.com/api/{APIKEY}/forecast/q/{LOCATION}.json"

def umbrella_today():
    # get the current date
    date = datetime.now()
    # if the current time is after 5pm, use tomorrow instead
    if date.hour >= 5:
        date += timedelta(1)
    date_tuple = (date.year, date.month, date.day)

    # download forecast from weather underground
    url = URL_TEMPLATE.format(APIKEY=APIKEY, LOCATION=LOCATION)
    r = requests.get(url)
    if not r.status_code == 200:
        raise RuntimeError("Request returned status code: {}".format(r.status_code))
    data = r.json()

    # order forecasts into dictionary {date: forecast}
    records = {}
    for record in data['forecast']['simpleforecast']['forecastday']:
        year = record['date']['year']
        month = record['date']['month']
        day = record['date']['day']
        records[(year, month, day)] = record

    # get forecast rainfall in millimetres
    forecast = records[date_tuple]
    rainfall_mm = forecast['qpf_day']['mm']

    if rainfall_mm is not None and rainfall_mm >= 1:
        return True
    else:
        return False

We can test that everything is working using the Python console:

>>> from umbrella import umbrella_today
>>> umbrella_today()
True

Uh oh! Looks like we’ll need to take a raincoat.

Next we need something to control the color of the LED. The module below uses the RPi.GPIO module to control a common anode LED. For a common cathode, you’ll need to swap the order of GPIO.HIGH and GPIO.LOW in the call to GPIO.output().

"""
led.py

This module allows the color of a common anode LED to be set by name: RED,
GREEN or BLUE.

For pin numbering on the Raspberry Pi, see:
https://www.raspberrypi.org/documentation/usage/gpio/
"""

import RPi.GPIO as GPIO
import atexit

PINS = [15, 13, 11] # red, green, blue

colors = {
    'RED': (1,0,0),
    'GREEN': (0,1,0),
    'BLUE': (0,0,1),
    None: (0,0,0),
}

def setup():
    GPIO.setwarnings(False)
    GPIO.setmode(GPIO.BOARD)
    for PIN in PINS:
        GPIO.setup(PIN, GPIO.OUT)
setup()

def set_color(name):
    """Set the color of the LED"""
    rgb = colors[name]
    for PIN, val in zip(PINS, rgb):
        GPIO.output(PIN, [GPIO.HIGH, GPIO.LOW][val])

# ensure the LED is turned off when the program exits
atexit.register(lambda: set_color(None))

You can use the module above to test your hardware setup. The two lines below import the set_color function, then set the color of the LED to blue.

>>> from led import set_color
>>> set_color('BLUE')

Finally, a short script to link the modules together. Every 30 minutes the weather forecast is checked and the color of the LED is updated accordingly.

"""
main.py

Update the forecast LED every 30 minutes
"""

from led import set_color
from umbrella import umbrella_today
from time import sleep

while True:
    if umbrella_today():
        set_color('BLUE')
    else:
        set_color('GREEN')
    sleep(60 * 30) # 30 minutes

The RPi.GPIO module requires root privilege to function, so call it with sudo. If you want to leave this running you could call it using screen, or with something more complicated (+ robust).

sudo python main.py

The Raspberry Pi, Python and the requests module were all used for convenience and also due to my RPi being in prominent view in my living room. The same result could be achieved using an ESP8266 and C++ without too much difficulty, even with the limited GPIO of the ESP-01. To avoid the burden of having to parse JSON on an IC the heavy living could be offloaded to script on a webserver which just returned a True/False response.