[Node-red] http 통신 오류 - 1

2022. 12. 26. 20:24개발/Node-red

local 환경의 Node-red에서

http 프로토콜로 웹 상의 버튼으로

4채널 릴레이 보드를 무선제어 했었다.

 

이를 새로운 AWS 서버의 Node-red에서 구현하도록 하는데

오류가 발생했다.

AWS instance의 ip:1880/login 의 주소에서

버튼을 생성해 제어하는 구조였는데

 

/login 페이지가 동작하지 않았다.

 

혹은 동작은 하는데

버튼이 생성되지 않는 오류가 발생했다.


위와 같이 Cannot GET /login 오류가 발생한다.


ESP32 소스도 평소에 AWS와 연동할 때 사용했던

LightTalk_AWS_4Relay_4 를 사용했다.

 

일단 아두이노 소스를 살펴보기 전에

Node-red 전체적인 흐름부터 짚고 넘어갔다.


Node-red 작동방식

전체적인 흐름이다. 부분별로 살펴보았다.


1. /login 후 html 적용

위와 같이 ip주소:1880/login 으로 접속했을 때

style, html, script 등을 정의해준다.

 

ip를 할당받은 뒤 사진에 표시한 html 소스에서

<html>
    <head>
        <meta name='viewport' content='width=device-width, initial-scale=1.0'/>
        <meta http-equiv='refresh' content='0; url=http://{{payload.publicIPv4}}:1880/display?act=3' method='get'> 
    </head>
    <body>
       </body>
</html

위와 같이 /display?act=3으로 이동한다.

 

2. /display 후 Switch

/display 를 get 해온 뒤

switch 노드에서 act 값을 구별해준다.

/login 에서 act=3 으로 설정했기 때문에

3번째의 출력단이 실행된다.

 

3. find.toArray

find.toArray 노드에서 위와 같이 msg를 생성한다.

collection, operation, payload, projection 값을

mongoDB 에 넘겨주면

mongoDB 에서 'localRecord' collection 값을 확인한다.

 

즉, mongoDB와 웹을 연동해서

mongoDB에 존재하는 데이터를 기반으로

버튼을 생성하고 웹의 틀이 잡히는 것이다.

 

4. 0,1,2,3 List

// 0,1,2,3 List 노드
	if(msg.payload[i].type==14) {
        s+="<td>"
            s+="<table>";
            s+="<tr>";
            s+="<td></td>";
            
            //for(j=0;j<4;j++)
                //s+="<td>"+String(j)+"</td>";
            s+="<td>"+"온풍"+"</td>";
            s+="<td>"+"냉풍"+"</td>";
            s+="<td>"+"환기"+"</td>";
            s+="<td>"+"LED"+"</td>";
        
            s+="</tr>";
            s+="<tr>";
            s+="<td>출력</td>";
            for(j=0;j<4;j++)
                s+="<td><span id='"+msg.payload[i].mac+"-out"+String(j)+"'></span></td>";
            s+="</tr>";
            s+="</table>";
        s+="</td>";
        s+="<td>"+msg.payload[i].name+"</td>";
    }

위와 같이 s 라는 문자열에

html 코드를 담아서 한 번에 출력한다.

html을 직접 실행할 수 없기 때문에

문자열에 담아서 웹으로 전송한다.

 

5. Web Socket scriptSocket

//Websocket script socket 노드
if(data.type==14) {
	if(data.out[0]=='1')
	document.getElementById(data.mac+'-out0').innerHTML = "<button class='button button-on' onclick='doit(\"{^mac^:^"+data.mac+"^,^type^:14,^outNo^:0,^value^:0}\");'></button>";
	else
	document.getElementById(data.mac+'-out0').innerHTML = "<button class='button button-off' onclick='doit(\"{^mac^:^"+data.mac+"^,^type^:14,^outNo^:0,^value^:1}\");'></button>";
	if(data.out[1]=='1')
	document.getElementById(data.mac+'-out1').innerHTML = "<button class='button button-on' onclick='doit(\"{^mac^:^"+data.mac+"^,^type^:14,^outNo^:1,^value^:0}\");'></button>";
	else
	document.getElementById(data.mac+'-out1').innerHTML = "<button class='button button-off' onclick='doit(\"{^mac^:^"+data.mac+"^,^type^:14,^outNo^:1,^value^:1}\");'></button>";
	if(data.out[2]=='1')
	document.getElementById(data.mac+'-out2').innerHTML = "<button class='button button-on' onclick='doit(\"{^mac^:^"+data.mac+"^,^type^:14,^outNo^:2,^value^:0}\");'></button>";
	else
	document.getElementById(data.mac+'-out2').innerHTML = "<button class='button button-off' onclick='doit(\"{^mac^:^"+data.mac+"^,^type^:14,^outNo^:2,^value^:1}\");'></button>";
	if(data.out[3]=='1')
	document.getElementById(data.mac+'-out3').innerHTML = "<button class='button button-on' onclick='doit(\"{^mac^:^"+data.mac+"^,^type^:14,^outNo^:3,^value^:0}\");'></button>";
	else
	document.getElementById(data.mac+'-out3').innerHTML = "<button class='button button-off' onclick='doit(\"{^mac^:^"+data.mac+"^,^type^:14,^outNo^:3,^value^:1}\");'></button>";
}

