This post is designed for beginners at Linux and circuits. If you've done a project like this before, maybe just skip to the code.
With COVID-19 on the upswing again, I thought it would be nice to build a small appliance that could tell me how bad it is outside, without having to look up the local statistics. Here's what I used:
- 1x Raspberry Pi Zero W
- 1x 8GB micro SD card
- 1x red LED
- 1x yellow LED
- 2x 200 ohm resistor
- 1x breadboard
The general idea is to create a device that scrapes the local COVID infection data and turns on a yellow light if the situation deserves a warning, and red if the situation is dire. We'll define what those situations mean in terms of data later.
Because web scraping is full of sadness, we'll be using the reliable covidtracking.com API. This will give us data at state granularity. As a bonus, if you live in the US, you can easily follow along and make one of these to track the pandemic in your state.
Let's start by building out the Pi. Grab a copy of Raspberry Pi OS Lite and Balena Etcher. Decompress the Raspberry Pi OS image. Insert your SD card in your computer, run Balena, and flash the decompressed image to your SD card. You should end up with an SD card that now has boot and rootfs paritions. Congratulations, you've installed Raspberry Pi OS for your Raspberry Pi Zero W.
That doesn't do us much good if we can't log into the pi once it's booted, so before we eject the SD card from our computer, let's set up networking. Navigate to the boot partition of SD card. Create an empty file called "ssh" in the top-level of the boot partition. If you're on linux, you can do this in the terminal with:
This will let us log into the pi using an SSH client after it's booted. Next, let's give the pi the network configuration for our local wireless network. Create and open the file "wpa_supplicant.conf" in the top-level of the boot partition on the SD card. Copy and paste in the following contents, replacing the SSID and PSK with that of your wireless network:
Now go ahead and eject your SD card, and install it in the pi zero W. Power it up, and it should come online. If your local network has local DNS, you can probably wait a few minutes and then log in using the pi@raspberrypi as the address. But for most of us, we'll need an IP address. How you determine the IP address of our Pi Zero W will vary depending on your local router, but generally you can go to 192.168.1.1 in your web browser, and it will bring you to a log-in page for your router. Log in (if you don't know what the credentials are, try looking on the bottom of the router, or searching for "default username and password [router brand]". Often, it's admin:admin or admin:password"). Look for a page that shows the active DHCP leases. If you see one for a device called raspberrypi, that's probably it! If you don't see any hostnames, grab whatever the most recently leased IP is. Try that with the instructions below, and if it doesn't work, grab the next most recent, and so on. If you still can't get it, power down the Pi and pop the SD card back in your computer, and make sure you got the wireless network credentials right.
Once you have your Pi's IP address, log into it using an SSH client. If you're on Windows, you might choose Putty. If you're on Linux, just open up the terminal and run the following, replacing raspberrypi with your Pi's IP:
The default password for the pi user is "raspberry". You should change that once you've logged in. Do that with:
and change the password as prompted, to whatever you want, so long as you'll remember it.
Since our Pi Zero W is a headless unit, meaning it only provides a terminal and not a desktop environment, we're going to need to do all of our development work in the SSH session, without a GUI. Since I've already written all of the code, you can skip this if you just want to clone the repo and aren't going to try hacking it with your own changes. But if you are going to make some tweaks, grab yourself a terminal editor. Nano is easy to get started with, but personally I'm a big fan of vim:
sudo apt install vim
Warning: vim is for advanced users, it takes some getting used to. But it's really powerful, and once you learn it, you'll have a great tool in your arsenal that works on almost any PC.
We'll start by writing the code to get the current COVID data. To get an idea what we're working with, let's grab the COVID data for New York State (if you want to follow along using a different state, just replace "ny" with your state's code in the URL):
This downloads a file called "daily.json" to the current directory. Let's open it and see what we're working with:
Okay. How to turn this into an indicator? I chose to use the positivity rate with respect to testing because it was easy to calculate and, for my area, testing has stabalized, i.e. if you want a COVID test, you can get one. This means that the positivity rate should roughly track any upticks or downswings in COVID infections at large. There are more robust ways to track the outbreak, such as inspecting the death rate, hospitalizations, etc., but they are more complicated and require tracking the data over time. For now, I have opted not to do that.
Our positivity rate can be calculated with:
positivity rate = new positive cases / total new cases
Arbitrarily, I've decided that a positivity rate of 2% will turn on the yellow light, and a positivity rate of 5% will turn on the red one.
Let's write the code.
We're gonna use Python because we don't hate ourselves. Python's great for tiny little projects like this. It's quick to develop in and requires very little of the developer - almost everything you need to do can be done with an imported library. In this case, we're going to use the
gpiozero package to control the LEDs,
requests to fetch the web data, and
json to parse it.
gpiozero requires some dependencies. Let's install those:
apt install pigpiod python-pip
pip install gpiozero
And now we can start working on our code. Like I said, I use vim for editing, but please use whatever you're comfortable with.
We'll start by importing the modules we'll need. Put this at the top of the file:
from gpiozero import LED
from time import sleep
Next, write the function to get the data from the covidtracking API and calculate the positivity rate.
r = requests.get('https://api.covidtracking.com/v1/states/ny/current.json', allow_redirects=True)
j = json.loads(r.content)
positivity_rate = float(j["positiveIncrease"]) / float(j["totalTestResultsIncrease"])
Here, the requests.get(...) call does the same thing that wget did - it grabs the JSON-formatted COVID tracking data from the internet and downloads it into variable
r is just a raw string, it's not very convenient for parsing. So we load it as a JSON object and turn it into a map, so that we can access different parts of the data by providing the key associated with it. A key is just a string that comes before a value in the string - in this case, we're using the keys "positiveIncrease" to grab the number of new positive tests, and "totalTestResultsIncrease" to grab the total number of new tests. If you stare at the original download from wget, you'll see these strings in there, and you'll see some numbers come after them.
Now we have a function that grabs data from the web, calculates the positivity rate, and returns it. Next is to create something that will use that to light the LEDs.
Let's start by initializing our pi to a known state and setting up some constant variables. We want to make sure both of our LEDs are off to start with, and that we have thresholds set for when to turn them on.
if __name__ == '__main__':
yellow_led = LED(2)
red_led = LED(3)
warning_threshold = 0.02
danger_threshold = 0.05
seconds_in_day = 86400
We set the LEDs up on GPIO pins 2 and 3 and set our yellow and red light thresholds to 0.02 and 0.05. We've also set up the variable
seconds_in_day - we're going to use this as our polling rate. In other words, we're going to check the COVID data status once every day. This is the most sensible thing to do, since the COVID data from the COVID tracking API updates once every day at 4PM EST. Let's make our polling loop:
p_rate = get_data()
print("Positivity rate: %f" % p_rate)
This will execute forever outputting the COVID positivity rate once per day. Now let's make the LEDs operate on that data. Put this in between the print statement and the sleep:
if p_rate > danger_threshold:
elif p_rate > warning_threshold:
This turns the yellow LED on if we've passed the warning threshold and turns the red LED on if we've passed the danger threshold. Only one of the LEDs will be lit at a time. If we're beneath both thresholds, neither LED will be lit.
Now all that's left is to wire up the LEDs. Let's set up the circuits.
LEDs are basic electronic components called diodes that only pass current in one direction. LED stands for Light Emitting Diode - they just happen to be a type of diode that makes visible light when they pass current. In order to hook up your diode in the correct direction (i.e., with the correct polarity) so that it actually makes light, you need to know which side of it is positive and which side is negative. For a through-hole LED, the long leg of the LED is the positive side, and the short leg is the negative (they're called through-hole because when they're installed in a circuit board, the legs go through the holes in the board).
We'll install the LEDs into a breadboard for convenience. If you decide to make this project permanent, you can think about soldering the circuit together and encasing it all in an enclosure (maybe in something that will diffuse the light! LEDs can light up a pretty big space if they're properly diffused). Set up your breadboard like this:
If you're new to breadboards, a key thing to understand is that each row of the breadboard is continuous - you can think of it as a wire that you get to plug electronic components into. These rows end just before the two vertical rails on either side, which are notionally for power (red) and ground (blue). The vertical rails are separate but also each continuous.
What we're doing is taking power from the positive rail of the breadboard, feeding it through a resistor, then to the LED, and then out to the negative rail of the breadboard. Note that each LED is powered by a separate power rail - this is important later! We need them to be separate so that we can control them each separately. The resistor acts as a current limiter for the LED, providing some assurance that the LED won't receive so much current that it burns up. In practice, a Pi's GPIO pins are (I believe) internally limited, so this isn't really required. But it's good practice any time you use LEDs or sensitive electronics. If you're curious how to choose the right resistor for the job, check out ohm's law.
Now that the breadboard is set up, we need some way to power it. Since we want to be able to control when the LEDs get power, we're going to hook the positive rails of the breadboard up to a couple of the Pi's GPIO pins. These GPIO pins can be turned on and off in software, making them ideal for this and many other electronics hacking projects. We're going to use pins 2 and 3, since that's what we wrote in our python code.
Since the Pi pins aren't labelled, you should look at pinout.xyz to help you get oriented and find the right pins. Once you do, connect pin 2 to the positive rail feeding the yellow LED, and pin 3 to the positive rail feeding the red LED. Connect a ground pin to the ground you're using on the breadboard.
Now run the code! If you've done everything right, your LEDs should turn off when the code first starts, and once the COVID positivity rate has been calculated, you should see the appropriate LED light up (if any). If you'd like, you can connect a third, green LED, that will indicate that the positivity rate is below 2%. That's left as an exercise to the reader.