[Node-red] http 통신 오류 해결

2022. 12. 27. 14:44개발/Node-red

2022.12.26 - [기록/개발 노트] - [Node-red] http 통신 오류 - 1

 

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

local 환경의 Node-red에서 http 프로토콜로 웹 상의 버튼으로 4채널 릴레이 보드를 무선제어 했었다. 이를 새로운 AWS 서버의 Node-red에서 구현하도록 하는데 오류가 발생했다. AWS instance의 ip:1880/login

iruk.tistory.com

Node-red에서 오류가 발생했었다.

이어서 수정한다.


아두이노 수정

  String sOut = "";
  sOut += String(digitalRead(RELAY1_PIN));
  sOut += String(digitalRead(RELAY2_PIN));
  sOut += String(digitalRead(RELAY3_PIN));
  sOut += String(digitalRead(RELAY4_PIN));
  
  doc["temp"] = temp.temperature;
  doc["humi"] = humidity.relative_humidity;
  doc["mac"] = sMac;
  doc["ip"] = WiFi.localIP().toString();
  doc["type"] = type;
  doc["out"] = sOut;
  //doc["email"]= sEmail;
  char jsonBuffer[512];
  serializeJson(doc, jsonBuffer);

  String out_str = sMac + "-out";
  const char* topic_out = out_str.c_str();
  client.publish(topic_out, jsonBuffer);

아두이노에서 mqtt 로 데이터 출력하는 포맷이다.

key 'out'을 추가했다.

out 은 각 릴레이와 연결된 핀 상태를 반환한다.

 

pull-up 저항 때문에 반대로 동작한다.

ESP32핀이 0일 때 릴레이가 on 되고

1일 때 릴레이가 off 된다.

 

즉 1번 on 2번 on 3번 off 4번 on 상태면

"0010" 이 out의 value로 반환된다.

위와 같이 정상적으로 데이터가 수신된다.

하지만 /login에는 나타나지 않는다.

 

예전에 /login이 정상적으로 동작했던

Node-red 소스를 살펴보았다.


정상적으로 동작했던 예전 flow

이 소스는 websocket 주소가 잘 동작한다.

오류 발생하는 flow

반면 같은 websocket 주소를 사용하는데

이 flow 에서는 오류가 발생한다.

 

혹시 주소가 같아서 충돌이 발생하나? 싶어서

주소를 변경해주었다.

websocket 주소 변경

주소를 겹치지 않도록 변경해주었으나

이번에는 /login 페이지는 동작하지만

버튼이 눌러도 변하지 않는다.

현재 사용하는 Node-red flow의 흐름상

사진에 표시한 부분에서 오류가 있지 않는 한

/login 페이지에 오류가 발생할 이유가 없다.

 

이 부분에 오류가 있다고 가정하고

한 줄 한 줄 살펴봤다.


0,1,2,3 List 노드 검토

