オープンソースのIoT向けノンプログラミング開発ツール「Node-RED」

横井一仁氏(以下、横井):本日は西山、東村、松浦、横井という4名で、Node-REDのv1.0の機能紹介をしていきます。よろしくお願いします。

田中正吾氏(以下、田中):お願いします。

横井:こちらが今日のメンバーです。まず私から、Node-RED v1.0のエディタ側の機能をご紹介します。そのあと西山から、サブフロー機能の拡張について紹介します。順番が入れ替わる予定なんですが、続いて東村から、サーバサイド側であるランタイムの変更点を紹介します。最後に松浦からですが、エディタの自動テストというものを1つ作りました。そのデモもあるそうなので見てみてください。では、始めていきたいと思います。

田中:お願いします。

横井:Node-REDは大半の方がご存知だと思いますが、一応最初のスライドを用意しておきました。

Node-REDは、ご存知のように、IoT向けのノンプログラミングの開発ツールです。下の絵のように部品を真ん中に持ってきて処理順につないで一連のプログラムを作っていく開発ツールになります。部品がインターネット上にたくさん公開されていまして、2,500種類ぐらいあります。メジャーなデータベースやWatsonのようなクラウド上で使えるAPIと、すぐつながるようなツールになっています。

(スライドの)3つ目にある「Linux Foundationのオープンソースソフトウェアです」というところが、ちょっとアピールしたいところです。Linuxと同じようにいろんな開発者が集まって開発をしているツールです。今は126人がGitHub上でコントリビューションしている状態で、日立メンバーもおります。今日司会されている田中正吾さんもコミットされてますよね。

田中:はーい!(笑)。

横井:あとはYouTube Liveを配信されている北崎さんもコントリビューションされております。

プロダクションにも使える「v1.0」

続きまして、v1.0のご説明をいたします。v1.0は2年ほど前にロードマップが出て、開発がどんどん進みまして、昨年の9月30日に正式版としてリリースされたバージョンです。このv1.0より前は、v0.20とかv0.19とか、最初に0が付いていたんですね。0がなくなって最初の番号が1になったということは、プロダクション向けに使っても大丈夫ですよということです。

オープンソースとして安定してきたことをきちんと表すバージョン番号になっています。ですのでNode-REDもプロジェクトとしても重要なバージョン番号になっております。スライドの左側は、それぞれ新機能です。これから1つずつご紹介していきます。

「2年ほど前にロードマップが出ました」と言ったんですが、この2年間で大きな機能をいくつか開発してきましたので、右側はそちらを書いております。

プロジェクト機能は、フローをNode-REDからGitHub上に直接保存する機能です。ウフルの船津さんがこのあとご紹介されると聞いております。Persistent Contextは、コンテキストのデータをメモリ上ではなくストレージ上に保存してデータを永続化する機能です。クラッシュしたときでもきちんとデータが保存される機能が開発されてきました。あとはあとからご紹介するUIテストですね。それぞれの機能を説明していきます。

バレットのカテゴリやノードの配置の変更

1つ目です。パレットのカテゴリが変更されて、ノードの配置も変更されました。

v0.20以前は左側にあるように、入力・出力機能というカテゴリが並んでいました。入力(input)のところは右側にしか端子がないノードが並んでいて、出力(output)のところは左側に端子があるノードが並んでいるというシンプルな並び方だったんですが、ちょっと分かりづらくなってきました。

機能(function)のところにどんどんノードが追加されていって、これからも増えていきそうだということもあり、きちんとネットワーク系のものはnetworkカテゴリで、例えばparserみたいなものはparserカテゴリと。そして共通なものは共通カテゴリに入れるように、きちんと整理されました。

そしてノードの組み合わせて使うノードも配置が変更されました。上下に配置して、わかりやすくなりました。

田中:これ、いいですね。

横井:はい。例えばhttp inとhttp responseは組み合わせて使うノードなので、きちんと上下に配置しています。「どこ行ったかな」ということがないように変更されています。

田中:僕も検索で「http」で絞り込んでいました(笑)。

横井:そうですね。この組み合わせはよくあるケースです。

ノードの実行完了を検知するcompleteノード