data의 값에 따라서 button-on, button-off css 버튼을 나타낸다.

css 값은

body {
    background: #eab0dc;
    font-family: Arial, Helvetica, sans-serif;
}
table, th, td {
    padding: 2px;
    border-collapse: collapse;
    border: 1px solid #dddddd;
    text-align: center;
    vertical-align: middle;
}


/* Full-width input fields */
input[type=text], input[type=password] {
  width: 150px;
  padding: 5px 10px;
  margin: 8px 0;
  display: inline-block;
  border: 1px solid #ccc;
  box-sizing: border-box;
}

/* Set a style for all buttons */
button {
  background-color: #4CAF50; /*Green*/
  color: white;
  padding: 14px 20px;
  margin: 8px 0;
  border: none;
  cursor: pointer;
}

.buttonMenu {
          padding: 5px 24px;
          margin-left:20%;
          background-color:black;
          border: none;
          border-color:black;
          color:white;
          text-align: left;
          text-decoration: none;
          display: inline-block;
          font-size: 16px;
          margin: 4px 2px;
          cursor: pointer;
        }
        .sidenav {
          height: 100%;
          width: 0;
          position: fixed;
          z-index: 1;
          top: 0;
          left: 0;
          background-color: #111;
          overflow-x: hidden;
          transition: 0.5s;
          padding-top: 60px;
        }
        .sidenav a {
          padding: 8px 8px 8px 32px;
          text-decoration: none;
          font-size: 18px;
          color: #818181;
          display: block;
                transition: 0.3s;
        }
        .sidenav a:hover {
          color: #f1f1f1;
        }
        .sidenav .closebtn {
          position: absolute;
          top: 0;
          right: 25px;
          font-size: 36px;
          margin-left: 50px;
        }

