Weather Display Matrix
2022-05-24 | By Adafruit Industries
License: See Original Project
Courtesy of Adafruit
Guide by John Park
Overview
Always know the current weather without even sticking you head out ‎the window with the Weather Display Matrix.‎
Using CircuitPython, this project queries the Open Weather Maps site ‎API to find out the current weather for your location. It displays the ‎temperature and weather condition icon, while scrolling additional ‎details such as humidity and conditions.‎
Want to change between Imperial and metric units? We've got you ‎covered! Use the Matrix Portal buttons or the added jumper on the ‎Metro M4 Airlift's pin D12 to do so.‎
Parts
or
Adafruit Matrix Portal - CircuitPython Powered Internet Display
Black LED Diffusion Acrylic Panel 12" x 12" - 0.1" / 2.6mm thick‎
‎5V 2.5A Switching Power Supply with 20AWG MicroUSB Cable‎
Micro B USB to USB C Adapter
Optional
Using M4 Airlift
If you have a Metro M4 AirLift, you can build this project easily - you ‎just need an RGB Matrix shield to help connect!‎
You will need a Metro M4 Airlift, matrix shield and matrix.
Install CircuitPython
CircuitPython is a derivative of MicroPython designed to simplify ‎experimentation and education on low-cost microcontrollers. It makes ‎it easier than ever to get prototyping by requiring no upfront desktop ‎software downloads. Simply copy and edit files on the CIRCUITPY drive ‎to iterate.‎
Set up CircuitPython Quick Start!‎
Follow this quick step-by-step for super-fast Python power :)‎
Download the latest version of CircuitPython for this board via ‎circuitpython.org
Further Information
For more detailed info on installing CircuitPython, check out Installing ‎CircuitPython.‎
Click the link above and download the latest UF2 file.‎
Download and save it to your desktop (or wherever is handy).‎
Plug your Metro M4 into your computer using a known-good USB cable.‎
A lot of people end up using charge-only USB cables and it is ‎very frustrating! So, make sure you have a USB cable you know ‎is good for data sync.‎
Double-click the Reset button next to the USB connector on your ‎board, and you will see the NeoPixel RGB LED (indicated by the arrow) ‎turn green. If it turns red, check the USB cable, try another USB port, ‎etc.‎
If double-clicking doesn't work the first time, try again. Sometimes it ‎can take a few tries to get the rhythm right!‎
You will see a new disk drive appear called METROM4BOOT.‎‎ ‎
Drag the adafruit_circuitpython_etc.uf2 file to METROM4BOOT.‎
The LED will flash. Then, the METROM4BOOT drive will disappear, and ‎a new disk drive called CIRCUITPY will appear.‎
That's it, you're done! :)‎
Build Metro M4 Airlift Matrix ‎Display
Assembly
Talking to an LED matrix display can be tricky! The 64 x 32 LED used ‎here has a whopping 2,048 pixels, and each can display RGB colors, ‎which makes for a whole lot of data to sling around. Thankfully, our ‎RGB Matrix shield paired with the Metro M4 Airlift does most of the ‎heavy lifting.‎
Let's assemble the boards and the display so we can get things running!‎
Shields Up
First, add the male headers, screw terminal block, and the 8x2-pin ‎socket to the Matrix shield, by following this guide. Be careful to ‎match the socket polarity to the silkscreen image on the board.‎
Be sure to also perform the clock pin mod as shown here.‎
Then plug the shield into the Metro M4 Airlift.‎
Power Connections
To provide power, we'll screw the wiring harness connectors to the ‎screw terminal blocks of the shield. Be sure to match the black wire ‎to GND and the red wire to +5Vout.‎
Now, simply plug the other end into the panel's power header. It can ‎only go in one way.‎
Data Cable
Plug in the two ends of the ribbon cable, note that the connectors are ‎keyed to only fit in the correct orientation.‎
Wall Adapter
We'll power the Metro M4 from the 5V 2.5 (or a 4A) DC wall adapter ‎plugged into the barrel jack. Even though USB can provide power to the ‎board, the current isn't adequate for lighting up hundreds and ‎thousands of LEDs!‎
For info on adding LED diffusion acrylic, see the page LED Matrix ‎Diffuser.‎
Using MatrixPortal
You can build this project with an all-in-one Matrix Portal board, it’s ‎definitely the easiest and least-expensive way to go about it.‎
You will need a matrix portal, matrix, and USB C power/data cable.
Prep the MatrixPortal
Power Prep
The MatrixPortal supplies power to the matrix display panel via two ‎standoffs. These come with protective tape applied (part of our ‎manufacturing process) which MUST BE REMOVED!‎
Use some tweezers or a fingernail to remove the two amber circles.‎
Power Terminals
Next, screw in the spade connectors to the corresponding standoff.‎
red wire goes to +5V
black wire goes to GND
Panel Power
Plug either one of the four-conductor power plugs into the power ‎connector pins on the panel. The plug can only go in one way, and that ‎way is marked on the board's silkscreen.‎
Board Connection
Now, plug the board into the left side shrouded 8x2 connector as ‎shown. The orientation matters, so take a moment to confirm that ‎the white indicator arrow on the matrix panel is oriented ‎pointing up and right as seen here and the MatrixPortal overhangs ‎the edge of the panel when connected. This allows you to use the edge ‎buttons from the front side.‎‎
‎Check nothing is impeding the board from plugging in firmly. If there's ‎a plastic nub on the matrix that's keeping the Portal from sitting flat, ‎cut it off with diagonal cutters.‎
For info on adding LED diffusion acrylic, see the page LED Matrix ‎Diffuser.‎
Install CircuitPython
CircuitPython is a derivative of MicroPython designed to simplify ‎experimentation and education on low-cost microcontrollers. It makes ‎it easier than ever to get prototyping by requiring no upfront desktop ‎software downloads. Simply copy and edit files on the CIRCUITPY drive ‎to iterate.‎
Set up CircuitPython Quick Start!‎
Follow this quick step-by-step for super-fast Python power :)‎
Download the latest version of CircuitPython for this board via ‎circuitpython.org
Further Information
For more detailed info on installing CircuitPython, check out Installing ‎CircuitPython.‎
Click the link above and download the latest UF2 file.‎
Download and save it to your desktop (or wherever is handy).‎
Plug your MatrixPortal M4 into your computer using a known-good ‎USB cable.‎
A lot of people end up using charge-only USB cables and it is ‎very frustrating! So, make sure you have a USB cable you know ‎is good for data sync.‎
Double-click the Reset button (indicated by the green arrow) on your ‎board, and you will see the NeoPixel RGB LED (indicated by the ‎magenta arrow) turn green. If it turns red, check the USB cable, try ‎another USB port, etc.‎
If double-clicking doesn't work the first time, try again. Sometimes it ‎can take a few tries to get the rhythm right!‎
You will see a new disk drive appear called MATRIXBOOT.‎‎ ‎
Drag the adafruit_circuitpython_etc.uf2 file to MATRIXBOOT.‎
The LED will flash. Then, the MATRIXBOOT drive will disappear, and a ‎new disk drive called CIRCUITPY will appear.‎
That's it, you're done! :)‎
Code the Weather Display Matrix
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 Wi-Fi 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 Wi-Fi access point connection, ‎Open Weather Maps key, and more.‎
Units
Want to change between Imperial and metric units? We've got you ‎covered! Use the Matrix Portal buttons or the added jumper on the ‎Metro M4 Airlift's pin D12 to do so.‎
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 buttons 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 are 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)
continueWith the json data from Open Weather Maps parsed, we can then scroll ‎through the text labels for weather description, humidity, wind ‎speed, and location.‎
LED Matrix Diffuser
LED Diffusion Acrylic
You can add an LED diffusion acrylic faceplate to your LED matrix ‎display. (Pictured here with the ON AIR project)‎
This can help protect the LEDs as well as enhance the look of the sign ‎both indoors and out by reducing glare and specular highlights of the ‎plastic matrix grid.‎
Measure and Cut the Plastic
You can use the sign to measure, and mark cut lines on the paper ‎backing of the acrylic sheet.‎
Then, use a tablesaw or bandsaw with a fine-toothed blade and a ‎guide or sled to make the cuts.‎
Note: It is possible to score and snap acrylic, but it can be very tricky to ‎get an even snap without proper clamping.‎
Peel away the paper backing from both sides and set the acrylic onto ‎your matrix display.‎
Uglu Dashes
The best method we've found for adhering acrylic to the matrix display ‎is to use Uglu Dashes clear adhesive rectangles from Pro Tapes. They ‎are incredibly strong (although can be removed if necessary), easy to ‎apply, and are invisible once attached.‎
Use one at each corner and one each at the halfway point of the long ‎edges, then press the acrylic and matrix panel together for about 20 ‎seconds.‎
Here you can see the impact of using the diffusion acrylic. (Pictured ‎here with the ON AIR sign project)‎
Stand
A very simple and attractive way to display your matrix is with the ‎adjustable bent-wire stand.‎
Alternately, you can use a frame, 3D printed brackets, tape, glue, or ‎even large binder clips to secure the acrylic to the sign and then mount ‎it on a wall, shelf, or display cabinet.‎
These mini-magnet feet can be used to stick the sign to a ferrous ‎surface.