続きまして、completeノードというものが、パレット上に新しく追加されています。

completeノードは、処理が終了したというイベントをキャッチして後続のノードに処理を渡せるノードです。これまでフローの終端のノードは、その処理が終了したかどうかがわからなかったのですが、completeノードは、処理が終了したというイベントをキャッチして後続のノードに処理を渡せるノードになっています。

こちらの例は、WebSocket経由でデータを外部に送るケースです。今までは、「きちんと送りました」ということがわからないという問題があったんですけど、きちんと送ったというイベントをcompleteノードでキャッチして、例えばログに吐き出すようなこともできるようになりました。

WebSocket以外にも、例えばMQTTブローカーへデータをmqtt outノードから送るとか、あるいは、emailノードできちんとメールが送信されたというのをログに書くという、そんなフローも書けるようになっています。

ビジュアルJSONエディタ

続いて、ビジュアルJSONエディタです。マウス操作と最低限のキー入力でJSONのデータを記述できるエディタです。これまでは普通にテキストで書いていたんですね。JSONのデータを書く時にはコーディングだったんです。それがマウスやタブレットでGUIで選択してJSONのデータを作れるようになりました。

要素の追加や、true/falseみたいなものはプルダウンメニューから選択して追加できます。これはコーディングできる人には必要ないかなと思うところなんですが、""を付け忘れることはよくあると思います。

田中:わりとありますよね(笑)。

横井:そういうのも回避できて、けっこう便利な機能かなと思っております。

アクションをキーワード検索できるアクションリスト

次にアクションリストというダイアログです。エディタ上でCtrl+Shift+pを押すと出てきます。ダイアログを出して、アクションをキーワード検索します。キーワードをある程度入力すると候補が出てきて、Enterを押すと、そのアクションが実行されるという機能です。Visual Studio Codeのようなエディタにもあるよく使う機能だと思います。

ショートカットキーに割り当てられていないアクションも実行できます。例えばこの例だと、「タブを追加する」というアクションですね。それをキーボードから実行できる機能を持つダイアログです。

追加された2つのショートカットキー

次はショートカットキーが新しく追加されましたという話です。

まず、Ctrl+dという、デプロイするアクションのショートカットキーが追加されました。もう1つが、Ctrl+yというショートカットキーで、やり直し(redo)ができるようになりました。今までCtrl+zで操作を元に戻すとことはできたんですけど、何度かCtrl+zで操作を戻っていくと戻し過ぎて本当に戻りたかった地点に行かなかったことがあったんですね。

田中:そうですね(笑)。

横井:それでやり直し(redo)ができなくて、設定が消えてしまったというのが事業部で起きまして。

田中:本気で使うが故の。

横井:そうですね。本気で使う部隊がリクエストを上げてくださいました。あとから登壇するメンバーの東村がこの機能を作りまして、Node-RED本体に入れた機能になっています。日立発の機能です。

ワイヤーに選択したノードを挿入する、ワイヤリング操作

続きましてワイヤリング操作です。

今までのv0.20以前ではワークスペース(真ん中の何もないところ)でCtrl+クリックすると、スクリーンショットのようなプルダウンメニューが出てきて、ここからノードを選択できるという機能があったんです。この機能の拡張として、ワイヤー上でCtrl+クリックを実行すると同じようなプルダウンメニューが出てきて、そのワイヤーの中に選択したノードを挿入できる機能です。

あとで時間があればデモをご紹介したいと思っています。

田中:ありがとうございます。

操作しやすくなった読み込み/書き込みダイアログ

横井:続きまして、読み込み/書き込みダイアログです。v0.20以前ですと、左側のスクリーンショットのようにメニューでサンプルフローを選択するときに、メニューをたどる操作が必要でした。この操作がけっこう難しくて、この黒いエリアからマウスのポインタが外れると全部消えちゃって最初からやり直しなんです。

田中:そうですね。これがVRデバイスとかだともっと辛い(笑)。

