How to fail with CircuitPython
All code will have errors. You might find it sooner or later, or the error might be in your code, in a library that you are using or even in CircuitPython itself!
It is also very common to write some code on your lab environment, test it a bunch of times, then take it out into the field and watch it fail in lots of ways you would never though of. Exactly the same happens when you give your device to a user or a kid who will test it in ways that will make your coding skills feel silly.
In my case this is happening for a weather station. This should be a simple project, right? Grab some data from a sensor like a Stemma QT thermometer, then send that data to Adafruit.IO and watch it get graphed in a lovely dashboard. Or at least that was my expectation for this project.
As you can see, I started getting missing data for days, and only when I went to the semi-remote location where I am testing my weather station, and manually rebooted the devices, I would get start getting data again. But now the graph is ruined, it has a large gap in the data and to all whom I showed the graphs, the first thing they mentioned is 'what happened to the missing data?'. Now what do I do?
Look at the last hours of the graph. Even if I am missing only a little bit of data, it looks terrible right? You, the reader have no idea how hot it is during the day, how cold it is during the night, or what is the coldest hour. Your eyes go straight to the missing data at the end, with the graphs looking like a shark tooth and being jumpy. What a disaster.
I thought about blaming Adafruit.IO or maybe CircuitPython or maybe it's the fault of the ESP32 that is reading the data. Then I thought about adding extra hardware to it like a TPL5110 power timer but that would require some soldering and would break the wifi workflow that I love so much from CircuitPython 8.
I cloud also get my code full of try
and except
Python blocks on all of the parts of my code. To be honest, I did tried to do it this way, but things would still keep failing so I kept adding more try/except
blocks until the code looked terrible. I was this close to having a project that was not fun for me, and that I would not like to share how it works with this amazing community that makes CircuitPython what it is. Not sharing? That doesn't sound like me at all. Time to do things a different way.
Loving to fail
There is a bit of a hidden gem in CircuitPython and it's the supervisor
module. I was only able to find a little bit of technical documentation without a simple example that I could just copy and paste into my project, and I also found another example which was close to what I wanted to do in Tod's CircuitPython tricks.
After staring at the code I had available for a couple of minutes, I realized that this is what I had to do. I needed to let go of my impulse of keep the code failure free, and embrace CircuitPython failing. I would let if fail any time it wanted, and then I would ask it to try again.
With two simple lines, my weather station code can now fail because the wifi router is down, because the Stemma QT cables had a problem with talking to the sensor, because it was running for so long that it was finding errors in the CircuitPython base code or the request library. It doesn't matter, the board will accept that something weird happened, and would try again. Try this in your code and love to fail as much as I do now!
import supervisor
supervisor.set_next_code_file(filename='code.py', reload_on_error=True)
It should not be this simple, right? Here's how it works:
In the first line of code we are calling a library called supervisor
. Don't worry, you don't have to install anything, it comes as a standard CircuitPython library and will probably work on any board that you have CircuitPython running on. What it does, is talk to one of the internal parts of CircuitPython and modify how it will behave.
The second like, is the one that changes how it works. It's using a function called set_next_code_file
. It's normally used so that you can run the well known code.py
file and if something happens, you can define a second file with another name and that code would do something like recover things, blink a light or beep to indicate that it will not behave as it would normally do. I am not calling a second file, instead I'm telling it to go to code.py
so that it can try again.
And my favorite part comes with the code that says reload_on_error=True
. The way CircuitPython works is that when it fails it will take you to the REPL console, and show you why it failed. This is a good thing as that is the way that we learn, we make mistakes, the REPL shows us what those mistakes are, and we go and fix them.
But for my weather station, once the code was ready and it was doing what I wanted, if there is a failure like the wifi is down, it would go to the console and wait for me to do something. This weather station is located on a dry forest in Costa Rica and it doesn't know that is not next to my computer where it's easy for me to tell it to try again. So what reload_on_error=True
tells it to do, is to change what it would have usually do and take me to the console, and instead it will do something similar as if I was close so the board, pushing the RESET button every time something unexpected happens.
This way, CircuitPython will keep on trying over and over until wifi comes back, and hopefully the graphs will have smaller gaps in the data, and they will look much better and should be able to work like a weather station should. Read the thermometer. Send it to Adafruit.IO. Go to sleep to save some battery.
I love you CircuitPython! You and me can fail as many times as we need to, as long as we do it together and without feeling bad. <3.