Libraries
We'll need to make sure we have these libraries installed. (Check out this link on installing libraries if needed.)
- adafruit_bitmap_font
- adafruit_bus_device
- adafruit_display_shapes
- adafruit_display_text
- adafruit_esp32spi
- adafruit_io
- adafruit_matrixportal
- adafruit_requests.mpy
- neopixel.mpy
Connect to the Internet
Once you have CircuitPython setup and libraries installed we can get your board connected to the Internet. The process for connecting can be found here.
Text Editor
Adafruit recommends using the Mu editor for editing your CircuitPython code. You can get more info in this guide.
Alternatively, you can use any text editor that saves simple text files.
Code
Click the Download: Zip File link below in the code window to get a zip file with all the files needed for the project. Copy weather_display_matrix.py from the zip file and place on the CIRCUITPY drive, then rename it to code.py.
You'll also need to copy the following files to the CIRCUITPY drive. See the graphic at the top of the page as to filenames and where they go):
- fonts directory, which contains three bitmap fonts
- loading.bmp
- openweather_graphics.py
- weather-icons.bmp
- secrets.py (after you edit to put your WiFi and weather credentials in the file, noted below)
# SPDX-FileCopyrightText: 2020 John Park for Adafruit Industries # # SPDX-License-Identifier: MIT # Matrix Weather display # For Metro M4 Airlift with RGB Matrix shield, 64 x 32 RGB LED Matrix display """ This example queries the Open Weather Maps site API to find out the current weather for your location... and display it on a screen! if you can find something that spits out JSON data, we can display it """ import time import board import microcontroller from digitalio import DigitalInOut, Direction, Pull from adafruit_matrixportal.network import Network from adafruit_matrixportal.matrix import Matrix import openweather_graphics # pylint: disable=wrong-import-position # Get wifi details and more from a secrets.py file try: from secrets import secrets except ImportError: print("WiFi secrets are kept in secrets.py, please add them there!") raise if hasattr(board, "D12"): jumper = DigitalInOut(board.D12) jumper.direction = Direction.INPUT jumper.pull = Pull.UP is_metric = jumper.value elif hasattr(board, "BUTTON_DOWN") and hasattr(board, "BUTTON_UP"): button_down = DigitalInOut(board.BUTTON_DOWN) button_down.switch_to_input(pull=Pull.UP) button_up = DigitalInOut(board.BUTTON_UP) button_up.switch_to_input(pull=Pull.UP) if not button_down.value: print("Down Button Pressed") microcontroller.nvm[0] = 1 elif not button_up.value: print("Up Button Pressed") microcontroller.nvm[0] = 0 print(microcontroller.nvm[0]) is_metric = microcontroller.nvm[0] else: is_metric = False if is_metric: UNITS = "metric" # can pick 'imperial' or 'metric' as part of URL query print("Jumper set to metric") else: UNITS = "imperial" print("Jumper set to imperial") # Use cityname, country code where countrycode is ISO3166 format. # E.g. "New York, US" or "London, GB" LOCATION = "Los Angeles, US" print("Getting weather for {}".format(LOCATION)) # Set up from where we'll be fetching data DATA_SOURCE = ( "http://api.openweathermap.org/data/2.5/weather?q=" + LOCATION + "&units=" + UNITS ) DATA_SOURCE += "&appid=" + secrets["openweather_token"] # You'll need to get a token from openweather.org, looks like 'b6907d289e10d714a6e88b30761fae22' # it goes in your secrets.py file on a line such as: # 'openweather_token' : 'your_big_humongous_gigantor_token', DATA_LOCATION = [] SCROLL_HOLD_TIME = 0 # set this to hold each line before finishing scroll # --- Display setup --- matrix = Matrix() network = Network(status_neopixel=board.NEOPIXEL, debug=True) if UNITS in ("imperial", "metric"): gfx = openweather_graphics.OpenWeather_Graphics( matrix.display, am_pm=True, units=UNITS ) print("gfx loaded") localtime_refresh = None weather_refresh = None while True: # only query the online time once per hour (and on first run) if (not localtime_refresh) or (time.monotonic() - localtime_refresh) > 3600: try: print("Getting time from internet!") network.get_local_time() localtime_refresh = time.monotonic() except RuntimeError as e: print("Some error occured, retrying! -", e) continue # only query the weather every 10 minutes (and on first run) if (not weather_refresh) or (time.monotonic() - weather_refresh) > 600: try: value = network.fetch_data(DATA_SOURCE, json_path=(DATA_LOCATION,)) print("Response is", value) gfx.display_weather(value) weather_refresh = time.monotonic() except RuntimeError as e: print("Some error occured, retrying! -", e) continue gfx.scroll_next_label() # Pause between labels time.sleep(SCROLL_HOLD_TIME)
Open Weather Maps API Key
We'll be using OpenWeatherMaps.org to retrieve the weather info through its API. In order to do so, you'll need to register for an account and get your API key.
Go to this link and register for a free account. Once registered, you'll get an email containing your API key, also known as the "openweather token".
Copy and paste this key into your secrets.py file that is on the root level of your CIRCUITPY drive, so it looks something like this:
secrets = { 'ssid' : 'your_wifi_ssid', 'password' : 'your_wifi_password', 'openweather_token' : 'xxxxxxxxxxxxxxxxxxxxxxxx' }
Adafruit IO Time Server
In order to get the precise time, our project will query the Adafruit IO Internet of Things service for the time. Adafruit IO is absolutely free to use, but you'll need to log in with your Adafruit account to use it. If you don't already have an Adafruit login, create one here.
If you haven't used Adafruit IO before, check out this guide for more info.
Once you have logged into your account, there are two pieces of information you'll need to place in your secrets.py file: Adafruit IO username, and Adafruit IO key. Head to io.adafruit.com and simply click the View AIO Key link on the left hand side of the Adafruit IO page to get this information.
Then, add them to the secrets.py file like this:
secrets = { 'ssid' : 'your_wifi_ssid', 'password' : 'your_wifi_password', 'openweather_token' : 'xxxxxxxxxxxxxxxxxxxxxxxx', 'aio_username' : '_your_aio_username_', 'aio_key' : '_your_big_huge_super_long_aio_key_' }
Problems Getting Data
If you have any problems getting the data to display correctly, check that the secrets.py file has the information noted above.
How it Works
Libraries
First we import libraries to help us behind the scenes. time
will allow us to pause between weather queries. board
gives us pin definitions. And, we'll use the digitalio
library to query a DigitalInOut
pin in the input Direction
with Pull
down resistor for mode selection.
The adafruit_matrixportal
library will be used both for Network
queries and for controlling the Matrix
display.
We'll also import the openweather_graphics
class to take care of the graphics, text display, scrolling and more.
Secrets
Next, we import the secrets needed for WiFi access point connection, Open Weather Maps key, and more.
Jumper
For the Metro M4 Airlift version, next we set up a digital input pin and a variable called jumper in order to switch the device from imperial to metric units.
Buttons
For the Matrix Portal version we can use the on-board buttons instead. By holding down one of the button while you power on the Matrix Portal, you can set unit mode of the display. The value is stored in non-volatile memory and is automatically used the next time the Matrix Portal is powered on.
- Hold Down for metric mode
- Hold Up for imperial mode
if hasattr(board, "D12"): jumper = DigitalInOut(board.D12) jumper.direction = Direction.INPUT jumper.pull = Pull.UP is_metric = jumper.value elif hasattr(board, "BUTTON_DOWN") and hasattr(board, "BUTTON_UP"): button_down = DigitalInOut(board.BUTTON_DOWN) button_down.switch_to_input(pull=Pull.UP) button_up = DigitalInOut(board.BUTTON_UP) button_up.switch_to_input(pull=Pull.UP) if not button_down.value: print("Down Button Pressed") microcontroller.nvm[0] = 1 elif not button_up.value: print("Up Button Pressed") microcontroller.nvm[0] = 0 print(microcontroller.nvm[0]) is_metric = microcontroller.nvm[0] else: is_metric = False if is_metric: UNITS = "metric" # can pick 'imperial' or 'metric' as part of URL query print("Jumper set to metric") else: UNITS = "imperial" print("Jumper set to imperial")
Data
We'll set a variable for the DATA_SOURCE
which will be used to query Open Weather Maps API next. This query will use the LOCATION
and UNITS
variables to form the request.
LOCATION = "Los Angeles, US" print("Getting weather for {}".format(LOCATION)) DATA_SOURCE = ( "http://api.openweathermap.org/data/2.5/weather?q=" + LOCATION + "&units=" + UNITS ) DATA_SOURCE += "&appid=" + secrets["openweather_token"]
API Query and JSON
Using this information, the code can then send a query to Open Weather Maps's API that looks something like this:
http://api.openweathermap.org/data/2.5/weather?q=Los Angeles, US&appid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
(where all of those 'x's are your token).
When this query is complete, it returns a JSON file that looks like this:
{ "coord": { "lon": -118.24, "lat": 34.05 }, "weather": [ { "id": 501, "main": "Rain", "description": "moderate rain", "icon": "10d" } ], "base": "stations", "main": { "temp": 287.42, "pressure": 1016, "humidity": 50, "temp_min": 285.15, "temp_max": 289.15 }, "visibility": 16093, "wind": { "speed": 3.6, "deg": 300 }, "rain": { "1h": 1.52 }, "clouds": { "all": 75 }, "dt": 1552073935, "sys": { "type": 1, "id": 3514, "message": 0.0087, "country": "US", "sunrise": 1552054308, "sunset": 1552096542 }, "id": 5368361, "name": "Los Angeles", "cod": 200 }
JSON Traversal
The JSON file is formatted in a way that makes it easy to traverse the hierarchy and parse the data. In it, you'll see keys, such as main
, description
, icon
, and temp
, and their respective values. So, here are some key : value pairs we care about for the weather station:
"main" : "Rain"
"description" : "moderate rain"
"icon" : "10d"
"temp" : "287.42"
In order to fetch this data from the file, we need to be able to describe their locations in the file hierarchically. This is helpful, for example, in differentiating between the 'main'
weather condition and the 'main'
section containing temperature and other data. To avoid name clashing we rely on JSON traversal.
In the openweather_graphics.py file, you'll see how this is done. For example, the main key is found in this hierarchy of the JSON file: ['weather'], [0], ['main']
This means there is a key at the top level of the JSON file called 'weather'
, which has a sub-tree indexed [0]
, and then below that is the 'main'
key.
This process is used to cast the values of the temperature, weather, description, and which icon to display from the directory of bitmap icons.
Scroll Time
You can customize your scroll hold time here:
SCROLL_HOLD_TIME = 0 # set this to hold each line before finishing scroll
Setting this value to 0 means there will not be a hold when a line of text reaches the edge of the display.
Display and Network Setup
The display and network setup is next, along with setting the call to the openweather_graphics
class:
matrix = Matrix() network = Network(status_neopixel=board.NEOPIXEL, debug=True) if UNITS == "imperial" or UNITS == "metric": gfx = openweather_graphics.OpenWeather_Graphics( matrix.display, am_pm=True, units=UNITS )
Main Loop
In the main loop we check the online time server once an hour to stay in sync with internet time, and we check the weather data every ten minutes, using the gfx
call to the openweather_graphics
class.
if (not localtime_refresh) or (time.monotonic() - localtime_refresh) > 3600: try: print("Getting time from internet!") network.get_local_time() localtime_refresh = time.monotonic() except RuntimeError as e: print("Some error occured, retrying! -", e) continue # only query the weather every 10 minutes (and on first run) if (not weather_refresh) or (time.monotonic() - weather_refresh) > 600: try: value = network.fetch_data(DATA_SOURCE, json_path=(DATA_LOCATION,)) print("Response is", value) gfx.display_weather(value) weather_refresh = time.monotonic() except RuntimeError as e: print("Some error occured, retrying! -", e) continue
With the json data from Open Weather Maps parsed, we can then scroll through the text labels for weather description, humidity, wind speed, and location.
Text editor powered by tinymce.