Node-REDでできること part6 (TIPS編)

# はじめに
## やりたいこと
* Node-REDでどんなことがどこまでできるのかを調べる
* part6(TIPS編)では、実装を作っている中でみつけた小技を紹介します。

## Node-REDのまとめ記事
* [Node-RED系の記事のまとめ](https://qiita.com/kazutxt/items/8c72448eefcba56dabef)

## 動作環境
* OS : CentOS Linux release 7.5.1804 (Core)
* Node-RED : v0.19.5
* docker : 18.09.1
    * Node-REDイメージ : nodere/node-red-docker (IMAGE ID/d4f07b39b24d)
    * Dockerを使いますが、ホストに立てても大丈夫かと思います。

## 免責事項
* 検証をして確実と思われる情報を載せておりますが、誤っている可能性もゼロではないので、参考程度にご利用ください
* 本来はコードと実行結果のコンソールログも載せるべきなのですが、数と量が多いので、一旦は画面イメージまでとさせてください。

# 調査結果
## メタフィールドの活用
* 一般的にフローの中で情報を共有したい場合は、flowコンテキストを使う

` context.get("hoge") `

ただし、これでは問題になるパターンがある

* フローコンテキストは多重処理や再実行時に共有されて、占有して使いたいときは、排他制御が必要
    * つまり、先行タスクが値を設定し取得する前に、後続タスクが値を書き換えてしまうと矛盾が起きる
* サブフローからは、参照ができない。
    * v0.2からはできるらしいが、現状最新版ではリリースされていない

そこで、無難にmsgオブジェクトのメタフィールドを使う

``` js:functionノード
msg._id = "user1";
msg._type = "admin";
return msg;
```

* そもそもmsgオブジェクトをノード間で引き渡していくのがNode-REDの思想なので、合致する
    * `return null` とするとフローが終了する
* msg.payloadは頻繁に書き換えられるが、メタフィールドが書き換えられることはない
* JSONノードなどで変換した際に、メタフィールドは含まれないので、使いようによっては便利

## セッションの異なるWebSocketへの出力
* WebSocketを異なる接続先に出力させる場合は、 `msg._session` の削除が必要
    * 入力用WebSocketから信号がきたら、出力用の別のWebSocketに信号を出すなど

``` js:functionノード
delete msg._session
return msg
```

* ちなみに、エラーが出ずただ信号が届かない状況になるので、すこし切り分けに焦るｗ


## コンテキストの削除

* contextにはget,set,keysしか定義されておらず、情報を消すことができない
    * context.set("hoge",null)としてもダメ
* 偶然発見したが以下で消せる
    * setterに値を入れず、キーだけを指定すると、エラーにならず登録を消せる。
    * API仕様にも記述がないし、裏機能かバグっぽいきがする。。。

```js:functionノード
context.set("hoge")
```

##　WatchDog

* タイムアウトを見張りたい
* 待ち合わせではなく、別経路が途中で止まったり極端に遅延した場合にWarningを出したいという意図
* 単に次の信号が来たら時間を延長させるという意味ではない
    * 信号A:0.3s
    * 信号B:5.2s // こいつを見つけたい
    * 信号C:0.4s
        * これらの信号は、多重であったりしばらく期間が空くこともあるとする

![pic1.jpg](https://qiita-image-store.s3.amazonaws.com/0/259228/6dd6b573-ec55-aa62-bb66-1cd821ecace4.jpeg)

```js:Functionノード
var ID = msg._msgid.replace(".","")
var timeout = context.get(ID);
var flag = 0;
//正規ルート(先のはず)
if(msg.payload === "PASS"){
    // 正規ルートが先だった
     if(timeout !== "TIMEOUT"){
         context.set(ID,"PASS")
         flag = 0;
    //タイムアウトが先だった(追いついた)
     }else{
         //変数を消しておく
         //第2引数を省略するとオブジェクトが消える
        context.set(ID)
        flag = 2;         
     }
}
//タイムアウトルート(後のはず)
if(msg.payload === "TIMEOUT"){
    // タイムアウトが先に来た
    if(timeout !== "PASS"){
        context.set(ID,"TIMEOUT")
        flag = 1
    // timeoutが後だった    
    }else{
        //変数を消しておく
        flag = 0;
         //第2引数を省略するとオブジェクトが消える
        context.set(ID)
    }
}
if (flag === 1){
  msg.payload = "timeoutが発生しました"
  return msg;
}else if(flag === 2){
    msg.payload = "timeoutに追いつきました"
    return msg;
}else{
    return null;
}

```

* msgオブジェクト(msg._msgid)が同じである仮定の下、ノードオブジェクトに文字列を入れることで確認
    * msgオブジェクトには「.」が含まれているので、除いています
    * 実装例では、正規ルート側はPASS、タイムアウト側はTIMEOUTを文字列としていれて、コントロールしています


もっと簡単なやりかたがある気がする。。。
## 任意箇所からの出力
* ログ出力などを集約しいて表示させたい
    * 仮想リンクでもよいが、サブフローから使えないのが問題
    * 仮想リンクには名前を付けられない

![pic2.jpg](https://qiita-image-store.s3.amazonaws.com/0/259228/c5a29f5b-e2d5-46c2-790a-a6bcc059eec7.jpeg)
* ログ出力する側のfunctionノードに名前を付け忘れました。すみません。

```js:ログ出力する
global.set("LogSend",node.send)
```

```js:ログ出力させる側
global.get("LogSend")(msg)
```

* TESTから延びるノードはdebugノードにつながっていないが、ログ出力ノードから信号を出して、メッセージを出せる

* ノードがオブジェクトで管理され、nodeは自分自身からは見れるので、いったん登録して、外部から使ってしまおうという作戦
* node.send()が次のノードに情報を伝える関数とのこと