横井:なので何度も何度も……。私はラズベリーパイのタッチパネルで操作するというのが、かなり難しくてできなかったんです。v1.0からは、右側のようにダイアログが出てきて、間違いなく選択できます。これも日立の私の隣の席の者がリクエストを上げて「これは使いにくい」と言って、開発コミュニティに聞いてもらえた機能です。

コンテキストサイドバーの自動更新機能

次はコンテキストのサイドバーです。コンテキストのサイドバーは、今までのエディタに入っていますが、そのサイドバーのデータを自動的に更新するチェックボックスが新しく追加されました。

今まではこのチェックボックスの隣にあるリロードボタンをクリックしないとコンテキストのデータが見えなかったんですが、このチェックを入れると値が変更されたときに自動的に値が変わるという見やすくなる機能が入っています。

ブラウザで任意の言語が選択可能に

こちらは言語選択の機能です。

今までのv0.20までのバージョンではブラウザの言語設定を使って、Node-REDに表示する言語をどれにするかをエディタ側が自動的に判別していました。v1.0からはデフォルトでその機能も入っているんですが、英語で表示をしたいと思ったら、英語の言語を選択すると画面を英語表示に切り替えることもできる機能です。日本人は英語にしたいケースしかないと思いますが、言語は今、ドイツ語、韓国語、中国語2種類も入っています。このうち韓国語と中国語の2/3ぐらいは日立のメンバーが入れた翻訳です。

私からは以上です。

田中:ありがとうございます。

横井:続いて西山から、サブフローの機能についてご紹介します。

独自ノードと同様の機能がサブフローでも可能に

西山:ここからサブフローの機能拡張についてお話しします。みなさんサブフローを使われているかと思うんですけど、サブフローは普通のノードと似ているようで微妙に違っています。 ここに示しているように、例えばサブフローのアイコンはもともと変更できなかったんですが、0.19からアイコンフォントを指定できるようになりました。

右側のサイドバーに表示される説明書きについても、0.20から、同じようにマークダウン形式で表示できるようになりました。カテゴリについてはもともとできます。処理については、独自ノードはJavaScript、サブフローはフローを書いてそれを処理します。

1.0では、いままではできなかった色指定と設定UIが定義できるようになりました。

サブフローのアイコン色指定機能

次に少しそれを紹介します。まずはアイコンの色指定です。サブフローのテンプレートのところに外観というタブがあります。カテゴリとかアイコンは、今までもそこで指定できたんですが、新しく色指定の項目が入りまして、この項目を使うことで、見た目を上にあるようなサブフローではなくて、ノードと合うようにできるようになりました。そうすると見た目上、一般のノードと同じような形で利用できます。

環境変数を設定するUIを作れるサブフロー設定UI定義機能

さらに右に行きまして、1.0では、サブフローの設定UIを定義する機能が加わりました。0.19ではサブフローに対して環境変数、インスタンス属性と呼ばれているんですが、これを定義するための機能が入ったんです。

いままでは変数名と値を書くだけだったんですけど、新しいサブフローの設定UIでは、このスライドにありますように、変数名に対して入力フィールドをどういう形式で入力するかを選べるようになります。スライドの左側が、その設定画面です。

例えばメニューを作ったり、チェックボックスを指定したり、好きな数値を上下させて選択したりとか、いろいろな形式を指定すると、右側にあるようなメニューが自動生成されます。この機能を使うと、一般のノードにあるような設定UIをサブフローに対しても同じように定義できます。

例えばこの例だと、APIKEYやALLMSGというような環境変数が設定されるので、その環境変数を読み込んでサブフローの中で使うことで、この設定に従っていろいろな処理をするサブフローを簡単に作ることができるようになる。これらの機能を使うと、一般のノードと同じような処理をするサブフローを、Node-REDのエディタだけで作ることができます。

将来的にはサブフローの再配布もサポート

次にいきます。今開発中の機能です。だいたい今まで追加された機能で、ノードとサブフローでできることがだいたい同じになってきました。そこで今度はサブフローを再配布して、ノードと同じように他の人に渡してインストールできる機能を今は開発しています。

