Arduino :: Lecture & TIPs

[미세먼지 측정기] IoT 공기질 측정기 만들기 (3)

지난 게시물에서 미세먼지 측정기를 만들 때 필요한 준비물에 대해 알아보았습니다. 미세먼지 측정기를 만들때 필요한 부품들을 다시 정리해보면 아래와 같습니다.

NodeMCU ESP8266-12E 보드
NovaFitness SDS011 미세먼지 센서
uBlox NEO6MV2 GPS 센서
DS18B20 온도 센서
SSD1306 0.96 인치 I2C OLED 디스플레이
IRIVER Tactile 외장 배터리 10000mAh
F-F 점퍼 케이블

 

하드웨어 조립 및 구성하기.

가장 먼저 ESP8266 MCU 보드에 OLED 디스플레이를 연결합니다. 0.96 인치 화면을 가진 SSD1306 OLED 부품이 정상적으로 작동하기 위해서는 네 개의 케이블 연결이 필요합니다. OLED 부품의 VCC 단자는 ESP8266 MCU 보드의 3v3 단자에, GND 단자는 GND 단자에, SCL 단자는 D2 단자에, SDA 단자는 D3 단자에 연결합니다.

이어서 온도센서의 + 단자는 NodeMCU 보드의 3.3v 단자에, out 단자는 NodeMCU 보드의 D4 단자에, 그리고 – 단자는 NodeMCU 보드의 GND 단자에 각각 연결합니다.

차례로, 미세먼지 센서의 5V 단자는 NodeMCU 보드의 VIN (5V) 단자에, GND 단자는 GND 단자에, TX 단자는 D1 단자에, RX 단자는 D0 단자에 연결해 준 후,

마지막으로 GPS 센서를 연결합니다. GPS 센서의 VCC 단자는 NodeMCU 보드의 3v3 단자에, RX 단자는 D7 단자에, TX 단자는 D6 단자에, GND 단자는 GND 단자에 연결해줍니다.

 

SSD1306 OLEDNodeMCU ESP8266-12E
VCC3v3
GNDGND
SCLD2
SDAD3
DS18B20 온도 센서NodeMCU ESP8266-12E
+3v3
outD4
GND
SDS011 미세먼지 센서NodeMCU ESP8266-12E
5VVIN (5V)
GNDGND
TXD1
RXD0
NEO6MV2 GPS 센서NodeMCU ESP8266-12E
VCC3v3
RXD7
TXD6
GNDGND
하드웨어 조립 및 구성하기 ⓒhappybono

 

센서를 통해 데이터를 읽은 후 ThingSpeak 서버에 POST API 를 사용하여 데이터를 전송합니다.

여기까지 모든 부품들을 알맞게 연결하셨으면, 소프트웨어 쪽도 작업하여야 미세먼지 측정기가 정상적으로 작동할텐데요, Arduino IDE (통합개발환경) 를 사용해 ESP8266 보드에 대한 펌웨어를 코딩하기 전, 올바른 라이브러리와 COM 드라이버를 가져오도록 합니다.

위에서 언급한 바와 같이, 저의 경우 HTTP 통신 프로토콜을 통해 ThingSpeak IoT 플랫폼에서 제공하는 GET 및 POST 방식과 REST 접근 방식을 통한 과정으로 데이터를 서버에 업로드했습니다. 물론, 이번 포스팅에서 소개드리는 ThingSpeak IoT 플랫폼이나 여타 온라인 플랫폼 (예 : Azure IoT, Google Cloud IoT Core, IBM Watson IoT 플랫폼 등) 을 이용하면 편리하고 간편하다는 장점이 존재하지만, 다수의 IoT 기기를 개발하여 사용하는 경우 데이터 업로드 조건이나 할당량에 대한 제한 또한 존재하는 관계로, 온라인 플랫폼을 활용하지 않고 개인 웹 서버를 직접 운용할 수도 있다는 점을 함께 언급해둡니다.

