28
14

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 3 years have passed since last update.

Node-REDAdvent Calendar 2019

Day 15

Node-REDにおけるコンテキストの活用について

Last updated at Posted at 2019-12-14

Node-REDにおけるコンテキストの活用について

Node-REDにおけるコンテキスト(Context)とは

Node-REDにおけるコンテキスト(Context)とは、Flowで受け渡すmsgオブジェクト以外の永続化機構:情報共有手段です。Node-REDでは、ノード間で情報を受け渡し場合、msgオブジェクトを使うのが一般的ですが、Flowの線の上を流れるmsgオブジェクトはFlow外には保存されず、Flowの一番最後のNodeに行くと消えてしまい、別にFlowを実行しても、msgオブジェクトは引き継がれません。

image.png

コンテキスト(Context)を使うと、オブジェクトをKey-Value形式で「永続化」することができます。
また、コンテキスト(Context)を使って、Node-REDの内外とデータのやり取りをすることができます。

コンテキストの種類

Node-REDのコンテキストには3つの種類があり、永続化されるデータスコープが異なります。異なるスコープのコンテキストには、同じkeyに別々の値を設定することができます。

(node)Context

単に「Context」とだけ書くと、単一Node内に永続化され、設定されたNode内でのみ値を使うことができます。
以下、他の種類のコンテキストと区別するため「(node)Context」と表現します。

(node)Contextを使えるのはFunction Nodeだけです。Function NodeのJavaScriptロジックはFunctionの実行が終わるとなくなってしまい、コード中にletconstで宣言した変数や定数はなくなってしまいますが、context.set(key,value)しておいたContextはNodeに永続化され、次回のFunction Node実行時にContext.get(key)で読み込むことで再利用することができます。
image.png

(node)Contextは、複数回実行されるFlow間で値を引き継ぐために利用されます。例えば、http-in NodeからのFlowでアクセスカウンターとして使われたり、定期実行されるFlowでのカウンターとして利用するなどのユースケースがあります。(node)Contextを使ったFunction nodeをコピペしても、保存するコンテキスト名の衝突を心配しなくてよくなります。

Flow コンテキスト

Flow コンテキストのスコープは、設定したFlowタブ内です。設定したFlowタブ外のNodeからはアクセスできません。

Flow コンテキストは、Inject Node、Function Node、Switch Node、Change Node、Template Nodeなど、msgオブジェクトへ値をset/getできるNode内で使うことができます。
image.png

image.png

Function Nodeでは、flow.set(key,value)で値をセットし、flow.get(key)で値を読み込むことができます。

flow.set("count",30);
let myCount = flow.get("count");

LinkノードでFlowタブ間をまたぐFlowを書く場合、移動前のFlowタブに設定したFlow コンテキストは移動先では使えないことに注意しましょう。

なお、context.flow.get() context.flow.set() と記載することもできます。(古い形式との互換性のための仕様)

Global コンテキスト

Global コンテキストのスコープはNode-RED内全域です。すべてのFlowタブからのFlowで共通に使うことができます。

Global コンテキストもFlow コンテキストと同様、Inject Node、Function Node、Switch Node、Change Node、Template Nodeなど、msgオブジェクトへ値をset/getできるNode内で使うことができます。

Function Nodeでは、global.set(key,value)で値をセットし、global.get(key)で値を読み込むことができます。

global.set("count",30);
let myCount = global.get("count");

なお、context.global.get() context.global.set() と記載することもできます。(古い形式との互換性のための仕様)

(node)Context / Flow コンテキスト / Global コンテキストの使い分け

コンテキストは永続化機構ですので、全域から使えるGlobal コンテキストだけ使えばいいと感じるかもしれません。しかし、Flowの書き出し/読み込みやFunction Nodeのコピペを想定した場合に、Globalコンテキストだけを使っていると、読み込み後のFlow内でコンテキストの名前が衝突してしまうことがあります。作成したFlowの再利用性を高めるために、コンテキストは最低限のスコープで使うようにしましょう。
image.png

コンテキストブラウザ(サイドバータブ)について

設定されたContextは、FlowEditorのサイドバータブの「コンテキストデータ」で参照することができます。通常はサイドバータブのウインドウ幅が狭いので、メニューから選択する形になりますが、サイドバータブを広げることで表示されます。
image.png

Nodeでは選択中のNodeの(node)Contextが表示され、Flowでは表示中のFlowタブのFlowコンテキストの内容が表示されます。

表示中にコンテキストの値が変更されても自動的に表示更新されません。 image.png を押すことで更新されます。
メニュー内のチェックを入れておくことで、フォーカス対象が切り替わった際に表示自動更新することができます。(が使いにくい!)