できれば1.1に入れたいと思っているんですけど、新しくサブフローに情報というタブを作って、そこにライセンスやバージョン、作者というようないろいろな情報を書き込んで、サブフローの右上のメニューのタブのところに、新しくエクスポートというボタンができますので、このボタンを押すと、JSON形式でサブフローをノードとしてエクスポートできます。これを例えば他の人に渡すとノードと同じようにインポートできます。

この機能を使えば、今までJavaScriptやHTMLを書いてノードを作らなくてはいけなかった場面で、ノードの組み合わせで新しいノードを作ることが簡単にできるようになる予定です。

サブフローについては以上です。

田中:ありがとうございます。

ランタイムの新機能

横井:続いて東村から、ランタイムの機能をご紹介します。

東村:日立製作所の東村です。よろしくお願いします。

田中:よろしくお願いします。

東村: Node-REDのランタイム側ということで、あまり目に見えることではないんですが、大きく変わったところもあるので、簡単に紹介させていただきます。

田中:重要なことです。

東村:ここでは大きく3つ書いています。フローの実行が非同期化されたということと、自分で独自のノードを作る人のための、ノード実装のAPIが追加されましたということ。今、その機能をうまく使って提案中の機能があるので、その紹介ということで、この3つを説明させていただきたいと思います。

メッセージの受け渡しが非同期に

まずはノードの非同期実行化です。Node-RED1.0から、ノード間のメッセージの受け渡しが非同期処理になっています。going-asyncということで、Node-REDのNickさんのブログを見れば書いてあるんですが。

普通のデータフローエンジンを考えていると、ノードが次のノードにメッセージを送信すると、すぐに制御が元のノードに戻ってきて別の処理を行うというようなイメージがあります。でも、これまでのNode-REDは同期処理だったので、ノードにメッセージを送ると送信先のノードでまず処理が実行されて、また次のノードが実行されて、最後のノードまで到達するとやっと元のノードに制御が戻ってくるという同期的な実行でした。

これが非同期実行になると、ノードのメッセージ送信の時点でこのノードの処理がすぐに終了して、メッセージはいったんイベントキューに積まれます。

どうしてこういう処理ができたかというと、メッセージのルーティングの部分をPluggableにカスタマイズできるようにしようという将来的な動きもあり、その準備がひとつ。それと、あるノードでやった処理がいつ終わったのかがわからなくなってしまうので、そのタイムアウトを正しく判断するための改善でもあります。また制御をいったんスケジューラに戻すことでI/Oスケジューリングを改善する。こうした目的があります。

メッセージをキューに積む

実際にコードのレベルで見てみるとどうなっているかと言うと……。

田中:見たいです。

東村:このNode.prototype.emitというのが、実際にノードの中でメッセージを受け取ったときの処理です。

インプット処理を受け取ると、非同期処理の場合は、ここでいったんsetImmediateとして処理がキューに積まれます。今までの1.0以前ではこのような関数が存在せず、直接EventEmitterのemitを直接呼びだして、emit内で同期実行する構図になっています。

非同期だと処理の実行順序が変わる

これで実際にどんな挙動の差異が出てくるかと言うと、これはNickさんのページを見るとアニメーションのSVGを使って非常にわかりやすく書かれているので、ここでは数字で書いています。

従来の同期実行では、ほぼ同時期に先頭のノードにあるイベントが、例えばhttp inとかで外からメッセージが飛んでくると、まず1つ目のメッセージに対する処理が1、2、3、4と終わってから最初のノードに制御が戻ってきて次のメッセージが5、6、7、8と、実行されます。

これが非同期実行になると、今までと同じように入ってくると最初のメッセージが処理されて、とりあえずメッセージを次のキューに送ります。するとまたここのhttp inのところで処理が始まってそのメッセージを次につなぐ。これを受け取った次のノードは、そのキューから取り出して次の処理が進むというように、順繰りにメッセージが進んでいきます。

これまで同期実行のときの奇妙な挙動として、debugノードをこういうかたちで各ノードの出力に付けた場合、同期実行ですと、実行順がまず上のフローを1番、2番、3番、4番と実行して処理が戻ってきて2番のところで5番に処理が進む。最後に処理が1番に戻ると今度は6番に進むというかたちになるので、このようなフローを書いたときにデバッグ出力が、この4番、5番、6番と逆順で出力されるという奇妙な状況がありました。

