nhnacademy-aiot1-5 / study

공부한 내용을 기록하는 저장소입니다.
MIT License
0 stars 0 forks source link

node-red #38

Open siddltkfkd opened 2 months ago

siddltkfkd commented 2 months ago

라즈베리파이 http://192.168.71.63:1880/#flow/91112e82c128f8dc

인플럭스 http://133.186.244.96:8086/signin?returnTo=/orgs/831b112dee69e9cb

정용's http://192.168.71.31:1880/#flow/b3d5938914b0d06c

은지's http://133.186.251.73:1880/#flow/7227804fae92d2f5

명세서(데이터 정제하기 위해 필요한 엑셀파일) https://nhnacademy.dooray.com/wiki/3619694603650156997/3772625257337470608

참고자료 모드버스 read/write function code https://kuwhawha.tistory.com/286

siddltkfkd commented 2 months ago

초창기 node-red 구조

라즈베리파이(공용)

Image

gateway instance(우리 팀)

Image

  1. 라즈베리 파이에서 기기 정보를 modbus로 읽어온다.
  2. 읽어온 정보를 mqtt out으로 내보낸다.(수석님 서버로)
  3. 라즈베리 파이에서 내보낸 데이터를 gateway에서 mqtt로 읽어온다.
  4. 읽어온 데이터를 가공해서 telegraf로 보낸다.

발생했던 이슈

어떻게 라즈베리파이에서 gateway로 데이터를 보내지? 그리고 gateway에서 정제한 데이터는 어떻게 telegraf에 연결하지?

맨 처음 우리는 modbus를 이용해서 데이터 통신을 하려고 했다. 라즈베리 파이 쪽에서는 modbus-server, modbus-write, modbus-flex-write 노드를 써서 여러가지 방법으로 데이터를 보내봤고, gateway 쪽에서는 modbus-server, modbus-response, modbus-getter 같은 노드를 써서 데이터를 읽으려고 시도해봤다. 그러나 modbus 노드를 사용해본 경험이 적어서 서버는 어떤것으로 설정해야하는지, getter의 input은 어떤걸 줘야하는지, 포멧은 어떻게 맞춰야하는지 공부하였고, modbus를 통해 데이터를 받아오는것에 성공하였다.

그런데 또 데이터가 true, false 배열로 나오는 이슈가 발생하였다. 그 이슈를 해결하는 도중 라즈베리 파이에서 게이트웨이 서버에 특정 포트로 접속하고, 게이트웨이에서는 그 포트의 연결을 기다리는 식으로 tcp 통신을 통해 데이터를 받아오는것에 성공하여 일단 tcp를 이용해 데이터를 받아오고 있었다. (알고보니 functioncode를 15로 맞추면 코일을 읽어온다고 한다.) 그렇게 tcp 통신을 통해 데이터를 받으며 게이트웨이에서 데이터를 파싱하는 도중 회의를 통해 mqtt로 데이터 통신을 하도록 구조를 변경하게 되었다.

그 이유는 telegraf와 node-red를 연결하는것에 문제가 발생했기 때문이다. 이 이슈를 해결하기 위해 telegraf쪽을 맡은 팀원 두명이 밤새 telegraf를 공부하며 많이 고생해주었다. node-red를 맡은 팀이 데이터 통신을 어떻게 해야할지 열심히 시도하는 동안, telegraf를 맡은 팀도 그 방법을 열심히 연구했다. 그 결과 mqtt로 데이터를 쏴주면 telegraf에서 그 토픽을 구독해서 받는 방식을 사용하기로 결정하였다. 그러나 mqtt로 데이터를 보내주려면 브로커가 필요했는데 mqtt 브로커는 또 어떻게 만들지에 대한 이슈가 발생하였다. 이슈에 대해 토론하던중 한 팀원이 두레이 위키에서 수석님이 만들어둔 mqtt 브로커 서버를 발견하였다. 그렇게 우리는 수석님의 서버를 통해 데이터를 주고받기로 결정하였다.

처음과 마지막 데이터가 NaN으로 들어오는 문제

