node-redプログラミング：カスタムノードを作成するときのTips

# はじめに

node-redのカスタムノード、[node-red-contrib-linking-device](https://flows.nodered.org/node/node-red-contrib-linking-device) の開発時に気づいたことや調べたことを書きます。

なお、公式サイトにある [独自のNodeを作成するガイド](https://nodered.jp/docs/creating-nodes/)の内容に目を通していることを前提とした内容となります。

また、こちらの記事もおすすめです：[Node-REDのノードをつくる手順](https://qiita.com/zuhito/items/1c65fdbb3743d9f87edd)

# node.js側 のTips

## nodeのcloseイベントへの非同期応答

再デプロイするときなどに、ノードを閉じるための'close'イベントが来ますが、ここで時間のかかる終了処理が必要になる場合、以下のように終了時にdone()コールバックを呼ぶ形での実装になります。

```
this.on('close', function(done) {
    doSomethingWithACallback(function() {
        done();
    });
});
```

15秒以内に処理が終了しないと `18-08-20 14:28:35 [error]  Error stopping node: TimeoutError: timed out after 15000ms` というエラーをnode-redが出します。これのデバッグをする際は、他のノードが存在しない状態で行って原因が本当に自分のカスタムノードにあるのかどうか、まず切り分けをすることをお勧めします。

自分の場合、このエラーに悩まされてなんどもプログラムをいじってたのですが、実は`mqtt out`ノードがタイムアウトの原因で、無駄な時間を使ってしまいました。

## ステータス表示を充実させる

正常動作しているかエラーかなどを、色と文言で表示すると、ユーザーは使いやすいし、自分もデバッグもしやすいです。

```
node.state({fill:'red', shape:'dot', text:'error'})
```

なお灰色を指定する際、`gray`ではなく`grey`なので注意。

# MQTT (Beebotte) フレンドリーなメッセージフォーマット

データ出力系ノードを作成する場合は、メッセージフォーマットを以下のようにしておくと、メッセージを MQTT out ノードにほぼそのまま渡して、BeebotteなどのMQTTブローカーにデータを送信できるので使いやすくなります。

- msg.payload.data : データを格納

例: 温度センサーのLinkingデバイスの場合、msg.payload.data = 27.5 (温度)

- msg.topic : MQTTのchannel, resource情報を含んだ"`channel`_`resource`" の文字列フォーマットとする

Linkingデバイスの場合、"linking/`デバイス名`\_`センサー種別`" としています。
例: msg.topic = "linking/Tukeru\_th012345\_temperature" 

こうしておいて、Beebotte側でChannel: `linking`, Resource: `Tukeru\_th012345\_temperature`を作成すればデータのPublishが出来るようになります。

# htmlで記述するUI側（編集ダイアログ）のTips

## ドキュメントにない標準アイコン

ノードに表示するアイコンですが、node-redのドキュメントに記入されているもの以外にも、いくつか標準アイコンが存在します。

ソースコードのiconフォルダ参照:
https://github.com/node-red/node-red/tree/master/editor/icons

## html側のスクリプトではjQueryが標準

自分でjQueryを読み込まなくても最初から$が使えるようになってます。

## 編集ダイアログでインタラクティブな処理を実現する

ドキュメントに書いてある例では、編集ダイアログで設定した内容をnodejs側に一方通行で通知するだけですが、自分が作ったノードの場合はスキャンしたデバイス一覧をリスト表示するなど、html側とnodejs側で相互に情報をやり取りする必要がありました。

既存のカスタムノードを参考にしたところ、以下のような仕組みになってました：

- html側はjQueryのgetJSONなどのAPIでnodejs側に非同期リクエストを行う
- nodejs側はhtml側からのリクエストを、ExpressベースのWebアプリケーションとして処理

## アイコンにFontAwesomeが使える

サンプルのhtmlを見ると、fa-xxx と見慣れないクラスがありますが、[Font Awesome](https://fontawesome.com/icons?d=gallery&m=free)を使ってます。
[Spinner icon](https://www.w3schools.com/icons/fontawesome_icons_spinner.asp) は回るアニメーションをCSSでサポートしているので、リクエスト中の表示に便利です。

## ノードにport labelをつける

出力ポートが複数ある場合、ポートラベルをぜひつけるべきです。html側で記述します。

## 編集ダイアログの UI Widgets

[Editor UI Widgets](https://nodered.jp/docs/api/ui/) で、TypedInput, EditableListというウィジェットが紹介されています。selectノードなどで使われている複雑な設定を可能にしています。

EditableListはnode-red-contrib-linking-deviceでも、デバイスのリスト表示に使わせてもらいました。その際、[node-red-contrib-generic-ble](https://github.com/CANDY-LINE/node-red-contrib-generic-ble) を参考にしています。

## デバッグのために

エディタのノード定義のhtmlファイルは、開発時とは別のVM184800'などのランダムな名前でブラウザには読み込まれているため、デバッグ時にソースコードが探しにくくなります。ノード定義のoneditprepare()に、console.log('mynode: editprepare')とかのログを入れておけば、Consoleから当該ファイルにすぐにジャンプするので便利です。

# ローカライゼーション

最初からやっておくことをおすすめします。なんとなく面倒に思って後回しにしてましたが、ドキュメントを読んだらそんなに複雑ではありませんでした。

なお、ドキュメントに
>コアのnode-redカタログには、よく使用されるいくつかのステータスメッセージがあります。

とありますが、よく使いそうなッセージの定義場所は、ソースのここです

https://github.com/node-red/node-red/tree/master/red/api/editor/locales

```JSON:editor.json
    "common": {
        "label": {
            "name": "名前",
            "ok": "OK",
            "done": "完了",
            "cancel": "中止",
            "delete": "削除",
            "close": "閉じる",
            "load": "読み込み",
            "save": "保存",
            "import": "読み込み",
            "export": "書き出し",
            "back": "戻る",
            "next": "進む",
            "clone": "プロジェクトをクローン",
            "cont": "続ける"
        }
    }
```

https://github.com/node-red/node-red/tree/master/nodes/core/locales

```JSON:messages.json
    "common": {
        "label": {
            "payload": "ペイロード",
            "topic": "トピック",
            "name": "名前",
            "username": "ユーザ名",
            "password": "パスワード",
            "property": "プロパティ"
        },
        "status": {
            "connected": "接続済",
            "not-connected": "未接続",
            "disconnected": "切断",
            "connecting": "接続中",
            "error": "エラー",
            "ok": "OK"
        }
    }
```