.buttonM {background-color: #ff66cc;color:white; width:100px; height:20px; padding: 0px 0px; font-size: 16px} /* 기기선택 */  
.buttonL {background-color: #ff66cc;color:white; width:100px; height:25px; padding: 0px 0px; font-size: 16px} /* 선택 */  
.buttonMenu {background-color: #000000;} 
.button2 {background-color: #008CBA;} /* Blue */
.button3 {background-color: #f44336;} /* Red */ 
.button4 {background-color: #e7e7e7; color: black;} /* Gray */ 
.button5 {background-color: #555555;} /* Black */
.button20 {width: 20%;} 
.button-on {border-radius: 100%; padding: 20px; font-size: 18px; margin: 0px 0px; background-color: #4CAF50;}
.button-off {border-radius: 100%; padding: 20px; font-size: 18px; background-color: #707070;}
.button-ledon {border-radius: 100%; padding: 10px; font-size: 8px; margin: 0px 0px; background-color: #ff4500;}
.button-ledoff {border-radius: 100%; padding: 10px; font-size: 8px; background-color: #707070;}

button:hover {
  opacity: 0.8;
}

/* Extra styles for the cancel button */
.cancelbtn {
  width: auto;
  padding: 10px 18px;
  background-color: #f44336;
}

/* Center the image and position the close button */
.imgcontainer {
  text-align: center;
  margin: 24px 0 12px 0;
  position: relative;
}

img.avatar {
  width: 40%;
  border-radius: 50%;
}

.container {
  padding: 16px;
}

span.psw {
  float: right;
  padding-top: 16px;
}

/* The Modal (background) */
.modal {
  display: none; /* Hidden by default */
  position: fixed; /* Stay in place */
  z-index: 1; /* Sit on top */
  left: 0;
  top: 0;
  width: 100%; /* Full width */
  height: 100%; /* Full height */
  overflow: auto; /* Enable scroll if needed */
  background-color: rgb(0,0,0); /* Fallback color */
  background-color: rgba(0,0,0,0.4); /* Black w/ opacity */
  padding-top: 60px;
}

/* Modal Content/Box */
.modal-content {
  background-color: #fefefe;
  margin: 5% auto 15% auto; /* 5% from the top, 15% from the bottom and centered */
  border: 1px solid #888;
  width: 80%; /* Could be more or less, depending on screen size */
}

/* The Close Button (x) */
.close {
  position: absolute;
  right: 25px;
  top: 0;
  color: #000;
  font-size: 35px;
  font-weight: bold;
}

.close:hover,
.close:focus {
  color: red;
  cursor: pointer;
}

/* Add Zoom Animation */
.animate {
  -webkit-animation: animatezoom 0.6s;
  animation: animatezoom 0.6s
}

@-webkit-keyframes animatezoom {
  from {-webkit-transform: scale(0)} 
  to {-webkit-transform: scale(1)}
}
  
@keyframes animatezoom {
  from {transform: scale(0)} 
  to {transform: scale(1)}
}

/* Change styles for span and cancel button on extra small screens */
@media screen and (max-width: 300px) {
  span.psw {
     display: block;
     float: none;
  }
  .cancelbtn {
     width: 100%;
  }
}

이 부분에서 global.set 으로 전역 값으로 지정해주었다.

따라서

 

ESP32가 MQTT로 데이터를 전송하면

mongoDB에 저장 후

그 데이터를 기반으로 웹 페이지를 작성한다.

 

만약 아두이노에서

MQTT 데이터를 정상적으로 보내고 있다면

굳이 검토해볼만한 문제는 없다고 생각했다.

 

어쨋든 웹 화면상의 처리는

Node-red에서 발생하기 때문에

주된 원인을 Node-red로 생각하고 찾아보았다.


5. Web Socket scriptSocket 에서 궁금증

//Websocket script socket 노드
if(data.type==14) {
	if(data.out[0]=='1')
	document.getElementById(data.mac+'-out0').innerHTML = "<button class='button button-on' onclick='doit(\"{^mac^:^"+data.mac+"^,^type^:14,^outNo^:0,^value^:0}\");'></button>";
	else
	document.getElementById(data.mac+'-out0').innerHTML = "<button class='button button-off' onclick='doit(\"{^mac^:^"+data.mac+"^,^type^:14,^outNo^:0,^value^:1}\");'></button>";
	if(data.out[1]=='1')
	document.getElementById(data.mac+'-out1').innerHTML = "<button class='button button-on' onclick='doit(\"{^mac^:^"+data.mac+"^,^type^:14,^outNo^:1,^value^:0}\");'></button>";
	else
	document.getElementById(data.mac+'-out1').innerHTML = "<button class='button button-off' onclick='doit(\"{^mac^:^"+data.mac+"^,^type^:14,^outNo^:1,^value^:1}\");'></button>";
	if(data.out[2]=='1')
	document.getElementById(data.mac+'-out2').innerHTML = "<button class='button button-on' onclick='doit(\"{^mac^:^"+data.mac+"^,^type^:14,^outNo^:2,^value^:0}\");'></button>";
	else
	document.getElementById(data.mac+'-out2').innerHTML = "<button class='button button-off' onclick='doit(\"{^mac^:^"+data.mac+"^,^type^:14,^outNo^:2,^value^:1}\");'></button>";
	if(data.out[3]=='1')
	document.getElementById(data.mac+'-out3').innerHTML = "<button class='button button-on' onclick='doit(\"{^mac^:^"+data.mac+"^,^type^:14,^outNo^:3,^value^:0}\");'></button>";
	else
	document.getElementById(data.mac+'-out3').innerHTML = "<button class='button button-off' onclick='doit(\"{^mac^:^"+data.mac+"^,^type^:14,^outNo^:3,^value^:1}\");'></button>";
}

위 소스를 보고 있었는데

data.out 을 다루고 있었다.

data.type 조건문 안에 data.out 조건문이 존재하는데

ESP32에서 전송하는 데이터 object에

type은 존재하지만 out은 없다.

 

따라서

ESP32에서 추가적으로 out 데이터를 전송해주면

문제가 해결될 것 같았다.


아두이노 소스 수정

  json = "{";
  json += "\"mac\":\""; json += sMac;  json += "\"";
  json += ",\"ip\":\""; json += WiFi.localIP().toString();  json += "\"";
  json += ",\"type\":"; json += type;
  json += ",\"out\":\""; json += sOut + "\"";
  json += ",\"in\":\""; json += sIn+"\"";
  json += "}";
  
  client.publish(topic_out, jsonBuffer);

기존에 14번 type을 다룰 때는 

out 과 in 데이터를 함께 전송했었다.

 

그 데이터가 mongoDB에 저장되고

웹 상에서 버튼이 정상적으로 생성되며

그 생성된 버튼을 클릭할 때 나오는 데이터를

강제로 처리해서 MQTT 데이터로 4채널을 제어했었던 것이다.

var outNo=msg.payload.outNo;
var value=msg.payload.value;

if(outNo==0){
   if(value==1 || value==0){
       msg.payload={"func":1}
   }
   global.set("func",0);
}
else if(outNo==1){
    if(value==1 || value==0){
       msg.payload={"func":2}
   } 
   global.set("func",1);
}
else if(outNo==2){
    if(value==1 || value==0){
       msg.payload={"func":3}
   } 
   global.set("func",2);
}
else if(outNo==3){
    if(value==1 || value==0){
       msg.payload={"func":4}
   } 
   global.set("func",3);
}
return msg;

위와같이 웹 버튼 클릭 시 출력되는 데이터

outNo 와 value를 처리문을 통해

ESP32에 송신하였다.

 

예전에 생성된 버튼으로

출력 데이터만 수정해서

동작을 시킨 뒤 아 되네~ 했던 것이었다.

 

아두이노 소스 수정은 내일 할 예정