In our house, we do these days have a smoke alarm and CO2 detector. These are warning us for the most dangerous things we must be safe of. Even if it’s the old dump “BEEP BEEP BEEP” alarms, or a smarter device like a Nest Protect, they only alert you if there is an emergency. Why don’t we monitor and advise about Indoor Air Quality (IAQ) on the same scale?
What is Indoor Air Quality?Wikipedia (source) put it this way: Indoor air quality (IAQ) is a term which refers to the air quality within and around buildings and structures, especially as it relates to the health and comfort of building occupants. IAQ can be affected by gases (including carbon monoxide, radon, volatile organic compounds), particulates, microbial contaminants (mold, bacteria), or any mass or energy stressor that can induce adverse health conditions.
There is not an official definition or guideline how to index this. For example the temperature: I like it 19 degrees in house, my wife likes it 24. For me the 24 degree would qualify very low, for her, the 19 degree is low. So we all have our own interpretation of some parameters. However there were done several researches that give some classification, mainly health based and with regards to gasses and particles.
For example IAQUK uses that information to create a diagram where it classifies a set of 14 parameters into an Excelent-to-Inadequate range:
As a disclaimer; in this project, I'll use a sensor that directly delivers me an IAQ Score. That is not my interpretation, but how Bosch/BSEC did build it for the BME680 sensor.
Primitive Detection of Air Quality with a CanaryAs an alert for Methane gas, miners did take a canary into the mine. Canaries were used because they have an extremely loud chirp. Additionally, the canary has the closest resemblance to the part of our nervous system that controls breathing. The miners did carry the bird in its cage into the mine. It was said that when a canary was about to die, it would start to shake the cage. If the canary did this, the miners knew to exit the mine. If the canary wasn't making any noise, they knew to make an even more urgent exit as something had caused the canary to die.
Living in a healthy environment, but also knowing what you can change to get a healthy environment, improves our quality of living. The techniques do exist hardware wise, and in expensive devices we can already buy this.
With simple DIY tools, we are going to build our own. All you need is a microcontroller unit (MCU, we use a Maxim MAX32620FTHR), a sensor (we use a BME680), some output to warn with (we use a LED and a speaker) and a battery (we use a Li-Po 500 mAh). Also we do send the information through LoRaWAN to make this a real IoT device (we use a RFM95W on a custom FeatherWing).
And top of the bill; it will be in the form of a Canary :)
Disclaimer: no Canaries wereharmed while building and testing this project
Project Explained: Bill of MaterialsI’ll first explain all the parts used and show how they are used in this project. Leaving the wires/screws/nuts/hot glue out, main components are as following:
- Bird-Shaped Housing
I bought this smoke detector second hand and it probably doesn't work anymore. It is meant to be ceiling mounted on a little branch. Perfect size and most important; non intrusive if this bird sits on your table or cupboard. Of course; until it starts chirping!
First step is to disassemble an existing smoke detector in the form of a bird:
- Maxim MAX32620FTHR
This is a small MCU in the size of the Adafruit Feathers. It shares (mostly) the same pinout and can be used with many existing FeatherWings.
The board is designed to be low-power and is able to charge a battery safely when connected to USB. Build-in is a PMIC and Fuel Gauge module to take care of the power consumption by using a clever power switching mechanism.
It has additional PMOD connectors, meaning there are more input/output pins to use, in a very simple way by just having a little ‘on-board breadboard’; just stick in your jumper wires!
- LoRaWAN Custom FeatherWing witha RFM95W and Helic Antenna
Connecting our sensor to the outside world can be done with for Wifi, Sigfox, NB-IoT or LoRa(WAN). The first options either have a higher battery consumption and/or need a subscription. So I picked the Low Power Long Range (LoRa) option in combination with The Things Network (TTN) which is free to use.
A bit more explanation of what LoRaWAN and The Things Network are; LoRa is the technique to send messages on the free wireless ISM network bands. The method is based on Chirps, which is an old radar technique. Sending data goes super-slow. Think about Morse coding, LoRa is slower... But by using this technique, with low power, a message can be send that survives a lot of noise, meaning it can be picked up by a gateway on a much further distance.
These gateways can be deployed by everybody, and can be private (costs might be involved) or public (free!). The Things Network did build a backbone for the gateways, and a transport protocol with front-end website to monitor your gateway and device. That is where the term LoRaWAN comes in; it is the full stack end-to-end to send and receive a message.
Together you build the network, so I have deployed my own LoRaWAN gateway based on a Raspberry Pi + RAK 831, which will be covered in another Hackster project later on. To make use of this project based on LoRaWAN, you need to be within reach of a gateway. Otherwise it will send your messages, but they will never get received.
Back to the project: Dan Watson and Adafruit both did design a FeatherWing for LoRa, which needs some custom wiring and an external antenna. As everything in my project needs to fit into the small plastic bird, I decided to design my own FeatherWing with a RFM95W module on it. I took the existing design from Dan Watson and adjusted that to have fixed onboard wiring and room for a helic antenna. Ordered the board at OSHPark to have the purple look.
Now it's time to heat up your soldering iron! Starting with the tiny bits; put the LoRaWAN FeatherWing together. Parts you will need: RFM95W module (for me 868 MHz), a helic antenna (for me 868 MHz), GPIO headers (1x12 and 1x10), 2 capacitors (1uF and 100nF) and a tantalum capacitor (47uF 10V). When completed, it will look like this:
Before soldering this to the bottom of the Maxim MAX32620FTHR, just plug them in and test if this is working. In the end, I didn't need soldering at all, all pins make a sturdy connection already when screwed together.
- BME680: Thermometer, Humidity, Barometer, Gas Sensor
To sense what a canary could sense, we need some module that resembles this bit. There is a variation of sensors existing that can kind of measure the same things. For Temperature, Humidity and Pressure, it’s obvious to use a BME280 (BMP280 is similar but doesn’t have the pressure part).
For gas sensors there is a list of options to pick; specific gasses can be detected with specific sensors. Main way how they work is the same, they use a so called MOX sensor (Metal Oxide Gas Sensors): a metal plate is heated up for an x temperature and x milliseconds, then the resistance of this plate is measured. Each gas has another resistance and each sensor does have a different type of metal plate and sensitivity. If you want to read more in detail, this page is a good start.
Sensors that you can have a look at for example: CCS811 (with Raspberry Pi at different I2C speed), SGP30 (proper I2C), MiCS-5524 (analog signal, but I2C version seems to exist without documentation), BME680 (the same as the BME280 + a gas sensor on I2C), MiCS-6814, MiCS-4514, a wide range of MQ-2/3/4/5/6/7/8/9/135 sensors that smell specific types of gas each, but are mostly analog, 5V or without documentation. Last option would be to measure particles, but that is larger and more power hungry than what fits into a plastic bird.
As I need the temperature and humidity for this project + gas sensing in the Indoor Air Quality range, I went for the BME680 from Pimoroni: all in one package, 3.3V and I2C.
Also an advantage is that there is proper documentation created by Bosch. And best of all: Bosch created a library file that does IAQ calculations based on raw data.
How does the Bosch BME680 sensor work? Their manual shows this simplified picture:
First readings for Temperature, Pressure and Humidity are taken. Then the hot-plate is heated up. After the configured heating time, the Gas reading is taken. Then the sensor sleeps for 2.x seconds. This cycle takes 3 seconds including the sleep time. Advantage for this, is that when taking the temperature reading, the sensor was not heated anymore.
The IAQ library from BSEC (Bosch Software Environmental Cluster) uses the raw data of the sensors, and spits out an IAQ index:
The code from Bosch BSEC is closed source, but the IAQ Rating Index from IAQ UK shows some examples how you could calculate it. Pimoroni also did a good calculation example for their sensor based on the combination of humidity and gas.
Next is soldering wires to the BME680. With jumper wires I do directly plug them into "port 1" on the bottom row, that is where the I2C pins are. Purple (V) is V, Blue (GND) is GND, Green (P1_6) is SDA, Yellow (P1_7) is SCL:
- RGB LED
To show the IAQ rating without making chirps at good levels, I need an indicator that shows with some light what is going on. You can use NeoPixels with digital command sets to show millions of colors, but I just need 10 colors max. An RGB LED does this job very well. Downside is that you need 3 pins for that. But the Maxim MAX32620FTHR has quite some pins available!
There is a single straight visible LED at the side of the bird, and a second LED inside the bird that lights up the full figure (see the bonus video at the end of the article!)
An RGB LED has "4 legs", and depending on the "direction", the longest leg is the cathode or anode. I used these, where the documentation says "Red / Common Cathode / Green / Blue" about the order of the legs (longest leg is the common).
Some details from the sales website we will need later on:
- Pin Length: 26*30*26*26 mm
- Pins Sequence: Red / Common Cathode / Green / Blue
- Wave length: 625-630 (Red) / 517-520 (Green) / 465-470 (Blue) nm
- Input Type: 2.0-2.2V (Red) / 3.2-3.4V (Green) / 3.2-3.4 (Blue)
As the longest leg (30 mm) is Cathode, it should be connected to Ground. The other 3 pins will get a + voltage applied through the GPIO pins.
Without soldering, I did bend the legs of the RGB LED a little and fitted them into a 2x2 header for jumper wires. Gray (GND) is the Cathode, Purple (P1_0) is Red, Blue (P1_2) is Blue and Green (P1_1) is Green. And they are connected to "port 1" in the top row:
- Speaker
A canary makes loud chirps. In the coal mine, the canary chirped all day, except when smelling gas. To not be too annoying, I’ll turn this around: my canary will chirp when it senses problems with your Indoor Air Quality. The Bird smoke alarm already had a speaker mounted, I'll re-use it. But for testing the build, I took the speaker from a "singing postcard".
I've crimped new connectors to the wires and connected in "port 0", bottom row, GND and P0_7:
- Battery: Li-Po 500 mAh
A portable project needs portable power. As the Maxim MAX32620FTHR can only charge a battery at 300 mAh, a 600 mAh battery takes about 2 hours to charge. If we design the project with power consumption in mind, this relatively small battery should still last for some time. And most important: it should fit in the plastic bird housing. I found a 500 mAh LiPo battery that fits these criteria.
The LiPo battery came without a connector, so crimped a JST connector to that as well:
You are now ready to connect that one to you board, but leave it aside for a moment. We will first work with the USB cable to prove everything works as expected. (Battery in the pictures was a temporary one, it will be replaced with a 500 mAh that fits within the bird housing)
Project Explained: Finished build before putting it into the housingJust a picture such that you have an idea of our current state of building:
Prices are not exact as the exchange rates on AliExpress fluctuate, and some parts I had to pay shipping fee for, but to give you an idea what to expect when building a project like this:
- MAX32620FTHR = 20 EUR (but I got it free for this contest)
- BME680 = 20 EUR from Pimoroni, but cheaper on AliExpress with other 'brands'
- RFM95W for LoRaWAN = 5 EUR on AliExpress
- Custom PCB for LoRaWAN = 9 EUR on OSHPark for 3
- 500 mAh LiPo battery = 5 EUR
- RGB LEDs, Headers, Screws, Capacitors, Helic antenna, wires etc I had on stock from other projects as they go per 100, but probably some 5 EUR in total.
Making 64 EUR in total for the electronics and hardware.
The bird housing I bought on Marktplaats (Dutch eBay) for 10 EUR, but that is up to what you like. Speaker was sourced from the birthday post :)
MBED Online CompilerTo program the MAX32620FTHR, there are 3 options: Arduino IDE and Ecplise, both installed on your local machine, or MBED which is an online compiler. For the MBED compiler there are the most examples available specific for the MAX32620FTHR. But the downside is that you cannot include arduino.h but instead have mbed.h which has a wide range of different commands. Most of them have a clear replacement, some are better, some you have to work around. And actually, the MBED compiler just accepted the Bosch library without any difficulty (opposed to the 20 steps that are described for Arduino IDE).
I assume you already created an MBED account, so head over to https://os.mbed.com/compiler/ to start the online compiler.
- Preparing the MAX32620FTHR
When you connect the MAX32620FTHR and MAX32625PICO boards together, this file can simply be save to the external drive that shows up on your computer. This is how you connect them:
On your computer it now shows a drive with the name "DAPLINK". When you build your program in the MBED online compiler, a.bin file is generated, just store that on the DAPLINK drive, wait for the MAX32625PICO board to stop flashing with the red light, and then reset the MAX32620FTHR. Your program will now be loaded an starts right away!.
- Testing the MAX32620FTHR
On the MBED website for the MAX32620FTHR, click at the right hand side the link to add the MAX32620FTHR board to your compiler. You are now ready to start developing!
On the right hand side, there are also some example programs available. Test your board with for example the PwmBlinky program.
Code: Chiiiiiirp! Indoor Air Quality Measurement and AlarmFinally we are there, we will start coding our IAQ-bird :)
I'll not in depth highlight every line of code right here. In the main.cpp file, I've made various comments on what the functions/lines do. I think that better explains what it's all about.
On the main processes (LoRaWAN stack, BME680/BSEC and PlayChirp), I've adopted existing work, and tweaked that to fit my needs.
- LoRaWAN / RFM95W with The Things Network
For the LoRaWAN / RFM95W see this document on the MBED website. Follow all steps for setting up a device, and you are are ready to continue with this project. If you use the same FeatherWing as I did, in the mbed_app.json file, set your pins as following:
{
"config": {
"lora-radio": {
"help": "Which radio to use (options: SX1272,SX1276)",
"value": "SX1276"
},
"main_stack_size": { "value": 4096 },
"lora-spi-mosi": { "value": "P5_1" },
"lora-spi-miso": { "value": "P5_2" },
"lora-spi-sclk": { "value": "P5_0" },
"lora-cs": { "value": "P5_5" },
"lora-reset": { "value": "P4_0" },
"lora-dio0": { "value": "P3_2" },
"lora-dio1": { "value": "P3_3" },
"lora-dio2": { "value": "P5_4" },
"lora-dio3": { "value": "NC" },
"lora-dio4": { "value": "NC" },
"lora-dio5": { "value": "P5_6" },
"lora-rf-switch-ctl1": { "value": "NC" },
"lora-rf-switch-ctl2": { "value": "NC" },
"lora-txctl": { "value": "NC" },
"lora-rxctl": { "value": "NC" },
"lora-ant-switch": { "value": "NC" },
"lora-pwr-amp-ctl": { "value": "NC" },
"lora-tcxo": { "value": "NC" }
},
"target_overrides": {
"*": {
"platform.stdio-convert-newlines": true,
"platform.stdio-baud-rate": 115200,
"platform.default-serial-baud-rate": 115200,
"lora.over-the-air-activation": true,
"lora.duty-cycle-on": true,
"lora.phy": "EU868",
"lora.device-eui": "{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }",
"lora.application-eui": "{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }",
"lora.application-key": "{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }"
}
},
"macros": ["MBEDTLS_USER_CONFIG_FILE=\"mbedtls_lora_config.h\""]
}
In the standard example, a dummy sensor value is send. Of course we do want our BME680 data to be included here!. Next paragraph is about that data, but first something else: LoRaWAN payload. To keep the messages small and short, the Payload in LoRaWAN is encoded in a HEX format. There are many ways to get that sorted, the "lazy way" is using CayenneLPP.
I'll build up a CayenneLPP payload:
// Clear the CayenneLPP buffer
Payload.reset();
// Fill the CayenneLPP buffer
Payload.addTemperature(0, bme680_temp_current / 1.00f);
Payload.addRelativeHumidity(1, bme680_hum_current / 1.00f);
Payload.addBarometricPressure(2, bme680_press_current / 100.00f);
Payload.addAnalogInput(3, bme680_gas_current / 1000.00f);
Payload.addAnalogInput(4, bme680_iaq_score / 1.00f);
Payload.addPresence(5, bme680_iaq_accuracy);
Payload.addPresence(6, ifttt_trigger);
The 6 values are chained together with Cayenne logic. For Chiiiiiirp! that looks like:
0067013101686C02732771030236CD04026493056601066602
Later on I'll show what you can do with this string of data, but to give an idea of where this payload comes from:
Channel - Type - Content in Hex:
00 67 0131
01 68 6C
02 73 2771
03 02 36CD
04 02 6493
05 66 01
06 66 02
- Bosch BSEC BME680 Library
For the BME680 the code is publicly available on GitHub, but for the BSEC library that does the IAQ calculations, you first need to agree with their disclaimer. After you ticked the box at the bottom of the page, a download link will appear.
In the \Arduino\ folder of the archive, use the PDF file "Integration Guidelines for Arduino platforms.pdf" as a guide for your first implementation. The document goes into much detail for how to include the LIB file.
For MBED, this is super simple: just upload the file, agree with the rename warning, done!
These are the files you will need, put them together in the "BSEC-driver" folder in your mbed compiler:
- From the downloaded archive: \example\ (bsec_integration files)
- From the downloaded archive: \algo\bin\Normal_version\armcc\Cortex_M4F\ (all files)
- From GitHub: https://github.com/BoschSensortec/BME680_driver (all files)
- Threads
The main processes will run in Threads, such that they continue to work, without putting the others on hold. Combined with EventQueue, the processes start every x seconds.
- BME680: measurement every 3 seconds. Repeating steps are done based on the BSEC "next" timing.
- LoRaWAN: sends update every 60 seconds. Repeating steps are done with an EventQueue.
- Chirp: this process keeps going based on EventQueue scheduled with a DoNothing every 100000 seconds. Depending on the IAQ rating, an EventQueue is chained to the main queue, or unchained if no sound is needed. Original idea for the PWM tones is from Sparkfun, but I've made various changes for implementing this in my bird with the PwmOut standard and more detailed tone frequencies.
- RGB LED: ongoing PWM signal to mix colors. The PwmOut library already takes care of threaded timing.
- Some additional notes:
Note that the RGB LED uses 3 PwmOut pins, and the speaker uses 1 PwmOut pin. This is just OK, as the standard script only has 5 PwmOut timers, that cannot be used for other purposes at the same time. In the source code file MAX326XXFTHR_PwmOut.cpp (line 50) of the Pwmout library, you can find the timer mapping.
After taking the pictures in above component descriptions, while putting the components together inside the bird, I changed pins. These are updated in the code snippets.
Visualizing data: The Things Network, Cayenne and IFTTTAt this stage, there is data flowing from the bird to The Things Network in CayenneLPP format. But now what to do with that...
This is how the message looks at the TTN console:
As showed, the 6 values are chained together with CayenneLPP logic. I want my data published with 2 integrations: Cayenne and IFTTT. TTN can automatically post my payload to these platforms, but they need to know how to handle the payload.
Cayenne can untangle this payload automatically, so nothing to do at this point. But if you want to see the payload contents at the TTN console, they can untangle the message for you. To do so, go to the "Payload Format" tab, and select "Cayenne LPP". In the data log, you can now see better readable data.
- TTN Integration with Cayenne
Follow the steps on the TTN website, they have a good and complete description that I can't do better. My Cayenne Device Dashboard and Project Dashboard now look like this:
The Device Dashboard shows all data that is received from The Things Network: all the channels in the payload, and additionally the RSSI and SNR of the message. To clean that up, the Project Dashboard shows only exactly what you want, with in this case a gauge that with colors show the IAQ data.
In Cayenne you can setup Triggers; messages that are send when a certain value below or above a preset number are received. Downside is that it keeps on sending, forever...
- TTN Integration with IFTTT
So we want to take a little different approach for the notifications. It should also include some more logic to be a real advice. Remember that we are not sending full text strings over LoRaWAN as these consume a lot of data. So at the TTN side, we have to work with our own logic. That is where we are going to untangle the Payload. Choose the "Payload Format" "Custom".
You now get the option to write a JavaScript function to do the encoding into a JSON formatted string:
function Decoder(b, port) {
// Decode an uplink message from a buffer
// (array) of bytes to an object of fields.
var temperature_0 = ((b[2] << 8) | b[3]) / 10;
var relative_humidity_1 = (b[6]) / 2;
var barometric_pressure_2 = ((b[9] << 8) | b[10]) / 10;
var analog_gas_3 = ((b[13] << 8) | b[14]) / 100;
var analog_iaq_score_4 = ((b[17] << 8) | b[18]) / 100;
var analog_iaq_accur_5 = (b[21]);
var analog_ifttt_6 = (b[24]);
var iaq_rate = "???";
var ifttt_trigger = false;
var ifttt_title = "Indoor Air Quality";
var ifttt_text = "";
if (analog_iaq_score_4 >= 300 && analog_iaq_accur_5 !== 0) { iaq_rate = "Hazardous" }
else if (analog_iaq_score_4 >= 200 && analog_iaq_accur_5 !== 0) { iaq_rate = "Very Unhealthy" }
else if (analog_iaq_score_4 >= 150 && analog_iaq_accur_5 !== 0) { iaq_rate = "Unhealthy" }
else if (analog_iaq_score_4 >= 100 && analog_iaq_accur_5 !== 0) { iaq_rate = "Little bad" }
else if (analog_iaq_score_4 >= 50 && analog_iaq_accur_5 !== 0) { iaq_rate = "Average" }
else if (analog_iaq_score_4 >= 0 && analog_iaq_accur_5 !== 0) { iaq_rate = "Good" }
else if ( analog_iaq_accur_5 === 0) { iaq_rate = "Inaccurate" }
if (analog_ifttt_6 !== 0) {
ifttt_trigger = true;
}
if (analog_iaq_score_4 >= 200 && analog_ifttt_6 === 1) {
ifttt_title = "DANGER! Indoor Air Quality is going down: " + iaq_rate;
ifttt_text = "Go outside, if it's better there. ";
} else if (analog_iaq_score_4 >= 100 && analog_ifttt_6 === 1) {
ifttt_title = "WARNING! Indoor Air Quality is going down: " + iaq_rate;
ifttt_text = "Create some fresh air: open a window. ";
} else if (analog_iaq_score_4 >= 50 && analog_ifttt_6 === 1) {
ifttt_title = "Indoor Air Quality is going down: " + iaq_rate;
ifttt_text = "Create some fresh air: open a window. ";
} else if (analog_iaq_score_4 >= 100 && analog_ifttt_6 === 2) {
ifttt_title = "Indoor Air Quality is getting better: " + iaq_rate;
ifttt_text = "Fresh air is coming back. For more fresh air: open a window. ";
} else if (analog_iaq_score_4 >= 0 && analog_ifttt_6 === 2) {
ifttt_title = "OK! Indoor Air Quality is back to normal: " + iaq_rate;
ifttt_text = "Fresh air returned. ";
} else {
ifttt_title = "Indoor Air Quality: " + iaq_rate;
}
ifttt_text += "IAQ-score: " + analog_iaq_score_4 + ", Temperature: " + temperature_0 + " °C, Humidity: " + relative_humidity_1 + " %, TVOC: " + analog_gas_3 + " Ohm, Pressure: " + barometric_pressure_2 +" hPa";
return {
trigger: ifttt_trigger,
ifttt_title: ifttt_title,
ifttt_text: ifttt_text
};
}
This will decode the payload format into an IFTTT title, text and trigger:
Exactly what we need for the IFTTT integration!
Now we need to create a TTN Integration with an IFTTT webhook. Start with creating the IFTTT part:
And find your key at the settings page.
Next is the TTN integration, add the IFTTT option and fill in the details:
That's it! Now you will receive messages on your mobile phone with the IFTTT app installed, and even pushed to your watch if you have notifications enabled:
Bonus!
Future OptionsThis project showed measuring and alerting can be done with a very small piece of powerful hardware.
Future options can be that IFTTT sends a command to your Hue enabled lights to show you a larger beacon of your IAQ status. Or you can motorize the window opener, to automatically open the windows. Plenty of ideas!
Comments