ThingSpeak 서버의 채널 활성화 및 API Key 사용을 위해 thingspeak.com 회원 가입 절차를 사전에 완료해주시면 시간을 절약하실 수 있습니다.

 

Arduino IDE 환경 설정하기

https://www.arduino.cc/en/Main/Software 에서 운영체제에 맞는 아두이노 IDE 를 다운받은 후 설치합니다.

NodeMCU (ESP8266) 와 호환되는 USB 드라이버, “CH341” 을 https://github.com/nodemcu/nodemcu-devkit/tree/master/Drivers 에서 설치합니다.

PC 운영체제에 따라, 해당하는 CH341 드라이버를 설치합니다. CH341SER.exe 를 실행 한 후 [INSTALL] 버튼을 클릭하여 설치를 진행합니다. ⓒ happybono

 

상단의 [File] 메뉴 하위의 [Preferences] 항목을 클릭하여 [Additional Board Manager URLs] 항목의 텍스트박스에 http://arduino.esp8266.com/stable/package_esp8266com_index.json 를 입력합니다.

 

Tools > Board > Board Manager … 에서 esp8266 보드를 설치합니다.

 

NodeMCU 와 PC 를 케이블로 연결 한 다음, Tools > Board 에서 NodeMCU 1.0 보드를 선택합니다.

 

Port 에서 NodeMCU 보드에 연결된 시리얼 포트를 선택합니다.

속도는 “115200” 이 기본 설정이며, 상황에 따라 속도를 조정하도록 합니다.

 

Arduino IDE 에 라이브러리 설치하기

Sketch > Include Library > Manage Libraries … 선택 후 Library Manager 창이 화면에 표시되면, “ssd1306” 을 입력하여 “ESP8266 and ESP32 OLED Driver for SSD1306 Displays” 라이브러리를 검색해 설치합니다.

온도 센서 (DS18B20) 라이브러리인 “DallasTemperature” 라이브러리 역시 동일한 방법으로 검색하여 설치하도록 합니다.

ThingSpeak IoT 플랫폼 (웹 서버) 에 데이터를 전송하려면 앞서 설계한 ESP8266 기반의 미세먼지 측정기가 Wi-Fi 네트워크에 우선적으로 연결되어야 합니다. 해당 기능 구현을 위해 적절한 라이브러리를 가져와 프로젝트에 포함시킨 후 연결할 무선 네트워크 SSID 와 비밀번호를 지정시켜 준 후 올바른 방법을 사용하여 Wi-Fi 를 켜고 연결하도록 구현하여야 합니다.

Wi-Fi 연결을 활성화하기 위해 connect_ap() 메서드에서 호출 한 다음 함수를 사용하여 연결을 초기화합니다.

#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);
}

성공적으로 Wi-Fi 네트워크에 연결되면, 콘솔 로그 화면에 “Got WiFi, IP address: ” 문구가 표시되고, 이어서 연결된 IP 주소가 화면에 출력되는데요, 이는 센서 데이터를 ThingSpeak IoT 플랫폼 (웹 서버) 에 전송가능한 상태로 전환되었다는 의미입니다!

 

센서 데이터 읽어오기

생성한 ThingSpeak Channel 페이지 내에서 [API Keys] 탭 하위의 [Write API Key] 에 표시된 문자열을 복사하여 소스 코드의 “API_KEY” 부분에 붙여넣습니다. ⓒ happybono

 

//
//    FILE: FinedustMonitorWithGPS.ino
//  AUTHOR: Jaewoong Mun
// CREATED: November 19, 2019
//
// Released to the public domain
//

#include <TinyGPS++.h>
#include <SoftwareSerial.h>
TinyGPSPlus gps;
SoftwareSerial ss(12, 13);
SoftwareSerial dust(D1, D0, false, 256);            //RX, TX Communication
#include "RunningMedian.h"

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

