My family lives in Fresno, which is a fantastic place to live, except that our air quality is quite poor.  I’m sure you can look up how poor and why it is on your own.  I recently discovered that California’s Central Valley has one of the best air quality monitoring networks in the country!  Various agencies collect air quality monitoring data at stations throughout the Valley, including 7 stations in Fresno County and 3 in the City alone!  The stations provide data which is used to predict air quality for the following day at schools, and to support a Real-Time Air Advisory Network (RAAN), which posts hourly ozone and PM2.5 data to a webpage.  Every morning, schools raise a flag indicating the air quality for the day.  The predicted air quality index determines the color of the flag.

It seems odd to me that the school warning system is based on a prediction and does not change with the air quality through the day.  The diurnal cycle of smog production results in huge fluctuations in ozone, which can creep up to very dangerous levels, even on a green flag day.  The static flag system at schools does not inform teachers and parents of these trends.  I don’t think very many people are even aware that State and local agencies (public servants) are producing all this air quality data, and I doubt that most people have the means to look it up.

As a Arduino-equipped disciple of Christ, I can’t help but consider how all the wonderful data being gathered might be better utilized to serve our community, my low-tech neighbors in particular.  A couple of web queries later, I’m well on my way to building a Real-Time Air Advisory Network People-Alerting Machine (RAANPAM)!  I figure I’ll use python script on a computer to look up the data online and tell an Arduino UNO how to display the current air quality data on some sort of big display (like a big color wheel) and sound a horn.

So far, I’ve learned enough python (VERY little required, cuz python is AWESOME!) to grab data from the California Air Resources Board (CARB) website.  CARB actually has more data, like hourly NOx, than the local Air District.  Here’s what I did in python to download the current ozone data (screws up if no data has been posted yet for the day):

This is my first time using python, so let me know if you have any pointers for me.  The idea is simply to download the HTML files from the CARB website that have the air quality data we’re looking for, parse it out with basic string functions like find(), and then somehow deliver that data to the next step for further processing/decision-making.  I imagine there’s a better way to access CARB’s data (SQL database) that wouldn’t depend on CARB maintaining the exact HTML format they use today, but this seems to work right now.  Probably to minimize redundant data pulls, the air quality query results page includes all the data for each pollutant, day, and monitoring station as a comma-separated list in a link to graph the data – a really convenient single string to grab all the data I’m looking for.

# library with now() function for getting date and time import datetime

# library with urlopen() function for loading a web page
import urllib

# Get the current time as a string
now = str(

# Convert current time to three ints (month, year, day)
year = int(now[0:4])
month = int(now[5:7])
day = int(now[8:10])

# Create the URL that will load today’s CARB ozone monitoring data at the Fresno-First monitoring station
ozoneurl = “” + str(year) + “&mon=” + str(month) + “&day=” + str(day) + “&hours=all&county_name=10-Fresno&basin=–AIR+BASIN–&latitude=–PART+OF+STATE–&report=HVAL&”

# Get a file-like object for the ARB web page with today’s ozone data.
f = urllib.urlopen(ozoneurl)
# Read from the object, storing the page’s contents in ‘ozonehtml’.
ozonehtml =

# GOAL OF NEXT FEW STEPS: Find and store the latest ozone data for processing

# Find the comma-separated ozone data (starting/ending place in the HTML)
# (the file has it embedded in a link to plot the data – pretty convenient!)
ozonedatafinish = ozonehtml.find(“Fresno-1st Street,3009″)
ozonedatastart = ozonehtml.find(“value=’”, ozonedatafinish – 500, ozonedatafinish) + 7

# Store the comma-separated ozone data
ozonedata = ozonehtml[ozonedatastart:ozonedatafinish]

# Show the data!