Skip to content

Instructions

Charlie Jaewoong Mun edited this page Jan 7, 2020 · 8 revisions

Step 1: Choosing and setting up the hardware for our IoT device

The physics process is called Mie scattering. It works on dust particulates that are bigger than 1/10 of the infrared light wavelength. So, the device can only measure particulates above a certain size. Most commercial sensors can measure particulates above 1um (1 micrometer) in size, which should provide readings for densities of PM2.5 (PM is Particulate Matter, which means particulates that are smaller than 2.5um in size). The accuracy of such inexpensive sensors is subject to a lot of debate. But, in general, they are considered fairly accurate, especially indoors.(There is even a PhD dissertation done on this topic!)

For air quality sensors, You may choose from a variety of sensors. The website AQICN has some good reviews for these sensors. The cheaper ones all work the same way: They have an infrared light source (LED or laser) and a light detector diametrically positioned across an air chamber. The detector measures light scattered by the fine dust or smog particulates in the air chamber, although, accuracy may varies, depends on the sensor models and price.

For this project, I chose to use the Nova PM Sensor SDS011 air quality sensor.
pm2.5_sensor_sds011.jpg

The sensor module requires 4 wires at least in order to operate properly : two for power supply and two for data. The power wires (GND and VCC) connect to the GND and Vin PINs on the NodeMCU board, as they draw 5V power from the NodeMCU. The data wire connects to two digital PINs on the NodeMCU. I choose to connect to [D0] and [D1]. The wiring is shown in The assembled prototype device with power source. above (the wire colors refer to the wires coming out of the air quality sensor the photo which located at the top). The NodeMCU board can be powered by using a regular Micro-USB connection. So, I attached a rechargeable USB portable battery to the prototype as power source.

Step 2: Reading sensor data

The data output from the sensor is a waveform with random peaks and troughs. Every peak indicates that the sensor has detected Particulate Matter (PM) greater than 1um in size. To read PM level from the sensor, the NodeMCU application needs to compute Lo Pulse Occupancy time (LPO time) in a given time unit. It needs to determine how much time (percentage) the data wire is in the low voltage state. The LPO value can then be converted to particulates per liter of air (or particulate mass per m³) using a response curve given in the product specification.

dust.ino

int stat = 1;
int cnt = 0;
char buf[10];

void do_dust(char c, void (*function)(int, int)) {
    //Serial.print("stat="+ String(stat) +", "+ "cnt="+ String(cnt) +" ");
    //Serial.print(c, HEX);
    //Serial.println(" ");

    if (stat == 1) {
       if (c == 0xAA) stat = 2;
    } else
    if (stat == 2) {
       if (c == 0xC0) stat =3;
       else stat = 1;
    } else
    if (stat == 3) {
       buf[cnt++] = c;
       if (cnt == 7) stat = 4;
    } else
    if (stat == 4) {
       if (c == 0xAB) {
          //check checusum
          stat = 1;
       }
       else {
          //Serial.println("Eh? wrong tailer");
       }
       cnt = 0;
       int pm25 = buf[0] + 256*buf[1];
       int pm10 = buf[2] + 256*buf[3];
       function(pm25, pm10);
    }
}

FineDustMonitorWithGPS.ino

#include <SoftwareSerial.h>
#include "RunningMedian.h"

RunningMedian pm25s = RunningMedian(19);
RunningMedian pm10s = RunningMedian(19);

SoftwareSerial ss(12, 13);
SoftwareSerial dust(D1, D0, false, 256);

