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에 송신하였다.
즉
예전에 생성된 버튼으로
출력 데이터만 수정해서
동작을 시킨 뒤 아 되네~ 했던 것이었다.
아두이노 소스 수정은 내일 할 예정
'개발 > Node-red' 카테고리의 다른 글
[Node-red] Custom node 중복사용 불가 해결 (0) | 2022.12.28 |
---|---|
[Node-red] http 통신 오류 해결 (0) | 2022.12.27 |
[Node-red] http 통신 Custom node 제작 (0) | 2022.12.23 |
[Node-red] Custom node 오류 - 중복사용 불가 (0) | 2022.12.22 |
[Node-red] 새로운 릴레이 노드 제작 - 1 (0) | 2022.12.21 |