The weather API of openweathermap.org has been a trusted source of local weather, reliably feeding my projects for quite a few years. Their API design is very complete and easy to understand, not to mention that it interfaces nicely with CircuitPython.
They recently changed the API service model for openweathermap.org. Although there is a "free" service tier, a credit card is required just in case the usage exceeds the free use threshold. My projects only need a single query response every 20 minutes, clearly falling in to the lower portion of the lowest tier, but I didn't like having to share a credit card number when no money would need to change hands. Call me old-fashioned, I guess. I started looking for better alternatives.
The National Weather Service API
After reviewing many of the free-ish weather API offerings, I stumbled on the mythological metrological holy grail, the NOAA National Weather Service (NWS) API Web Service. The NWS API provides free access to alerts, observations, and forecasts without the need for a user account or API key. I was pretty excited to find that NOAA's NWS data was available to the general public since I assumed that other weather APIs use NOAA NWS as an essential data source for their services.
The NWS API incorporates the entire national network of NWS offices and stations. A large range of services are available through the API including alerts, forecasts, aviation weather advisories, and summary report products. For my projects, I'm primarily interested in obtaining current local weather observations such as temperature, humidity, and wind speed/direction. We'll focus on that limited data scope for this Playground Note.
Queries are requested with a simple URL call to https://api.weather.gov, such as
https://api.weather.gov/stations/KSEA/observations/latest
to see the latest weather values for the Seattle-Tacoma airport in Washington state. You can test the query by pasting the URL into your browser.
API Specification
The API Specification page is a good place to spend some time to learn about the service since it lists all the available queries and includes the ability to test an API query. The testing feature is very useful for finding an observation station in your area and to see how to formulate a query URL.
JSON Data Structure
Behind the API is a hierarchical data structure that primarily focuses on Forecast Offices (a.k.a. Zones) and Observation Stations. For most projects, the station layer will meet the need for current and historical weather observations.
Finding Your Local Observation Station
Locating and identifying the observation station for your area isn't as straightforward as you'd think it would be; it's not as easy as just entering your city or zip code. It's a multi-step process to achieve our objective of finding the name (identifier) of the observation station that produces the data we need.
The (More Difficult but Precise) Points Method to Locate a Station
Step 1: Find Your Coordinate Points
First, you will need to know the latitude and longitude coordinates (the "points") of your locale. Most map applications can help with that. For example, dropping a pin onto an Apple Map will show the coordinates.
The map details show the airport located at 47.45˚N latitude and 122.31˚W longitude, which is 47.45˚ above the equator and 122.31˚ to the west of the Greenwich meridian. The NWS API requires that values above the equator and East of the Greenwich meridian be positive values. Southern hemisphere latitude values and longitude values West of the meridian are expressed as negative values. For the Sea-Tac airport, the NWS API point pair would be modified slightly and becomes 47.45,-122.31 with just a comma between the values (no spaces).
You can also use the points that correlate to your home or business -- or tent if you're camping in the woods. The next step will locate the forecast office responsible for the zone that surrounds you.
Step 2: Locate the Nearest Forecast Office
The next step in the process to find the observation station's designation, we'll use the lat/long (points) to find the closest NWS forecast office. Here's the API query URL that'll provide the information:
https://api.weather.gov/zones?point=47.45,-122.31
So apparently SEW is the NWS forecast office that covers that zone. Also, it looks like there are at least nine observation stations in the hierarchy associated with the SEW office. I'll bet one of those is Sea-Tac. Let's check.
Step 3: Find an Observation Station
We'll construct a query URL to examine the properties of the listed observation stations:
https://api.weather.gov/stations/?id=KBFI,KPAE,KPLU,KPWT,KRNT,KSEA,KTCM,KTIW,WPOW1
This query will produce the properties of each station in the list. And here's the one we need:
It's KSEA with a name property of Seattle, Seattle-Tacoma International Airport. The station coordinates are spot-on. The elevation is about 130 meters (427 feet). The other properties of the station include boundary coordinates of its forecast, county, and fire weather zones, handy for creating polygons to superimpose on a map.
To view the properties of a single station, just use:
Step 4: Verify -- Check the Latest Observations
We now have an observation station that should work for the project. Let's check the current weather data to be certain we've made the correct choice. The query URL is:
And there you have it. As of 17:56 GMT, it's 13.9˚C (57˚F) at the airport. We'll use this query URL in our project's CircuitPython code to extract the weather description, temperature, humidity, wind speed, and wind direction from the JSON results. The dew point value may also be handy for monitoring the potential for metal corrosion in the workshop.
The Slightly Simpler Method to Locate a Station: Where Airplanes Live
You may have noticed that the observation station identifier KSEA is very similar to what you may see on your airplane luggage tag, the three-letter IATA airport code SEA. That is no coincidence; almost all airport-based NWS observation stations have an identifier that follows the International Civil Aviation Organization's (ICAO) four-letter airport code scheme. The "K" prefix is a country code that represents the contiguous (kontiguous?) United States. So if the weather data of a nearby airport will work for you, it's likely that the ICAO code will suffice for identifying the NWS observation station. By the way, many small community airports have observation stations, but be aware that quality may not be as reliable as the observations of larger airport stations.
Using the API with CircuitPython
Up to this point, we've been using a browser to tour weather.gov and to find our local observation station. Now let's incorporate the API query URL into a CircuitPython project.
The following code example connects to your Wi-Fi access point as specified in the settings.toml file in the root directory of the microcontroller (MCU) board, an ESP32-S3 Feather in this case. After connecting, a socket pool and requests session are created. If the connection process encounters a Wi-Fi access error, the MCU will be reset in a minute to try again.
In the main while True loop, an attempt is made to retrieve the latest observations from weather.gov for the SeaTac airport. If retrieving the response json result is successful, the results are extracted and converted from metric to imperial values as needed. If not successful, the MCU is rebooted in order to try again.
Note: cedargrove_temperaturetools.unit_converters can be found in the CircuitPython Community Bundle. It is available using the circup command-line library installation utility.
# Retrieve and print Sea-Tac weather observations from weather.gov
# CircuitPython code for ESP32-S3 Feather
import os
import time
import ssl
import microcontroller
import supervisor
import adafruit_connection_manager
import wifi
import adafruit_requests
from cedargrove_temperaturetools.unit_converters import celsius_to_fahrenheit
try:
# Connect to WiFi access point
print(f"Connecting to {os.getenv("CIRCUITPY_WIFI_SSID")}")
wifi.radio.connect(os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD"))
print("SUCCESS: connected to WiFi access point")
except Exception as wifi_access_error:
print(f"FAILED to connect to WiFi. Error: {wifi_access_error}")
print("MCU will soft reset in 60 seconds.")
time.sleep(60)
supervisor.reload() # soft reset: keeps the terminal session alive
# Initialize a requests session
pool = adafruit_connection_manager.get_radio_socketpool(wifi.radio)
requests = adafruit_requests.Session(pool, ssl.create_default_context())
# Get weather observations from the Sea-Tac station
DATA_SOURCE = f"https://api.weather.gov/stations/KSEA/observations/latest"
while True:
# Fetch and update the weather conditions from weather.gov
print(f"Fetching Weather Observations from {DATA_SOURCE}")
try:
with requests.get(DATA_SOURCE):
response = requests.get(DATA_SOURCE)
# print(f"response.json(): {response.json()}")
print("SUCCESS: Fetch Observations")
# Extract temperature and weather condition data from API response
if response is not None:
_w_desc = response.json()["properties"]["textDescription"]
if _w_desc is None or _w_desc == "":
_w_desc = "---"
_w_temp_c = response.json()["properties"]["temperature"]["value"]
if _w_temp_c is None:
_w_temp_f = None
else:
# Convert Celsius to Fahrenheit
_w_temp_f = round(celsius_to_fahrenheit(_w_temp_c), 1)
_w_humid = response.json()["properties"]["relativeHumidity"]["value"]
if _w_humid is not None:
_w_humid = round(_w_humid, 1)
_w_wind_speed = response.json()["properties"]["windSpeed"]["value"]
if _w_wind_speed is None:
_w_wind_speed = 0
else:
# Convert kph to mph
_w_wind_speed = round(_w_wind_speed * 0.6214, 0)
_w_wind_dir = response.json()["properties"]["windDirection"]["value"]
if _w_wind_dir is None:
_w_wind_dir = -999
else:
_w_wind_dir = round(_w_wind_dir, 0)
_w_wind_gusts = response.json()["properties"]["windGust"]["value"]
if _w_wind_gusts is None:
_w_wind_gusts = 0
else:
# Convert kph to mph
_w_wind_gusts = round(_w_wind_gusts * 0.6214, 0)
_w_timestamp = response.json()["properties"]["timestamp"]
if _w_timestamp is None:
_w_timestamp = "---"
print("=" * 25)
print(_w_timestamp)
print(f"Weather Temp: {_w_temp_f}˚F")
print(f"Weather Humidity: {_w_humid} %")
print(f"Weather Wind Speed: {_w_wind_speed}mph")
print(f"Weather Wind Gusts: {_w_wind_gusts}mph")
print(f"Weather Wind Direction: {_w_wind_dir}˚")
print(f"Weather Description: {_w_desc}\n")
# Throttle query rate to 1 per min
time.sleep(60)
except Exception as update_weather_error:
print("FAILED: Fetch Observations")
print(str(update_weather_error))
print("MCU will hard reset in 60 seconds.")
time.sleep(60)
microcontroller.reset() # hard reset
Resources
DJDevon3's Web APIs and You is an exceptional guide to understanding how to use application program interfaces with CircuitPython. It's a must-read if you are developing projects that need APIs.
Summary
The NWS Web API Service appears to be fairly mature as of this writing, although it is a living schema that is expected to evolve. From my experience, there have been a few occasions when observations froze and didn't resume for hours. Also, some properties such as wind gust values reverted to a None value (no observation) after an outage and has yet to be corrected. However, for my projects that only require a limited set of current observations, the service is acceptable.
The service also has 34 linkable color graphic icons that show the weather for either day or night. The small size is 56 by 56 pixels; large is 134 by 134 pixels.
In the near future, after converting a few weather monitoring projects to the new API, I'm planning to more deeply explore the API to figure out forecasts and report products. I will also spend some time to contribute to service and operational improvements, if possible.
Wish List (in progress)
- Adafruit IO
- Collection of selectable server-side feeds for a client's AIO account with the latest weather observations for a particular latitude/longitude or NWS station, perhaps built on the existing AIO+ weather integration
- Use the throttling feature in the
adafruit_ioCircuitPython library to manage to AIO service levels (free = 30 feeds/min, AIO+ = 60 feeds/min). Would be particularly useful for managing feed rates from multiple AIO projects. - Provide a CircuitPython library for displaying weather icons.
- NWS Web API
- Observation station identifier query using a zip code or geographic name (city/state, national park, etc.)
- Background aggregation scheme that provides estimated observation values during station outages
- Documented service level specifications including request rate limits
- Feature and service request process
- Comprehensive set of code or process examples for each product and service
This page (weather.gov: A Truly Free Weather API) was last updated on July 26, 2024.