Edited at

Node-REDのNode作成はじめの1歩

More than 3 years have passed since last update.

公式ドキュメントにあるNode作成の基本であるCreating your first nodeを読み解きながら実際に試して詳細まで把握します。


NodeはNodeの設定を編集するダイアログを定義するHTMLファイルとNodeの動作を定義するJavaScriptファイルの2つのファイルで構成されます。これらのファイルは、nodesディレクトリまたはnodesDirの設定で定義されたディレクトリのいずれか配置します。また、NodeはNPMで管理することもできます。


これは、そのままですが補足記事は以下になります。

* nodeDirについてはこちら

* NPMについてはこちらなど


シンプルなNode


次の例は流れてくるメッセージのうちアルファベットをすべて小文字に変換する動きをします。



lower-case.js

module.exports = function(RED) {

function LowerCaseNode(config) {
RED.nodes.createNode(this,config);
var node = this;
this.on('input', function(msg) {
msg.payload = msg.payload.toLowerCase();
node.send(msg);
});
}
RED.nodes.registerType("lower-case",LowerCaseNode);
}


Node.jsではmodule.exportsに関数を代入することで外部からアクセスできるようになります。また、関数はRED引数によってNode-REDの様々な機能へアクセスすることができます。


Node.jsでのモジュール定義は[NodeJS] モジュール定義について学ぶなどが詳しいです。REDについては難しく考えないで以下のようにconsole.logで中身見てみましょう(ワークスペースにlower-case Nodeをプロットしていないと何も表示されません)


lower-case.js

module.exports = function(RED) {

function LowerCaseNode(config) {
console.log(RED); // <- 追加
RED.nodes.createNode(this,config);
var node = this;
this.on('input', function(msg) {
msg.payload = msg.payload.toLowerCase();
node.send(msg);
});
}
RED.nodes.registerType("lower-case",LowerCaseNode);
}

以下のようにREDのプロパティがConsoleに表示されるので眺めてみるとNode-REDを操作できる色々なものが詰め込まれているのがわかります。Nodeを作るとはREDを利用してPluggableにNode-REDを拡張するということがよくわかります。

