Projects Research & Design Hub

Device Measures Indoor Air Quality

Written by Carlo Tauraso

Bluetooth-Based Design

Unhealthy air in indoor environments has been linked to diseases such as asthma, allergies, lung infections and more. That’s driven the demand for sophisticated indoor air quality (IAQ) measurement systems, but many have serious limitations. In this project article, Carlo shares the details of his design of IAQnet project. The Bluetooth-based system creates a network of IAQ monitoring tags that enables users to evaluate the healthiness of multiple environments.

Air pollution has become a problem that cannot be underestimated, due to its implications in the increasingly frequent catastrophic events of which we are powerless spectators. In recent years, governments have tried to limit not only global emissions but also possible sources of pollution in the buildings we live in.

Many researchers have shown a strong correlation between exposure to pollutants in indoor environments and some widespread diseases, such as asthma, allergies, lung infections, some forms of cancer and diseases affecting the cardiovascular system. Terms such as SBS (Sick Building Syndrome) and THS (Toxic Home Syndrome) have been coined to highlight and group all those symptoms of health deterioration of occupants in environments where there is polluted air. It has been calculated that people spend about 90% of their time indoors, in places such as schools, offices and apartments, so the health impact of the air we breathe in these environments is much greater than that resulting from outdoor air pollution.

On the market we have seen, in recent years, a rapid spread of air quality monitoring systems, especially those integrated into ventilation systems. Many of these are limited to measuring the indoor air quality (IAQ) in a single room and near the ventilation system.

Setting out to create an educational application that involves Bluetooth LE (BLE) technology, I thought of combining business with pleasure by developing a network of IAQ monitoring tags that allows evaluating the healthiness of multiple environments and one that is very simple to install—taking advantage of the Android smartphone features. This is how IAQnet was born—a small system consisting of one or more monitoring tags based on Bluetooth technology and an Android app communicating with them, displaying the values of some sources of pollution present in our house.

Indoor air quality can be influenced by various kinds of contaminants, and currently there is no standard measurement method. One of the most promising methodologies is monitoring the levels of VOC (volatile organic compounds) and carbon dioxide (CO2). VOCs are compounding whose toxicity depends on their density in the air we breathe.

The VOCs include: benzene, which is generated in the production of plastic materials; chlorofluorocarbons (CFCs), which are present in cleaning products and coolants; methylene chloride, which is present in adhesives and spray paints; formaldehyde, which is present in plastic laminates on wood; acetone, which is found in many paints; and numerous other chemicals.

For this project I used a low-cost breakout board with a CCS811 sensor from AMS AG [1]. It’s an ultra-low-power digital sensor with an I2C interface that integrates an MOx (Metal Oxide) gas sensor, to detect a wide range of VOCs and to predict TVOC (total volatile organic compounds). It includes a microcontroller (MCU) that uses an algorithm to process the values measured by presenting a TVOC value at the output, and then converts it into the equivalent CO2 level. The TVOC output range is from 0 to 1,187ppb.

Clearly this equivalent level of CO2 is not a direct measure of the CO2 present in the environment, but rather the result of an equation application. Therefore, the sensor provides TVOC concentrations and an estimation of CO2 or “eCO2.” The eCO2 output range is from 400 to 8,192ppm. TVOC measurement is more important than CO2 in terms of health impact. However, the equivalent CO2 makes it possible to add the sensor output to ventilation standards and implement it for ventilation systems, thus reducing the energy consumption compared to time-scheduled ventilation. The equivalent CO2 allows the detected TVOC value to be interpreted more clearly, through the use of tables (Figure 1) that associate the concentration of CO2 with the need for ventilation. For example, to have a healthy environment in a room, the concentration of CO2 should not exceed 1,000ppm.

Association of the CO2 concentration in indoor air with the need for ventilation

For more precision, it is possible to compensate gas readings with variations in temperature and humidity. I therefore added another low-cost breakout board with an HTU21D sensor from Measurement Specialties [2]. It is a highly accurate temperature and relative humidity sensor. It also uses an I2C interface, so I share the same bus used for the CCS811. Default resolution is set to 12 bits relative humidity and 14-bit temperature readings, and is more than sufficient for our purposes. Measured data is transferred in 2-byte packages, MSB first. But the measured values require a conversion carried out by applying the formulas indicated on page 15 of the HTU21D datasheet [3].

