---
# System prepended metadata

title: 'How to Build a Temperature, Humidity and Air Quality Monitoring Device for your Room'

---

# How to Build a Temperature, Humidity and Air Quality Monitoring Device for your Room

By Johannes Wega (jw223ma, johanneswega@gmail.com)

This tutorial explains how to build an IoT device that monitors the air quality as well as the temperature and humidity of your room. Furthermore, the device notifies you when the quality of the air in the room is bad and ventilation is required in order to avoid e.g. tiredness or other detrimental effects of bad indoor air quality. This can for instance help you to study more efficiently as your concentration is enhanced with good air quality.

If all the material is available, the project can easily be completed in a day or less.

## Objective

As students, I think we can all relate that studying can be quite tiring. I myself, as a Chemistry student, always have to think at the long nights of writing lab protocols. However, focused on studying, one often forgets to open the window. Even though studying might be tiring itself, bad air quality can make us tired even more quickly and often we realize too late that the air in our room is bad. Thus, as my project, I wanted to build a device that monitors the quality of the air in my room and notifies me when to open my window. As a chemist (who is also completely new to IoT), it is also very interesting to me to measure the concentration of certain molecules in the air. Moreover, I also added a temperature and humidity sensor to the device. The overall project plan is summarized in the image below.

![](https://i.imgur.com/T8P3HVO.jpg =800x)

I hope to be able to gain insights into how the quality of the air in my room affects my general feeling as well as my study performance. Additionally, it will be interesting to see if there will be any differences in the air quality, temperature, or humidity between Sweden and Germany. (I'm an international student at Uppsala University and I'll be back in Sweden in mid-August) 


## Air Quality 
For low-cost IoT applications there are often two parameters, namely, the concentration of carbon dioxide (CO₂) and the concentration of the total amount of volatile organic compounds (tVOC), used to characterize indoor air quality as cheap sensors exist which make it possible to measure these values. 

CO₂, is a good indicator for human activity in a room. As we breathe, we consume oxygen and exhale CO₂. Oxygen is needed by our cells to produce energy which keeps us going when studying (to take the example from earlier). Hence, a higher concentration of CO₂ in our room equals a lower concentration of Oxygen which can make us tired, dizzy, or even give us a headache. The recommended concentration of CO₂ in a room should not exceed 1400 ppm. 

VOCs, are as the name suggests, all substances which evaporate easily. They are produced by us humans, by cleaning chemicals, or even by our furniture. High levels of VOCs can lead to dizziness, tiredness and can have a negative impact on our cognitive abilities. Ventilation is recommended if the level of VOCs exceeds 220 ppb.

Sources:
- https://www.velux.com/what-we-do/research-and-knowledge/deic-basic-book/ventilation/indoor-air-quality?consent=preferences,statistics,marketing&ref-original=https%3A%2F%2Fwww.google.com%2F, accessed on 12.07.21.
- https://www.aivc.org/sites/default/files/members_area/medias/pdf/VIP/VIP%2033_CO2%20General.pdf, accessed on 12.07.21.


## Material


|                                     Hardware                                     |                                                        Vendor                                                       |  Cost  |
|:--------------------------------------------------------------------------------:|:-------------------------------------------------------------------------------------------------------------------:|:------:|
|                 Lopy4 <br /> ![](https://i.imgur.com/GJjfSCD.png =150x)                |                                     Pycom <br /> https://pycom.io/product/lopy4/                                    | 38.45€ |
|         Lopy4 expansion board <br /> ![](https://i.imgur.com/XEja0kP.png =150x)        |                              Pycom <br /> https://pycom.io/product/expansion-board-3-0/                             | 17.60€ |
| CCS811 - CO₂/tVOC air-quality sensor <br /> ![](https://i.imgur.com/5hhvIoH.jpg =100x) |    Amazon <br /> https://www.amazon.de/gp/product/B092HK27K7/ref=ppx_yo_dt_b_asin_title_o01_s00?ie=UTF8&psc=1    | 10.30€ |
|  DHT11 - temperature/humidity sensor <br /> ![](https://i.imgur.com/xB5Cxu1.png =80x) | Amazon <br /> https://www.amazon.de/dp/B07L83K6CF/ref=cm_sw_em_r_mt_dp_ZKCAHB78T9T8PCV55VWS?_encoding=UTF8&psc=1 |  4.50€ |

All hardware that I had to purchase to realize this project together with the respective price of the components and where I purchased them, is summarized in the table above.

Thereby, the heart of the project is the Lopy4 microcontroller which enables the communication with the sensors and thus the data collection as well as the wireless transmission of the collected data to the internet. The expansion board offers an easy way to connect the Lopy to a computer in order to program it to read the sensor values and to transmit the data. The Lopy 4 has been chosen for this project as it is the recommended microcontroller for this course and furthermore offers many advantages for IoT applications due to its easy usage and its many build-in wireless connection protocols (WLAN, LoRa, SigFox, Bluetooth). Also, since I was already familiar with Python it was another advantage for me to use the Lopy. 

The CCS811 sensor is used to measure the concentration of CO₂ and the total volatile organic compounds (tVOC) in the air which are good indicators of air quality (see air-quality parameters). The sensor enables a measurement of both parameters in the following ranges:
- CO₂: 400-8192 ppm
- tVOC: 0-1187 ppb

The DHT11 sensor is used to measure the temperature and humidity of the air in the room. The sensor enables a measurement of both parameters in the following ranges:
- Temperature: 0-50 °C
- Humidity: 20-90 %

Additionally, a breadboard, a few jumper cables as well as a micro USB cable are needed to connect the sensors to the Lopy and the Lopy to the computer respectively. I already had these things lying around, however, they can also be purchased cheaply from e.g. Amazon:
- Breadboard/Jumper cables e.g. from https://www.amazon.de/dp/B071RG9MFT/ref=cm_sw_em_r_mt_dp_GBWJDWTV7A7WPXSS478T?_encoding=UTF8&psc=1 for 5.99€ 
- Micro USB cable e.g from https://www.amazon.de/dp/B08LDBPXM6/ref=cm_sw_em_r_mt_dp_DGDZHWSCQQ87TXGF8Y64 for 2.99€


## Computer Setup

I am running Ubuntu 20.04.2.0 LTS (Focal Fossa) on my laptop and chose the Atom IDE to program the lopy. Here, I will therefore explain how to set up Atom to program the lopy on Ubunutu 20.04. If you are using a different OS it is equally easy to set up Atom and various tutorials exist online which you can follow. 

Atom can be installed easily via the terminal using 

```
sudo apt install atom 
```
Once successfully installed you can open atom and should be greeted by this nice welcoming screen: 

![](https://i.imgur.com/tY2FC2I.png =700x)

To be able to upload code to the lopy the pymakr plugin needs to be installed. For this just press on "Install a Package" and you should get forwarded to a different window in where you can search for plugins. 

<center><img width="500" src="https://i.imgur.com/0cjRqWx.png"></center><br>

Simply search for "pymakr" and press "install" to add the plugin to the atom IDE. If everything went right you should now see the pycom terminal at the bottom of the IDE. Once you plugged the lopy onto the expansion board and connect the expansion board via the micro USB cable to a USB port of your computer, the pycom console should recognize your lopy. You can verify this by clicking on "Connect Device". If there is an entry called something like "/dev/ttyACMx (Pycom)" (x can be different for you as it specifies the USB port you used) your computer recognizes the lopy. 

<center><img width="500" src="https://i.imgur.com/sTKzXyY.png"></center><br>

However, if you actually try to connect the lopy you will most likely get an error message or it will take forever until the lopy is connected. This is because the plugin needs node.js to function. Node.js can be easily installed via the terminal using:
```
sudo apt install nodejs
```
Furthermore, you also need to enable the permission to be able to use your USB port for writing code onto the lopy, otherwise, the device will end up trying to connect forever. You can do this by running: 
```
sudo usermod -a -G dialout $USER
```
and then rebooting your computer. Finally you should then be able to communicate with your lopy. In the pycom terminal in Atom press on "Connect Device" and then "/dev/ttyACMx (Pycom)". The terminal should then output something this: 

```
Connecting to /dev/ttyACM1...
>>> 
```
To verify that everything is working you could for instance just try to run a python command like printing something. 
```
>>> print("Yay! I successfully set up atom to program my lopy.")
Yay! I successfully set up atom to program my lopy.
```
Congratulations! Now you are ready to run scripts or upload whole projects to your lopy which you can do using the dedicated buttons to the left of the terminal. 

Actually, pycom recommends updating the firmware of the lopy and the expansion board. This was however not necessary for me to program the lopy. If you run into any problem it might be worth a try to update the firmware (https://docs.pycom.io/updatefirmware/device/). 

## Putting Everything Together
The sensors are connected to the lopy expansion board according to the following schematic:

<center><img width="600" src="https://i.imgur.com/RghFvSD.png"></center><br>

In real life it should looks something like this: 

<center><img width="600" src="https://i.imgur.com/QW918cI.jpg"></center><br>

This setup is only a development setup. However, if the sensors and a programmed ESP32 microcontroller would be connected to a professional PCB and the whole device enclosed into a 3D printed casing, the device could probably also be used in an industrial setting to for instance monitor the air quality in classrooms. Monitoring the air quality in public places is especially recommend during "these times" as the spread of Covid is significantly more easy in "dirty" air were loads of nucleation sites for the virus exist.  

## Platform

For this project, I chose to use the free-of-charge Ubidots STEM platform (https://ubidots.com/stem/). The platform is a beginner-friendly cloud-based service and it is furthermore quite simple to send data to ubidots using the lopy. The free STEM account, however, limits the data that you can send to ubidots. However, that was not a problem for me as I was planning to record sensor data every second for a total of 90 s and then transmit only the average value of the sensor data recorded during that time in order to only transfer quite accurate values (see code section).

I decided to use Ubidots mainly due to two reasons. Firstly, I found the dashboard and the subsequent widgets available quite visually appealing. I also tried out pybytes at first but I was not quite pleased by their design. Secondly, using ubidots it is extremely simple to trigger events which is a core part of this project as I want to get warned by receiving an email if the air quality in my room is bad. 

As I said, you can create an Ubidots STEM account completely free of charge and you will then also get a Ubidots token (see this link on how to view it: https://help.ubidots.com/en/articles/590078-find-your-token-from-your-ubidots-account) that you will need later in the code section in order to transmit data.


## The Code

### How to Flash the Code on to your Lopy

The whole code base for this project can be downloaded from this GitHub repository:
- https://github.com/johanneswega/room_air_quality_IoT_device.git

Once you downloaded all files, create a folder (I named it "Project") and paste the downloaded files and folders into it. Then, open atom and press on "File" -> "Add project folder" and select the folder. Your project directory should then be displayed on the right in atom.

<center><img width="200" src="https://i.imgur.com/WsOGVJy.png"></center><br>

Before uploading the project to your lopy using the pycom terminal and the dedicated button on the left to it, you first should edit the ```keys.py``` file and paste in your wifi credentials as well as your Ubidots token. 

```python=
wifi_ssid = "your wifi ssid"
wifi_password = "your wifi password"
ubidots_token = "your Ubidots token"
```

After editing the ```keys.py``` file you should simply be able to upload the project to the lopy and if everything is connected correctly the terminal should first greet you with: 
```
Wifi connected successfully
```
and every 1.5 min you should see the averaged sensor values being printed:
```
{'temperature': {'value: 25'}, 'humidity': {'value: 56.42'}, 'co2': {'value: 1570.36'}, 'tvoc': {'value: 233.60'}}
```
In Ubidots, a new device with four variables should pop up called "lopy". You should verify that sensor data is really sent to Ubidots.

![](https://i.imgur.com/zjDa8cc.png)

### How the Code Works

The core file of any micropython project is the ```main.py``` file. The while loop in this file is continuously run and the sensor values are read and subsequently sent to the cloud. However, in order to read out sensor data, most sensors require libraries. All libraries of the project are found in the ```lib``` folder and can be imported simply by using 
```python 
import name of lib file without .py
```
The libraries necessary for both sensors (```dht.py``` and ```CCS811.py```) were downloaded from the course GitHub repository:
- https://gitlab.lnu.se/1dt305/sensor-libs

The code used to initialize the sensors and read the sensor data in the ```main.py``` is also based on the examples code snippets provided on the repository. If you do not have access to an LNU account, both libraries can also be downloaded from their original source: 
- DHT11: https://github.com/JurassicPork/DHT_PyCom
- CCS811: https://github.com/Notthemarsian/CCS811

As mentioned earlier, the ```keys.py``` file just provides a neat way to hide your wifi and Ubidots credentials. The ```urequest.py``` file is a necessary library in order to send the sensor data to Ubidots via Webhooks. The ```ubidots.py``` uses the ```urequest``` library and contains the function ```post_var``` used in ```main.py``` to transmit the data to Ubidots. The ```urequest.py``` was downloaded from this repository: 
- https://github.com/jotathebest/micropython-lib/blob/master/urequests/urequests.py

and the ```ubidots.py``` file is based on the tutorial provided by Ubidots on how to configure a pycom device to send data using webhooks (https://help.ubidots.com/en/articles/961994-connect-any-pycom-board-to-ubidots-using-wi-fi-over-http). 

Once your device is powered, the first file that is run on it is the ```boots.py``` which connects the lopy to wifi and sets some required settings for the Ubidots library to function properly.
```python=
from machine import UART
from network import WLAN
import machine
import keys
import os

# settings for ubidots 
uart = UART(0, baudrate=115200)
os.dupterm(uart)
machine.main('main.py')

# connect to Wifi
wlan = WLAN(mode=WLAN.STA)
wlan.connect(ssid=keys.wifi_ssid, auth=(WLAN.WPA2, keys.wifi_password))
while not wlan.isconnected():
    machine.idle()
print("Wifi connected successfully")
```
After the device connect to your wifi successfully, the ```main.py``` is run:

```python=
import time
from machine import Pin
import pycom
import ubidots
from machine import I2C
from dht import DHT # library from: https://github.com/JurassicPork/DHT_PyCom
import CCS811 # library from: https://github.com/Notthemarsian/CCS811

# turn off blinking of blue LED
pycom.heartbeat(False)

# DHT11 setup
th = DHT(Pin('P23', mode=Pin.OPEN_DRAIN), 0) # 0 specifies that the DHT11 is used because the library can also be used with another sensor
time.sleep(2)

# CCS811 setup
i2c = I2C(0)                         # create on bus 0
i2c = I2C(0, I2C.MASTER)             # create and init as a master
i2c = I2C(0, pins=('P9','P10'))      # PIN assignments (P9=SDA, P10=SCL)
i2c.init(I2C.MASTER, baudrate=10000) # init as a master
ccs = CCS811.CCS811(i2c=i2c,addr=90)
time.sleep(2)

# function to calaculate the average of a list
def average(liste):
    return sum(liste)/len(liste)

while True:
    # intialize empty variable lists 
    temp_list = []
    hum_list = []
    CO2_list = []
    tVOC_list = []

    # make readings with both sensors 
    # wait a second each and add sensors values to lists
    for i in range(45):
        # read temperature and humidity from DHT11
        result = th.read()
        time.sleep(1)
        temp = result.temperature
        hum = result.humidity
        temp_list.append(temp)
        hum_list.append(hum)

        # read c(CO2) and c(tVOC) from CCS811
        ccs.data_ready()
        time.sleep(1)
        CO2 = ccs.eCO2
        tVOC = ccs.tVOC
        CO2_list.append(CO2)
        tVOC_list.append(tVOC)

    # send the average sensor value collected in the last 90s to ubidots
    post_var("lopy", average(temp_list), average(hum_list), average(CO2_list), average(tVOC_list))
```

After importing all necessary libraries, the sensors are configured in accordance with the wiring in order to read data from them later. The CCS811 thereby communicates with the lopy via I²C (lines 16-22). Depending on the CCS811 module you are using the I²C address might be either 90 or 91 (line 21). If you don't get any readings from the sensor you should try changing the address. 

After the sensors are configured, the program enters the infinite ```while``` loop. Here, four empty lists for the four parameters are initialized. Then, the program enters a ```for``` loop in which both sensors are read. First, the temperature and humidity is read by the DHT11, then the device waits for 1s, and then the concentration of CO₂ and tVOC is measured by the CCS811 after which the device waits another second before entering the ```for``` loop again. While the sensor readings are made, the respective read-out parameter is added to the list of the respective variable. The ```for``` loop runs for a total of 90s after which the average sensor values measured during that time, calculated using the ```average``` function and the respective variable list, are sent to Ubidots using the ```post_var``` function. Then, the while loop is entered again from the beginning and the whole process is repeated. Sending only average sensor values ensures that only quite accurate data is sent to the cloud.

## Transmitting the Data / Connectivity

As described prior, the averaged sensor data is transmitted every 90s to the internet. Hereby, the data is packaged as a JSON object and send via webhooks through wifi to the Ubidots API. 

I chose wifi as the means to transmit the data for this project because the device is placed in my room where the wifi connection is good and stable. Furthermore, as I was planning to send data quite frequently to the cloud since the concentration of gases in the air can drastically change in as little as half an hour, I needed to choose a protocol where the size of the data being sent is not really a concern. Hence, I believe wifi is best suited for this project in contrast to for instance LoRa or Sigfox. 

Initially, I wanted to transmit averaged sensor data every minute. However, Ubidots limits the data ingestion for the free STEM accounts (https://help.ubidots.com/en/articles/636672-plans-billing-what-are-dots). Per day a Ubidots STEM user has a capacity of 4,000 "dots". Hereby, a dot is a data point containing a value and a timestamp. So if I wanted to send 4 dots every minute I would end up using 5,760 dots/day which is outside of the 4,000 dots/day limit. Therefore, I settled sending data every 90s which makes brings my data ingestion to 3,840 dots/day. 

## Presenting the Data

### The Dashboard

In the code section, you have already seen that a new device named "lopy" popped up on your Ubidots account which also retrieves our four parameters. To build a nice visual representation of the transmitted data, you can create a Dashboard in Ubidots in a very straightforward way. For this, go to ```Data``` → ```Dashboards``` and add any widget you like using the ```+``` button on the top right corner. When you have chosen a widget, press on ```+ add variable``` then on ```lopy``` and then chose the parameter you want to display. The pannel further enables you to set ranges, the color of the widget, and so on. I came up with the following design for the dashboard:   

![](https://i.imgur.com/VIiG5LC.png)

where I have two gauge widgets for the air quality parameters, a thermometer widget for the temperature as well as an indicator widget for the humidity. Furthermore, I also added line chart widgets for each variable in order to see the time evolution of the parameters. 

Using Ubidots, data is saved every time it is sent. The data is then stored in the Ubidots database for a total of 30 days. 

It is also quite cool to be able to check the data on your phone from technically anywhere in the world.

<center><img width="230" src="https://i.imgur.com/5LK5sXu.gif"></center><br>


### Events
Another advantage of using Ubidots is that it is extremely easy to create automated events that trigger some kind of alarm. For this project, I wanted to be warned by receiving an email (on my phone) if the air quality in my room is bad and when I should open my window. I will explain how to create such an alarm if the CO₂ concentration in my room exceeds 1400 ppm.

First, go to ```Data``` → ```Events``` and press on the ```+``` button to create a new event. Then, press on ```select variable``` → ```lopy``` → ```co2```. Now you can simply add your if-clause with the dedicated buttons. 

<center><img width="650" src="https://i.imgur.com/ONB4i0d.png"></center><br>

Continuing on, you can then specify the then-action if your if-clause is true. I chose to receive an email in that scenario. You can add your email address and the message you want to receive simply by filling out the premade form. 

<center><img width="650" src="https://i.imgur.com/6KT2cdg.png"></center><br>

I added a similar event for when the tVOC level exceeds 220 ppb. If everything went right, you should then receive an email once the air quality in your room is bad! 

<center><img width="200" src="https://i.imgur.com/LWPBAVk.jpg"></center><br>


## Finalizing the Design

*This step is optional.*

As I didn't want the device to be based on loosely connected wires on a breadboard, I decided to build a simplistic case for my project. For this, I took an old Tupperware box and cut out a fraction of the plastic of the lid. I then attached a custom PCB to the lid by drilling holes and securing the PCB with screws and nuts. Next, I soldered the sensors and jumper cable connections to the PCB and connected the PCB to the lopy expansion board which I placed into the Tupperware. I also cut out a small rectangle on the right of the bottom part for the micro USB cable. I'm quite happy with how the casing turned out and now I also have a good way to transport the device back to Sweden.

<center><img width="500" src="https://i.imgur.com/HDaRTfc.jpg"></center><br>

Overall, I'm rather satisfied with how my first IoT project turned out and everything seems to work well. It is probably possible to reduce the cost of the project since the lopy4 might be too advanced as only wifi is used. An ESP32 microcontroller which can be programmed in micropython and has a build-in module for wifi communication can for instance be purchased for as cheaply as 6€:  
- https://www.amazon.de/dp/B07QFQ617H/ref=cm_sw_em_r_mt_dp_XZW7G0KXBGVDJ484NGGS?_encoding=UTF8&psc=1

in comparison to the lopy4 + expansion board for around 55€. Furthermore, if you have access to a 3D printer or some professional PCB manufacturing you can also significantly optimize the casing/design of the project.

## Short Demo Video

{%youtube bvMGkE49Cv4 %}