またNode-RED 1.0以降では、image.png ボタンで設定されたコンテキストを削除することができます。
image.png

コンテキストのライフサイクル

初期化

コンテキストはNode-REDのプロセスメモリ内に永続化されますので、Node-RED自体の再起動時にはなくなってしまいます。FlowコンテキストやGlobalコンテキストは、Flowの「デプロイ」ではなくなりませんが、(node)Contextは「デプロイ」時に初期化されます。
起動後、設定されていないContextを参照すると、JavaScriptのNaN値となります。加工するときに不便ですので、isNaNを使い3項演算子で初期化するか、初期化Flow(初期化Injectから始まるFlow)で初期化を行いましょう。


// 3項演算子を使ったflowコンテキスト count の初期化
let count = isNaN(flow.get("count")) ? 0 : flow.get("count");
count++;
flow.set("count",count);
return msg;

image.png
flowコンテキスト初期化Flow例(デプロイ時にFlowコンテキストが初期化されないように、switch Nodeで分岐している)

消去

コンテキストは、前述したようにコンテキストサイドバー操作で消去できます。また、Change Nodeで「値の削除」を指定してすると、Flowの中で削除できます。
image.png

Function Node内ではundefinedをValueにsetすることで、当該Keyを持つコンテキストを消去することができます。


// flowコンテキスト count の削除
flow.set("count",undefined);

Key一覧の取得

Function Node内では、flow.keys() を使うことで、Flowコンテキストのkey一覧を配列で取得することができます。設定されているコンテキストを調べたり、コンテキストに対して一括で処理する場合に使うことができます。
なお、global.keys()も準備されていますが、settings.js内の設定exportGlobalContextKeys: false,によってデフォルト無効にされています。必要な場合はsettings.jsでexportGlobalContextKeys: true,とすることでglobal.keys()を使うことができます。

バックアップ

以下のようなFlowを使ってコンテキストを定期的にファイルやデータベースに定期的にバックアップすることで、Node-REDが万が一非計画停止してしまった場合にも、コンテキストの内容を救うことができます。

image.png

image.png

バックアップサンプルflow
[{"id":"bafc3309.55a918","type":"change","z":"fac83ddb.89f0d","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"count","tot":"global"}],"action":"","property":"","from":"","to":"","reg":false,"x":590,"y":220,"wires":[["aebf1a35.3373"]]},{"id":"aebf1a35.3373","type":"json","z":"fac83ddb.89f0d","name":"","property":"payload","action":"str","pretty":false,"x":930,"y":220,"wires":[["dd613fe9.0d14a8"]]},{"id":"dd613fe9.0d14a8","type":"file","z":"fac83ddb.89f0d","name":"","filename":".node-red/count.bak","appendNewline":false,"createDir":false,"overwriteFile":"true","encoding":"none","x":1300,"y":220,"wires":[[]]},{"id":"e01cdedd.3545f","type":"inject","z":"fac83ddb.89f0d","name":"","topic":"30秒おきバックアップ","payload":"","payloadType":"date","repeat":"30","crontab":"","once":false,"onceDelay":0.1,"x":260,"y":220,"wires":[["bafc3309.55a918"]]},{"id":"96b08ca6.ade97","type":"inject","z":"fac83ddb.89f0d","name":"","topic":"起動時リストア","payload":"","payloadType":"date","repeat":"","crontab":"","once":true,"onceDelay":0.1,"x":230,"y":460,"wires":[["4da3658c.1e3214"]]},{"id":"4da3658c.1e3214","type":"switch","z":"fac83ddb.89f0d","name":"global.countチェック","property":"count","propertyType":"global","rules":[{"t":"null"}],"checkall":"true","repair":false,"outputs":1,"x":580,"y":460,"wires":[["dbaf4489.4236f8"]]},{"id":"dbaf4489.4236f8","type":"file in","z":"fac83ddb.89f0d","name":"","filename":".node-red/count.bak","format":"utf8","chunk":false,"sendError":false,"encoding":"none","x":860,"y":460,"wires":[["eec7ec57.ecf89"]]},{"id":"eec7ec57.ecf89","type":"json","z":"fac83ddb.89f0d","name":"","property":"payload","action":"obj","pretty":false,"x":1070,"y":460,"wires":[["f427a483.f714a"]]},{"id":"f427a483.f714a","type":"change","z":"fac83ddb.89f0d","name":"","rules":[{"t":"set","p":"count","pt":"global","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1340,"y":460,"wires":[[]]}]

高度な使い方

npmモジュールのGlobal コンテキスト経由の利用