{ init: [Function],

start: [Function: start],
stop: [Function: stop],
nodes:
{ init: [Function: init],
load: [Function: load],
createNode: [Function: createNode],
getNode: [Function],
eachNode: [Function],
addFile: [Function: addFile],
addModule: [Function: addModule],
...


Nodeは新しいインスタンスが作成されるたびに呼び出されるLowerCaseNode関数によって定義されます。


これは先ほどREDconsole.logで中身を見る際に注意書きしましたが、ワークスペースにNodeがプロット(配置)されていない状態ではConsoleに何も表示されない、つまりインスタンス化されていないということになります。

では以下のようにNodeを3つ配置してみたらどうでしょうか?

3つ分プロパティが表示されたと思います。これで解るのはNodeがパレットに配置されているだけでは上記のJSファイルで定義したNodeの動作はインスタンス化されていないということです。


最初にすべてのNodeでの共有機能を初期化するためにRED.nodes.createNodeを呼び出します。その後、Node固有のコードを記述します。この例ではNodeはNodeにメッセージが到着するたびに呼び出されるinputイベントにリスナーを登録します。このリスナーの中でメッセージを小文字に変換し、後続のフローに変換したメッセージを渡すためにthis.sendを呼び出します。


関数の最初でRED.nodes.createNodeを呼び出すのは必須のようです。では、RED.nodes.createNodeを呼び出した後のthisがどういう状態なのか以下のようにconsole.logを追加して見てみます。

module.exports = function(RED) {

function LowerCaseNode(config) {
RED.nodes.createNode(this,config);
console.log(this); // <- 追加
var node = this;
this.on('input', function(msg) {
msg.payload = msg.payload.toLowerCase();
node.send(msg);
});
}
RED.nodes.registerType("lower-case",LowerCaseNode);
}

Consoleには以下のように表示されます。

{ id: '5c0d29a1.a3f2d8',

type: 'lower-case',
z: 'f56b199.f0a94e8',
_closeCallbacks: [],
wires: [ [ '17d78d74.e82873' ] ],
_wireCount: 1,
send: [Function],
_wire: '17d78d74.e82873' }
{ id: '112248a6.eeddb7',
type: 'lower-case',
z: 'f56b199.f0a94e8',
_closeCallbacks: [],
wires: [ [ 'b308844.f4cf778' ] ],
_wireCount: 1,
send: [Function],
_wire: 'b308844.f4cf778' }
{ id: 'f8fc3c36.0703c',
type: 'lower-case',
z: 'f56b199.f0a94e8',
_closeCallbacks: [],
wires: [ [ 'b2266937.4dd998' ] ],
_wireCount: 1,
send: [Function],
_wire: 'b2266937.4dd998' }

一方、flow設定を見てみると以下のようになってます。


flows_hostname.local

{"id":"1fce0738.e031f9","type":"inject","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"x":112,"y":65,"z":"f56b199.f0a94e8","wires":[["5c0d29a1.a3f2d8"]]},

{"id":"5c0d29a1.a3f2d8","type":"lower-case","name":"","x":274,"y":71,"z":"f56b199.f0a94e8","wires":[["17d78d74.e82873"]]},
{"id":"17d78d74.e82873","type":"debug","name":"","active":true,"console":"false","complete":"false","x":489,"y":74,"z":"f56b199.f0a94e8","wires":[]},
{"id":"af2b7be1.50d488","type":"inject","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"x":119,"y":132,"z":"f56b199.f0a94e8","wires":[["112248a6.eeddb7"]]},
{"id":"112248a6.eeddb7","type":"lower-case","name":"","x":281,"y":138,"z":"f56b199.f0a94e8","wires":[["b308844.f4cf778"]]},
{"id":"b308844.f4cf778","type":"debug","name":"","active":true,"console":"false","complete":"false","x":496,"y":141,"z":"f56b199.f0a94e8","wires":[]},
{"id":"f452540.f0badb","type":"inject","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"x":113,"y":203,"z":"f56b199.f0a94e8","wires":[["f8fc3c36.0703c"]]},
{"id":"f8fc3c36.0703c","type":"lower-case","name":"","x":275,"y":209,"z":"f56b199.f0a94e8","wires":[["b2266937.4dd998"]]},
{"id":"b2266937.4dd998","type":"debug","name":"","active":true,"console":"false","complete":"false","x":490,"y":212,"z":"f56b199.f0a94e8","wires":[]}

つまり、RED.nodes.createNodeによって設定ファイルに従ってでNodeが初期化されているのがわかります。idは各Node固有のIDでzはNode TypeのIDを表しているようです。

面白いのがwiresプロパティで上記フローのinject -> lower-case -> debugをワイヤーでつないでいる様を表しています(例えばinject Nodeのwires5c0d29a1.a3f2d8というIDが指定されていて、それが後続のlower-case Nodeのidだったりします)


最後にLowerCaseNodeは小文字に変換したメッセージをnode.sendを利用して後続に送り出します。


ここで、var node = thisとして一旦thisnodeという変数に代入しています。これを理解するために以下のようにinputイベントリスナの関数内でthisconsole.logしてみます。

module.exports = function(RED) {

function LowerCaseNode(config) {
RED.nodes.createNode(this,config);
var node = this;
this.on('input', function(msg) {
console.log(this); // <- 追加
msg.payload = msg.payload.toLowerCase();
node.send(msg);
});
}
RED.nodes.registerType("lower-case",LowerCaseNode);
}

Consoleに何も表示されないですね。イベントが発生していないからです。

では、以下のinject Nodeのスイッチをクリックしてイベントを発生(lower-case Nodeにメッセージを流す)してみます。

thisの中身は以下のように_eventsプロパティが追加されました。

{ id: '5c0d29a1.a3f2d8',

type: 'lower-case',
z: 'f56b199.f0a94e8',
_closeCallbacks: [],
wires: [ [ '17d78d74.e82873' ] ],
_wireCount: 1,
send: [Function],
_wire: '17d78d74.e82873',
_events: { input: [Function] } }

つまり、イベントリスナの関数内でのthisと区別する為にvar node = thisとして、イベントリスナの関数内でsend関数を利用する際はnode.sendとしています。


lower-case.html

<script type="text/javascript">

RED.nodes.registerType('lower-case',{
category: 'function',
color: '#a6bbcf',
defaults: {
name: {value:""}
},
inputs:1,
outputs:1,
icon: "file.png",
label: function() {
return this.name||"lower-case";
}
});
</script>

<script type="text/x-red" data-template-name="lower-case">
<div class="form-row">
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
</script>

<script type="text/x-red" data-help-name="lower-case">
<p>A simple node that converts the message payloads into all lower-case characters</p>
</script>



NodeのHTMLファイルにはEditor上でのNodeの設定を行う編集ダイアログやヘルプテキストを定義します。この例ではNodeにユーザが任意で付与できるnameプロパティがあります。nameプロパティはワークスペース上の複数の同Nodeを区別するために広く使用されている慣習があります。


ここは特に説明不要かと思います。