char* ssid = "SSID";                                //Wifi 네트워크 이름 (SSID)
char* password = "SSID_PASSWORD";                   //Wifi 네트워크 비밀번호 (Password)
String api_key = "API_KEY";                         //ThingSpeak Channel 에 할당된 Write API Key

//#define PLAIVE_SERVER_ENABLE
#define THINGSPEAK_SERVER_ENABLE

boolean wifi_ready;
float map_x, map_y;
String s_map_x, s_map_y, status;
int pm25i, pm10i;

//초기 세팅 (Initialize.)
void setup() {
  Serial.begin(115200);
  dust.begin(9600);
  ss.begin(9600);
  setup_oled();
  wifi_ready = connect_ap(ssid, password);
  
  if (!wifi_ready) nowifi_oled();

  Serial.println("\nFinedust Sensor Box V1.3, 2019/12/25 HappyBono");
}


void got_dust(int pm25, int pm10) {                 //formula for dust sensor just use!!
  pm25 /= 10;
  pm10 /= 10;
  pm25s.add(pm25);
  pm10s.add(pm10);
  do_oled(pm25, pm10);                              //print pm25, pm10 in oled
}


//서버에 데이터 보내기 (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
    Serial.println("dst: pm25=" + String(int(pm25s.getMedian())) + " / pm10=" + String(int(pm10s.getMedian())) + "/ s=" + String(status));
    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
}

unsigned long mark = 0;
boolean got_interval = false;

