Several months ago, a friend recommended a CO2 monitor that he had found on Amazon.
It worked great, he said! And he discovered that CO2 levels in his house were way higher than expected, which led him to improve the ventilation through his home.
Normal people would’ve taken the recommendation and bought the $42.99 product from Amazon.
I’m not normal (unfortunately), and I’d also been looking for a good reason to test out the LoRaWAN product we make at my company, Blues. That’s why over the last month I overengineered a CO2 home monitoring solution for my home—complete with four custom-built devices and an open-source web application for monitoring the data.
You can view this dashboard for yourself at https://co2-home-monitor.netlify.app.
In this article I’ll show you how it works, and how you can build a solution like this yourself.
HardwareAt Blues we make the Notecard, a system on module that makes connectivity easy for a number of radio access technologies.
Our Notecard for LoRa is a 30mm x 35mm device that makes communicating with LoRaWAN gateways simple through an intuitive JSON API.
All Notecards have an M.2 edge connector, and we provide a number of carrier boards that make it easy to plug in a Notecard and leverage other development-friendly features.
For this project I went with the Notecarrier F for my indoor devices, and a Notecarrier B for my outdoor device (more on that in a minute). The Notecarrier F had everything I needed for this project—a JST connector for attaching a LiPo battery, a Qwiic connector for attaching a CO2 sensor, and headers for connecting a feather-compatible microcontroller.
For a host microcontroller I went with the Blues Swan—mostly because I work for Blues and had a few sitting around my office.
Basically host would’ve worked for this app as the firmware is pretty simple, but I do like the Swan because it’s easy to program and it works well in my favorite editor, PlatformIO.
And finally for a sensor I used the SCD-40 from Adafruit, which I liked because it has simple Qwiic connectors (no wiring!).
The nice thing about this setup is all of my hardware fit together like LEGO pieces. Here’s what one of my devices looks like fully assembled.
I created three of these devices for indoor use, as I wanted to be able to monitor CO2 levels in multiple places throughout my house.
I also plugged in my Blues Indoor LoRaWAN Gateway to make sure the devices would be able to communicate once I had them configured.
Before I got coding I wanted to solve one last hardware problem though, as I wanted to put one of these devices outside.
OutsidePlacing a monitor outdoors is a nice-to-have for any indoor air quality solution, as it establishes a baseline. In my case, I would know my indoor ventilation is working perfectly if my indoor CO2 levels matched the levels outdoors. Plus—it’s just cool to have a device working outdoors.
For my outdoor device I decided to try out this slick enclosure from Voltaic.
What I like about this enclosure is it comes with a 6 Volt solar panel (you can’t see it in the picture above but you can see it here), as well as an integrated Lithium Ion Capacitor (LIC). The combination gives you everything you need to power a device off of solar—which is cool!
My only problem is the Notecarrier F from my indoor devices didn’t physically fit in the enclosure, so I went with the smaller Notecarrier B, and wired my carrier board to my host using this guide.
For power, all I had to do was connect the JST from the LIC to the JST on the Notecarrier B, and after a few weeks of having this in the wild I’m shocked how well it works—I haven’t had to charge it yet.
With my devices all physically connected, next I had to load firmware onto my hosts.
FirmwareThe firmware for this project is relatively simple, as all it needs to do is take a sensor reading every hour, and queue that data on the Notecard for sending to my LoRaWAN gateway.
This project’s firmware is open source and available on GitHub. It may be helpful to view the main firmware source file as you go through this section.
The firmware is written in Arduino, and if you’ve used Arduino before you know its firmware has a pretty basic structure.
void setup() {
// your setup code goes here
}
void loop() {
// your code to run in a loop goes here
}
But because my devices are running on battery power I didn’t want my host looping indefinitely, so instead I used a handy little trick that the Notecard provides.
#include <Notecard.h>
Notecard notecard;
void setup() {
notecard.begin();
...
}
void loop() {
// Request that the Notecard put the host to sleep
J *req = notecard.newCommand("card.attn");
JAddStringToObject(req, "mode", "sleep");
JAddNumberToObject(req, "seconds", 3600);
notecard.sendRequest(req);
// Delay 1 second in case the host fails to sleep and try again
delay(1000);
}
The first thing to know about this code is that I’m using the Notecard’s Arduino SDK. This SDK makes it easy to start communicating the Notecard, and to issue it requests.
The request in the loop function is called card.attn, which you can use to provide hardware notifications to the host.
With the configuration provided above, the card.attn request will tell the Notecard to pull its ATTN pin low for 3600 seconds (one hour). And because the Notecard’s ATTN pin is wired to the the host’s enable pin (which happens automatically on the Notecarrier F, as long as its DIP switch is set to N_ATTN
), it’ll place to the host to sleep for that interval.
You can read more about this technique in Putting a Host to Sleep Between Sensor Readings.
The end result of this technique is a huge battery savings, as your host only wakes up to run once an hour. Your Notecard still runs all the time, but it idles at ~8µA@5V, so it’s a perfect module to use to use to control virtually any battery-powered project.
Because your host is normally sleeping, all of the main logic for taking sensor readings happens in setup()
. Here’s what that looks like.
#include <Notecard.h>
#include <SensirionI2CScd4x.h>
#include <Wire.h>
Notecard notecard;
SensirionI2CScd4x scd4x;
void setup() {
// Provide visual signal when the Host MCU is powered
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, HIGH);
notecard.begin();
scd4x.begin(Wire);
uint16_t error;
error = scd4x.startPeriodicMeasurement();
// Read Measurement
uint16_t co2;
float temp;
float humidity;
error = scd4x.readMeasurement(co2, temp, humidity);
// Add a Note to the Notecard
J *req = notecard.newRequest("note.add");
if (req != NULL)
{
JAddStringToObject(req, "file", "data.qo");
J *body = JAddObjectToObject(req, "body");
if (body)
{
JAddNumberToObject(body, "co2", co2);
JAddNumberToObject(body, "temp", temp);
JAddNumberToObject(body, "humidity", humidity);
JAddNumberToObject(body, "voltage", getVoltage());
}
notecard.sendRequest(req);
}
error = scd4x.stopPeriodicMeasurement();
}
Most of this logic is standard code for getting sensor readings from an SCD40. There are two things in here I want to call out though.
// Provide visual signal when the Host MCU is powered
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, HIGH);
Although writing firmware that can put a host to sleep can save a ton of battery, it can also make debugging harder. This is a bit of code that turns on the host’s onboard LED when it’s powered on, so you always have a visual indication of whether the host is currently running or not.
J *req = notecard.newRequest("note.add");
if (req != NULL)
{
JAddStringToObject(req, "file", "data.qo");
J *body = JAddObjectToObject(req, "body");
if (body)
{
JAddNumberToObject(body, "co2", co2);
JAddNumberToObject(body, "temp", temp);
JAddNumberToObject(body, "humidity", humidity);
JAddNumberToObject(body, "voltage", getVoltage());
}
notecard.sendRequest(req, 5);
}
This code uses the Notecard’s note.add request to queue data on the Notecard. The Notecard acts as a queue by default, and any data you queue there will be synchronized to the cloud at a cadence you provide.
In my project that configuration is done through a Notecard setup script, which lets you load all of your Notecard configuration through a CLI. Here’s the main request that controls how often data synchronizes.
{
"req":"hub.set",
"mode":"periodic",
"voutbound":"usb:15;high:60;normal:360;low:1440;dead:0",
"vinbound":"usb:60;high:1440;normal:1440;low:1440;dead:0"
}
On the Notecard, outbound intervals control how often data flows from the Notecard to its cloud backend, Notehub, and inbound intervals control how often data flows from the cloud back to your local device.
The vinbound
and voutbound
values are voltage-variable intervals, and are another battery-savings feature build into the Notecard. To take voutbound
for example, usb:15
tells the Notecard to send outbound data every 15 minutes when running on USB power; high:60
tells the Notecard to send outbound data every 60 minutes when on high battery power; and so on.
You need to call the Notecard's card.voltage request so it knows what values like “high” and “low” mean. In the case of this project I’m setting card.voltage to"lipo"
for my indoor devices, as they’re powered by LiPo batteries, and"lic"
for my outdoor device, as it’s powered by a lithium-ion capacitor.
And with that final configuration in place, I was good to go. All I had to do is flash the firmware to my devices, and watch the data flow into the Notecard's cloud backend, Notehub.
But wait—this is a LoRaWAN project, so... how did the data make it to the cloud?
That’s the magic of the Notecard. Instead of worrying about the specifics of a radio access technology, instead you queue your data on the Notecard using its JSON APIs, and let it handle the messy details of actually interacting with a LoRa transceiver, cellular modem, or Wi-Fi module.
Web ApplicationAlthough it’s great to see my data flowing into Notehub, as a last step I wanted to build a dashboard for visualizing my data. Notehub provides integrations with a ton of cloud platforms, from the big clouds like AWS and Azure, to dedicated IoT platforms like Datacake, Ubidots, and Blynk.
Or, if you’re like me and like to keep things simple, you can instead just use the Notehub API to access your data whenever. As such, I built out a simple web application for this project that uses the Notehub API, and lets you view the CO2 of all of your devices in one place.
You can also view detailed information from an individual device.
The web application is open source, available on GitHub, and MIT licensed, so feel free to use it verbatim, or fork it for your own use.
FindingsThe best part about having a project like this in your home is you can actually learn a thing or two about your own air quality.
For example, one afternoon after my wife and I were working with the door closed, I opened my web app and saw this concerning spike in office CO2.
After seeing this I added some Notehub alerts so that I get emailed when I get spikes over 2000PPM, as that’ll give me a heads up that I should try to increase ventilation in that area.
Honestly I’m a little concerned about what I’m going to find this winter, as winter in Michigan (where I live) is cold, and opening the windows is not an option unless I want my furnace to heat up the outdoors.
But knowing is half the battle, and now that I have monitoring set up I can run experiments. For example, if I buy an air purifier, or have maintenance done on my furnace, I now have real data reporting set up to see whether those steps made a real difference.
Wrapping UpIf you want to build something like this for yourself I’d recommend starting out with a Blues Starter Kit. The Starter Kit for LoRaWAN is a great pick if you want to build with LoRaWAN like I did in this article, but the Cell+WiFi kit is just as easy to use.
Fun fact: this project’s firmware works when using a Notecard Cellular or Notecard WiFi with zero code changes—that’s the power of using Blues! All you have to do is swap Notecards.
Finally, if you have any questions feel free to reach out in the comments. I had a lot of fun building this and would be happy to share any tips or advice on problems you might be hitting.
Comments