This guide describes a Raspberry Pi device that determines the levels at which atmospheric particulate matter - a primary component in air pollution - exists in a given environment.
The sensor utilized here is accurate enough to produce measurements in line with accepted institutional reporting. It also includes an integrated environmental sensor to allow measurement of temperature, humidity and barometric pressure (T/H/P).
The device runs a bespoke sensor-focused data streaming platform written in Python and easily usable on any Debian-like Linux distribution.
The platform includes a library and tools to configure & manage sensors and publish their data as well as tools to be used elsewhere in the data infrastructure (i.e. not on the sensor devices) to transform, interpret and persist the resulting sensor data stream in useful ways.
How: HardwareI won't be going into any detail about how to setup a Raspberry Pi, install Raspbian on it, enable the I²C interface, enable WiFi at first-boot or enable SSH at first-boot because other folks already have done the job quite well indeed. You will need all of those for the remainder of this guide, however.
The Sensirion SPS30 particulate matter sensor uses JST's ZH-style 1.5mm-pitch connector (datasheet), for which sourcing a pre-built cable assembly in the US turned out to be a significant challenge!
Sensirion's support team did their best to help but their suggestion directed me to nothing more than the bare female connector for which I've neither the know-how nor materials to turn into a usable assembly. Fortunately, I was able to eventually find what I needed.
I also wanted to include environmental sensing in the unit and it just so happened that I'd ordered a number of BME280 sensors on nice breakout boards recently.
To foster modularity and ease-of-use among devices I prefer non-permanent connection methods such as male/female Dupont-style headers and connectors, which is what I've used here.
The bulk of the construction required was very simple: I soldered the BME280 onto a small perma-proto board into which the jumpers from the Raspberry Pi enter - carrying 3V3 & 5V to satisfy both the BME280 and SPS30 respectively - and out to which the ZH-5 assembly (carrying only 5V power of course) connects to the SPS30.
(Note the important correction in the caption above: many thanks to ti-tipakorn for alerting me to it! I'll update the Fritzing diagram when I have the time.)
One of the main benefits of the I²C bus is device addressing, allowing many devices to share the same physical bus and here allowing me to bring only five leads (5V
, 3V3
, GND
, SDA
& SCL
) onto my board for both devices.
Just in case the diagram doesn't make it entirely clear, the pin connections from the Pi are as follows (links leading to pinout.xyz further info):
5V
from pin 23V3
from pin 1SDA
from pin 3 (Broadcom pin 2)SCL
from pin 5 (Broadcom pin 3)GND
from any ground pin. Illustrated above as pin 14 to better fit in the diagram, however "zed" actually uses pin 6 as it is closest to the I²C pins.
It's important to note that the SPS30 is a dual-mode device and therefore can operate in either UART (serial) or I²C mode. Accordingly, care must be taken to heed this highlighted comment from the interface specification, as I've shown above by connecting both pins 4 and 5 to GND
:
I use this small board that exposes MicroUSB's power and ground directly, allowing me to avoid using the MicroUSB power connector on the RPi itself, which wouldn't allow it to fit in the enclosure in the desired orientation.
(Astute readers may notice this is an update: the original construction used the Adafruit Powerboost 1000C as I'd not yet had the MicroUSB breakout boards available. The project photo does indeed show "zed" with a Powerboost 1000C instead. Good eyes!)
Speaking of enclosures: I've found the outdoor electrical section of my local hardware store akin to a Maker's candy store: I find inspiration every time I wander that aisle.
In this case I chose a 2-outlet weatherproof electrical conduit box, with the two outlets on opposite sides from each other (180 degrees): I'm hoping that this will create passive airflow through the enclosure. I've also mounted the SPS30 along the axis between the two outlets, directly along the path of the airflow (the SPS30's inlet is that small white rectangle in the green portion, just below the black fan cover).
Everything is crammed into the enclosure, "secured" with a bit of duct & Gorilla tape, and hung on a wall a few inches below the ceiling plane (or: a few feet above "head level").
This mounting strategy, along with the enclosure, helps to ensure the sensors are affected by short-term changes to the external environment as little as possible.
As a software engineer by trade with extensive hardware exposure, I've always admired hardware but never done much with it on my own. This year I set out to change that starting with a spare RPi I've had lying around for years.
That small exploration spiraled out of control and where there's software to be written I'm naturally inclined to write it. That's where RPJiOS came from.
You may note I've elided many details regarding the software itself: I'm ambitiously planning an separate project guide entirely on that! Stay tuned (and wish me luck)...
Onto the setup. All of the following commands must be run on the Raspberry Pi itself. Enabling WiFi & SSH at first-boot is easy.
The following has been tested to work - after a critical fix to the process was identified and graciously detailed by William R, many thanks! - on a Pi Zero W with a clean install of Raspbian Stretch Lite 2018-11-13.
- You'll need
git
for this, so be sure to install it first if you haven't already:
$ sudo apt install -y git
- Clone the repository:
$ git clone https://github.com/rpj/rpi.git
- Within the new
rpi
directory created by the clone, runsetup.sh
:
$ cd rpi/
$ ./setup.sh
Please do not call setup.sh
as the superuser (root). The script may request sudo
access from you for specific installations of required utilities, but overall you do not want to run this script as the superuser.
The setup script can take awhile depending on how clean your install is and the speed of your device: on a Zero W with a fresh install of the OS it takes about 20 minutes (standard residential internet connection).
Once setup succeeds (and if it doesn't please get in touch and let me know!):
- Create a
config.json
file enabling the SPS30@0.5Hz & BME280@1Hz, which will look like this:
{
"redis_config": { "host": "localhost" },
"sensors": [
{
"name": "BME280",
"config": { "frequency" : 1 }
},
{
"name": "SPS30",
"config": {
"frequency": 0.5,
"shared_object_path": "CHECKOUT_PATH/rpi/env/lib/python2.7/site-packages/rpjios/devices/libsps30.so"
}
}
]
}
(Important: you must replace CHECKOUT_PATH
with the enclosing path into which you initially cloned the git repository, e.g. /home/pi
. I'll fix this annoyance someday.)
(config.json
has many other options not illustrated, see here for examples.)
If you're having trouble finding the libsps30.so
driver library (I've heard reports it may not be getting built by setup.sh as it should be), I've posted a pre-built version of the binary for recent Raspbian OSes. Simply download that file and place it in the directory you've configured as the shared_object_path
above (if following this example that'll be /home/pi/rpi/env/lib/python2.7/site-packages/rpjios/devices/
) and you'll be all set!
- Activate the python virtual environment, set your
REDIS_URL
and runbin/sensors-src
with yourconfig.json
path as the sole argument. You'll see the sensors being initialized:
$ source env/bin/activate
(rpjios venv) $ export REDIS_URL="redis://localhost"
(rpjios venv) $ ./bin/sensors-src config.json
* Loading configuration from config.json
* Using Redis configuration: {u'host': u'localhost'}
* Configured sensors: BME280, DS18S20 (disabled), NetInfo (disabled), SysInfo (disabled), TEPT5700 (disabled), LM335 (disabled), LM335 (disabled), Soil (disabled), DHTXX (disabled), SPS30
* Loaded <rpjios.SensorBase 'BME280' type=BME280 channel=zed:sensor:BME280>:
{'name': 'BME280', 'hostname': 'zed', 'frequency': '1Hz', 'location': None, 'calibration_data': {'dig_H1': 75, 'dig_H3': 0, 'dig_H2': 332, 'dig_H5': 50, 'dig_H4': 400, 'dig_H6': 30, 'dig_P9': 5000, 'dig_P8': -12000, 'dig_T1': 27678, 'dig_T3': 50, 'dig_T2': 26477, 'dig_P1': 37060, 'dig_P3': 3024, 'dig_P2': -10666, 'dig_P5': -2, 'dig_P4': 6811, 'dig_P7': 12300, 'dig_P6': -7}, 'channel': 'zed:sensor:BME280', 'description': 'Bosch I2C low power pressure, temperature & humidity sensor'}
* Loaded <rpjios.SensorBase 'SPS30' type=SPS30 channel=zed:sensor:SPS30>:
{'name': 'SPS30', 'hostname': 'zed', 'driver_version': '1.0.0-6-g836d5fc', 'frequency': '0.5Hz', 'location': None, 'serial_number': "'7B4242424242D1'", 'channel': 'zed:sensor:SPS30', 'description': 'Sensiron SPS30 particulate matter sensor'}
...
- To easily verify that the data is flowing as expected, use
redis-cli
locally:
$ redis-cli
127.0.0.1:6379> PSUBSCRIBE *
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "*"
3) (integer) 1
1) "pmessage"
2) "*"
3) "zed:sensor:BME280"
4) "{\"source\": \"zed:sensor:BME280\", \"type\": \"VALUE\", \"ts\": 1546035881.156941, \"value\": {\"temperature\": 82.04924976702895, \"timestamp\": 1546035881.0, \"humidity\": 35.60792598092815, \"pressure\": 1018.030467350658}}"
...
(You'll see here that RPJiOS uses the namespace convention of "hostname:type:sensorname
" for the key names it publishes.)
- To easily transform and/or forward the emitted sensor data onto another
redis
instance, use thedownsample
tool. - This example forwards everything from the local (the
-i
option) SPS30 sensor only (-p
) onto a host named "frank" (-o
) at a frequency one-tenth that (-r
) at which the sensor emits data:
(rpjios venv) $ ./bin/downsample -i "redis://localhost" -o "redis://frank" -r 10 -p "*SPS30*"
(downsample
is very flexible and has many usage modes, a few more examples of which can be found in the README
. Ultimately though, the source is your best source of information. Pun definitely intended.)
- Of course, you'll likely want to run these commands outside the virtual environment, for example at boot time. No problem! The python binary included in the virtual environment makes that easy:
(rpjios venv) $ which python
/home/pi/rpi/env/bin/python
Use this path instead of the system python to invoke any of the tools discussed above and you'll be all set.
Daemonization is left as another exercise for the reader, as there are a number of ways to go about it depending on your need.
Lastly, you'll see later that this data is then piped to ThingSpeak for aggregation, visualization, analysis and action.
The tool to accomplish this is very simple and was written in an about an hour, illustrating the ease of attaching new functionality to the data stream.
To run it, ensure you're in the virtual environment (or using the appropriate python binary) and use the channel ID and write API key as the only two command-line arguments:
(rpjios venv) $ ./bin/thingspeak CHANNEL_ID WRITE_API_KEY
As you might have noticed, there are many other other sensor types supported by RPJiOS, including a couple of "virtual" sensors such as Sys and Net that require no additional hardware. Give them a try!
Lessons- The SPS30 is incredibly sensitive! Despite my best efforts to isolate it from everyday human activity, it very clearly knows when we're bustling and moving about, regardless of where in the house we are. Very impressive!
- In all my years of using python and C in isolation, I'd never before tried to directly integrate a C shared library with python. It was remarkably easy! Yet another reason to love python.
- Everything you could ever possibly need is available very cheaply on eBay or AliExpress but you must plan ahead! Shipping will almost always be of the cheapest-and-slowest variety, so while it has been (thus far) exceedingly reliable for me, they are also quite consistent with the long ship times.
RPJiOS.com will take you to the ThingSpeak dashboard for the derived Air Quality Index (based on this formula), generated by the integrated MATLAB analysis engine.
Furthermore, this is the channel for the raw SPS30 data and this the channel for the raw BME280 environmental data.
That MATLAB integration with ThingSpeak also allows one to generate striking yet useful dashboards:
(Don't worry, I already know my timestamps are off.)
Additionally, ThingSpeak's React engine enables triggered actions such as a warning on Twitter when the air quality reaches a threshold level. Neat!
FeedbackIs welcomed & encouraged! Please leave a comment or message me here, get in touch with me on Twitter (here too) or submit an issue on GitHub. The community is what makes being a Maker so much fun! Thank you for your interest!
Comments