gateway에서 mqtt를 받아올 때 바이너리 버퍼 대신 문자열로 받아오도록 설정했다. 그랬더니 문자열 앞 뒤로 "[", "]"이 붙어서 parseInt가 제대로 적용되지 않아 NaN으로 나오는 문제가 발생했다. 처음에는 values[0].slice(0, values[0].length)같은 방식으로 앞/뒤 대괄호를 제거해주었다.

let values = msg.payload.split(",");
if (values.length == 6) {
    msg.payload = ({
        "operation-heartbit": parseInt(values[0].slice(1, values[0].length)),
        "temperature": parseInt(values[1])/10,
        "frequency": parseInt(values[2])/100,
        "program version": values[3].slice(0,1) + "x" + values[3].slice(1, values[3].length-1),
        "present CO2 use(month)": parseInt(values[4])/10
    })
}
return msg;

그러나 이 방식을 선택하면 values의 값을 가져올 때마다 parseInt를 해야해서 번거로웠다. 그래서 맨 처음에 slice로 앞, 뒤 대괄호를 제거해주고, for문을 돌려서 모든 값을 int로 변환한 후 파싱하는 방법을 선택하였다.

let values = msg.payload.slice(1, msg.payload.length-1).split(",");
for (let i=0; i<values.length; i++) {
    values[i] = parseInt(values[i]);
}

16byte, 32byte 데이터가 섞여있음.

Image 라즈베리파이에서 데이터를 보내줄 때 16byte 버퍼 스트림으로 데이터를 보내주고있는데, 명세서에 따르면 size가 2인것과 4인것이 섞여있는 문제가 발생. 처음에는 size가 4인 경우 뒤쪽 것만 가지고 오는 방식을 사용함(숫자가 작아서 앞부분이 다 0이었음) 그러다가 voltage쪽에서 데이터가 밀려서 들어오는것같은 느낌을 받음 알고보니 16byte int 두개를 shift 연산을 해서 32byte int로 만들어서 가공했어야했다는것을 깨닳음 shift 연산을 하는 방법을 찾다가 buffer에 크기를 4만큼 할당한 다음 BigEndian 방식으로 각 16 byte 데이터를 쓰고, buffer의 값을 32 byte로 읽는 방식을 채택함.

let buffer = function(left, right){
    let buffer = Buffer.alloc(4);
    buffer.writeUInt16BE(left, 0);
    buffer.writeUInt16BE(right, 2);
    return buffer.readUInt32BE(0);
}
let total = ({
        "V123(LN) average": buffer(values[0], values[1])/100,
        "V123(LL) average": buffer(values[2], values[3])/100,
        "V123(LN) unbalance": values[4]/100,
        "V123(LL) unbalance": values[5]/100
    })

라즈베리파이에서 데이터를 정제해서 보내줘도 되지않을까?

원래 우리가 계획한 구조는 다음과 같다. NHN Academy Network : Office, Class A Modbus Server에서 Rasberry Pi로 정제되지 않은 바이트 버퍼 스트림을 전송 NHN Cloud Network : 각 팀에서 gateway 인스턴스를 올리고 gems-3500 명세서를 보고 각자 데이터를 정제 -> telegraf로 전송

오늘 아침에 한 회의에서 어차피 둘 다 수석님 mqtt로 보내줄건데 gateway를 거치지 않고 라즈베리 파이에서 데이터를 가공해서 보내주면 어떻겠냐는 의견이 나왔다. 원래 방식대로라면 다른 팀들은 각각 바이트 버퍼 스트림을 명세서를 보고 유효한 정보로 가공해야했다. 그러나 우리가 json 형식으로 데이터를 전송하도록 결정을 하면서 다른 팀들의 수고를 덜어줄 수 있게 되었다.

gateway에 작성했던 노드들을 라즈베리 파이로 옮기기 전, 우리는 좀 더 효율적으로 파싱하는 방법이 없을까 고민해봤다. 기존에는 16바이트 2개를 buffer 함수에 넣으면 big endian 방식으로 32바이트로 읽어주는 함수를 만들어 명세서를 보고 일일히 함수에 넣고 파싱해주었다.

이를 개선하기위해 처음 시도한 방법은 테이블의 각 행마다 function을 만들어 토픽과 페이로드를 지정하여 getter로 데이터를 가져오는 방식이었다. 이 방법은 좋은 시도였지만 각 행마다 function을 만들어야한다는 불편함이 있었다. topic과 payload에서 변하지 않는 부분은 고정시키고, 변하는 부분만 바꿔주도록 자동화 시키는 방법을 찾아봤다.

