Aug 18, 2014

[IBM IoT Foundation Bootcamp @Korea] 5. Visual Tool for Wiring the IoT -- Node-RED

Flow-based Programming
Node-RED는 Flow-based Programming 개념을 따르는 전형적인 개발 방법론을 제시하고 있다. 주요 특징은 다음과 같다.
  • Node-RED Node라는 Things와 Logic의 단위를 만들고 이를 message passing 방식의 Node 간의 연결을 flow라는 형태로 2차 저작이 가능한 구조: Flow Library를 통한 industry scenario를 반영한 2차 저작물의 공유, 생산성 향상
  • Web Browser 방식의 UI를 통한 event flow 설계 지원
  • Edge-of-Network 환경과 Cloud 환경에서 실행하기에 적합한 light-weight runtime
User Interface
사용자 인테페이스는 아래와 같이 6개의 핵심 컴포넌트로 구성되어 있으며, 직관적이며 단순한 화면 구조이다.


1. Node palette
2. Canvas
3. Info Tab
4. Debug Tab
5. Deploy
6. Options Menu

Function Nodes
Function 노드는 JavaScript로 작성된 임의의 로직을 수행할 수 있게 한다. JavaScript의 function scope을 이용하여 모듈화하는 개발 방법론을 기반으로 하고 있다.
  • Input: msg라는 이름을 가진 JSON 객체
    • 일반적으로 msg는 payload라는 property를 갖고, 이는 message body에 해당
    • msg가 topic이라는 property를 갖는 경우도 있다.
  • Output
    • Single output인 경우는 단일 JSON 객체를 return하는 것이 일반적이나 복수 개의 JSON 객체로 구성된 배열을 return하는 경우는 배열에 포함된 객체들을 순차적으로 return한다.
    • Multiple output인 경우는 output 수에 대응하는 JSON 객체들로 구성된 배열을 return한다.
    • 조건에 따라 더이상 진행하지 않고 중단해야 하는 경우는 null을 return한다. 
    • 요약 정리하면 output은 최대 2차원 배열 형태로 구성할 수 있으며, multiple output port에 각 port는 순차적으로 복수 개의 메시지를 송출할 수 있다.
  • (Function Local) Context
    • context라는 이름을 가진 JSON 객체에 access 가능하며, 이는 JavaScript의 closure 방식으로 관리되는 객체로서 Node-RED가 실행되는 동안 유지된다. 
    • Flow 구조 상 동일 function 노드를 반복적으로 visit하는 상황에서 context를 이용하여 처음 visit할 때부터 마지막 visit할 때까지 계속 변화 추적하는 상태 변수들을 관리할 수 있다.
  • Global Context
    • 앞에서 설명한 context는 하나의 function 노드가 단일 Node-RED lifetime 동안 reference 가능한 것이지만, 여기서 설명하는 global context는 Node-RED의 canvas 내에 있는 모든 function 노드들이 공통으로 reference 가능한 context 객체이다.
    • 이러한 Global Context는 setting.js 파일의 functionGlobalContext라는 property에 정의함으로써 Node-RED가 시작할 때 바로 load되어 access 가능하게 된다. 
* Function 노드 관련 추가 상세 정보는 여기를 참조

Custom Nodes
Node-RED 노드는 Node-RED가 시작할 때 setting.js 파일의 nodesDir이라는 property에 정의된 directory 아래 혹은 디폴트로는 "nodes"라는 directory 아래에 위치한 노드 구성 파일들을 읽어서 palette에 등록되는 방식으로 관리된다.
따라서, Node-RED 노드 구성 파일 구조에 맞추어 개별적으로 작성하여 "nodes"라는 directory에 갖다 놓음으로써 손쉽게 Custom Node를 만들어 사용할 수 있다.

이제 Custom Node를 만들기 위해 구성 파일들을 만드는 방법에 대하여 알아 보자. Node-RED 노드는 런타임 상의 로직을 포함한 JavaScript 파일과 노드의 UI 구성 정보를 포함한 HTML 파일로 구성된다.

JavaScript 파일
JavaScript constructor pattern을 활용하여 new로 새로운 instance를 생성할 수 있게하는 구조로 설계되어 있다. "use strict"를 통해 내부에 구현된 코드에 대해 ECMAScript 5 기준을 엄격하게 적용하여 검사한다. (이는 권장사항이지만 필수는 아니다.)

module.exports = function(RED) {
    'use strict';
}

인자로 받는 RED는 Node-RED 인스턴스를 의미하는 객체이다. 여기에 각 개별 노드들을 관리하는 모듈을 reference하고 있는 nodes라는 property가 포함되어 있다. 본 Custom Node가 가질 기능은 특정 JavaScript constructor function 안에 구현해서 RED.nodes.registerType(typeName, constructorFunctionName)을 이용하여 등록한다.

module.exports = function (RED) {
    'use strict';
    
    function CustomNode(config) {
        
    }
    
    RED.nodes.registerType('custom', CustomNode);
};

