Wednesday, April 15, 2015

TOPIC : [Node-RED] 온도와 습도 실시간 모니터링


작성일:
2015413

작성자:
최의신


1. 개요

이번에는 DHT22 센서를 이용한 온도와 습도를 실시간으로 모니터링 하는 것을 만들 것이다.

DHT22 센서의 값을 읽는 것은 ATtiny85를 이용하며, 이 값은 시리얼 포트로 Node-RED 서버로 전송이 된다.
전송되는 온도와 습도의 JSON 형식은 다음과 같다.

{ "temperature":23.12, "humidity":45 }
  

2. 환경 구성

1) 개발 환경
l  Windows 7
l  Node.js – v0.10.37
l  Node-RED – v0.10.4
l  Arduino IDE 1.0.6
Arduino 1.0.6에서 ATtiny85 링커에 문제가 있어 오류가 발생한다.
다음의 URL에서 ATtiny85 패치를 다운받아 Arduino IDE에 적용한다.

http://forum.arduino.cc/index.php/topic,116674.0.html

2) 회로도








3) 부품
l  USB To Serial : 1
l  ATtiny 85 : 1
l  DHT22 : 1

4) 구현


3. Arduino Source

ATtiny85에서 DHT22 센서를 사용하려면 Arduino에서 기본적으로 제공하는 라이브러리는 사용할 수 없다. 다음의 URL에서 다운로드 하여 라이브러리에 적용한다.

http://playground.arduino.cc/Main/DHTLib

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <SoftwareSerial.h>
#include <dhtlib.h>

const int DHT_PIN = 2; // 온도, 습도 센서
const int RX_PIN  = 3; // 시리얼 포트
const int TX_PIN  = 4; // 시리얼 포트

SoftwareSerial SOFT_SERIAL(RX_PIN, TX_PIN);

dht DHT;

void setup()
{
  SOFT_SERIAL.begin(9600);
}

void loop()
{
  String payload = "{\"temperature\":";

  int chk = DHT.read(DHT_PIN);
  if ( chk == DHTLIB_OK )
  {
    payload += DHT.temperature;   // 온도(화씨)
    payload += ",\"humidity\":";   
    payload += DHT.humidity;  // 습도
  }
  else {
    payload += "-272,\"humidity\":0";
  }

  payload += "}";
 
  SOFT_SERIAL.println(payload);

  delay(2000);  // 2sec
}


4. Node-RED 구성

온도와 습도를 모니터링 하기 위한 Node-RED 구성은 다음과 같다.

Node
설명
실시간 온도,습도 모니터링
Node-RED 서버에서 GET /tempchart 요청을 수신한다. 즉 웹 서버의 역할을 하게 된다.
온도,습도 차트
요청에 대한 응답 문서를 생성한다.
응답문서는 WebSocket으로 전달된 데이터를 차트 입력으로 전달하는 JavaScript를 포함한다.
Tempchart
요청에 대한 응답 문서를 전송한다.
온도센서
ATtiny85에서 전송된 온도, 습도 데이터를 수신하는 시리얼 포트이다.
온도,습도 전송
시리얼 포트에서 전달된 데이터를 WebSocket에 연결된 클라이언트로 전송한다.

Import Source
Flow를 구성하는 소스는 첨부된 temp.json 파일을 참조한다.
그리고 Node-REDImport 기능을 이용해 쉽게 입력할 수 있다.
  
