This is the second chapter in the "Finding an openweathermap Alternative," my latest choose-an-adventure saga exploring the weather API wilderness. The first chapter, weather.gov: A Truly Free Weather API, looked at the completely-free NOAA NWS API Web Service as a replacement for openweathermap.org's API. The next area in our search is the weather power-up module of the premium (not-free but affordable) Adafruit IO Plus (AIO+) service and its CircuitPython interface.
We all know about AIO's ability to collect and display sensor data as well as its integration with messaging and control protocols such as MQTT, IFTTT, and Zapier. But did you realize that, besides increasing throughput and feed limits, AIO+ also adds SMS and Apple WeatherKit services to the mix?
My Workshop Corrosion Monitor project needs 12 AIO feeds; 5 for locally connected sensors and 7 for external weather observations. The monitor can reliably detect a corrosion condition in real time by using its attached sensors. To predict when future corrosion events may happen, the monitor needs to know what's going on with the weather outside of the workshop. Rather than installing dedicated external weather sensors (requiring holes in the wall and reliable weather-proofing), openweathermap.org's API was initially used to obtain the outside weather conditions and trends. The API was an excellent implementation up until they served notice that a credit card would be required for the "free" tier of the service. That's when I started looking for alternatives.
The large number of AIO feeds for this project had already caused me to upgrade to AIO+. At the time, I didn't realize Apple WeatherKit was included in the premium service. Now I know. Replacing the openweathermap.org API service with AIO+ weather was therefore a no-new-cost alternative for the monitor project. Besides, if I'm going to be paying for a premium service, I'd rather be sending the money to Adafruit who is committed to openly describing and supporting reliable service levels, providing tutorials, and other efforts to assure product longevity.
Using MQTT to Query the Weather Service
The Message Queuing Telemetry Transport (MQTT) protocol along with a CircuitPython library or two provide a mechanism to access the AIO+ weather observations data. An MQTT subscription to a topic associated with the local weather data source produces a new json payload whenever the observation changes, usually about once every 20 minutes.
An added benefit of using MQTT with AIO is its support for publishing local sensor data to AIO feeds. The combination of retrieving information from and publishing to AIO is a perfect fit for projects like the corrosion monitor that need to integrate local and remote sensor data.
AIO+ Apple WeatherKit json Data Structure
The Apple WeatherKit json data structure is fairly flat; metadata is at the same level as the current observation values. Forecast data is also available as a part of the overall weather topic, but for the scope of the project and this example, we'll limit it to just the current observation data.
Creating a Local Weather Observation Topic
AIO+ makes it very easy to create up to 5 weather location topics. For this example, we'll be looking at the Seattle-Tacoma International Airport (Sea-Tac) as our local weather station.
Step 1: Create a Weather Location and Feed Key
After subscribing to the AIO+ service, navigate from your account's Adafruit IO page to the Power-Ups/Weather page. Click on Add Location to begin.
The Create a New Weather Service Integration window will appear. You may either type in the latitude/longitude pair or use the interactive map to pinpoint your location.
After clicking Create, the Weather by Apple WeatherKit page with the list of defined locations will appear. Note the ID integer value associated with the entered location -- you'll need that for CircuitPython to subscribe to the MQTT topic area with the current weather observations.
While still on the Weather by Apple WeatherKit page, click on the View button corresponding to the location of interest. A Weather Record window will appear, showing the location and current observation values. Click on Show JSON to see all of the data values (metadata, current, and forecasts) for the location.
Using AIO+ Weather with CircuitPython
It's time to incorporate the AIO+ weather service into a CircuitPython project. We'll be using the Adafruit ESP32-S3 Feather in this case.
The following code example defines two MQTT "callback" methods then connects the microcontroller board (MCU) to your Wi-Fi access point as specified in the settings.toml file in the root directory of the MCU. 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.
Next, the MQTT helper will be instantiated and associated with the MQTT client, aio followed by subscribing to the MQTT weather topic's current observations.
In the main while True loop, an MQTT watcher is called. For the next 5 seconds, the watcher keeps an eye on all subscribed topics to check for updates. If a topic had changed, the message callback method executes and the current observation json results are placed in an object for parsing into printable values.
Finally, after printing the observation data, the process sleeps for 55 seconds to control the AIO query rate to a single query per minute.
# Retrieve and print Sea-Tac weather observations from AdafruitIO+
# CircuitPython code for ESP32-S3 Feather
import os
import time
import json
import ssl
import supervisor
import adafruit_connection_manager
import wifi
import adafruit_requests
import adafruit_minimqtt.adafruit_minimqtt as mqtt
from adafruit_io.adafruit_io import IO_MQTT
# Get weather observations from the Sea-Tac station
W_TOPIC_KEY = 2730 # Integer value
W_TOPIC_DESC = "Sea-Tac Airport"
def subscribe(client, userdata, topic, granted_qos):
"""Callback method for when the client subscribes to a new feed."""
print(f" ... subscribed to '{topic}' with QOS level {granted_qos}")
def message(client, feed_id, payload):
"""Callback method for when a subscribed feed has a new value."""
global weather_data
if feed_id == "weather":
weather_data = json.loads(payload)
try:
# Connect to Wi-Fi 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())
# Connect to AIO and MQTT
# Initialize the MQTT Client object (keep_alive default is 300 sec)
mqtt_client = mqtt.MQTT(
broker="io.adafruit.com",
username=os.getenv("aio_username"),
password=os.getenv("aio_key"),
socket_pool=pool,
ssl_context=ssl.create_default_context(),
)
# Initialize AIO MQTT helper and connect to AIO
aio = IO_MQTT(mqtt_client)
print(" ... IO MQTT helper initialized")
aio.connect()
print(f" ... IO MQTT connected: {mqtt_client.is_connected()}")
# Connect the callback methods to AIO
aio.on_subscribe = subscribe
aio.on_message = message
# Subscribe to the AIO+ weather topic
aio.subscribe_to_weather(W_TOPIC_KEY, "current")
# Initialize the weather_data and history variables
weather_data = None
weather_data_old = None
while True:
# Fetch and update the weather conditions from AIO
aio.loop(5)
if weather_data:
_w_desc = weather_data["conditionCode"]
_w_temp_c = weather_data["temperature"]
_w_humid = weather_data["humidity"]
_w_wind_speed = weather_data["windSpeed"]
_w_wind_dir = weather_data["windDirection"]
_w_wind_gusts = weather_data["windGust"]
_w_timestamp = weather_data["metadata"]["readTime"]
if weather_data != weather_data_old:
print("-" * 13 + " NEW " + "-" * 13)
else:
print("-" * 30)
print(_w_timestamp)
print(f"Weather Temp: {_w_temp_c}˚C")
print(f"Weather Humidity: {_w_humid} %")
print(f"Weather Wind Speed: {_w_wind_speed}kph")
print(f"Weather Wind Gusts: {_w_wind_gusts}kph")
print(f"Weather Wind Direction: {_w_wind_dir}˚")
print(f"Weather Description: {_w_desc}\n")
weather_data_old = weather_data # to watch for changes
else:
print(f" ... waiting for weather observations from {W_TOPIC_DESC}")
# Throttle query rate to 1 per min
time.sleep(55)
Resources
If you are just starting to use AIO or need a refresher, the Welcome to Adafruit IO learning guide is very helpful.
Summary
The AIO+ Weather plus-up is very promising. It consists of a stable observation data source, Apple WeatherKit, and a mature collection of CircuitPython libraries. Being able to access reliable local weather data is essential for any weather project, especially since it also opens the door for AIO-based projects with a creative fusion of local sensors.
Apple WeatherKit appears to aggregate weather data from multiple sources and doesn't depend on a single NWS observation station like weather.gov. Aggregating weather observations improves reliability and adjusts as needed for observation station accuracy issues and outages. The aggregation method used must be similar to that of openweathermap.org, as the observation data is very close.
The granularity of observation values in Apple WeatherKit is better than weather.gov, a benefit of data aggregation. Note the smoother weather temperature (yellow) line in the graph. Both data sources were queried every 20 minutes. Wind speed and humidity also displayed similar granularity improvement.
Wish List
- It's a challenge when first learning to use MQTT with the AIO+ weather service. It takes some effort to discover useful examples. Most examples are generally good, but coding tutorials specifically for the weather module were not easy to find and required quite a bit of sandbox play (experimentation) to achieve a working algorithm.
- It's been difficult to find examples for how to control AIO feed rates in order to stay under account limits, particularly for multiple projects that independently query and publish to the same AIO account's feeds. Some further research is needed -- knowing that there's a user-facing throttle management scheme in AIO that's supported by the CircuitPython AIO libraries.
- As noted in Chapter 1, a server-side implementation of selectable Apple WeatherKit data presented as AIO feeds would simplify the integration with projects -- and would be a welcome feature that could create a storm of new project innovation.
- An indexed library of day/night graphical icons linked to the description field, similar to openweathermap.org or weather.gov.
This page (AIO+ Weather: A Premium Alternative for Local Observations) was last updated on July 17, 2024.