그러다 한 팀원이 csv paser를 발견해 이걸 사용해보자는 의견을 내주었다. csv paser에 payload로 데이터를 집어넣어줘야하는데 inject로 데이터를 집어넣기는 어려워보였다. 어떻게 데이터를 넣지 고민하다가 templete을 사용하면 쉽게 데이터를 넣을 수 있다는것을 알게되었다. 그렇게 csv 명세서를 파싱하여 payload에 modbus-getter에 어떤 데이터를 가져올지 지정하고, mqtt로 발행할 토픽도 지정해주었다. getter로 데이터를 가져오면 switch문을 통해 payload의 버퍼 길이가 4이면 32바이트, 2면 16바이트로 payload의 값을 대체해주고, formatting 함수를 통해 time, value 형식으로 포멧팅해준 뒤 mqtt로 데이터를 보내주는것에 성공하였다.

그 뒤 명세서대로 자릿수를 맞추기 위해 value를 scale 값으로 나눠주도록 설정했는데 modbus-getter를 통과하면 메세지 property가 전부 지워져 scale을 메세지에 남길 수 없는 이슈가 발생하였다. 우리는 이 이슈를 getter 노드의 optionals에서 "Keep Msg Properties"를 체크해주는것으로 해결하였다.

siddltkfkd commented 2 months ago

바뀐 node-red 구조에 대한 설명

Image

  1. inject 일정시간(1분)마다 데이터를 받아오도록 신호를 보냄
  2. csv, parser csv 데이터(gems3500 문서)를 넣고 파싱
  3. function
    
    msg.topic = "data/s/nhnacademy/b/gyeongnam/p/"
            +"<<place>>"
            +"/d/gems-3500/e/electrical_energy/"
            +msg.payload.type
            +"/"
            +msg.payload.desc;

msg.scale = msg.payload.scale;

msg.payload = { "value": 1, "fc": 4, "unitid": 1, "address": msg.payload.addr, "quantity": msg.payload.size / 2 };

return msg;

토픽, payload, 16byte, 32byte 파싱을 위한 scale 지정
- 토픽에서 변하는 부분 : 
  - place : class_a, office
  - type : 테이블 항목(타입)
  - desc : description(설명)
- payload에서 변하는 부분 :
  - address : 읽어올 레지스터 주소
  - quantity: 얼마나 읽어올지

4. change msg.topic
topic의 place를 change 노드를 이용해 각각 office, class_a로 변경.
5. modbus-getter
input정보에 따라 modbus 데이터를 받아옴
6. switch, function
scale에 따라 16byte로 읽을지, 32byte로 읽을지 결정하고 읽음
7. formatting
수석님 서버에서 보내는 온습도, 출입 센서들과 똑같이 포멧팅

msg.payload = { "time": new Date().getTime(), "value": msg.payload / msg.scale };


8. mqtt
수석님 서버로 mqtt out

## 이점
1. 기존에는 테이블 컬럼 하나하나 function을 만들어야했음.
그러나 template과 csv 파서를 통해 토픽과 모드버스 데이터를 가져오기 위해 필요한 페이로드를 자동으로 설정하는것이 가능해짐. 
2. 지정한 scale에 따라 16/32 바이트로 자동으로 payload에 value를 지정해주는것도 가능해짐.
3. 원래는 라즈베리파이 -> gateway -> influx 순으로 동작하는 구조였음
그러나 라즈베리파이 -> influx 순으로 동작하도록 구조를 바꿔 gateway를 거치지 않아도 됨
4. 라즈베리파이에서 데이터를 정제해서 보내주므로 각 팀들이 데이터를 정제하는 수고를 덜어줌
siddltkfkd commented 2 months ago

gems3500 장치 ip 주소 Image

수석님 설명을 듣는 우리 팀원들 Image

라즈베리파이에 OS를 다운받고 이미지를 올리는 중 Image

Image

Image

Image

32비트 이미지 올리다가 실패.. Image

caboooom commented 2 months ago

ㅋㅋㅋㅋㅋ정말 너무 고생했어용.........