지난 게시물에서 미세먼지 측정기를 만들 때 필요한 준비물에 대해 알아보았습니다. 미세먼지 측정기를 만들때 필요한 부품들을 다시 정리해보면 아래와 같습니다.
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 OLED | NodeMCU ESP8266-12E |
|---|---|
| VCC | 3v3 |
| GND | GND |
| SCL | D2 |
| SDA | D3 |
| DS18B20 온도 센서 | NodeMCU ESP8266-12E |
|---|---|
| + | 3v3 |
| out | D4 |
| - | GND |
| SDS011 미세먼지 센서 | NodeMCU ESP8266-12E |
|---|---|
| 5V | VIN (5V) |
| GND | GND |
| TX | D1 |
| RX | D0 |
| NEO6MV2 GPS 센서 | NodeMCU ESP8266-12E |
|---|---|
| VCC | 3v3 |
| RX | D7 |
| TX | D6 |
| GND | GND |

센서를 통해 데이터를 읽은 후 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 에서 설치합니다.

상단의 [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 플랫폼 (웹 서버) 에 전송가능한 상태로 전환되었다는 의미입니다!
센서 데이터 읽어오기

//
// 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 (필드) 들에 데이터가 성공적으로 업로드 되는지 최종적으로 검증 및 확인합니다.


[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으로 조언부탁드립니다.
안녕하세요. jhoonj9869 님.
Tools > Board > Board Manager … 에서 esp8266 보드를 설치 시 2.5.2 버전으로 설치해보시기 바랍니다.
답글 내용이 도움이 되셨기를 바라며,
고맙습니다.
덕분에 잘 해결되었습니다. 감사합니다.
안녕하세요. 우선 좋은 글 포스팅 해주셔서 감사합니다.
관련 지식과 경험이 전무한 상태에서 관심만 갖고 있다가
여타 블로그와 다르게 부품 하나하나까지 자세한 글 써주셔서
용기내어 부품도 주문하고 하나하나 따라 해보고 있습니다.
그런데 OLED 관련 코드는 공개를 일부러 안해주신 것인지..,
본문에 없는데 혹시 실례가 되지 않는다면 관련코드도
올려주실 수 있으신지요?
(아내에게 미세먼지 측정기를 만들어준다고 했다가
진땀을 빼고 있는 상황입니다 ㅎㅎ)
염치없지만 아무쪼록 부탁드립니다. 감사합니다.
안녕하세요! 우선 올려주셨으나 스팸 필터링 된 댓글과 본 댓글 모두 확인하였습니다.
그런데 전체적인 상황을 자세하게 올려주시면 답변을 드리는데 도움이 될 것으로 보이는데요.
OLED 관련 코드는 1 번 항목에 대해 답변주시면 별도로 대응해드리도록 하겠습니다.
감사합니다.