Node-REDでできること part2 (応用編)

# はじめに
## やりたいこと
* Node-REDでどんなことがどこまでできるのかを調べる
* part2(応用編)では、Node-REDの設定ファイルや小技などをまとめました

## 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を使いますが、ホストに立てても大丈夫かと思います。

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

# 調査結果
## 変数共有
**調べたこと**

* 各ノード、フロー間の情報のやりとりをどのように行うのか確認する
* contextオブジェクトの中にデータを保持しておくことができ、別の個所でその値を参照できる仕組みがある
    * ローカルノードコンテキスト変数：単一のノードで共有できる変数
    * フローコンテキスト変数：フローで共有できる変数
    * グローバル変数：全体で共有できる変数
* コンソールのコンテキストタブでも値を確認できる

**ソースコードイメージ**

```
// ローカルノードコンテキスト変数
// 同じノードが再度利用されるときに、前の値を引き継げる
var con_count = context.get('con_count')||0;
con_count += 1;
context.set('con_count',con_count);

// フローコンテキスト変数
// 同じフロー内にいるノードで情報を引き継げる。再利用時も引き継がれる
var flow_count = flow.get('flow_count')||0;
flow_count += 1;
flow.set('flow_count',flow_count);

// グローバル変数
// すべての場所から参照可能
var global_count = global.get('global_count')||0;
global_count += 1;
global.set('global_count',global_count);
```

## 時間測定
**調べたこと**

* 性能を測定する際には、処理時間を測定したいケースが度々発生するので、測定方法を確認する
* 前述の変数とJSの基本機能であるDate関数を使えば簡単に実現できる
    * functionノードAで今の時間を記録し、処理終了後にfunctionノードBでも今の時間を記録する。両者の差分を取って経過時間を測定する

**ソースコードイメージ**

``` 
// functionノードA
global.set('global_count',new Date().getTime());

```
```
// functionノードB
var start = global.get('global_count');
var diff = new Date().getTime() - start;
msg.payload = diff + "ミリ秒"
```

**画面イメージ**
![X1.jpg](https://qiita-image-store.s3.amazonaws.com/0/259228/46cc17d2-951e-ac98-1b8f-1825c9155272.jpeg)

## 定期実行
**調べたこと**

* 単にInjectノードの設定で指定した時間間隔を設定するだけ
    * 指定した時間間隔、日時などある程度細かく設定できる
   
**画面イメージ**
![X2-1.jpg](https://qiita-image-store.s3.amazonaws.com/0/259228/c2a0878f-d922-8aef-0d75-e1da9b177e47.jpeg)
![X2-2.jpg](https://qiita-image-store.s3.amazonaws.com/0/259228/3a1ed9cc-578f-c9ec-ba76-5fac549097da.jpeg)


## 複数入力の時間内判定
**調べたこと**

* 複数の入力が一定時間内に送られたらtrueを返すロジックが必要になったので、実現性を確認する
* フローコンテキストに時間とイベントを紐づけて記録し、チェック機能で両者を取り出して時間を測るだけ

```
// Input1側(Input2側もInput1をInput2にするだけ)
var time_table = flow.get("time_table") || {}
var user_data = time_table[msg.payload] || {}
user_data["Input1"] = new Date().getTime()
time_table[msg.payload] = user_data
flow.set("time_table",time_table)
return msg;
```
```
// 取り出し&比較ロジック
var time_table = flow.get("time_table") || {}
var user_data = time_table[msg.payload] || {}
var in1_time = user_data["Input1"]
var in2_time = user_data["Input2"]
try{
    var diff = (in1_time - in2_time) / 1000
    msg.diff = time_table
    if(Math.abs(diff) < 5){
        msg.payload = "5秒未満"
        msg.flag = true
    }else{
        msg.payload = "5秒以上"
        msg.flag = false
    }
    
}catch(e){
    msg.flag = false
}
return msg;
```

**画面イメージ**
![X3.jpg](https://qiita-image-store.s3.amazonaws.com/0/259228/7b789494-d6ca-f91f-2e80-5f3ecd5eb870.jpeg)


## functionノードのログ出力
**調べたこと**

* functionノード内からログに出力する機能を確認する
  * 警告以上のログは、コンソールに出力される
  * ログファイルには指定されたログレベル以上のものが記録される
    * ログファイルに記述されるlevelについては、以下の通り定義されている模様
        * off:1, fatal:10, error:20, warn:30, info:40, debug:50, trace:60, audit:98, metric:99
        
```
// functionノードでの記述
node.trace("トレースレベルのログ")
node.debug("デバッグレベルのログ")
node.log("インフォレベルのログ");
node.warn("警告レベルのログ");
node.error("エラーレベルのログ");
```

## functionノードの状態設定
**調べたこと**

* node.statusを使えば、状態を変えられるらしいので、確認する
    * fill,shape,textの3つを指定できる
* statusノードにも伝播される

```
// 緑の〇でOKをステータスとして表示させる
node.status({fill:"green",shape:"dot",text:"OK"});
```
**画面イメージ**
![X4.jpg](https://qiita-image-store.s3.amazonaws.com/0/259228/a72cdf24-667b-088a-8e7d-740dfb59a46a.jpeg)

## JSのrequire
**調べたこと**

* functionノードにモジュールを直接requireすることはできないので、実現方法を確認する
    * settings.jsに記述し、functionノードから参照する
    * settings.jsへの修正が必要なため、Node-REDの再起動が必要？
    
```
// settings.js内の設定
// axiosをfunctionノードで使いたい
functionGlobalContext: {
    axios:require('axios')
},
```

```
// functionノードの中
const axios = global.get('axios');
```

## SSL化
**調べたこと**

* Node-REDへのアクセスおよび、http/wsをhttps/wssにする方法を確認する
* 鍵を作成して特定の場所に置けばよい
    * http/ws と https/wssの共存はできない模様

```
// settings.js
// XXXには、認証に必要な鍵と認証書のパスを記述する
var fs = require("fs");

https: {
     key: fs.readFileSync('XXX'),
    cert: fs.readFileSync('XXX')
},

```
## ﾕｰｻﾞ認証
**調べたこと**

* Node-REDのフローを特定の人だけに変更できるようにするため、認証をする方法がないか確認する
    * いくつかの手段があるが、ひとまずbasic認証で可とする

```
// settings.js
// XXXには、ユーザ名とパスワードのMD5のハッシュ値をいれる
httpAdminAuth: {
  user:"XXX",
  pass:"XXX"
},
```

## ログファイル出力
**調べたこと**

* Node-REDはデフォルトではコンソールにしか出力できないらしいので、ログファイル出力の方法を確認する
* logstashに送る方法もあるらしいが、うまくいかなかった

```
// settings.js
// XXXにはログファイルのパスを入れる
var fs = require("fs");
fs.writeFile('XXX',''); // 初期化

logging: {
  file:{
    level: 'debug',
    metrics: false,
    audit: false,
    handler : function(settings){
      return function (msg){
       fs.appendFile('XXX',(new Date(msg.timestamp)).toISOString() + " : " + JSON.stringify(msg) + '\n');
      }
    }
  }
},
```

## デスクトップ版
**調べたこと**

* Serverを立てずに利用できると、利用できる人が増えるのではないかという発想で探したら、electronを使ったデスクトップアプリ版が存在していた
    * https://github.com/zuhito/electron-node-red/releases
* デスクトップアプリ版の差異を確認する
    * 2つのバージョンがあり、組み込まれているノードの数が異なる
    * Node.jsを入れない影響か、作業環境の影響か、ノードの追加インストールとフローの書き出しができなかった