void loop() {
  while (dust.available() > 0) {
    do_dust(dust.read(), got_dust);
    yield();                                          //loop 에서 while 문을 사용하는 경우 yield 를 포함해주어야 합니다.

  //Serial.println(map_x);
  //Serial.print("pm 25 : ");
  //Serial.println(int(pm25s.getMedian()));

  if (millis() > mark) {//one minute(60000) interval
    mark = millis() + 60000;
    got_interval = true;
  }

  if (got_interval) {
    got_interval = false;
    do_interval();
  }
  yield();
}

Step 3: Connecting our IoT device to ThingSpeak or Plaive

In my previous Wiki article, I described how to set up IoT projects on ThingSpeak Data-analysis Platform. Please follow the instructions in that article to setup an IoT project in your ThingSpeak account and create API key for your device.

In the FinedustMonitor.ino, we will first assign the device ID and access token that was created from ThingSpeak Data-analysis Platform to this device. This code segment must be customized for each device.

FineDustMonitorWithGPS.ino

char* ssid = "";
char* password = "";
String api_key = "";
//#define PLAIVE_SERVER_ENABLE
#define THINGSPEAK_SERVER_ENABLE

Step 4: Sending the data to ThingSpeak IoT Platform and analyzing the data

After the device is connected, it can send messages to the ThingSpeak IoT service.

FineDustMonitorWithGPS.ino

//서버에 데이터 보내기 (Sending collected data to server.)
void do_interval() {
  if (wifi_ready) {
#ifdef PLAIVE_SERVER_ENABLE
    do_server_plaive(api_key, int(pm25s.getMedian()), int(pm10s.getMedian()), get_temperature(), s_map_x, s_map_y);
#else
#ifdef THINGSPEAK_SERVER_ENABLE
    do_server_thingspeak(api_key, int(pm25s.getMedian()), int(pm10s.getMedian()), get_temperature(), s_map_x, s_map_y, status);
#else
    do_server_default(api_key, int(pm25s.getMedian()), int(pm10s.getMedian()), get_temperature(), s_map_x, s_map_y);
#endif
#endif
  }                                                    //wifi is ok
}

wifi.ino

#include <ESP8266WiFi.h>

boolean connect_ap(char* ssid, char* password) {
  int count = 60;                          // 최대 60 회 연결 시도 중 wifi 연결하면 성공, 아니면 실패
  Serial.println();
  Serial.print("connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
    //wifi_oled(count);
    if (!count--) {
      Serial.print("\nNO WIFI");
      return(false);
    }
  }
  Serial.print("\n Got WiFi, IP address: ");
  Serial.println(WiFi.localIP()); 
  return(true);
}

server.ino

const char* host_plaive = "data.plaive.10make.com";  
const char* host_thingspeak = "api.thingspeak.com";
const char* host_default = "finedustapi.10make.com";

const int httpPort = 80;

#include <ESP8266WiFi.h>
#include <WiFiClient.h>
WiFiClient client;
String data;
String contentType;

void do_server_plaive(String api_key,int pm25, int pm10, float temperature, String map_x, String map_y) {

  data = "api_key="+ String(api_key) + "&field1=" + String(pm25) + "&field2=" + String(pm10) + "&field3=" + String(temperature) + "&field4=" + String(map_x) + "&field5=" + String(map_y);
  //contentType= "application/x-www-form-urlencoded";

  //서버 통신 공식 client.println 을 사용하여야 합니다.
  if(client.connect(host_plaive,httpPort)){
    Serial.println("connected");
    client.print("GET /insert.php?");
    client.print(data); 
    client.println(" HTTP/1.1");
    client.println("Host: " + String(host_plaive)); // SERVER ADDRESS HERE AS WELL
    client.println("Cache-Control: no-cache");
    //client.println("Content-Type: application/xE-www-form-urlencoded"); 
    //client.print("Content-Length: "); 
    //client.println(data.length()); 
    client.println("Connection: close");
    client.println(); 
    //client.print(data); 
  }
    
  //서버 통신이 되지 않으면
  else{
    Serial.println("connection failed: ");
    return;
  }
}

void do_server_thingspeak(String api_key,int pm25, int pm10, float temperature, String map_x, String map_y, String status) {

  data = "api_key=" + String(api_key) + "&field1=" + String(pm25) + "&field2=" + String(pm10) + "&field3=" + String(temperature) + "&field4=" + String(map_x) + "&field5=" + String(map_y) + "&status=" + String(status);
  //contentType= "application/x-www-form-urlencoded";

  //서버 통신 공식 client.println 을 사용하여야 합니다.
  if(client.connect(host_thingspeak,httpPort)){
    Serial.println("connected");
    client.print("GET /update?");
    client.print(data); 
    client.println(" HTTP/1.1");
    client.println("Host: " + String(host_thingspeak)); // SERVER ADDRESS HERE AS WELL
    client.println("Cache-Control: no-cache");
    
    //client.println("Content-Type: application/xE-www-form-urlencoded"); 
    //client.print("Content-Length: "); 
    //client.println(data.length()); 
    client.println("Connection: close");
    client.println();
     
    //client.print(data); 
  }
    
  //서버 통신이 되지 않으면
  else{
    Serial.println("connection failed: ");
    return;
  }
}

Conclusion

In this article, I discussed how to build an IoT air quality sensor using the NodeMCU hardware board and development kit, and then connect the device to ThingSpeak or Plaive Service. The ThingSpeak IoT Platform provides an easy-to-use hosted service to aggregate and manage data from IoT devices.