As shown in the schematic (Figure 2), the circuit consists of a core module based on Nordic Semiconductors’ nRF51822 SoC and two breakout boards—one for detecting temperature/humidity, and one for detecting the concentration of VOCs. The nRF51822 is a multiprotocol SoC for ULP wireless applications [4]. It incorporates an Arm Cortex M0 CPU, 256KB flash memory, 32KB RAM memory and a powerful radio transceiver. The nRF51 series RF transceiver is interoperable with BLE (Bluetooth low energy) and other 2.4GHz protocol implementations such as ANT, Gazelle and others. In this project I used BLE to develop the Android app. This makes the app for reading the data detected by the tag simpler and more affordable, even for less experienced readers. However, the system also allows the implementation of an ad hoc communications protocol that is also fully air-compatible with the nRF24L series that I used in a thermal monitoring system project published a few years ago entitled “ Build a Thermal Monitoring Network” (Circuit Cellar 288, July 2014).

The IAQnet tag schematic

For the prototype, I used another breakout board from Waveshare [5], with the nrf51822 in the basic configuration (Figure 2). The oscillator circuit consists of a crystal at 16MHz with two capacitors C1 and C2. The capacitors C9 and C11 have decoupling function on their power source pins. Now, let’s look at the antenna section. For space reasons I have used the model with an integrated PCB antenna. The circuit has an impedance network adapter with capacitors and inductors (L1, L2, L3, C3, C4, C5 and C6). Adapting the impedance is critical to avoid losing power. Table 1 shows the parts list.


Advertise Here

Parts list for the IAQnet project

Parts List
C1, C2 12pF capacitors
C3 2.2nF capacitor
C4 1pF capacitor
C5 3.9pF capacitor
C6 1.5pF capacitor
C7, C8, C11, C12 100nF capacitors
C9 1nF capacitor
C10 47nF capacitor
C13 1µF capacitor
R1, R2, R3 4.7kΩ resistors
X1 16MHz crystal
L1 4.7nH inductor
L2 27nH inductor
L3 3.3nH inductor
U1 nRF51822
U3 CCS811

Pins P0.00 and P0.01 are configured, respectively, as data line (SDA) and clock line (SCL) of the I2C communication bus between the nrf51822 and the HTU21D/CCS811. R1 and R2 are pull-up resistors. Note that both breakout boards contain two SMD pull-up resistors. On the CS811 board they are 4.7kΩ (472SMD), and on the HTU21D they are 10kΩ (103SMD). To avoid connecting the two pairs in parallel—reaching a total resistance that is too low—there are pads on the board that allow you to connect or disconnect the integrated pull-up resistors. In this project, I used only the 4.7kΩ resistors present on the HTU21D board.

The CCS811 chip operates in polling mode. A measurement is performed every second (DRIVE_MODE = 001). The host software cyclically reads data from the sensor, performing a 4-byte data read to the register named ALG_RESULT_DATA. Each pair of values should be converted to a 16-bit type field value. In this way it is possible to obtain the eCO2 and TVOC values directly. The CCS811 supports compensation for relative humidity and ambient temperature. So, before every TVOC reading, it is possible to update ENV_DATA registers with temperature and humidity values from HTU21D.

In power-sensitive applications, the WAKE pin is controlled by a GPIO pin. In my project, I tie it to ground, so the chip never enters sleep mode. The ADDR pin is low, so I2C transactions use the 7-bit address 0x5A—which is different from the address used by the HTU21D (0x80), because the I2C bus is shared. The RESET pin is an active low input, and is pulled up to VCC by default, so I connect an external 4.7kΩ pull-up resistance (R3) to avoid erroneous resets. It is also worth considering that the CCS811 sensor has a 20-minute condition period before accurate readings are generated. Furthermore, the manufacturer AMS advises customers to run the CCS811 for 48 hours, because the performance in terms of sensitivity changes during early use.

To ensure a simple and very small assembly, I created an interconnection layer. It is a small card that allows you to simply connect the three cards without having to add any other components. In Figure 3, the green small card is in the center. The interconnection board also includes a connection strip for a battery, and the JTAG bus (VCC, SWDIO, SWCLK, GND) to update the firmware. Finally, for debugging purposes, I have included some messages in the firmware during the initialization and measurement phases. They are sent through a serial interface that uses pins P0.05 RX, P0.06 TX, P0.07 CTS and P0.12 RTS. If you connect a TTL-to-RS232 converter to these pins, you can view information in terminal as:38,400bps/8/none/1/no flow ctrl.

The tag assembly

In Figure 4, you can see the top and bottom copper layers of the interconnection layer. As you can see, it is a small card with a very simple scheme to connect the three breakout boards together. Assembly takes place by welding the cards to the interconnecting layer, one above the other, like a sandwich. In Figure 5 you can see the assembled prototype board.


Advertise Here

The interconnection layer

The assembled prototype board