Function NodeのJavaScriptロジックは、Node-RED内では専用のインスタンス(サンドボックス)内で起動されるため、外部モジュールをimportしたりrequireしたりすることはできません。Node-REDの機能をnpmモジュール等の外部モジュールで拡張するためには、Custom Nodeを書く必要があります。
しかし、Node-REDの設定ファイルであるsettings.js内にfunctionGlobalContextプロパティを設定することで、起動時にGlobal Contextとして外部npmモジュールを読み込むことができます。
例えば、settings.jsで

functionGlobalContext: {
    osModule:require('os')
},

と設定することで、Function Node内でnode.js標準モジュールOSを以下のように使うことができます。

const os = global.get("osModule");
msg.payload = os.loadavg();
return msg; // msg.payload=[m01,m05,m15]  システムの1, 5, 15分のロードアベレージを配列で得る

requireするnpmモジュールは、Node-RED実行時のnode.js標準パスにあるもの、もしくは.node-red/node_modules配下に導入したものを使うことができます。Node-RED内のみで使うnpmモジュールを導入する場合は、カスタムノードの導入時と同じように

cd .node-red
npm install モジュール名

として導入することで、Node-REDの環境にのみモジュールを導入することができます。

コンテキストストア

前述のとおり、Node-REDのデフォルト設定では、コンテキストの内容はNode-REDのプロセスメモリ内に永続化されます。プロセスメモリはアクセス速度が速い反面、Node-REDプロセスの停止・再起動で内容が消去されてしまい、長期間のデータ蓄積などに使いにくいです。
Node-RED 0.19以降で有効になった「コンテキストストア」を設定することにより、Node-REDの外にコンテキストを永続化できるようになりました。

利用できるコンテキストストアの種類

Node-RED 1.0時点では、コンテキストストアとして標準でmemorylocalfilesystemがサポートされています。Context Store APIを持つCustom Nodeをインストールすることで、コンテキストストアを追加できます。現在、Custom Nodeとして公開されているコンテキストストアは多くないですが、たとえば
node-red-contrib-context-consulを導入することで、HashiCorp社のConsulを使うことができるようです。

コンテキストストアの設定

設定はsettings.jsで実施します。contextStorageプロパティを記載することで設定できます。

contextStorage: {
   default: "memoryOnly",
   memoryOnly: { module: 'memory' },
   file: { 
       module:"localfilesystem",
       config: {
           dir: '/home/pi/csfile',
           cache: 'true',
           flushInterval: 10
       }
  }
},

例えば、上記のようにsettings.jsに設定すると、filememoryOnlyの2つの名称を持つコンテキストストアが設定されます。デフォルトがmemoryOnlyとなりますので、コンテキストストアを指定しない場合、memoryOnlyが使われます。また、fileは、/home/pi/csfile/context内に非同期で保存され、10秒ごとに内容が書き出されるコンテキストストアになります。定期的に出力されるファイルの構成は以下のようになります。

/home/pi/csfile/context
├── fac83ddb.89f0d (Flowコンテキスト)
│   └── flow.json 
└── global (Globalコンテキスト)
    └── global.json

個々のコンテキストは、JSON.parse形式の文字列です。

# more global/global.json
{
    "countfile": {
        "uptime": 3899,
        "timestamp": 1576203152309
    }
}

localfilesystemの詳しい仕様はLocal Filesystem Context Storeを参照してください。

コンテキストストアの使い分け

前述の設定のように複数のコンテキストストアを設定した場合、get/setのAPIに引数を追加することで、どちらに永続化するかを使い分けることができます。

var m = global.get("datam",memoryOnly); // memoryOnlyが使われる
var f = global.get("dataf",file);       // fileが使われる
var d = global.get("datad");            // 指定しない場合、デフォルトはmemoryOnly
var m = global.set("datam","abc", memoryOnly); // memoryOnlyにセット
var f = global.set("dataf","123",file);        // fileにセット
var d = global.set("datad","abc");             // 指定しない場合、デフォルトはmemoryOnly

Swich Node等のNodeでは、末尾にContext Storeを選択するメニューが表示され、選択できます。

image.png

また、コンテキストブラウザでもコンテキストストアの種類が表示されます。

image.png

なお、コンテキストのkeyは、コンテキストストアが異なる場合に同じものを設定することができます(別々のものとして扱われる)。ただ、間違いのもとになるので同じkeyを使わないようにすべきでしょう。

公式情報ほか

コンテキストに関する公式情報は
本家 https://nodered.org/docs/user-guide/context
日本語 https://nodered.jp/docs/user-guide/context
にあります。

また、Function Nodeで使えるAPIについては
https://nodered.org/docs/user-guide/writing-functions#storing-data
に情報がありますので、参考にしてください。

28
14
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
28
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?