//아두이노가 반복적으로 작동하는 부분 (Where Arduino works repeatedly.)
void loop() {
  if (ss.available() <= 0) {
    // Serial.println("SIGNAL STATUS : WEAK");
    s_map_x = String(map_x, 6);
    s_map_y = String(map_y, 6);
  }
  else {
    while (ss.available() > 0) {
      //Serial.println("SIGNAL STATUS : GREAT");
      if (gps.encode(ss.read())) {
        //Serial.println("GPS READ");
        Serial.println(ss.read());
        if (gps.location.isValid()) {
          //Serial.println("LOCATION : GREAT");
          map_x = gps.location.lat();
          map_y = gps.location.lng();
          Serial.println(String(map_x, 6));
          Serial.println(String(map_y, 6));
        }
      }
      s_map_x = String(map_x, 6);
      s_map_y = String(map_y, 6);
      yield();
    }
  }
  while (dust.available() > 0) {
    do_dust(dust.read(), got_dust);
    yield();                                          //loop 에서 while 문을 사용하는 경우 yield 를 포함해주어야 합니다.

    //Serial.println(map_x);
    //Serial.print("pm 10 : ");
    //Serial.println(int(pm10s.getMedian()));

    /* AQI (실시간 대기질 지수) 등급 분류를 위한 코드입니다.
       실시간 대기질 기준 수치는 국제 표준인 WHO 대기질 수치 기준으로 계산하였습니다.

       http://www.euro.who.int/__data/assets/pdf_file/0005/78638/E90038.pdf
       https://airnow.gov/index.cfm?action=aqibasics.aqi */


    // 초미세먼지 AQI (실시간 대기질 지수) 등급을 분류합니다.
    //   0 이상   8 이하 : 1
    //   9 이상  16 이하 : 2
    //  17 이상  26 이하 : 3
    //  27 이상  34 이하 : 4
    //  35 이상  43 이하 : 5
    //  44 이상  51 이하 : 6
    //  52 이상  ∞  이하 : 7

    int pm25m = int(pm25s.getMedian());

    if (pm25m < 9) {
      pm25i = 1;
    } else if (pm25m < 17) {
      pm25i = 2;
    } else if (pm25m < 27) {
      pm25i = 3;
    } else if (pm25m < 35) {
      pm25i = 4;
    } else if (pm25m < 44) {
      pm25i = 5;
    } else if (pm25m < 52) {
      pm25i = 6;
    } else {
      pm25i = 7;
    }

    // 미세먼지 AQI (실시간 대기질 지수) 등급을 분류합니다.
    //   0 이상  16 이하 : 1
    //  16 이상  31 이하 : 2
    //  32 이상  51 이하 : 3
    //  52 이상  68 이하 : 4
    //  69 이상  84 이하 : 5
    //  85 이상 101 이하 : 6
    // 102 이상  ∞  이하 : 7

    int pm10m = int(pm10s.getMedian());

    if (pm10m < 16) {
      pm10i = 1;
    } else if (pm10m < 32) {
      pm10i = 2;
    } else if (pm10m < 52) {
      pm10i = 3;
    } else if (pm10m < 69) {
      pm10i = 4;
    } else if (pm10m < 85) {
      pm10i = 5;
    } else if (pm10m < 102) {
      pm10i = 6;
    } else {
      pm10i = 7;
    }

    /* ThingSpeak 채널 내 Status Update (상태 업데이트) 영역에 표시되는 문구이므로,
        종합적인 정보 표현을 위해 초미세먼지와 미세먼지 등급을 비교 한 후
        두 가지 중 높은 등급 기준으로 경고 혹은 권고메시지를 표시합니다. */

    // 분류된 초미세먼지 등급이 미세먼지 등급보다 같거나 높은 경우, 초미세먼지 등급을 기준으로 내용을 표시하기 위하여 아래의 문자열을 status 변수에 저장합니다.

   switch ((pm25i >= pm10i) ? pm25i : pm10i) {
      case 1:
        status = "Excellent%20(1)%20:%20The%20air%20quality%20is%20excellent.%20As%20air%20pollution%20poses%20no%20threat,%20conditions%20are%20ideal%20for%20outdoor%20activities.";
        break;

      case 2:
        status = "Very%20Good%20(2)%20:%20The%20air%20quality%20is%20very%20good.%20As%20air%20pollution%20poses%20little%20or%20no%20risk,%20conditions%20very%20good%20for%20outdoor%20activities.";
        break;

      case 3:
        status = "Moderate%20(3)%20:%20Air%20quality%20is%20acceptable.%20however,%20for%20some%20pollutants,%20there%20may%20be%20a%20moderate%20health%20concern%20for%20specific%20people%20who%20are%20unusually%20sensitive%20to%20air%20pollution.";
        break;

      case 4:
        status = "Satisfactory%20(4)%20:%20Members%20of%20sensitive%20groups%20may%20experience%20health%20effects,%20other%20people%20should%20limit%20spending%20time%20outdoors,%20especially%20when%20they%20experience%20symptoms%20such%20as%20cough%20or%20sore%20throat.";
        break;

      case 5:
        status = "Bad%20(5)%20:%20Everyone%20may%20begin%20to%20experience%20health%20effects,%20members%20of%20sensitive%20groups%20may%20experience%20more%20serious%20health%20effects.%20Not%20recommended%20for%20outdoor%20activities.";
        break;

      case 6:
        status = "Severe%20(6)%20:%20Everyone%20may%20experience%20more%20serious%20health%20effects.%20People%20at%20risk%20should%20be%20avoided%20to%20go%20outside%20and%20should%20limit%20outdoor%20activities%20to%20a%20minimum.%20Outdoor%20activities%20are%20discouraged.";
        break;

      case 7:
        status = "Hazardous%20(7)%20:%20People%20at%20risk%20should%20avoid%20going%20outside%20and%20should%20limit%20outdoor%20activities%20to%20a%20minimum.%20Outdoor%20activities%20are%20strongly%20discouraged.";
        break;
    }

    //Serial.println("PM2.5 = " + String(int(pm25s.getMedian())) + " / " + String(pm25i));
    //Serial.println("PM10.0  = " + String(int(pm10s.getMedian())) + " / " + String(pm10i));
  }

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

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

 

SDS011 미세먼지 센서의 스펙 상에 기재된 프로토콜을 참고하여 코드를 작성하여야 합니다. 시작되는 Header 값이 AA 인 것을 확인하여 데이터의 시작 부분을 확인할 수 있고, 이어서 CO 문자 값 이후에 데이터 (Data 1, Data 2… Data 5, Data 6) 가 기록되어 있으니, 데이터 수집을 시작해 총 7 바이트를 받아들이도록 로직을 구현하면 되겠습니다.

데이터를 쭈욱 받아들이다가, 마지막 메시지 (MessageTail) 로 AB 문자 값을 확인한 후 초기 상태로 전환해, 데이터의 첫 시작 부분을 확인하는 로직으로 돌아가도록 합니다.

 

 

//
//    FILE: dust.ino
//  AUTHOR: Jaewoong Mun
// CREATED: November 19, 2019
//
// Released to the public domain
//

int stat = 1;
int cnt = 0;
// 버퍼를 10 개 잡습니다.
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) {
       // 메시지 시작 AA (Header 부분)
       if (c == 0xAA) stat = 2;
    } else
    if (stat == 2) {    
       // "CO" 가 나오면 데이터 수집을 시작합니다.
       if (c == 0xC0) stat = 3; 
       else stat = 1;
    } else
    if (stat == 3) {
       buf[cnt++] = c;
       // 총 7 바이트 읽어들입니다.
       if (cnt == 7) stat = 4;
    } else
    if (stat == 4) {
      // 스펙상 MessageTail, "AB" 가 나오면 초기 상태로 전환합니다.
      if (c == 0xAB) {
          //checksum
          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);
    }
}

  