これが非同期実行になると、より素直な感じで1番からメッセージを2、3のここの2つに出されると、この順でメッセージが実行される。またここでも2番から4番と5番にメッセージが送られると4、5が実行されて最後は6に。結果としてデバッグログに3、5、6の順でちゃんと出るので、直感に非常に近いかたちになるのではないかなと思います。

引数に渡された関数で成否を返す

次ですが、ノード実装のための新APIとしてNode Send APIが準備されました。これもNickさんのブログで『Knowing when a node is done』として書かれています。非同期実行がデフォルトとなって各ノードの処理の終わりがある意味わかりやすくなったんですが、結局node.send()を実行したら終わりなのかいうと、そうでもないというかたちになっています。

例えばsend()を複数回呼ぶ場合とか、先ほどのcompleteノードのところでもありましたように、外側のシステムにメッセージを送って、そのノードが終わってしまうような、あるいは非同期処理で次のノードのメッセージの送信がinputコールバックの中で実行されないような場合。こうした場合に、いつ終わったのかわからないので、終了したことを明示的に表すAPIを再設計します。先ほどのcompleteノードの実装のためでもあります。

ノードを実装するときは、そのインプット、前の段のノードからメッセージを受け取った場合のコールバックハンドラとしてinputハンドラを作るんですが、今までこのメッセージの引数しか渡っていなかったところにsendとdoneという関数が渡されるようになります。

これを使って、このメッセージ処理にエラーが発生した場合は、doneにエラーを入れて返す。そうではなくてちゃんとうまくいった場合には、今まではnode.sendという関数を使って送っていたんですが、これからは渡されてきたsend関数を使ってメッセージを送って終了を表す。こうしたかたちで表現することになります。

提案中のGracefulシャットダウン機能は、メッセージの消失を避け、安全に終了させられる

このdoneとかをうまく使ったものとして、Gracefulシャットダウンという機能を今提案中です。

今までNode-REDランタイムというものは、仕掛中の処理があるかどうかに関わらず、Ctrl+cを押すとバッと処理が終わる。例えばWeb APIのエンドポイントを作っていた場合、エンドポイントの中で重い処理をしていた場合だと、例えば1つのフローの中に仕掛中のメッセージが3つあった場合でも、シャットダウンをしてしまうとそのまま消滅してしまう。「なんかメッセージが返ってこないなぁ」と、なってしまう。

それを、ちゃんと終了したということを理解してうまくシャットダウン処理を動かす。メッセージが流れているのであれば、まだ処理は終わらずhttp outの処理が完全に終了したあとでシャットダウンを開始するという処理ができるようになります。

どうやってやるかを簡単に言うと、sendとdoneの対をきちんと区別して、sendが送ったらジョブが1つ増えた、doneが呼ばれたらジョブが1つ減ったというのをカウンターで管理することで処理の終了がトレースできる。こういう仕組みを今、入れようとしています。

ランタイム側の機能は、これで終了です。次は松浦さんに渡します。

UI自動テスト機能について

松浦由真 氏(以下、松浦):松浦です。よろしくお願いします。

田中:よろしくお願いします。

松浦:UI自動テスト機能について紹介します。この機能はエディタの開発者やノードの開発者を対象としたテスト機能になっています。指定した操作を画面上で実行して、想定通りに動くかどうかを自動でテストできます。以前作ったフローは、UIの変更によって動かなくなるというデグレードを発見する手段としてお使いいただけます。

実行方法を説明します。UI自動テストはコマンドライン上で実行しますが、オプションでブラウザを表示するかどうかを選択できます。ブラウザを表示しない場合は、左側に示したようにgrunt test-uiと実行するとテストを実行できます。実行が終わるとテストの結果がコマンドラインに出力されます。

ブラウザを表示したい場合は、grunt test-uiの後ろに--non-headlessのオプションを付けて実行をします。--non-headlessを指定するとコマンド実行後にブラウザが表示されて、指定したテストシナリオがブラウザ上で実行されている様子を目で見ることができます。ここを後ほど動いている様子をお見せしたいなと思います。

