Air quality monitoring is an interesting topic to explore with the rises in pollution, allergy sensitivity, awareness of health & fitness, and technology innovation. The consumer marketplace has seen innovative products released bringing more awareness to air quality monitoring in the home. One such product is the smart scale. These smart scales monitor a variety of health related parameters and also the air quality. The air quality is sent to the cloud and an app can alert you to the changes in the air quality so you will know when an area needs ventilation with fresh air. Having an awareness of the air quality could allow for an improved quality of life. This article shows a method of exploring air quality monitoring by measuring carbon dioxide, volatile organic compounds (VOC), and dust levels using the Arduino* ecosystem and sending the data to a cloud service provider.
The Intel® Edison platform is a natural fit for starting a new prototype or migrating an existing one given its fast processor, large memory size, and integrated connectivity for WiFi and Bluetooth. The Arduino ecosystem provides a capable set of hardware and firmware libraries to experiment with using the Intel® Edison Compute Module and Intel® Edison Arduino Breakout Board.
To learn more about the Intel Edison platform, please see the link below:
http://www.intel.com/content/www/us/en/do-it-yourself/edison.html
Hardware Components:
This project uses the following hardware components for the air quality monitoring system:
- Intel® Edison Compute Module
- Intel® Edison Arduino Breakout Board
- Common Cathode RGB LED + 3 x 1kΩ resistors
- GP2Y1010AU0F Optical Dust Sensor + 150Ω resistor + 220 µF electrolytic capacitor
- MQ-135 Gas Sensor
- K-30 CO2 Sensor
- PIR Motion Sensor
Figure 1 - Hardware Diagram
Theory of Operation:
Figure 1 shows the hardware component connections to the Intel® Edison Arduino Breakout Board. The system uses an RGB LED as a simple visual indication system for displaying the air quality.
To determine the total air quality of an area, three sensors are used:
1. An optical dust sensor is used to measure the dust in the area.
2. A gas sensor is used to measure the Volatile Organic Compounds (VOC) such as smoke.
3. A CO2 sensor is used to measure the carbon dioxide levels with an I2C interface.
In addition, a motion sensor is used for helping the system get the best representation of the total air quality in an area, by filtering out temporary increases in dust concentration caused by movement, and temporary increases in CO2 concentration caused by a person breathing close to the sensors.
When there is no motion detected, the firmware reads the air quality sensors, analyzes the sensor data, updates the visual indication system, and sends the air quality data to the cloud. The details of the system are further discussed in the Firmware section.
To learn more about the sensors, please see the data sheets at the links below:
http://www.kosmodrom.com.ua/pdf/MQ135.pdf
https://www.sparkfun.com/datasheets/Sensors/gp2y1010au_e.pdf
http://www.co2meter.com/collections/co2-sensors/products/k-30-co2-sensor-module
http://www.ladyada.net/media/sensors/PIRSensor-V1.2.pdf
Configuring the I2C Clock Frequency:
It is important to note that at the time of this writing, the default I2C clock frequency on Intel® Edison is above 100kHZ which is outside the specification of the K-30 CO2 sensor. The K-30 CO2 sensor supports a maximum I2C clock frequency (SCL) of 100kHz. The Intel® Edison I2C clock frequency can be changed to 100kHZ following a few steps:
-Ensure that the latest Intel® Edison Yocto firmware image is installed:
http://www.intel.com/support/edison/sb/CS-035180.htm
-Open an Edison Linux terminal and login as root:
https://software.intel.com/en-us/articles/getting-started-with-the-intel-edison-board-on-windows
-cd /sys/devices/pci0000:00/0000:00:09.1/i2c_dw_sysnode
-echo std > mode
-cat mode
To learn more about the Intel® Edison compute module and the I2C peripheral, please see the link below:
Firmware:
The following code shows the includes, macros, and functions for the air quality system. Functions for Initialization, Main Loop, Reading Motion Sensor, Reading Air Quality Sensors, Analyzing Total Air Quality, Updating Visual Indication LED, and Sending Data to a Cloud Service Provider are discussed.
Includes:
#include <Wire.h>
Macros:
//Pin Defines #define gasSensorPin A1 #define dustSensorPin A0 #define dustSensorLEDPin 2 #define redRGBLEDPin 3 #define greenRGBLEDPin 4 #define blueRGBLEDPin 5 #define motionSensorPin 6 //Air Quality Defines #define AIR_QUALITY_OPTIMAL 2 #define AIR_QUALITY_GOOD 1 #define AIR_QUALITY_BAD 0 #define AIR_QUALITY_UNKNOWN -1 #define MAX_SENSOR_READINGS 10 #define SENSOR_READING_DELAY 1000 //Motion Sensor Defines #define MOTION_NOT_DETECTED 0 #define MOTION_DETECTED 1 #define MOTION_DELAY_TIME 1000 //Dust Sensor Timing Parameters (from p.5 of datasheet) #define SAMPLE_DELAY 280 //Sampling #define PULSEWIDTH_DELAY 40 //Pw #define PERIOD_DELAY 9680 //T //Gas Sensor Thresholds #define GAS_SENSOR_OPTIMAL 140 #define GAS_SENSOR_GOOD 200 //Dust Sensor Thresholds #define DUST_SENSOR_OPTIMAL 125 #define DUST_SENSOR_GOOD 250 //CO2 Sensor Thresholds #define CO2_SENSOR_OPTIMAL 800 #define CO2_SENSOR_GOOD 2000
Functions:
Initialization: This function initializes the serial debug interface, the I/O pins, and the I2C interface.
void setup() { Serial.begin(9600); pinMode(gasSensorPin, INPUT); pinMode(dustSensorPin, INPUT); pinMode(dustSensorLEDPin, OUTPUT); pinMode(redRGBLEDPin, OUTPUT); pinMode(greenRGBLEDPin, OUTPUT); pinMode(blueRGBLEDPin, OUTPUT); pinMode(motionSensorPin, INPUT); Wire.begin(); }
Main Loop: The main loop initializes the system, checks for motion, reads the air quality sensors, analyzes the total air quality, updates the indication LED, and sends the data to a cloud service.
void loop() { // -- Init int airQuality = 0; int motion = 0; int sensorAirQuality[3] = {0,0,0}; //0-Gas Sensor, 1-CO2 Sensor, 2-DustSensor Serial.println(""); // -- Check for motion motion = readMotionSensor(); if (motion == MOTION_NOT_DETECTED) { // -- Read Air Quality Sensors readAirQualitySensors(sensorAirQuality); // -- Analyze Total Air Quality airQuality = analyzeTotalAirQuality(sensorAirQuality[0],sensorAirQuality[1],sensorAirQuality[2]); // -- Update Indication LED updateIndicationLED(airQuality); // -- Update Air Quality Value for Cloud Datastream updateCloudDatastreamValue(CHANNEL_AIR_QUALITY_ID, airQuality); // -- Send Data To Cloud Service sendToCloudService(); } }
Reading Motion Sensor: The motion sensor is read by sampling the sensor’s digital output pin. If motion is detected, the sensor output pin will go HIGH. The function attempts to filter glitches and returns whether motion was detected or not.
int readMotionSensor() { // -- Init int motionSensorValue = MOTION_NOT_DETECTED; int motion = MOTION_NOT_DETECTED; Serial.println("-Read Motion Sensor"); // -- Read Sensor motionSensorValue = digitalRead(motionSensorPin); // -- Analyze Value if (motionSensorValue == MOTION_DETECTED) { delay(MOTION_DELAY_TIME); motionSensorValue = digitalRead(motionSensorPin); if (motionSensorValue == MOTION_DETECTED) { motion = MOTION_DETECTED; Serial.println("--Motion Detected"); updateIndicationLED(AIR_QUALITY_UNKNOWN); } } return motion; }
Reading Air Quality Sensors: This function calls the individual gas, co2, and dust sensor functions. The function takes a pointer to integer array for storing the air quality results for each sensor.
void readAirQualitySensors(int* sensorAirQuality) { Serial.println("-Read Air Quality Sensors"); sensorAirQuality[0] = readGasSensor(); sensorAirQuality[1] = readCO2Sensor(); sensorAirQuality[2] = readDustSensor(); }
Reading Gas Sensor: The gas sensor can detect gases such as NH3, NOx, alcohol, Benzene, and smoke. The gas sensor contains an analog voltage output that is proportional to the gas levels in the air. An A/D conversion is performed to read this sensor. The function reads the sensor, averages the readings, analyzes the sensor data, and returns the air quality for this sensor.
int readGasSensor() { // -- Init int airQuality = 0; int gasSensorValue = 0; // -- Read Sensor for (int i=0; i < MAX_SENSOR_READINGS; i++) { gasSensorValue += analogRead(gasSensorPin); delay(SENSOR_READING_DELAY); } gasSensorValue /= MAX_SENSOR_READINGS; //Average the sensor readings // -- Update Cloud Datastream Serial.print("--gasSensorValue = "); Serial.println(gasSensorValue); updateCloudDatastreamValue(CHANNEL_GAS_SENSOR_ID, gasSensorValue); // -- Analyze Value if (gasSensorValue < GAS_SENSOR_OPTIMAL) { airQuality = AIR_QUALITY_OPTIMAL; } else if (gasSensorValue < GAS_SENSOR_GOOD) { airQuality = AIR_QUALITY_GOOD; } else { airQuality = AIR_QUALITY_BAD; } return airQuality; }
Reading Dust Sensor: The dust sensor contains an optical sensing system that is energized using a digital output pin. An A/D conversion is then performed to sample the sensor’s analog voltage output that is proportional to the dust in the air. This function reads the sensor, averages the readings, analyzes the sensor data, and returns the air quality for this sensor.
int readDustSensor() { // -- Init int airQuality = 0; int dustSensorValue = 0; // -- Read Sensor for (int i=0; i < MAX_SENSOR_READINGS; i++) { digitalWrite(dustSensorLEDPin,LOW); //Enable LED delayMicroseconds(SAMPLE_DELAY); dustSensorValue += analogRead(dustSensorPin); delayMicroseconds(PULSEWIDTH_DELAY); digitalWrite(dustSensorLEDPin,HIGH); //Disable LED delayMicroseconds(PERIOD_DELAY); delay(SENSOR_READING_DELAY); } dustSensorValue /= MAX_SENSOR_READINGS; //Average the sensor readings // -- Update Cloud Datastream Serial.print("--dustSensorValue = "); Serial.println(dustSensorValue); updateCloudDatastreamValue(CHANNEL_DUST_SENSOR_ID, dustSensorValue); // -- Analyze Value if (dustSensorValue < DUST_SENSOR_OPTIMAL) { airQuality = AIR_QUALITY_OPTIMAL; } else if (dustSensorValue < DUST_SENSOR_GOOD) { airQuality = AIR_QUALITY_GOOD; } else { airQuality = AIR_QUALITY_BAD; } return airQuality; }
Reading CO2 Sensor: The CO2 sensor returns a CO2 concentration level in parts per million (ppm). The CO2 sensor is read through the I2C interface. This function reads the sensor, averages the readings, analyzes the sensor data, and returns the air quality for this sensor.
int readCO2Sensor() { // -- Init int airQuality = 0; int co2SensorValue = 0; int tempValue=0; int invalidCount=0; // -- Read Sensor for (int i=0; i < MAX_SENSOR_READINGS; i++) { tempValue = readCO2(); // see http://cdn.shopify.com/s/files/1/0019/5952/files/Senseair-Arduino.pdf?1264294173 for this function (tempValue == 0) ? invalidCount++ : co2SensorValue += tempValue; delay(SENSOR_READING_DELAY); } if (invalidCount != MAX_SENSOR_READINGS) { co2SensorValue /= (MAX_SENSOR_READINGS - invalidCount); //Average the sensor readings } // -- Update Cloud Datastream Serial.print("--co2SensorValue = "); Serial.println(co2SensorValue); updateCloudDatastreamValue(CHANNEL_CO2_SENSOR_ID, co2SensorValue); // -- Analyze Value if (co2SensorValue < CO2_SENSOR_OPTIMAL) { airQuality = AIR_QUALITY_OPTIMAL; } else if (co2SensorValue < CO2_SENSOR_GOOD) { airQuality = AIR_QUALITY_GOOD; } else { airQuality = AIR_QUALITY_BAD; } return airQuality; }
Analyzing Total Air Quality: This function determines the total air quality for the area by analyzing the gas, co2, and dust air quality values passed to this function. The function returns the total air quality level for the area.
int analyzeTotalAirQuality(int gasAirQuality, int co2AirQuality, int dustAirQuality) { int airQuality = 0; Serial.println("-Analyze Total Air Quality"); if (gasAirQuality==AIR_QUALITY_BAD \ || dustAirQuality==AIR_QUALITY_BAD \ || co2AirQuality==AIR_QUALITY_BAD) { Serial.println("--Air Quality Is BAD"); airQuality = AIR_QUALITY_BAD; } else if (gasAirQuality == AIR_QUALITY_OPTIMAL \ && dustAirQuality == AIR_QUALITY_OPTIMAL \ && co2AirQuality==AIR_QUALITY_OPTIMAL) { Serial.println("--Air Quality Is OPTIMAL"); airQuality = AIR_QUALITY_OPTIMAL; } else { Serial.println("--Air Quality Is Good"); airQuality = AIR_QUALITY_GOOD; } return airQuality; }
Updating Visual Indication LED: This function updates the indication LED to the appropriate color for the air quality value that is passed to this function. The LED turns blue for optimal air quality levels, green for good air quality levels, and red for bad air quality levels. The LED turns magenta if motion is detected.
void updateIndicationLED(int airQuality) { Serial.println("-Update Indication LED"); // --Turn off all colors digitalWrite(redRGBLEDPin,LOW); digitalWrite(greenRGBLEDPin,LOW); digitalWrite(blueRGBLEDPin,LOW); // --Update Indication LED if (airQuality == AIR_QUALITY_UNKNOWN) { digitalWrite(redRGBLEDPin,HIGH); digitalWrite(greenRGBLEDPin,HIGH); digitalWrite(blueRGBLEDPin,HIGH); } else if (airQuality == AIR_QUALITY_OPTIMAL) { digitalWrite(blueRGBLEDPin, HIGH); } else if (airQuality == AIR_QUALITY_GOOD) { digitalWrite(greenRGBLEDPin, HIGH); } else { digitalWrite(redRGBLEDPin, HIGH); } }
Sending Data to a Cloud Service Provider:
To connect Intel® Edison to a WiFi network, please see the link below:
http://www.intel.com/support/edison/sb/CS-035342.htm
Figure 2 - xively.com feed
In this example, xively.com is used as the cloud service provider that the air quality data is sent to. Figure 2 shows an example feed with four channels. The channels are further discussed in the Functions section. Integration with xively.com requires the Http Client and Xively libraries added to the Arduino IDE. Please see the link below to learn more about xively.com, creating an account, Arduino tutorials, and library integration with the Arduino IDE.
https://xively.com/dev/tutorials/arduino_wi-fi/
The following code shows an example of the includes, macros, and functions that can be added to the air quality system to add xively.com support.
Includes:
#include <WiFi.h> #include <HttpClient.h> #include <Xively.h>
Macros:
//Xively.com Defines #define XIVELY_FEED <enter your feed number here> #define XIVELY_KEY <enter your key string here> #define XIVELY_HTTP_SUCCESS 200 #define CHANNEL_AIR_QUALITY "AIR_QUALITY" #define CHANNEL_AIR_QUALITY_ID 0 #define CHANNEL_GAS_SENSOR "GAS_SENSOR" #define CHANNEL_GAS_SENSOR_ID 1 #define CHANNEL_CO2_SENSOR "CO2_SENSOR" #define CHANNEL_CO2_SENSOR_ID 2 #define CHANNEL_DUST_SENSOR "DUST_SENSOR" #define CHANNEL_DUST_SENSOR_ID 3 #define MAX_CHANNELS 4
Global Variables:
//Xively Datastream XivelyDatastream datastreams[] = { XivelyDatastream(CHANNEL_AIR_QUALITY, strlen(CHANNEL_AIR_QUALITY), DATASTREAM_FLOAT), XivelyDatastream(CHANNEL_GAS_SENSOR, strlen(CHANNEL_GAS_SENSOR), DATASTREAM_FLOAT), XivelyDatastream(CHANNEL_CO2_SENSOR, strlen(CHANNEL_CO2_SENSOR), DATASTREAM_FLOAT), XivelyDatastream(CHANNEL_DUST_SENSOR, strlen(CHANNEL_DUST_SENSOR), DATASTREAM_FLOAT) }; //Xively Feed XivelyFeed feed(XIVELY_FEED, datastreams, MAX_CHANNELS); //Xively Client WiFiClient client; XivelyClient xivelyclient(client);
Functions:
Updating the data stream: This function is called to update the values for a xively.com channel datastream. The function is passed the channelID, and the datastream value. In this system as shown in Figure 2, four datastreams are used. The datastreams are updated with raw sensor data from the gas, co2, and dust sensor functions. In addition, a datastream is also updated in the main loop with the total air quality value.
void updateCloudDatastreamValue(int channelID, int value) { // -- Update the Datastream Value datastreams[channelID].setFloat(value); }
Sending the Datastreams to Xively: This function performs a PUT operation to a xively.com feed. The function returns the status of successful or the error code. The main loop calls this function.
void sendToCloudService() { int status=0; Serial.println("-Send To Cloud Service”); // -- Upload the Datastream to Xively status = xivelyclient.put(feed, XIVELY_KEY); // -- Verify Transaction if (status == XIVELY_HTTP_SUCCESS) { Serial.println("--HTTP OK"); } else { Serial.print("--ERROR: "); Serial.println(status); } }
Summary:
Hope you enjoyed exploring air quality monitoring with the Intel Edison platform. Challenge yourself to add additional indication showing the status of each sensor, to add enhancements to the cloud service experience with alert triggers when the air quality changes, and also look for opportunities to integrate air quality monitoring with other systems.
About the Author:
Mike Rylee is a Software Engineer at Intel Corporation with a background in developing embedded systems and apps on Android*, Windows*, iOS*, and Mac*. He currently works on enabling for Android and the Internet of Things.
++This sample source code is released under the Intel Sample Source License
Notices No license (express or implied, by estoppel or otherwise) to any intellectual property rights is granted by this document.
Intel disclaims all express and implied warranties, including without limitation, the implied warranties of merchantability, fitness for a particular purpose, and non-infringement, as well as any warranty arising from course of performance, course of dealing, or usage in trade.
This document contains information on products, services and/or processes in development. All information provided here is subject to change without notice. Contact your Intel representative to obtain the latest forecast, schedule, specifications and roadmaps.
The products and services described may contain defects or errors known as errata which may cause deviations from published specifications. Current characterized errata are available on request.
Copies of documents which have an order number and are referenced in this document may be obtained by calling 1-800-548-4725 or by visiting www.intel.com/design/literature.htm.
Intel and the Intel logo are trademarks of Intel Corporation in the U.S. and/or other countries.
*Other names and brands may be claimed as the property of others
© 2015 Intel Corporation.