//
//    FILE: oled.ino
//  AUTHOR: Jaewoong Mun
// CREATED: November 19, 2019
//
// Released to the public domain
//

#include "SSD1306.h"

SSD1306  display(0x3c, D3, D2);  //Data, Clock

void setup_oled() {
  display.init();
  display.clear();  
  display.flipScreenVertically();
  display.setFont(ArialMT_Plain_10);
  display.drawString(0,13, "initialize OLED...");  
  display.display();
}

void wifi_oled(int cnt) {
  display.clear();  
  display.setFont(ArialMT_Plain_10);
  display.drawString(0,13, "waiting wifi..."); 
  display.drawString(0,24, String("ssid= ")+String(ssid));
  display.drawString(0,35, String("password= ")+ String(password)); 
  display.drawString(0,53, String(cnt));
  display.display();  
}

void nowifi_oled() {
  display.clear();  
  display.setFont(ArialMT_Plain_10);
  display.drawString(0,0, "NO WIFI: skip wifi..."); 
  display.drawString(0,13, "waiting dust value..."); 
  display.drawString(0,24, String("Check dust sensor,"));
  display.drawString(0,35, String("if you see this message."));
  display.display();  
}

bool m = false;
int v1[128], v2[128];

void do_oled(int pm25, int pm10) {
  String m1;
  display.clear();  
  display.setFont(ArialMT_Plain_10);
  if (m) m1 = "*"; else m1 = " ";
  m = m?false:true;  
  display.drawString(123,0, m1);  
  if (!wifi_ready) display.drawString(0,53, "No WiFi");
  display.drawString(0,0, "pm 2.5");
  display.drawString(64,0, "pm 10.0");  
  display.setFont(ArialMT_Plain_24);
  display.drawString(0,13, String(pm25));
  display.drawString(64,13, String(pm10));

  int vmax = 0;
  int vmin = 999;
  for (int i=0; i<128; i++) {
    if (i<127) { v1[i] = v1[i+1]; v2[i] = v2[i+1]; }
    else { v1[i] = pm25; v2[i] = pm10; }
    vmax = vmax>v2[i]?vmax:v2[i]; // assume that v2 > v1 always
    vmin = vmin<v1[i]?vmin:!v1[i]?vmin:v1[i];
  }

  const int YSPAN = 26;
  int range = vmax - vmin;
  float scale = range>YSPAN?float(YSPAN)/range:1.;
  int delta = vmax>YSPAN?vmin:0;

  for (int i=0; i< 128; i++) {
    if (v1[i] && v2[i]) {
      display.drawVerticalLine(i, 63-int(scale*(v2[i]-delta)), !int(scale*(v2[i]-v1[i]))?1:int(scale*(v2[i]-v1[i]))); 
    }
  }
  display.display();
}

 

