17
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Node-REDAdvent Calendar 2018

Day 11

Node-RED パズルで遊んでフローに親しんでもらいたい

Last updated at Posted at 2018-12-10

Node-RED の最大の特徴は、フロー だと思います。画面上でノードを線(フロー)で繋ぐだけで、理解し易い表現を維持しつつ、いろいろなロジックを実現できる素晴らしい機能ですよね。

今回は Node-RED の編集画面をうまく使って、初心者に パズルゲーム を解く感覚で、フローに親しんでもらおう!という試みです。具体的には、以下のようなパズルを作成し、線を繋ぐことでゴールまで進んでもらいます。
image.png

まずは問題の準備

まずは簡単な問題を試してみましょう!以下の 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":[]}]

メニューから「読み込み」にある「クリップボード」を選択し、
image.png
表示された読み込みダイアログのテキストエリアに JSON データをペーストし、「新規のタブ」に「読み込み」を実施します。
image.png
読み込まれたフローを配置すると以下のようになります。
image.png
これで問題の準備は完了です!

左にある「スタート」の数値が 1 で、右にあるゴールの数値が 4 です。スタートからゴールまで正しく線(フロー)を繋ぐことで、ゴールに届く数値を 4 にするのがこのパズルの目的です。

問題を解いてみましょう

対象は初心者ですから、間違うこともあります。まずは以下のように線(フロー)を繋いだとします。
image.png
フローを「デプロイ」した後、「スタート」と記載されている Inject ノードの左にあるボタンをクリックしましょう。
image.png
すると「スタート」から数値 1 が送信され、フローが動作します。

各計算ノードの下のステータス領域には計算の結果が表示され、最後の「ゴール」ノードの下にはパズルの結果が表示されます。今回は赤のアイコンと共に「間違っています!」と表示されていますので、繋ぎ方が正しくなかったようですね。
image.png
では、線(フロー)を繋ぎなおして、もう一度「デプロイ」してみましょう。今回は以下のように繋いでみました。
image.png
「スタート」と記載されている Inject ノードの左にあるボタンをクリックして、再びフローを実行してみましょう!

今回は「ゴール」に緑のアイコンが表示され「正解です!」と表示されました。
image.png
正しいフローを実行できたので、最初のパズルは無事にクリアです!おめでとうございます。

今回のパズルの仕組み

仕組み、と言ってもたいしたことはありません。Node-RED の基本機能のうち、ノードエディタで表示できる「ステータス」機能を利用してフローの様子を視覚的に見せているのがポイントです。

スタート用ノード

スタート用ノードは普通に inject ノードを使っているだけです。ペイロードで指定した値を、ちゃんと名前のところにも設定してあげる、ぐらいしか注意点はありません。
image.png
image.png

計算用ノード

計算用ノードは function ノードで、こちらは名前と、コードの赤枠の部分の処理をあわせているのがポイントです。
image.png
image.png
コードは以下のように記載しています。

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 ノード、ほぼそのままです。
image.png
名前をパズルっぽく表現した、のがポイントでしょうか。
image.png
地味ですが、一応は出力にもラベルを設定しています。
image.png
ステータスを出したいので同種の機能を function ノードで作成しようか、とも思ったのですが。Node-RED のフローに慣れてもらうのが本来の目的なので、あえて switch ノードそのままで利用してみました。

なお以下の奇数/偶数の実装方法に少し困りまして、
image.png
以下のように JSONata で条件を書いてみました。やっぱり便利ですね。
image.png

ゴール用ノード

ゴール用ノードも計算用ノードと似ており、やはり Function ノードを使用しています。アイコンは変えています。
image.png
指定したコードも、計算ノードを少しシンプルにしたようなコードになっています。if 文の中にこのパズルの答え合わせがあることに注意してください。

var status = {fill:"red",shape:"dot",text:"間違っています!"};

if (msg.payload === 4) {
    status.text = "正解です!";
    status.fill = "green";
}

node.status(status);
return msg;

追加の問題

フロー課題 その2

では、計算ノードを増やした第2問にもチャレンジしてみてください。
image.png
問題の 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問にもチャレンジしてみてください。
image.png

[{"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

第4問は第3問の発展形です。
image.png

[{"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

第5問は奇数/偶数の分岐ノードを使用してみました。
image.png

[{"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 (回答例)

image.png

フロー課題 その3 (回答例)

image.png

フロー課題 その4 (回答例)

image.png

フロー課題 その5 (回答例)

image.png

というわけで

Node-RED 編集画面を使ったフローのパズル問題を幾つか作成して、ご紹介しました。Node-RED の初心者向け講習会などで、最初に頭の体操として解いてもらう、プログラミング初心者の方にまずはパズル感覚で親しんでもらう、などいくつか活用方法が考えられます。

皆さんもこのような仕組みを使って、何か面白いパズルを作ってみませんか?

それではまた!

17
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?