Software apps and online services
In this project, I show you how to track the Air Quality (using public data from the popular PurpleAir Air Quality Sensors) and displaying it on a retro Analog Gauge that shows the color-coded Air Quality Index (AQI). It is built with a Raspberry Pi, a micro-servo, an RGB LED and a ProtoStax Enclosure along with some Python.
Let's take a quick look at the system. It has a color-coded Analog Gauge for displaying the Air Quality Index, a number from 0 to 300: (source: https://www.airnow.gov/aqi/aqi-basics/)
- 0-50 - Green - Good (Air quality is satisfactory, and air pollution poses little or no risk)
- 51-100 - Yellow - Moderate (Air quality is acceptable. However, there may be a risk for some people, particularly those who are unusually sensitive to air pollution)
- 101-150 - Orange - Unhealthy for Sensitive Groups (Members of sensitive groups may experience health effects. The general public is less likely to be affected)
- 151-200 - Red - Unhealthy (Some members of the general public may experience health effects; members of sensitive groups may experience more serious health effects)
- 201-300 - Purple - Very Unhealthy (Health alert: The risk of health effects is increased for everyone.)
- 301+ - Maroon - Hazardous (Health warning of emergency conditions: everyone is more likely to be affected)
It has a micro-servo that rotates to display the given AQI, along with an RGB LED that displays that color. The RGB LED gives us a quick glance at the AQI color. The analog needle gives us an indication of where along the yellow band is the Air Quality, for example (i.e. is it closer to green, or is it closer to orange, or somewhere in between?)
It gets the Air Quality information from a specified Purple Air sensor (PM2.5 data returned in JSON format), and computes a 10 minute average AQI (Air Quality Index), and displays that value on the analog gauge and RGB LED.Quick Steps Overview
Let's take a quick look at the steps involved in this project:
- Put together the circuitry and the enclosure
- Configure the Raspberry Pi for GPIO and get the code from GitHub
- Test out LED circuitry by running led_test.py
- Micro-servo needs to be calibrated - run servo_test.py to find the appropriate duty cycles for calibration
- Enter above calibration duty cycles into aqi_monitor.py
- Find out STATION ID of your favorite PurpleAir Air Quality Sensor
- Run python aqi_monitor.py -s <STATION_ID> to display the AQI on your monitor!
Here is a video showing the various steps in a visual format. If you'd rather read about it, continue on below (or do both!) 😊Setting up your Raspberry Pi
You need to enable GPIO on your Pi and also install the prerequisite of RPi.GPIO
$ sudo apt-get install rpi.gpio
Here are the Fritzing diagram and schematic.
We use software PWM to control the micro-servo and the 3 colors of the RGB LED.
The RGB LED resistors were chosen to keep the current consumption to about 3mA for each color. The RGB LED used is a Common Anode one, so the common lead is connected to 3.3v and each of the legs of the LED (red, blue, green) are in turn connected to a GPIO pin. Because it is Common Anode, the logic is reversed - setting the GPIO high will turn OFF the corresponding LED color, and setting it LOW will turn on the color.
We also use a pulldown resistor of 10k for the servo control line. This is to prevent servo drift and random movements when the GPIO is turned off, due to signal noise. This can happen because the Raspberry Pi GPIO is left floating when turned off, and the pulldown resistor helps to keep the signal grounded when not active.
We start off mounting the Raspberry Pi on the base plate of the ProtoStax Enclosure in Platform Configuration. We also use the ProtoStax Micro Servo Analog Gauge Add-On, which is a modified Top Plate to include cutouts for the micro-servo and RGB LED. This configuration allows us to finish the circuitry and test everything out before we close up the enclosure, as shown below. We also use the Analog Gauge Scale for Air Quality Index, and a needle horn for the micro-servo, as shown:
Once the circuit has been wired up, the next step is to verify everything is ok! Using the led_test.py program, you can test out your RGB LED.
pi@pi3a:~/Work/Servo $ python led_test.py
Input comma seprated RGB numbers :
You can enter RGB values (0-255) comma separated, and it will set the appropriate colors. For example, 255, 0, 0 is red, 0, 255, 0 is green and 0, 0, 255 is blue. These will test that the individual lights are working.Test and Calibrate the Micro-Servo
We also want to test our micro-servo and ensure that it too is ok. But we have a little bit more to do here! We want to calibrate it with our Analog Gauge, so we can display the right values.
A micro-servo such as the SG90 or SG92R (used in this project) can rotate about 180 degrees. However, it does not quite get the full 180 degrees due a bunch of factors like the gears and potentiometers used in the servo.
Our analog gauge goes from 0 to 300 in 180 degrees. We want to figure out the maximum safe range of motion for the servo without jittering or stalling. I've found that I was able to get around 150 degrees of motion. This can get us from 0 to about 250 on the AQI scale, which is Very Unhealthy Air - hopefully we never get there or exceed that value!!
The servo is controlled by square-wave pulses ranging from 0.5 ms to 2.5 ms in a 20 ms pulse width. This makes it suitable to use PWM to control the servo. The above ranges result in a PWM Duty cycle of about 2 to 12. We stay between 3 and 11 to be on the safe side and not over-stress the servo.
We use servo_test.py to test out safe limits for the micro-servo, as well as figure out the duty cycles for 0 reading and 150 reading, which will enable us to do a two-point calibration.
pi@pi3a:~/Work/Servo $ python servo_test.py
Test different duty cycles to find out the:
* MIN DUTY CYCLE (this will correspond to HIGHEST indicator reading, usually around 250),
* MAX DUTY CYCLE (this will correpond to the 0 indicator
* CENTER DUTY CYCLE - find out which duty cycle gets you to a reading of 150
Note these values for use in the aqi_monitor.py program
Enter Duty Cycle (usually between 2 and 12 for SG92R, but the exact limits vary):
The way the micro-servo is positioned, the highest duty cycle we choose would correspond to the needle reading of 0, and reducing the duty cycle makes it go clockwise, to the lowest duty cycle we want to get to.
Using this, we can get readings between 0 and about 250 for the AQI, which means it won't be able to display AQI values higher than that (it will stop at the max value - there are safeguards in the code to make sure it never exceeds the duty cycle range we set).
Once we figure out the highest duty cycle we want to use (11, in my case), we then want to do the zero calibration. We place the analog gauge needle horn on the servo, pointing as close to zero as possible (going a little less than zero). We then decrease the duty cycle (remember, decreasing duty cycle makes it go clockwise) until the needle reads 0 (10.9 in my case) and then note that value.
We also note the lowest duty cycle that it can get to (3 in my case - this corresponds to the highest data reading - what that actual value is not important here).
We then try to position the needle to the center value of 150, by adjusting the duty cycle, and note the duty cycle when the needle is centered (5.75 in my case). We note this center duty value.
We'll update aqi_monitor.py with these values -
self.MIN_DUTY = 3.0 # ADJUST THESE VALUES FOR YOUR SERVO
self.MAX_DUTY = 10.9 # ADJUST THESE VALUES FOR YOUR SERVO
self.CENTER_DUTY = 5.75 # ADJUST THESE VALUES FOR YOUR SERVO
self.LOWEST_VALUE = 0 # Correspoding to MIN_DUTY
self.CENTER_VALUE = 150
This will give the program enough information to calibrate itself and the degrees of rotation required to get to the required value. We've thereby simplified the calibration setup. The program solves the equations for the AQI to duty cycle mapping utilizing this two-point calibration (Zero and 150), and also makes sure it never exceeds the min and max duty cycles.Additional Notes about Micro-Servo usage with Raspberry Pi
The Raspberry Pi GPIO pin cannot supply too much current (about 16mA). However, 5V pin can supply as much as the power supply can provide. Micro servos like the SG90 or SG92R consume about 100-300 mA at about 5V, which can be provided by the RPi 5v power supply without any problem.
The RPi GPIO also operates at 3.3V and not 5V. However, the servo control signal itself can be at 3.3v and doesn't draw much current, which means the GPIO pin can be used to drive it.
The control pulse for the servo can be provided by PWM, and that's what we're using here, using the RPi.GPIO library's PWM module. We utilize a software based PWM here and it does not have the strict time guarantees of a dedicated hardware PWM, but it is ok for our purposes.
Utilizing libraries such as pigpio will give you much more precise PWM and reduce the overall jitteriness of the servo - if you want something more advanced to delve into, rewrite the code to utilize those libraries! 😊
Additionally, when exiting the program, it calls GPIO.cleanup, which sets the pins to input and disables internal pulls. This can leave the pin in a floating state, and this noise could cause the micro-servo to move mysteriously even when the program is not even running! To counteract this and minimize noise, I've added a 10k pulldown resistor from the control GPIO pin to ground.Running the AQI monitor
After the calibration step, running the AQI monitor is pretty straightforward. It accepts as a command-line argument the station ID of the Purple Air Sensor you wish you utilize the calculate and display the AQI. Check the figure below to see how to get the Purple Air Station ID.
We can then use this station ID as a command line argument to the aqi_monitor.py program:
python aqi_monitor.py -s 85589
This way, you can very easily switch to another station of interest and start displaying those values by simply running the program with a new command line argument.
aqi_monitor.py computes and displays a 10 minutes average - so the value that's displayed may not correspond right away to the value displayed on Purple Air, which is by default its own 10-minute average computation. The popup window however shows the real time data as well as different averages in a smaller section below the main number: Your initial reading should match this "Now" data.
We store 10 minutes worth of data in a circular buffer and compute the average from that list, so we have our own implementation of a 10 minute average. You can experiment with increasing or decreasing this interval and computing different average values.A little bit about the code
I've tried to use an object-oriented approach to coding it. There are 3 main objects:
AnalogGauge, which is responsible for translating an AQI value to the corresponding PWM duty cycle required to display that value, based on the 2-point calibration. Setting the analog gauge to the right value is as easy as
AQI, which is responsible for mapping the PM2.5 concentration values we get from the Purple Air data (or the 10 minute averages we compute) to the corresponding Air Quality Index. It also maps the given Air Quality Index to the appropriate color.
aq = aqi.getAQIfromPM25(avPM)
(r, g, b) = aqi.mapAQItoColor(aq)
RGBLED - this manages the RGB LED and translates the given color value in RGB to the requisite PWM duty cycles to be used for each LED color.
rgbLED.setRGBled(r, g, b)
Every minute, we get the JSON results for the specified Purple Air Sensor, and read in the PM2.5 concentration value, which we store to a circular buffer that stores 10 minutes worth of data. Every minute, we compute the new 10 minute average for PM2.5 and then compute the AQI for that, and display the corresponding value and color on the Analog Gauge and RGB LED.
You can change this logic - for example, you could compute 10 different AQI values and then compute the average. Just know that there are many ways to skin a cat! 🐱Going Forward
Once you get comfortable playing around with the code sample and understanding the code, it is always nice to try to extend your learning with doing more.
Here are a few suggestions on how you can take this project forward:
- Learn about LED forward voltages and how to compute the resistor value to be used for each color - modify the resistors to increase the current to the LEDs to make them brighter. We current use about 3mA each, so you can go a little higher. Be careful however to not exceed the Pi's GPIO current limits - the GPIO can only provide so much current - more than that can damage the board.
- Modify the color values for the AQI to color mapping.
- Use sandpaper to lightly sand the clear RGB LED - this will make it diffuse the light and mix the red, green and blue colors better. With the clear RGB LED, the individual lights are more clearly visible and there is less mixing - it is useful to observe the separate lights in action in the clear RGB LED though.
- Use the gpiozero servo library instead of RPi.GPIO. Use pigpio with it to improve the performance of the servo and reduce jitter.
- Make it compute a 30 minute average for the AQI. Compute the 30 minute average of PM2.5 values and then compute the associated AQI. Alternately, compute the AQIs of all the 30 minute PM2.5 values, and then compute the average of the 30 AQIs - are they the same?
- Make the script run in the background, so that even when you are logged out of your Pi, the script continues to run and update your position (Hint: use nohup)
- Make the script run on boot up, so it will automatically get launched when you power up your Pi!
Can you think of other ways to extend this project? Share it with us below! 😊
Happy Making! 😊