//
//    FILE: temperature.ino
//  AUTHOR: Jaewoong Mun
// CREATED: November 19, 2019
//
// Released to the public domain
//

#include <OneWire.h>
#include <DallasTemperature.h>

#define ONE_WIRE_BUS D4

OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature DS18B20(&oneWire);

void setup_temperature() {
  DS18B20.begin();
}

/* Datatype for floating-point numbers, a number that has a decimal point.
 *  Floating-point numbers are often used to approximate analog and continuous values, 
 *  because they have greater resolution than integers.
 *  Floating-point numbers can be as large as 3.4028235E+38 and as low as -3.4028235E+38.
 */
float get_temperature() {
  float temp;
  do {
    DS18B20.requestTemperatures(); 
    temp = DS18B20.getTempCByIndex(0);
    delay(100);
  } while (temp == 85.0 || temp == (-127.0));
  return temp;
}

 

서버에 데이터 보내기

https://thingspeak.com/update?key=[API Key]&Field1=50 과 같은 형식으로 ThingSpeak 에 데이터를 업로드 할 수 있습니다. 미세먼지 측정기와 같은 값이 여러 개로 묶인 다중 데이터의 경우 https://thingspeak.com/update?key=[API Key]&Field1=50&Field2=25&Field3=10 과 같이 “&” 문자로 필드 값을 구분해 업로드할 수 있습니다.

//서버에 데이터 보내기 (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
    Serial.println("dst: pm25=" + String(int(pm25s.getMedian())) + " / pm10=" + String(int(pm10s.getMedian())) + "/ s=" + String(status));
    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
}

 

//
//    FILE: server.ino
//  AUTHOR: Jaewoong Mun
// CREATED: November 19, 2019
//
// Released to the public domain
//

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 을 사용하여야 합니다.

  /*  Write Data with Get :

      Usage :
      https://data.plaive.10make.com/insert.php?api_key=<write_api_key>&field1=123 */

  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) {
  if (client.connect(host_thingspeak, httpPort)) {
    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 을 사용하여야 합니다.

    /*  Write Data with Get :
        https://www.mathworks.com/help/thingspeak/writedata.html

        Usage :
        https://api.thingspeak.com/update?api_key=<write_api_key>&field1=123 */

    //Serial.println("data = " + String(data));
    //Serial.println("connected");
    client.print("GET /update?");
    client.print(data);
    client.println(" HTTP/1.1");
    client.println("Host: " + String(host_thingspeak));
    client.println("Cache-Control: no-cache");
    client.println("Connection: close");
    client.println();

    //String answer = getResponse();
    //Serial.println("response = " + String(answer));
  } 
  
    //서버 통신이 되지 않으면
  else {
    Serial.println("connection failed: ");
    return;
  }
}