UI自動テストの構成

次に、UI自動テストがどう構成されているのかを説明します。テストスクリプトをGruntコマンドで実行すると、テストフレームワークのMochaがWebdriverIOのAPIを実行するようになっています。WebdriverIOはUI操作を自動化するためのライブラリで、browser.click()などのAPIが用意されています。

Mochaから呼び出されたWebdriverIOがChromeブラウザを起動して画面を操作します。このような構成になっています。

Node-REDのソースコードの構成

次にNode-REDのソースコードの構成をお話したいと思います。Node-REDの下のtestディレクトリの下にeditorディレクトリがあって、その中にUI自動テストのソースを配置する構成になっています。pageobjectsとspecsという2つのディレクトリがありますが、pageobjectsにはNode-REDを操作するAPIを用意しています。

現在ではcoreノードのうちすべてではないんですが、ここのスライドに記載したノードはAPIが用意しているような状態になっています。specsの中にはテストシナリオを格納しているんですが、このテストシナリオを上のpageobjectsが提供するAPIを使って作成するようなかたちになります。

テストシナリオは、こう書く

テストシナリオの書き方を説明します。まずはテストしたいシナリオを決めます。今回は簡単なシナリオを用意しました。

まず始めにinjectノードとchangeノード、debugノードをワークスペースに配置します。次にchangeノードでmsg.payloadに"Hello World!"の文字を指定します。その次にこれら3つのノードをつなげます。ここまでで右の図のフローができるようになります。そのあとにデプロイボタンを押して、injectノードのボタンをクリックして、一番最後にデバッグタブに"Hello World!"の文字が出力されることを確認します。この流れをテストスクリプトに置きたいと思います。

テストコードを右側に載せましたが、1番から6番までのシナリオを実行しています。

1番では3つのノードをワークスペースに配置しているんですが、ここはworkspace.addNode("inject")と書くことで、injectノードをワークスペースに配置することができます。changeノードとdebugノードも同じ書き方です。

次にchangeノードに"Hello World!"の文字を指定します。changeNode.edit()とすると、画面上でchangeノードをダブルクリックしたときと同じ操作をします。ダブルクリックをしてエディタを開いてruleSetの関数でpayloadに文字列を指定します。その次にclick.Ok()でOKボタンを選択してエディタを閉じるといった操作になっています。ここまでで2番が完了です。

3番では3つのノードをワイヤーでつなげます。injectNode.connect(changeNode)と書くと、injectノードとchangeノードがつながり、3番の2つ目のchangeNode.connect(debugNode)と書くと、changノードとdebugノードがつながります。

ここまででノードの設定が完了したので、4番のデプロイボタンを押します。これはworkspace.deploy()で実行ができます。

次に5番のinjectノードのボタンをクリックです。ここはinjectNode.clickLeftButton()で実行します。

最後に6番のデバッグタブでの確認のところですが、debugTab.open()でデバッグタブを表示して、debugTab.getMessage()で、一番上に表示される内容を確認することができます。

ここで"Hello World!"と表示されていることが確認できれば、このテストケースは成功することになります。先ほど紹介したディレクトリにテストシナリオが既にいくつか格納されているので、実際にシナリオを書くときは、参考にしていただければと思います。

シナリオテストの実行をデモで紹介

機能について簡単に説明しましたが、いくつかテストシナリオを実行した動画を見ていただこうと思います。

コマンドプロンプトでGruntコマンドを実行します。今回ブラウザを表示したいので--non-headlessのオプションを指定して実行をしています。このコマンドを実行するとビルドしたあとにChromeのブラウザを表示します。

今回の動画では簡単なテストシナリオを4つ実行しています。フローを作成して、デプロイしてデバッグタブを確認するという流れを何回か実行をします。

ここでChromeが表示されます。ちょっと速いので目で追うのはけっこう大変なんですが、フローを作ってデプロイしてデバッグタブを見るということを何度か実行しています。これが最後のシナリオになって……。

ここでテストが終わるとコマンドプロンプトに結果が表示されて4つのシナリオが成功したことがわかります。このような感じでUIテストを実行することができます。

機能紹介は以上です。