CustomNode라는 이름을 갖는 constructor function 내부에 제일 먼저 추가해야 할 로직은 실제 인스턴스를 생성하는 것이다. this context에 config라는 이름으로 받은 JSON 객체에 정의된 설정 정보를 이용해서 인스턴스를 생성한다.

module.exports = function (RED) {
    'use strict';
    
    function CustomNode(config) {
        RED.nodes.createNode(this, config);
    }
    
    RED.nodes.registerType('custom', CustomNode);
}; 

Node-RED 노드에 발생하는 대표적인 두가지 이벤트는 노드로 메시지가 수신되는 경우(여기서는 'input'이라 이벤트 명을 설정)와 새로운 flow가 deploy되거나 Node-RED를 종료할 때 이를 알리는 'close' 이벤트가 발생하는 경우가 있다. 이를 위하여 이벤트 발생시 수행될 callback을 작성하여 등록하는 작업이 필요하다. 'input' 이벤트 처리하는 callback에서 기능 수행 코드를 작성하고 기능 수행이 완료된 시점에서 send()를 이용하여 flow 상 다음 노드로 메시지를 보내도록 한다. 앞에서 언급한 모든 것을 반영한 JavaScript 파일의 코드 템플릿은 다음과 같다.

module.exports = function (RED) {
    'use strict';
    
    function CustomNode(config) {
        RED.nodes.createNode(this, config);
        
        this.on('input', function (msg) {
            var newMsg;
            
            this.send(newMsg);
        });
        
        this.on('close', function () {
        
        });
    }
    
    RED.nodes.registerType('custom', CustomNode);
};

Logging을 위하여 this.log(logMsg), this.warn(warnMsg), this.error(errorMsg)를 사용할 수 있다. 이중 warn(), error()는 Node-RED가 running하고 있는 환경의 console, log 파일에 출력될 뿐만 아니라 editor의 debug 탭에도 출력된다.

HTML 파일
HTML 파일에는 노드가 editor 상에서 어떻게 보여져야 하는지를 정의한다. 이는 아래의 3개 파트로 구성되어 있다.
1) Edit Dialog 파트: 종방향으로 입력 필드 배열
  • 입력 필드 각각은 <div>로 wrapping하고 이것의 class는 "form-row"
  • 문자열 혹은 숫자값 입력: <input type = "text">
  • Boolean 선택: <input type="checkbox">
  • 다중 선택: <select>
  • Label과 같이 출력되는 아이콘은 <i>를 이용해서 출력하며 Font Awesome의 아이콘을 사용


<script type="text/x-red" data-template-name="inject">
    <div class="form-row">
        <label for="node-input-payloadType"><i class="icon-envelope"></i> Payload</label>
        <select id="node-input-payloadType" style="width:125px !important">
          <option value="date">timestamp</option>
          <option value="none">blank</option>
          <option value="string">string</option>
        </select>
    </div>
    <!-- partly omitted -->
    <div class="form-row">
        <label for="node-input-topic"><i class="icon-tasks"></i> Topic</label>
        <input type="text" id="node-input-topic" placeholder="topic">
    </div>
    <!-- partly omitted -->
    <div class="form-row" id="node-once">
        <label>&nbsp;</label>
        <input type="checkbox" id="node-input-once" style="display: inline-block; width: auto; vertical-align: top;">
        <label for="node-input-once" style="width: 70%;">Fire once at start ?</label>
    </div>
    <!-- partly omitted -->
</script>

2) Help Text 파트: 아래 예시와 같이 노드를 선택하였을 때 도움말이 Info 탭에 출력되게 하는 기능을 구현하며, <p>를 이용하여 description 작성


<script type="text/x-red" data-help-name="inject">
    <p>Pressing the button on the left side of the node allows a message on a topic to be injected into the flow. This is mainly for test purposes.</p>
    <p>If no payload is specified the payload is set to the current time in millisecs since 1970. This allows subsequent functions to perform time based actions.</p>
    <p>The repeat function does what it says on the tin and continuously sends the payload every x seconds.</p>
    <p>The Fire once at start option actually waits 50mS before firing to give other nodes a chance to instantiate properly.</p>
    <p><b>Note: </b>"Interval between times" and "at a specific time" will use cron. This means that 20 mins will be at the next hour, 20 mins past and 40 mins past - not in 20 minutes time.
    If you want every 20 mins from now - use the basic "interval" option.</p>
</script>

3) 노드 정의 파트
JavaScript 파일에서와 동일한 RED.nodes.registerType()을 이용하여 등록을 진행한다.

<script type="text/javascript">
    RED.nodes.registerType('inject',{
        category: ,
        defaults: ,
        inputs: ,
        outputs: ,
        icon: ,
        color: ,
        label: ,
        labelStyle: ,
        align: ,
        oneditprepare: function () { },
        oneditsave: function () { }
    });
</script>

No comments:

Post a Comment