//String getResponse(){
//  String response;
//  long startTime = millis();
//
//  delay( 200 );
//  while ( client.available() < 1 ){
//        delay( 5 );
//  }
//
//  if( client.available() > 0 ){ // Get response from server.
//     char charIn;
//     do {
//         charIn = client.read(); // Read a char from the buffer.
//         response += charIn;     // Append the char to the string response.
//        } while ( client.available() > 0 );
//    }
//  client.stop();
//
//  return response;
//}

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

  data = "api_key=" + String(api_key) + "&pm25=" + String(pm25) + "&pm10=" + String(pm10) + "&temp=" + String(temperature) + "&latitude" + String(map_x) + "&longitude" + String(map_y);
  contentType = "application/x-www-form-urlencoded";

  //서버 통신 공식 client.println 을 사용하여야 합니다.

  if (client.connect(host_default, httpPort)) {
    Serial.println("connected");
    client.print("GET /insert.php?");
    client.print(data);
    client.println(" HTTP/1.1");
    client.println("Host: " + String(host_default)); // SERVER ADDRESS HERE AS WELL AS ABOVE.
    client.println("Content-Type: application/xE-www-form-urlencoded");
    client.print("Content-Length: ");
    client.println(data.length());
    client.println("Connection: close");
    client.println();
    //Serial.println(data);
    //client.print(data);
  }

  //서버 통신이 되지 않으면
  else {
    Serial.println("Connection Failed: ");
    return;
  }
}

 

컴파일 & 업로드

NodeMCU (ESP8266-12E) 보드를 PC 에 연결하고, 컴파일 및 업로드를 진행하도록 합니다.

 

데이터 업로드 검증하기

ThingSpeak 플랫폼 내 생성해 둔 Channel (채널) 의 각 Field (필드) 들에 데이터가 성공적으로 업로드 되는지 최종적으로 검증 및 확인합니다.

5 comments

  1. [2022.07.03] 개인정보 (전자메일 주소) 관련하여 마스킹 처리하였습니다.

    안녕하세요. IoT 미세먼지 측정기 제작을 해야하는데 작성하신 글이 매우 도움이 됐습니다.
    이 글과 깃허브에 올려주신 내용 참고해서 만들어보고 있는데 softwareserial 관련 오류가 있어서 도움 받을수 있을까요? 완전초보라 간단한 오류일 것 같은데 쉽지가 않네요.아래 오류메시지 남깁니다.

    exit status 1

    no matching function for call to ‘SoftwareSerial::SoftwareSerial(const uint8_t&, const uint8_t&, bool, int)’

    jhoon****@****.com으로 조언부탁드립니다.

    1. 안녕하세요. jhoonj9869 님.

      Tools > Board > Board Manager … 에서 esp8266 보드를 설치 시 2.5.2 버전으로 설치해보시기 바랍니다.

      답글 내용이 도움이 되셨기를 바라며,
      고맙습니다.

  2. 안녕하세요. 우선 좋은 글 포스팅 해주셔서 감사합니다.
    관련 지식과 경험이 전무한 상태에서 관심만 갖고 있다가
    여타 블로그와 다르게 부품 하나하나까지 자세한 글 써주셔서
    용기내어 부품도 주문하고 하나하나 따라 해보고 있습니다.
    그런데 OLED 관련 코드는 공개를 일부러 안해주신 것인지..,
    본문에 없는데 혹시 실례가 되지 않는다면 관련코드도
    올려주실 수 있으신지요?
    (아내에게 미세먼지 측정기를 만들어준다고 했다가
    진땀을 빼고 있는 상황입니다 ㅎㅎ)
    염치없지만 아무쪼록 부탁드립니다. 감사합니다.

    1. 안녕하세요! 우선 올려주셨으나 스팸 필터링 된 댓글과 본 댓글 모두 확인하였습니다.

      그런데 전체적인 상황을 자세하게 올려주시면 답변을 드리는데 도움이 될 것으로 보이는데요.

      1. 부품을 모두 동일하게 사용하신 상태인지 여부와, 동일하게 사용하시지 않았으면 어떠한 부품들로 만들고 계신지 알고싶습니다.
      2. OLED 관련 코드는 1 번 항목에 대해 답변주시면 별도로 대응해드리도록 하겠습니다.

      감사합니다.

Leave a Reply

Discover more from Dream big, Achieve more.

Subscribe now to keep reading and get access to the full archive.

Continue reading