ESP8266 와 PMS7003 을 사용한 IOT 미세먼지 측정기
메이커활동/HW&SW 2016. 10. 28. 16:20ESP8266 와 PMS7003 을 사용한 IOT 미세먼지 측정기
사진에는 개발 중 테스트를 위한 별도의 NodeMCU 가 하나 더 있습니다.
PMS7003 이 Fritzing 에 없으므로 10 핀 짜리 커넥터와 만능기판을 사용해서 비슷하게 만들어 사용했다.
ESP8266 에서 할 일
1. nodeMCU 를 사용하여 PMS7003 의 먼지데이터를 가져온다.
2. nodeMCU 를 사용하여 AP 에 연결한다.(스마트폰으로 nodeMCU 를 AP 에 연결한다.)
3. AP 와 연결된 nodeMCU 는 Thingspeak 와 sparkfun 서버에 값을 전송한다.
4. Lua 언어를 사용하지 않고 아두이노 IDE 를 사용하여 작업한다.
5. PUT/GET 을 사용하여 서버에 값을 전달한다.
세부 작업 내용
## Arduino IDE 에서 ESP8266 보드 사용을 위한 준비
1. Arduino IDE 실행
2. 파일(FILE)
3. 환경설정(Preference)
Additional Board Manager URLs : 에 http://arduino.esp8266.com/stable/package_esp8266com_index.json 입력
4. Tools 에서 Board : >> Board Manager 실행
5. esp8266 검색 후 설치(Install)
6. Tools >> Board >> NodeMCU 1.0 (ESP-12E Module) 선택
7. Sketch >> Include Library >> Manage Libraries 선택
8. WiFiManager 검색 후 설치
esp8266 검색
NodeMCU 1.0 (ESP-12E Module) 선택
WiFiManager 검색 후 설치 (ESP8266 보드 설치 후 자동으로 설치되는 예제와 라이브러리 외에 필요)
## PMS7003 와 NodeMCU 연결
NodeMCU 는 1개의 하드웨어 시리얼을 가지고 있으며, 이 시리얼이 다른 포트로 연결되어 사용할 수 있다. 이때 Serial.swap() 함수를 사용한다.
단, 사용해 보았으나 펌웨어의 안정성문제인지 지나치게 노이즈가 많아서 도저히 사용할 수 없을 정도로 판단되었다. Serial.swap() 함수는 사용하지 않았다.
소프트웨어 시리얼의 사용은 아두이노에서 사용했던 방법이다. NodeMCU 에서도 이 방식을 사용해보려고 하였으나 제대로 된 값을 받아내지 못했다. 다른 작업을 하면서 소트프웨어시리얼로 통신을 하는 것이 아직까지 안정적이지 못해 보인다.
하드웨어 시리얼 사용하기로 결정.
결국 RX, TX 를 사용하기로 결정했다. 단, USB 로 PC 와 연결해서 프로그램실행파일을 업로드해야 하므로 PMS7003 은 실행파일업로드가 마쳐진 다음에 NodeMCU 와 연결하면 된다. 개발과정중에 이러한 작업을 반복해야 하므로 두개의 슬라이드 스위치를 두었다. 두개의 스위치는 NodeMCU 의 RX/TX 와 PMS7003 의 TX/RX 를 연결하거나 끊는 용도로 사용한다. 즉, NodeMCU 에 프로그램을 업로드할 때는 스위치를 끄고, 업로드후에는 다시 스위치를 켜서 PMS7003 과 NodeMCU 가 서로 통신이 되도록 하였다.
AP-SW 는 D3 (GPIO0) 의 핀에 해당되며 여기에 들어오는 입력값에 따라 AP 선택이 가능해진다.
## NodeMCU Firmware
아두이노의 max(), min() 이 ESP8266 에서 제대로 작동하지 않는 문제를 발견하고 재정의하여 사용하였다. #include <ESP8266WiFi.h> 이후에 다음의 내용을 넣으면 된다.
#undef max
#define max(a,b) ((a)>(b)?(a):(b))
#undef min
#define min(a,b) ((a)>(b)?(b):(a))
전체 프로그램이 많아 져서 ino 파일과 h 파일로 나눠서 저장하였다. 각각의 다음과 같다.
아두이노 소스
/*
PMS7003 - ESP8266 (nodeMCU)
WiFi Selectable AP & Clients
D3 (GPIO0) --- > PULLUP
CLIENT 1 - Thingspeak
CLIENT 2 - data.sparkfun.com
made by winduino.co.kr (2016.10.28.)
*/
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <DNSServer.h>
#include <WiFiManager.h> //https://github.com/tzapu/WiFiManager
#include "ThingSpeak.h"
#include "PMS7003s.h"
// Thingspeak
unsigned long myChannelNumber = 111111;
const char * myWriteAPIKey = "11111111111111";
// sparkfun client using get
const char* host = "data.sparkfun.com";
const char* streamId = "11111111111112";
const char* privateKey = "11111111111113";
#define TRIGGER_PIN 0
#define DEBUG 1
#define MEAN_NUMBER 10
#define MAX_PM 0
#define MIN_PM 32767
#define VER 20161025
#ifndef MAX_FRAME_LEN
#define MAX_FRAME_LEN 64
#endif
#undef max
#define max(a,b) ((a)>(b)?(a):(b))
#undef min
#define min(a,b) ((a)>(b)?(b):(a))
int status = WL_IDLE_STATUS;
//const int MAX_FRAME_LEN = 64;
int pm1_0=0, pm2_5=0, pm10_0=0;
unsigned int tmp_max_pm1_0, tmp_max_pm2_5, tmp_max_pm10_0;
unsigned int tmp_min_pm1_0, tmp_min_pm2_5, tmp_min_pm10_0;
byte i=0;
unsigned long previousMillis = 0;
const long interval = 1000;
bool ledState = LOW;
bool startNumber = true;
WiFiClient client;
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
pinMode(TRIGGER_PIN, INPUT);
Serial.begin(9600);
}
void loop() {
digitalWrite(LED_BUILTIN, LOW); delay(500);
digitalWrite(LED_BUILTIN, HIGH); delay(500);
if ( digitalRead(TRIGGER_PIN) == LOW ) {
wifiManagerStart();
startNumber = ~startNumber;
}
else {
if (startNumber == true ) {
ThingSpeak.begin(client);
startNumber = false;
}
if(i==0) {
tmp_max_pm10_0 = tmp_max_pm2_5 = tmp_max_pm1_0 = MAX_PM;
tmp_min_pm10_0 = tmp_min_pm2_5 = tmp_min_pm1_0 = MIN_PM;
}
if (pms7003_read()) {
tmp_max_pm1_0 = max(PMS7003S.concPM1_0_CF1, tmp_max_pm1_0);
tmp_max_pm2_5 = max(PMS7003S.concPM2_5_CF1, tmp_max_pm2_5);
tmp_max_pm10_0 = max(PMS7003S.concPM10_0_CF1, tmp_max_pm10_0);
tmp_min_pm1_0 = min(PMS7003S.concPM1_0_CF1, tmp_min_pm1_0);
tmp_min_pm2_5 = min(PMS7003S.concPM2_5_CF1, tmp_min_pm2_5);
tmp_min_pm10_0 = min(PMS7003S.concPM10_0_CF1, tmp_min_pm10_0);
pm1_0 += PMS7003S.concPM1_0_CF1;
pm2_5 += PMS7003S.concPM2_5_CF1;
pm10_0 += PMS7003S.concPM10_0_CF1;
i++;
}
if(i==MEAN_NUMBER) {
pm1_0 = ((pm1_0-tmp_max_pm1_0-tmp_min_pm1_0)/(MEAN_NUMBER-2));
pm2_5 = ((pm2_5-tmp_max_pm2_5-tmp_min_pm2_5)/(MEAN_NUMBER-2));
pm10_0= ((pm10_0-tmp_max_pm10_0-tmp_min_pm10_0)/(MEAN_NUMBER-2));
thingSpeakClient(pm1_0, pm2_5, pm10_0);
sparkfunClient(pm1_0, pm2_5, pm10_0);
delay(20000); // ThingSpeak will only accept updates every 15 seconds.
pm1_0=pm2_5=pm10_0=i=0;
}
}
}
void wifiManagerStart() {
WiFiManager wifiManager;
if (!wifiManager.startConfigPortal("OnDemandAP")) {
delay(3000);
ESP.reset();
delay(5000);
}
}
void thingSpeakClient(int pm1_0, int pm2_5, int pm10_0) {
ThingSpeak.setField(1,pm1_0);
ThingSpeak.setField(2,pm2_5);
ThingSpeak.setField(3,pm10_0);
ThingSpeak.setField(4,VER);
ThingSpeak.setLatitude(42.0000);
ThingSpeak.setLongitude(-71.0000);
ThingSpeak.setElevation(100);
ThingSpeak.writeFields(myChannelNumber, myWriteAPIKey);
}
void sparkfunClient(int pm1_0, int pm2_5, int pm10_0){
const int httpPort = 80;
if (!client.connect(host, httpPort)) {
return;
}
String url = "/input/";
url += streamId;
url += "?private_key=";
url += privateKey;
url += "&dust10=";
url += pm1_0;
url += "&dust100=";
url += pm10_0;
url += "&dust25=";
url += pm2_5;
client.print(String("GET ") + url + " HTTP/1.1\r\n" +
"Host: " + host + "\r\n" +
"Connection: close\r\n\r\n");
unsigned long timeout = millis();
while (client.available() == 0) {
if (millis() - timeout > 5000) {
client.stop();
return;
}
}
}
두개의 슬라이드 스위치를 모두 OFF 시킨 후 (연결이 끊어진 쪽) 컴파일-업로드를 진행한다.
## AP 설정방법
NodeMCU 에는 프로그램이 실행되는 상태이다. 이 상태에서 AP-SW 를 누른 상태에서 NodeMCU 를 리셋(RST버튼) 시킨다.
AP-SW 누름 -> NodeMCU RST 누름 -> NodeMCU RST 뗌 -> (약 3초후)AP-SW 뗌
1. 스마트폰으로 OnDemandAP 에 접속
공유기에 접속하게 되면 이후 자동적으로 Thingspeak 와 Sparkfun 서버에 접속하여 20초마다 한번씩 3개의 미세먼지데이터를 보내준다. 각각의 사이트로 가서 데이터를 확인한다. AP 정보는 NodeMCU 의 EEPROM 에 저장되므로 AP 가 변경될 때 한번만 설정하면 된다.
Thingspeak 는 데이터를 그래프화해서 쉽게 확인할 수 있게 해 준다.
sparkfun 에서 제공하는 서비스는 별도의 로그인이 필요없다. 데이터를 저장하고 json 이나 csv 등의 형식으로 다운로드 할 수 있다.
NEXT. 라즈베리 서버로 Thingspeak 나 data.sparkfun.com 과 유사한 서비스를 제공해보자. (진행중)