Node-RED の最大の特徴は、フロー だと思います。画面上でノードを線(フロー)で繋ぐだけで、理解し易い表現を維持しつつ、いろいろなロジックを実現できる素晴らしい機能ですよね。
今回は Node-RED の編集画面をうまく使って、初心者に パズルゲーム を解く感覚で、フローに親しんでもらおう!という試みです。具体的には、以下のようなパズルを作成し、線を繋ぐことでゴールまで進んでもらいます。
まずは問題の準備
まずは簡単な問題を試してみましょう!以下の JSON データをクリップボードにコピーして、
[{"id":"f30474b6.061cb8","type":"inject","z":"5bd94f8d.06751","name":"スタート: 1","topic":"","payload":"1","payloadType":"num","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":110,"y":100,"wires":[[]]},{"id":"6ae4cfc9.5241f","type":"function","z":"5bd94f8d.06751","name":"ゴール: 4","func":"var status = {fill:\"red\",shape:\"dot\",text:\"間違っています!\"};\n\nif (msg.payload === 4) {\n status.text = \"正解です!\";\n status.fill = \"green\";\n}\n\nnode.status(status);\nreturn msg;","outputs":1,"noerr":0,"x":640,"y":100,"wires":[[]],"icon":"node-red/alert.png"},{"id":"2acb2b87.1fd974","type":"comment","z":"5bd94f8d.06751","name":"フロー課題 その1","info":"","x":100,"y":40,"wires":[]},{"id":"20d6a96f.a026d6","type":"function","z":"5bd94f8d.06751","name":"+ 1","func":"var status = {fill:\"red\",shape:\"dot\",text:\"\"};\n\nif (typeof msg.payload === \"number\") {\n status.text = msg.payload + \" -> \";\n // ----------\n msg.payload += 1;\n // ----------\n status.text += msg.payload;\n status.fill = \"green\";\n}\n\nnode.status(status);\nreturn msg;","outputs":1,"noerr":0,"x":350,"y":100,"wires":[[]]},{"id":"53adc9a9.1c8598","type":"function","z":"5bd94f8d.06751","name":"x 2","func":"var status = {fill:\"red\",shape:\"dot\",text:\"\"};\n\nif (typeof msg.payload === \"number\") {\n status.text = msg.payload + \" -> \";\n // ----------\n msg.payload *= 2;\n // ----------\n status.text += msg.payload;\n status.fill = \"green\";\n}\n\nnode.status(status);\nreturn msg;","outputs":1,"noerr":0,"x":350,"y":180,"wires":[[]]},{"id":"341d3ff3.a56ba","type":"comment","z":"5bd94f8d.06751","name":"スタートからゴールまで線を繋ぎましょう!","info":"","x":530,"y":40,"wires":[]}]
メニューから「読み込み」にある「クリップボード」を選択し、
表示された読み込みダイアログのテキストエリアに JSON データをペーストし、「新規のタブ」に「読み込み」を実施します。
読み込まれたフローを配置すると以下のようになります。
これで問題の準備は完了です!
左にある「スタート」の数値が 1 で、右にあるゴールの数値が 4 です。スタートからゴールまで正しく線(フロー)を繋ぐことで、ゴールに届く数値を 4 にするのがこのパズルの目的です。
問題を解いてみましょう
対象は初心者ですから、間違うこともあります。まずは以下のように線(フロー)を繋いだとします。
フローを「デプロイ」した後、「スタート」と記載されている Inject ノードの左にあるボタンをクリックしましょう。
すると「スタート」から数値 1 が送信され、フローが動作します。
各計算ノードの下のステータス領域には計算の結果が表示され、最後の「ゴール」ノードの下にはパズルの結果が表示されます。今回は赤のアイコンと共に「間違っています!」と表示されていますので、繋ぎ方が正しくなかったようですね。
では、線(フロー)を繋ぎなおして、もう一度「デプロイ」してみましょう。今回は以下のように繋いでみました。
「スタート」と記載されている Inject ノードの左にあるボタンをクリックして、再びフローを実行してみましょう!
今回は「ゴール」に緑のアイコンが表示され「正解です!」と表示されました。
正しいフローを実行できたので、最初のパズルは無事にクリアです!おめでとうございます。
今回のパズルの仕組み
仕組み、と言ってもたいしたことはありません。Node-RED の基本機能のうち、ノードエディタで表示できる「ステータス」機能を利用してフローの様子を視覚的に見せているのがポイントです。
スタート用ノード
スタート用ノードは普通に inject ノードを使っているだけです。ペイロードで指定した値を、ちゃんと名前のところにも設定してあげる、ぐらいしか注意点はありません。
計算用ノード
計算用ノードは function ノードで、こちらは名前と、コードの赤枠の部分の処理をあわせているのがポイントです。
コードは以下のように記載しています。
var status = {fill:"red",shape:"dot",text:""};
if (typeof msg.payload === "number") {
status.text = msg.payload + " -> ";
// ----------
msg.payload += 1;
// ----------
status.text += msg.payload;
status.fill = "green";
}
node.status(status);
return msg;
// ----------
のコメントで挟まれた部分が、実際の計算部分で、計算内容によって変わります。
status
オブジェクトが処理の中心で、ステータス表示の内容をこれで管理しています。処理の最後で node.status(status)
と画面に表示させているのがわかります。
今回は「計算」ノードなので、if
文の中身は typeof msg.payload === "number"
としています。フローで渡された値が数値でなければ、赤いエラー表示になるわけです。
例えば + " !"
という処理をする「文字列処理」ノードであれば、if
文の中身は typeof msg.payload === "string"
にすべきですね。
分岐ノード
分岐ノードは switch ノード、ほぼそのままです。
名前をパズルっぽく表現した、のがポイントでしょうか。
地味ですが、一応は出力にもラベルを設定しています。
ステータスを出したいので同種の機能を function ノードで作成しようか、とも思ったのですが。Node-RED のフローに慣れてもらうのが本来の目的なので、あえて switch ノードそのままで利用してみました。
なお以下の奇数/偶数の実装方法に少し困りまして、
以下のように JSONata で条件を書いてみました。やっぱり便利ですね。
ゴール用ノード
ゴール用ノードも計算用ノードと似ており、やはり Function ノードを使用しています。アイコンは変えています。
指定したコードも、計算ノードを少しシンプルにしたようなコードになっています。if
文の中にこのパズルの答え合わせがあることに注意してください。
var status = {fill:"red",shape:"dot",text:"間違っています!"};
if (msg.payload === 4) {
status.text = "正解です!";
status.fill = "green";
}
node.status(status);
return msg;
追加の問題
フロー課題 その2
では、計算ノードを増やした第2問にもチャレンジしてみてください。
問題の JSON データは以下になります。
[{"id":"f30474b6.061cb8","type":"inject","z":"5bd94f8d.06751","name":"スタート: 1","topic":"","payload":"1","payloadType":"num","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":110,"y":100,"wires":[[]]},{"id":"6ae4cfc9.5241f","type":"function","z":"5bd94f8d.06751","name":"ゴール: 5","func":"var status = {fill:\"red\",shape:\"dot\",text:\"間違っています!\"};\n\nif (msg.payload === 5) {\n status.text = \"正解です!\";\n status.fill = \"green\";\n}\n\nnode.status(status);\nreturn msg;","outputs":1,"noerr":0,"x":640,"y":100,"wires":[[]],"icon":"node-red/alert.png"},{"id":"2acb2b87.1fd974","type":"comment","z":"5bd94f8d.06751","name":"フロー課題 その2","info":"","x":100,"y":40,"wires":[]},{"id":"20d6a96f.a026d6","type":"function","z":"5bd94f8d.06751","name":"+ 1","func":"var status = {fill:\"red\",shape:\"dot\",text:\"\"};\n\nif (typeof msg.payload === \"number\") {\n status.text = msg.payload + \" -> \";\n // ----------\n msg.payload += 1;\n // ----------\n status.text += msg.payload;\n status.fill = \"green\";\n}\n\nnode.status(status);\nreturn msg;","outputs":1,"noerr":0,"x":350,"y":100,"wires":[[]]},{"id":"53adc9a9.1c8598","type":"function","z":"5bd94f8d.06751","name":"x 2","func":"var status = {fill:\"red\",shape:\"dot\",text:\"\"};\n\nif (typeof msg.payload === \"number\") {\n status.text = msg.payload + \" -> \";\n // ----------\n msg.payload *= 2;\n // ----------\n status.text += msg.payload;\n status.fill = \"green\";\n}\n\nnode.status(status);\nreturn msg;","outputs":1,"noerr":0,"x":350,"y":180,"wires":[[]]},{"id":"341d3ff3.a56ba","type":"comment","z":"5bd94f8d.06751","name":"スタートからゴールまで線を繋ぎましょう!","info":"","x":530,"y":40,"wires":[]},{"id":"ada6b121.9d565","type":"function","z":"5bd94f8d.06751","name":"+ 2","func":"var status = {fill:\"red\",shape:\"dot\",text:\"\"};\n\nif (typeof msg.payload === \"number\") {\n status.text = msg.payload + \" -> \";\n // ----------\n msg.payload += 2;\n // ----------\n status.text += msg.payload;\n status.fill = \"green\";\n}\n\nnode.status(status);\nreturn msg;","outputs":1,"noerr":0,"x":350,"y":260,"wires":[[]]}]
フロー課題 その3
では、分岐ノードが追加された第3問にもチャレンジしてみてください。
[{"id":"230becfa.e30cb4","type":"inject","z":"8d4f0d2b.2e2fa","name":"スタート: 1","topic":"","payload":"1","payloadType":"num","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":150,"y":140,"wires":[[]]},{"id":"12b0e3dc.6bd48c","type":"function","z":"8d4f0d2b.2e2fa","name":"ゴール: 10","func":"var status = {fill:\"red\",shape:\"dot\",text:\"間違っています!\"};\n\nif (msg.payload === 10) {\n status.text = \"正解です!\";\n status.fill = \"green\";\n}\n\nnode.status(status);\nreturn msg;","outputs":1,"noerr":0,"x":690,"y":140,"wires":[[]],"icon":"node-red/alert.png"},{"id":"d23c40d5.6bf8a","type":"comment","z":"8d4f0d2b.2e2fa","name":"フロー課題 その3","info":"","x":140,"y":80,"wires":[]},{"id":"e3990a0.94dbbf8","type":"function","z":"8d4f0d2b.2e2fa","name":"+ 1","func":"var status = {fill:\"red\",shape:\"dot\",text:\"\"};\n\nif (typeof msg.payload === \"number\") {\n status.text = msg.payload + \" -> \";\n // ----------\n msg.payload += 1;\n // ----------\n status.text += msg.payload;\n status.fill = \"green\";\n}\n\nnode.status(status);\nreturn msg;","outputs":1,"noerr":0,"x":390,"y":140,"wires":[[]]},{"id":"f8325a77.8a6808","type":"comment","z":"8d4f0d2b.2e2fa","name":"数の大きさによって出口が違うノードが登場!","info":"","x":580,"y":80,"wires":[]},{"id":"707287ad.b54308","type":"switch","z":"8d4f0d2b.2e2fa","name":"9以下↓ / 10以上↑","property":"payload","propertyType":"msg","rules":[{"t":"gte","v":"10","vt":"num"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":410,"y":240,"wires":[[],[]],"outputLabels":["10以上","9以下"]}]
フロー課題 その4
[{"id":"75687ea7.6aaf7","type":"inject","z":"8d4f0d2b.2e2fa","name":"スタート: 1","topic":"","payload":"1","payloadType":"num","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":170,"y":460,"wires":[[]]},{"id":"cd891ad4.6c6138","type":"function","z":"8d4f0d2b.2e2fa","name":"ゴール: 16","func":"var status = {fill:\"red\",shape:\"dot\",text:\"間違っています!\"};\n\nif (msg.payload === 16) {\n status.text = \"正解です!\";\n status.fill = \"green\";\n}\n\nnode.status(status);\nreturn msg;","outputs":1,"noerr":0,"x":710,"y":460,"wires":[[]],"icon":"node-red/alert.png"},{"id":"d21de8fe.44ce78","type":"comment","z":"8d4f0d2b.2e2fa","name":"フロー課題 その4","info":"","x":160,"y":400,"wires":[]},{"id":"877ce50a.1cf9f8","type":"function","z":"8d4f0d2b.2e2fa","name":"x 2","func":"var status = {fill:\"red\",shape:\"dot\",text:\"\"};\n\nif (typeof msg.payload === \"number\") {\n status.text = msg.payload + \" -> \";\n // ----------\n msg.payload *= 2;\n // ----------\n status.text += msg.payload;\n status.fill = \"green\";\n}\n\nnode.status(status);\nreturn msg;","outputs":1,"noerr":0,"x":410,"y":460,"wires":[[]]},{"id":"cb5bb9fd.6ef7d8","type":"comment","z":"8d4f0d2b.2e2fa","name":"順番に注意して!","info":"","x":690,"y":400,"wires":[]},{"id":"b31e1ac2.a0a5d8","type":"function","z":"8d4f0d2b.2e2fa","name":"+ 3","func":"var status = {fill:\"red\",shape:\"dot\",text:\"\"};\n\nif (typeof msg.payload === \"number\") {\n status.text = msg.payload + \" -> \";\n // ----------\n msg.payload += 3;\n // ----------\n status.text += msg.payload;\n status.fill = \"green\";\n}\n\nnode.status(status);\nreturn msg;","outputs":1,"noerr":0,"x":410,"y":540,"wires":[[]]},{"id":"d9c784b1.02e448","type":"switch","z":"8d4f0d2b.2e2fa","name":"9以下↓ / 10以上↑","property":"payload","propertyType":"msg","rules":[{"t":"gte","v":"10","vt":"num"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":430,"y":620,"wires":[[],[]],"outputLabels":["10以上","9以下"]}]
フロー課題 その5
[{"id":"f30474b6.061cb8","type":"inject","z":"5bd94f8d.06751","name":"スタート: 1","topic":"","payload":"1","payloadType":"num","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":110,"y":100,"wires":[[]]},{"id":"6ae4cfc9.5241f","type":"function","z":"5bd94f8d.06751","name":"ゴール: 7","func":"var status = {fill:\"red\",shape:\"dot\",text:\"間違っています!\"};\n\nif (msg.payload === 7) {\n status.text = \"正解です!\";\n status.fill = \"green\";\n}\n\nnode.status(status);\nreturn msg;","outputs":1,"noerr":0,"x":640,"y":100,"wires":[[]],"icon":"node-red/alert.png"},{"id":"2acb2b87.1fd974","type":"comment","z":"5bd94f8d.06751","name":"フロー課題 その5","info":"","x":100,"y":40,"wires":[]},{"id":"341d3ff3.a56ba","type":"comment","z":"5bd94f8d.06751","name":"今度は奇数と分数で分岐します!","info":"","x":590,"y":40,"wires":[]},{"id":"35511ba2.8b9ac4","type":"function","z":"5bd94f8d.06751","name":"+ 3","func":"var status = {fill:\"red\",shape:\"dot\",text:\"\"};\n\nif (typeof msg.payload === \"number\") {\n status.text = msg.payload + \" -> \";\n // ----------\n msg.payload += 3;\n // ----------\n status.text += msg.payload;\n status.fill = \"green\";\n}\n\nnode.status(status);\nreturn msg;","outputs":1,"noerr":0,"x":350,"y":100,"wires":[[]]},{"id":"cd3e7fc8.a39dc","type":"switch","z":"5bd94f8d.06751","name":"奇数↑ / 偶数↓","property":"$number(payload) % 2","propertyType":"jsonata","rules":[{"t":"eq","v":"1","vt":"num"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":350,"y":180,"wires":[[],[]],"outputLabels":["奇数","偶数"]}]
追加の問題の回答集
このページを読んでいる皆さんには簡単すぎる問題だとは思いますが、一応、それぞれの回答例も記載しておきます。
フロー課題 その2 (回答例)
フロー課題 その3 (回答例)
フロー課題 その4 (回答例)
フロー課題 その5 (回答例)
というわけで
Node-RED 編集画面を使ったフローのパズル問題を幾つか作成して、ご紹介しました。Node-RED の初心者向け講習会などで、最初に頭の体操として解いてもらう、プログラミング初心者の方にまずはパズル感覚で親しんでもらう、などいくつか活用方法が考えられます。
皆さんもこのような仕組みを使って、何か面白いパズルを作ってみませんか?
それではまた!