The firmware that runs on the core module consists of two parts: a BLE protocol stack (S110) and a user application. The nRF51 series SoCs are programmable with software stacks available from Nordic Semiconductors. These stacks are known as SoftDevice. They make application development flexible, so we can concentrate on the logic of the user application. Moreover, it is possible to integrate the same hardware on platforms that use other protocols by replacing the SoftDevice with the appropriate one. In this project, the user application acquires the values of the two sensors, and makes them available to an Android app via Bluetooth by calling SoftDevice’s BLE functions.

I used the SDK V.10 for the nrF51 series and the SoftDevice S110—both supplied by Nordic Semiconductors. Development of firmware/software for BLE peripherals cannot be fully explained in the few pages of an article. So, I’ll only summarize the fundamental concepts for understanding this project, referring to the extensive literature that can be found on the Internet.

The basic concepts—also used in the app development—are: GAP, GATT and its objects. GAP (generic access profile) defines the general topology of a BLE network. Connecting devices can have two different roles: central and peripheral. In my project, the central device (acts as a client) is the smartphone, and the peripheral (acts as a server) is the tag with sensors. The peripheral uses GAP during the advertising phase, when they send some frames to be discovered on air from the central device. It is important to note that a central-peripheral device can be connected to multiple devices. In my project, the smartphone can query the entire network of tags one by one, thereby keeping the entire home under control. When a peripheral is connected to a central device, it stops to send advertising data, so another device would not be able to find the peripheral and connect to it.


Advertise Here

Every BLE peripheral has a profile GATT (generic attribute profile). It is the top of the ATT (attribute protocol)—a protocol that defines how a server exposes data to a client, and how they are structured. Every profile contains definitions and properties of services and characteristics. When you connect to a device, you use GATT services to communicate. A profile can have one or more services, and each service can have one or more characteristics. Usually services represent features, whereas properties define operations that can be performed on a characteristic, such as read, write, notify and indicate. Every attribute (service and characteristic) is distinguished by its UUID (Universal Unique Identifier). The official BLE standard adopted 16-bit UUIDs, while the custom ones get 128-bit UUIDs assigned.

In my project, I define a custom service with two characteristics. One is read only, and contains a string of four values: temperature, humidity, TVOC and eCO2. The other is writeable and allows commands to be sent to the tag. This second one will be used for features that I would like to develop in the future, such as activating/deactivating a tag from the network, or starting a ventilation system connected to the tag when certain values are reached.

After explaining the basic concepts, it is possible to better understand the firmware logical flow, which is summarized in Listing 1. As you can see, after the initialization of the GAP, the advertising phase and the start of the service that exposes the two characteristics, the rest of the firmware is just an infinite loop that reads relative humidity and temperature, writes them to the CCS811 registry and then reads TVOC and eCO2—updating the values of service characteristics.

Listing 1
The firmware logical flow

INITIALIZE GAP PARAMETERS [device name, connection interval] INITIALIZE ADVERTISING PARAMETERS [advertising interval, timeout] INITIALIZE CUSTOM SERVICE PARAMETERS [assign UUID, create characteristics, assign permissions] INITIALIZE UART [only for debugging purposes] INITIALIZE I2C BUS

HTU21D CONFIGURATION [Resolutions: Temperature 12bits, Rel. Humidity 14bits]

CCS811 read HW ID [reading 0x81 for CCS811] CCS811 read STATUS
IF APP_VALID=1 [a valid firmware image is present] START SENSOR FIRMWARE [transition from boot mode to firmware mode] CCS811 read STATUS
IF FW_MODE=1 [the sensor firmware is ready] CCS811 CONFIGURATION MODE1 [measurement every second and interrupts disabled] START ADVERTISING [to be found by a mobile device] CUSTOM SERVICE LISTENING FOR CONNECTIONS SHARING TWO CHARACTERISTICS

I developed the app using the Android Studio development environment, and an interesting template called Android BluetoothLeGatt Sample that was provided with the environment. This sample demonstrates how to create a custom service for managing connection and data communication with a GATT server. You therefore have every component necessary to transmit arbitrary data between devices by Bluetooth LE API.

Recently, a useful discussion space on GitHub was created about this sample [6]. The GitHub discussion also contains all the modification proposals, as well as code examples. Obviously, I had to change the sample for my purposes. Explaining how to develop an Android app that uses the Bluetooth protocol is beyond the scope of this article, so I will focus on the most significant changes I made.

The sample is created with a default interface. The first step is to design the new interface for viewing the four monitoring parameters. I therefore inserted four TextView controls, each connected to a data field. TextView is a user interface control that is used to set and display the text to the user. One was for the temperature (id.temp), one for the relative humidity (id.hum), one for the TVOC (id.tvoc), and the last for the eCO2 (id.eco2).