[{"id":"435211f1.bcadf","type":"websocket-listener","path":"/ws/tempchart","wholemsg":"false"},{"id":"6509f941.05888","type":"serial-port","serialport":"COM6","serialbaud":"9600","databits":"8","parity":"none","stopbits":"1","newline":"\\n","bin":"false","out":"char","addchar":false},{"id":"13e94a7d.ec16b6","type":"serial in","name":"온도센서","serial":"6509f941.05888","x":152,"y":198,"z":"da8cdc72.b6d95","wires":[["ae363af8.51c9c8"]]},{"id":"ae363af8.51c9c8","type":"websocket out","name":"온도,습도 전송","server":"435211f1.bcadf","client":"","x":413,"y":198,"z":"da8cdc72.b6d95","wires":[]},{"id":"d956b81c.26a948","type":"http in","name":"실시간 온도,습도 모니터링","url":"/tempchart","method":"get","x":137,"y":89,"z":"da8cdc72.b6d95","wires":[["16e164bf.e91e9b"]]},{"id":"16e164bf.e91e9b","type":"template","name":"온도,습도 차트","field":"payload","template":"<!DOCTYPE HTML>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\" />\n  <meta HTTP-EQUIV=\"CACHE-CONTROL\" CONTENT=\"NO-CACHE\" />\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n  <title>실시간 온도, 습도 측정기</title>\n  <link rel=\"stylesheet\" href=\"//code.jquery.com/mobile/1.4.3/jquery.mobile-1.4.3.min.css\" />\n  <script src=\"//code.jquery.com/jquery-1.11.1.min.js\"></script>\n  <script src=\"//code.jquery.com/mobile/1.4.3/jquery.mobile-1.4.3.min.js\"></script>\n  <script src=\"//cdnjs.cloudflare.com/ajax/libs/highcharts/4.0.4/highcharts.js\"></script>\n\n  <script>\n     var chart;  // 온도\n     var chart2;  // 습도\n\n     if(location.protocol==\"https:\"){\n          var wsUri=\"wss://\"+window.location.hostname+\":1880/ws/tempchart\";\n     } else {\n          var wsUri=\"ws://\"+window.location.hostname+\":1880/ws/tempchart\";\n     }\n     var ws=null;\n\n     function wsConn() {\n          ws = new WebSocket(wsUri);\n          ws.onmessage = function(m) {\n               console.log('[@.@] ' + new Date().toLocaleString() + m.data);\n               if (typeof(m.data) === \"string\" && m. data !== null){\n                    var msg =JSON.parse(m.data);\n                    var today = new Date();\n                    today.setHours(today.getHours() + 9);\n                    var t = today.getTime();\n                    chartAddPoint([t, msg.temperature], [t, msg.humidity]);\n               }\n          }\n          ws.onopen = function() {\n               console.log(\"[@.@] connecting...\");\n          }\n          ws.onclose   = function()  {\n               ws = null;\n               setTimeout(wsConn, 10000);\n          }\n          ws.onerror  = function(){\n               console.log(\"[@.@] connection error\");\n          }\n     }\n     wsConn();\n\n     function chartAddPoint(tval, hval)\n     {\n          var series = chart.series[0],\n          shift = series.data.length > 20;\n          chart.series[0].addPoint(eval(tval), true, shift);\n          \n          var series2 = chart2.series[0],\n          shift2 = series2.data.length > 20;\n          chart2.series[0].addPoint(eval(hval), true, shift2);\n     }\n\n     $(function() {\n          // 온도\n          chart = new Highcharts.Chart({\n               chart: {\n                    renderTo: 'temp',\n                    defaultSeriesType: 'spline',\n\n               },\n               title: {\n                    text: '실시간 온도 데이터'\n               },\n               xAxis: {\n                    type: 'datetime',\n                    tickPixelInterval: 120,\n                    maxZoom: 20 * 1000\n               },\n               yAxis: {\n                    minPadding: 0.2,\n                    maxPadding: 0.2,\n                    title: {\n                         text: '온도 ( °C )',\n                         margin: 20\n                    }\n               },\n               series: [{\n                    name: '온도',\n                    data: []\n               }]\n          });\n          // 습도\n          chart2 = new Highcharts.Chart({\n               chart: {\n                    renderTo: 'humi',\n                    defaultSeriesType: 'spline',\n\n               },\n               title: {\n                    text: '실시간 습도 데이터'\n               },\n               xAxis: {\n                    type: 'datetime',\n                    tickPixelInterval: 120,\n                    maxZoom: 20 * 1000\n               },\n               yAxis: {\n                    minPadding: 0.2,\n                    maxPadding: 0.2,\n                    title: {\n                         text: '습도 ( % )',\n                         margin: 20\n                    }\n               },\n               series: [{\n                    name: '습도',\n                    data: []\n               }]\n          });\n\n     });\n      </script>\n</head>\n<body>\n  <div id=\"temp\" style=\"width: 100%; height: 300px; margin-left:-5px;\"></div>\n  <div id=\"humi\" style=\"width: 100%; height: 300px; margin-left:-5px;\"></div>\n</body>\n</html>","x":412,"y":89,"z":"da8cdc72.b6d95","wires":[["96792a32.6986d8"]]},{"id":"96792a32.6986d8","type":"http response","name":"tempchart","x":658,"y":88,"z":"da8cdc72.b6d95","wires":[]}]


위 노드의 동작을 간단하게 살펴보면 실시간 온도,습도 모니터링”, “온도,습도 차트”, “tempchart” 노드들은 웹 서비스를 위한 구성으로 “http://127.0.0.1:1880/tempchart” 주소로 접속한 사용자에게 웹 페이지를 전송한다.
전송된 웹 페이지에는 WebSocket으로 연결을 시도하며, 연결이 성공한 경우 온도와 습도 데이터를 수신하게 된다. <클라이언트 역할>

if(location.protocol=="https:"){
  var wsUri="wss://"+window.location.hostname+":1880/ws/tempchart";
} else {
  var wsUri="ws://"+window.location.hostname+":1880/ws/tempchart";
}

온도센서”, “온도,습도 전송노드들은 시리얼 포트로 전송된 온도와 습도 데이터를 WebSocket으로 연결된 클라이언트에 전송한다 <서버 역할>



5. 실행

temp.json 파일의 내용이 정상적으로 Import 되었다면 CMD 창에서 다음의 명령을 수행한다.

D:\node-red-0.10.4>node red.js

Node-RED 서버 기동이 완료되면 브라우저로 다음의 주소에 접속한다.

http://127.0.0.1:1880/tempchart

정상적으로 수행이 되고 있다면 다음과 같은 화면을 볼 수 있을 것이다.




[참고자료]
[1] DHT22