Weather display on an LED matrix
Posted on Wed 24 April 2024 in Making
Using CircuitPython on a HUB75 LED matrix
I bought a couple of surplus LED matrix panels a while back, and I finally got around to playing with them. My panels are 16x32 HUB75-compatible panels. I planned to "stack" them to create an effective 32x32 matrix. Adafruit makes a neat little ESP32-based microcontroller, the MatrixPortal that features HUB75 output and built-in WiFi support. I picked up one of them and then had everything I needed.
I wanted to do something useful with the matrix. Eventually I settled on showing the current weather conditions on it. I have an Ambient Weather weather station. It uploads data to the Ambient Weather site. Nicely, they provide a simple API where I can pull my data back down.
Given the really low resolution of my matrix, I planned to show one weather stat at a time, then cycle to the next. For example, show the temperature, then a moment later show the wind speed, and so forth. I planned to poll the API every few minutes to get updated data.
CircuitPython
While I could program the MatrixPortal in C, I'm a lot more comfortable with Python. Adafruit publishes a version of CircuitPython for this board. CircuitPython is a slimmed-down version of Python meant to run on microcontrollers. I downloaded the version for my board. The install process is pretty simple. You plug the board into a USB port and copy the CircuitPython files to the board. That's it.
It's important to note again that CircuitPython is a subset of Python. You could probably install some Python (PyPI) libraries, but more commonly you'll use the libraries that Adafruit publishes. These are provided as files that you copy as needed to the lib folder on the MatrixPortal (or other board you're using).
The MatrixPortal has pretty limited memory and storage. You need to be careful to install only the libraries you need to reduce the chance of running out of memory for your code. Make sure to delete any libraries you're not using off the board, too.
There's no compile step with CircuitPython. Simply copy your code.py file to your microcontroller. It will reboot and load your new version.
Challenges
Like Python, CircuitPython includes an automatic garbage collector to free memory when it's no longer used. However, the MatrixPortal was so limited in memory, I found I needed to force collection regularly in my program. Without that, the program would crash with an out of memory error.
Liberal use of gc.collect()
helped a lot, but didn't fully resolve the issue. For a while, I was convinced that the requests library was too heavy for the MatrixPortal. I even considered giving up on the project till I realized the silly error I was making. By default, the Ambient Weather API call I was making was returning dozens of data readings when I wanted only the most recent one. Once I added the &limit=1
param to the URL, I was able to get the program working.
The other challenge I had to overcome was how to handle the two panels I was trying to use as if it were one larger panel. The trick turned out to be the serpentine
parameter. In the following call, tile_rows
specifies I have two panels. serpentine=False
indicates I want them laid out serially, not in a serpentine fashion. You'd think that wouldn't matter with just two panels. But without that, I could not get the orientation of the two panels to work correctly.
matrix = Matrix(width=32, height=32, tile_rows=2, serpentine=False)
Adding a background graphic
Once I had the basic text version working, I decided to get fancy and show a GIF as a background. I used a graphics editor to create a 32x32 pixel image (to match my matrix size). The gifio
built-in library can be used to show static or animated GIFs. Note that the OnDiskGif()
function loads the GIF, but not the image within it until you call the next_frame()
method. (That's the method you'd call to load subsequent frames in an animated GIF.)
odg = gifio.OnDiskGif('/background.gif')
odg.next_frame() # critical or no image will appear
# Depending on your display the next line may need Colorspace.RGB565
# instead of Colorspace.RGB565_SWAPPED. If the colors are wonky,
# try changing this.
mygif = displayio.TileGrid(odg.bitmap,
pixel_shader=displayio.ColorConverter
(input_colorspace=displayio.Colorspace.RGB565_SWAPPED))
group.append(mygif)
My GIF is pretty simple. Some blue at the top to represent the sky. Some green at the bottom to represent grass. It's simple, but it made me happy to have it there.
Full code
Below is the full contents of my code.py file. The secrets.py file contains a simple dict named secrets
whose keys are my SSID, WiFi password, Ambient Weather keys, and so forth.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 |
|