I placed a button linked to the onClickUpdateData event, which updates the interface data fields with the values in the service characteristic that correspond to those received by the tag via Bluetooth. The definition of the various interface components is grouped in the iaq_net_layout.xml file. This file is read when the OnCreate function ( is executed during the app boot. The correct layout is loaded thanks to the setContentView(R.layout.iaq_net_layout) instruction. In DeviceControlActivity I also define the mTempmHummEco2 and mTVOC data fields.

To complete the interface development, it is necessary to remove the references to any fields of old layout by inserting those to the new layout fields, such as mTvoc = (TextView) findViewById(, for the relative humidity values. In this way we have linked the interface to the internal variables that will store the measured values.

Figure 6 shows how the app starts by scanning all the Bluetooth devices present in the surrounding area. Tap on the IAQtag link (C8:41:E5:BF:8F:01 MAC address), and the app will connect with the tag displaying the detected data. Now, by clicking on the “Update Data” button, the values are updated in real time.

Figure 6
Android app IAQnet

Figure 6
Android app IAQnet

I then moved on to the development of the functions for processing the values represented in the interface. I added a reading function for the service characteristic readCustomCharacteristic() (, and inserted in the function mGattUpdateReceiver the necessary instructions when the measurement data is ready to be displayed. In the firmware, I chose to send the data from the tag already preformatted in a string 20 characters long. In the reception sequence I call a function to extract the individual values and assign them to the respective internal variables. At this point, all that remains is to link the instructions to the interface button interaction by entering a recall to the readCustomCharacteristic() (Listing 2).

Listing 2
Data extraction and link to interface button interaction

private void displayData(String data) {
if (data != null) {
//Extract data fields from received string

public void onClickUpdateData(View v){
if(mBluetoothLeService != null) {

When you click on the button, you call the function readCustomCharacteristic, and the following actions occur: the values are requested from the tag that updates its service characteristic, the characteristic value is read by the smartphone via BLE updating its own service, then the individual values are extracted from the string and inserted into the internal variables. These are linked to the interface that displays values on the smartphone screen.

From Android 5, for all the BLE apps you need to add some permissions in the manifest file to access the device location, and call a function that requires the user to authorize this activity when the app is running. This can be done by adding a verifyPermissions in the OnCreate event (, as shown in Listing 3.

Listing 3
Permissions and a function to verify them

public static void verifyPermissions(DeviceScanActivity activity) {
int permission = ActivityCompat.checkSelfPermission(activity, Manifest.permission.ACCESS_FINE_LOCATION);
if (permission != PackageManager.PERMISSION_GRANTED) {
// We don’t have permission so prompt the user
new String[] {

An interesting future development would be to use the notification system to update the data on the Android app. Notifications can be sent to the client periodically or whenever the characteristic value changes. The smartphone can register for these notifications, so ambient parameters (IAQ, Temp, Hum, eCO2) are automatically displayed, rather than being requested by a refresh command.

This project can serve as the basis for developing any network of environmental tags. It is sufficient to replace the sensors with those needed for your application. The changes to the firmware and the Android app are relatively simple and relate to the same functions implemented in this project. After all, once the tag has taken the measurements and made them available in the service characteristic, they can be requested from the app via Bluetooth.

Another feature I would like to implement is a second service characteristic that is remotely writable. You could use it to encode a series of commands that could be interpreted by the tag, which should execute them as soon as they are sent via Bluetooth. For now, I hope this circuit can be a starting point for those who want to experience the world of Bluetooth and Android development. 

For detailed article references and additional resources go to:

Adafruit |
Nordic Semiconductor |
TE Connectivity |
Waveshare |


Keep up-to-date with our FREE Weekly Newsletter!

Don't miss out on upcoming issues of Circuit Cellar.

Note: We’ve made the Dec 2022 issue of Circuit Cellar available as a free sample issue. In it, you’ll find a rich variety of the kinds of articles and information that exemplify a typical issue of the current magazine.

Would you like to write for Circuit Cellar? We are always accepting articles/posts from the technical community. Get in touch with us and let's discuss your ideas.

Sponsor this Article
+ posts

Carlo Tauraso ( studied computer engineering at the University of Trieste in Italy and wrote his first assembler code for the Sinclair Research ZX Spectrum. He is currently a senior software engineer, who does firmware development on network devices and various types of micro-interfaces for a variety of European companies. Several of Carlo’s articles and programming courses about Microchip Technology PIC MCUs (USB-PIC, CAN bus PIC, SD CARD, C18) have been published in Italy, France and Spain. In his spare time, Carlo enjoys playing with radio scanners and homemade metal detectors.

Supporting Companies

Upcoming Events

Copyright © KCK Media Corp.
All Rights Reserved

Copyright © 2024 KCK Media Corp.

Device Measures Indoor Air Quality

by Carlo Tauraso time to read: 14 min