var i,j;
var s,sOut;
var r;
sOut="<table>"
sOut+="<tr>   <th>모델</th>  <th>모니터링</th> <th>이름</th> </tr>"
for(i=0;i<msg.payload.length;i++) {
    //r="<input type='hidden' name='chip' value='"+msg.payload[i].chip+"'>";
    s="";
    s+="<tr><th>";
    s+="<form action='/display'>";
        s+="<input type='hidden' name='mac' value='"+msg.payload[i].mac+"'>";
        s+="<input type='hidden' name='act' value='1'>";
        s+="<button type='submit' name='value' value='0' class='button buttonM'>"+msg.payload[i].model+"</button></a>";
    s+="</form>";
    s+="</th>";
    
    if(msg.payload[i].type==100) {
        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+="</tr>"
    
    sOut=sOut+s+"<br>";
} 

var newMsg={};
newMsg.payload=msg.payload;
msg.payload={};
msg.payload.list=sOut;
msg.payload.style=global.get("style");
msg.payload.script=global.get("script");
msg.payload.menu=global.get("menu");
return [msg,newMsg];

html 적용 후 바로 실행해볼 수 없기 때문에 ( javascript )

s 라는 문자열에 html 태그 및 형식을 담아

msg로 리턴한다.

sOut에 담아 msg.payload.list로 출력하면

 

뒤에 이어지는 html노드 및

http response에 의해

웹이 형식에 맞게 생성된다.

 

s+="<tr>";
s+="<td></td>";
            
s+="<td>"+"히터"+"</td>";
s+="<td>"+"냉풍"+"</td>";
s+="<td>"+"환기"+"</td>";
s+="<td>"+"LED"+"</td>";
        
s+="</tr>";

위 코드로 인해

표의 1행이 생성된다.

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>";

 

for문을 사용해 4개의 <td> 태그로 4개의 버튼 공간을 생성한다.

 수신받은 msg의 mac 주소 말단에

-out + j ( 0 ~ 4 )를 추가해 각 칸의 id를 할당받는다.

사진의 1 2 3 4 칸마다

mac-out0 mac-out1 식으로 id가 부여된다.


Web Socket script 노드 검토

var ws;
var wsUri = "ws:";
var loc = window.location;
console.log(loc);
if (loc.protocol === "https:") { wsUri = "wss:"; }
// This needs to point to the web socket in the Node-RED flow
// ... in this case it's ws/simple
wsUri += "//" + loc.host + loc.pathname.replace("display","ws/simple990115");

function wsConnect() {
	console.log("connect",wsUri);
	ws = new WebSocket(wsUri);
	//var line = "";    // either uncomment this for a building list of messages
	ws.onmessage = function(msg) {
		var line = "";  // or uncomment this to overwrite the existing message
		// parse the incoming message as a JSON object
		var data = JSON.parse(msg.data);
		//var data = msg.data;
		console.log(data);
		if(data.type==100) {
			if(data.out[0]=='0')
				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]=='0')
				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]=='0')
				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]=='0')
				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>";

		}
	}
            ws.onopen = function() {
                // update the status div with the connection status
                //document.getElementById('status').innerHTML = "connected";
                //ws.send("Open for data");
                console.log("connected");
            }
            ws.onclose = function() {
                // update the status div with the connection status
                //document.getElementById('status').innerHTML = "not connected";
                // in case of lost connection tries to reconnect every 3 secs
                setTimeout(wsConnect,3000);
            }
        }
        
        function doit(m) {
            if (ws) { ws.send(m); }
        }

평소에 수정할때는 if(data.type) 부분만 다뤘었다.

                if(data.type==100) {
                    if(data.out[0]=='0')
                        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]=='0')
                        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]=='0')
                        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]=='0')
                        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>";
                
                }

수신받은 msg 중 out 의 값에 따라서

HTML 버튼 2가지 중 하나를 출력한다.

HTML 버튼은 on 상태 off 상태 2가지가 있다. ( 색깔의 차이 )

 

즉 버튼을 누를때마다 마치 버튼이 실제로 변하는 것처럼

새로운 형태의 버튼을 즉각적으로 출력해주는 것이다.

 

이 부분에는 딱히 오류가 없어서

윗줄부터 살펴보았다.

        var ws;
        var wsUri = "ws:";
        var loc = window.location;
        console.log(loc);
        if (loc.protocol === "https:") { wsUri = "wss:"; }
        // This needs to point to the web socket in the Node-RED flow
        // ... in this case it's ws/simple
        wsUri += "//" + loc.host + loc.pathname.replace("display","ws/simple990115");

websocket 관련 Uri 를 선언하는 부분인데

ws/simple990115 가 선언되어있었다.

 

데이터는 아까 수정했던

위 사진대로의 Uri에 전송하고

 

script 처리는 잘못된 Uri와 연동되어서

/ws/simplee 에서 버튼이 동작하지 않는 것이었다.

 wsUri += "//" + loc.host + loc.pathname.replace("display","ws/simplee");

위와 같이 수정 후 테스트 해보았다.


해결

위와 같이 정상적으로 동작한다.

환기 버튼을 누르면

 

msg.out 값이 1111에서 1101로 변한다.

그에 맞춰서 웹 페이지의 버튼도 변한다.

해결!


1. webSocket 에 대한 개념 부족

webSocket 의 모든걸 알고있었다면

무조건 Uri먼저 확인해봤을 것 같은데

 

처음에는 아두이노 혹은 mongoDB에 저장하는 과정이

원인이라고 생각했다.

 

더 빨리 해결할 수 있었을텐데 아쉽다.

2. ESP32( 아두이노 )의 출력데이터 out 생각못함

아두이노 에서는 mqtt 데이터만 잘 송신하면

다른 문제는 없을거라고 확실한 근거없는 판단

 

데이터가 잘 송신되긴 하지만

data.out 이 필요했는데

out 데이터 없이 동작 하고있었다.