Node-REDのHowTo(その5)

Node-REDのメモ 応用編 BME280+Websocket+Dashboard

Node-REDでBME280で測定したデータをWebsocketで飛ばして、Dashboardでグラフ表示するときの設定手順のメモ。
鶏頭で忘れっぽいのでメモ。

それぞれの基本は、Node-REDのHowTo(その1) ~ (その4)参照

BME280データをWebsocketで送信(RaspberryPi)

フローの例


[
    {
        "id": "411d9021.cb1948",
        "type": "tab",
        "label": "BME280+Websocket",
        "disabled": false,
        "info": ""
    },
    {
        "id": "72021e21.cce078",
        "type": "Bme280",
        "z": "411d9021.cb1948",
        "name": "",
        "bus": "1",
        "address": "0x76",
        "topic": "bme280",
        "extra": false,
        "x": 300,
        "y": 60,
        "wires": [
            [
                "571951b6.480168",
                "4ce01eb0.f1d438"
            ]
        ]
    },
    {
        "id": "571951b6.480168",
        "type": "websocket out",
        "z": "411d9021.cb1948",
        "name": "",
        "server": "",
        "client": "f24a6df3.ed0608",
        "x": 660,
        "y": 60,
        "wires": []
    },
    {
        "id": "4ce01eb0.f1d438",
        "type": "debug",
        "z": "411d9021.cb1948",
        "name": "",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "false",
        "x": 570,
        "y": 140,
        "wires": []
    },
    {
        "id": "480f869f.968e18",
        "type": "function",
        "z": "411d9021.cb1948",
        "name": "DummyData",
        "func": "msg.payload = {\n\t\t\"temperature_C\": Math.floor((Math.random() * (  40 - (-30)) * 100) / 100) + (-30),\n\t\t\"humidity\":      Math.floor((Math.random() * ( 100 -    0 ) * 100) / 100) +    0,\n\t\t\"pressure_hPa\":  Math.floor((Math.random() * (1100 -  800 ) * 100) / 100) +  800,\n\t\t\"model\":\"DUMMY\"\n}\n\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "x": 310,
        "y": 140,
        "wires": [
            [
                "571951b6.480168",
                "4ce01eb0.f1d438"
            ]
        ]
    },
    {
        "id": "c6ba67a3.1c69c",
        "type": "inject",
        "z": "411d9021.cb1948",
        "name": "",
        "topic": "",
        "payload": "true",
        "payloadType": "bool",
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "x": 130,
        "y": 60,
        "wires": [
            [
                "72021e21.cce078"
            ]
        ]
    },
    {
        "id": "ab623444.9c5148",
        "type": "inject",
        "z": "411d9021.cb1948",
        "name": "",
        "topic": "",
        "payload": "true",
        "payloadType": "bool",
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "x": 130,
        "y": 140,
        "wires": [
            [
                "480f869f.968e18"
            ]
        ]
    },
    {
        "id": "f24a6df3.ed0608",
        "type": "websocket-client",
        "z": "",
        "path": "ws://PiDev25.local:1880/ws/bme280",
        "tls": "",
        "wholemsg": "false"
    }
]
 

WebsocketからBME280データを受信(サーバ)

これだけでWebsocketからデータは受信できる。
このとき、Websocketの受信ノードのmag.payloadはJSON文字列なので、objectに変換してやらないと後段で使用できない。
そのため、Websocketのノードの出力をjsonノードで変換してやる必要がある。

この状態でRaspberryPi側でBME280のデータ送信をトリガすれば、受信したデータで処理ノードが実行される

Websocket経由のBME280データをDashboardでグラフ表示(その1)(サーバ)

上記、WebsocketからBME280データを受信(サーバ)の処理ノードとして

[!WARNING] これで理屈的には大丈夫なハズなんだけど、{{msg.payload.pressure_hPa}}を指定すると、値は正常に表示されるけど、グラフが正常に表示されないことがある。。。
どうやら、この形で指定すると、値が1000を超えるとグラフ表示がおかしくなるようだ。バグか?
下の(その2)の方法で回避可能。

フローの例


[
    {
        "id": "61238c01.1e1fdc",
        "type": "tab",
        "label": "Websocket_recv_BME280",
        "disabled": false,
        "info": ""
    },
    {
        "id": "2163c454.8e4654",
        "type": "json",
        "z": "61238c01.1e1fdc",
        "name": "",
        "property": "payload",
        "action": "obj",
        "pretty": false,
        "x": 310,
        "y": 120,
        "wires": [
            [
                "8a687382.d3a38",
                "54ecaf20.30611",
                "1e9e6439.3de04c",
                "54f5a1ee.e4fb5"
            ]
        ]
    },
    {
        "id": "de53f105.be0bb",
        "type": "websocket in",
        "z": "61238c01.1e1fdc",
        "name": "",
        "server": "107d62cf.5a6d8d",
        "client": "",
        "x": 134,
        "y": 121,
        "wires": [
            [
                "2163c454.8e4654",
                "8a687382.d3a38"
            ]
        ]
    },
    {
        "id": "8a687382.d3a38",
        "type": "debug",
        "z": "61238c01.1e1fdc",
        "name": "",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "false",
        "x": 530,
        "y": 40,
        "wires": []
    },
    {
        "id": "54ecaf20.30611",
        "type": "ui_gauge",
        "z": "61238c01.1e1fdc",
        "name": "",
        "group": "9912b800.6921d8",
        "order": 0,
        "width": 0,
        "height": 0,
        "gtype": "gage",
        "title": "温度",
        "label": "℃",
        "format": "{{msg.payload.temperature_C | number:1}}℃",
        "min": "-10",
        "max": "40",
        "colors": [
            "#0000ff",
            "#e6e600",
            "#ca3838"
        ],
        "seg1": "0",
        "seg2": "30",
        "x": 510,
        "y": 120,
        "wires": []
    },
    {
        "id": "1e9e6439.3de04c",
        "type": "ui_gauge",
        "z": "61238c01.1e1fdc",
        "name": "",
        "group": "9912b800.6921d8",
        "order": 0,
        "width": 0,
        "height": 0,
        "gtype": "gage",
        "title": "湿度",
        "label": "%",
        "format": "{{msg.payload.humidity| number:1}}%",
        "min": "0",
        "max": "100",
        "colors": [
            "#ff8080",
            "#00ff00",
            "#00ffff"
        ],
        "seg1": "50",
        "seg2": "60",
        "x": 510,
        "y": 160,
        "wires": []
    },
    {
        "id": "54f5a1ee.e4fb5",
        "type": "ui_gauge",
        "z": "61238c01.1e1fdc",
        "name": "",
        "group": "9912b800.6921d8",
        "order": 0,
        "width": 0,
        "height": 0,
        "gtype": "gage",
        "title": "気圧",
        "label": "hPa",
        "format": "{{msg.payload.pressure_hPa| number:0}}hPa",
        "min": "900",
        "max": "1050",
        "colors": [
            "#00ff00",
            "#00ff00",
            "#00ff00"
        ],
        "seg1": "",
        "seg2": "",
        "x": 510,
        "y": 200,
        "wires": []
    },
    {
        "id": "107d62cf.5a6d8d",
        "type": "websocket-listener",
        "z": "",
        "path": "/ws/bme280",
        "wholemsg": "false"
    },
    {
        "id": "9912b800.6921d8",
        "type": "ui_group",
        "z": "",
        "name": "グループ1",
        "tab": "9c00695d.f96b1",
        "disp": true,
        "width": "6",
        "collapse": false
    },
    {
        "id": "9c00695d.f96b1",
        "type": "ui_tab",
        "z": "",
        "name": "タブ2",
        "icon": "dashboard",
        "disabled": false,
        "hidden": false
    }
]
 

Websocket経由のBME280データをDashboardでグラフ表示(その2)(サーバ)

(その1)での不具合を回避するため、msg.payloadのオブジェクト内のそれぞれの変数をバラすfunctionノードを追加する

// make deep copy
var msg_temp  = JSON.parse(JSON.stringify(msg));
var msg_hum   = JSON.parse(JSON.stringify(msg));
var msg_press = JSON.parse(JSON.stringify(msg));

msg_temp.topic    = "temperature_C";
msg_temp.payload  = msg.payload.temperature_C;
msg_hum.topic     = "humidity";
msg_hum.payload   = msg.payload.humidity;
msg_press.topic   = "pressure_hPa";
msg_press.payload = msg.payload.pressure_hPa;

return [msg_temp, msg_hum, msg_press];

[!NOTE] var msg_temp = msg;などとしてはいけない。
この場合、msg_tempmsgの浅いコピーとなってしまうため、その下でmsg_temp.payloadを変更すると、msg.payloadも変更されてしまうことになる。
これを防ぐため、深いコピーを作成している。これにはmsgをJSON文字列化して、再度パースすることで対応している。

フローエディタ上で、どの端子がどの信号か分からなくなるのを防ぐため、端子に名前を付けることができる。
(付けなくても動作上は問題ない)

フローの例


[
    {
        "id": "9c09bf08.8c36a8",
        "type": "tab",
        "label": "Websocket_recv_BME280_2",
        "disabled": false,
        "info": ""
    },
    {
        "id": "1e53a14c.4133a7",
        "type": "json",
        "z": "9c09bf08.8c36a8",
        "name": "",
        "property": "payload",
        "action": "obj",
        "pretty": false,
        "x": 310,
        "y": 120,
        "wires": [
            [
                "855491ee.723a88",
                "3cee4c1c.c6861c"
            ]
        ]
    },
    {
        "id": "ada05d07.d2d8b",
        "type": "websocket in",
        "z": "9c09bf08.8c36a8",
        "name": "",
        "server": "107d62cf.5a6d8d",
        "client": "",
        "x": 134,
        "y": 121,
        "wires": [
            [
                "1e53a14c.4133a7",
                "855491ee.723a88"
            ]
        ]
    },
    {
        "id": "855491ee.723a88",
        "type": "debug",
        "z": "9c09bf08.8c36a8",
        "name": "",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "false",
        "x": 690,
        "y": 40,
        "wires": []
    },
    {
        "id": "51b57ef4.80809",
        "type": "ui_gauge",
        "z": "9c09bf08.8c36a8",
        "name": "",
        "group": "34e8ddba.6c67fa",
        "order": 0,
        "width": 0,
        "height": 0,
        "gtype": "gage",
        "title": "温度2",
        "label": "℃",
        "format": "{{value | number:1}}℃",
        "min": "-10",
        "max": "40",
        "colors": [
            "#0000ff",
            "#e6e600",
            "#ca3838"
        ],
        "seg1": "0",
        "seg2": "30",
        "x": 670,
        "y": 80,
        "wires": []
    },
    {
        "id": "be7cac56.7bcc4",
        "type": "ui_gauge",
        "z": "9c09bf08.8c36a8",
        "name": "",
        "group": "34e8ddba.6c67fa",
        "order": 0,
        "width": 0,
        "height": 0,
        "gtype": "gage",
        "title": "湿度2",
        "label": "%",
        "format": "{{value | number:1}}%",
        "min": "0",
        "max": "100",
        "colors": [
            "#ff8080",
            "#00ff00",
            "#00ffff"
        ],
        "seg1": "50",
        "seg2": "60",
        "x": 670,
        "y": 120,
        "wires": []
    },
    {
        "id": "f36e338d.e77e6",
        "type": "ui_gauge",
        "z": "9c09bf08.8c36a8",
        "name": "",
        "group": "34e8ddba.6c67fa",
        "order": 0,
        "width": 0,
        "height": 0,
        "gtype": "gage",
        "title": "気圧2",
        "label": "hPa",
        "format": "{{value | number:0}}hPa",
        "min": "900",
        "max": "1050",
        "colors": [
            "#00ff00",
            "#00ff00",
            "#00ff00"
        ],
        "seg1": "",
        "seg2": "",
        "x": 670,
        "y": 160,
        "wires": []
    },
    {
        "id": "3cee4c1c.c6861c",
        "type": "function",
        "z": "9c09bf08.8c36a8",
        "name": "BME280",
        "func": "// make deep copy\nvar msg_temp  = JSON.parse(JSON.stringify(msg));\nvar msg_hum   = JSON.parse(JSON.stringify(msg));\nvar msg_press = JSON.parse(JSON.stringify(msg));\n\nmsg_temp.topic    = \"temperature_C\";\nmsg_temp.payload  = msg.payload.temperature_C;\nmsg_hum.topic     = \"humidity\";\nmsg_hum.payload   = msg.payload.humidity;\nmsg_press.topic   = \"pressure_hPa\";\nmsg_press.payload = msg.payload.pressure_hPa;\n\nreturn [msg_temp, msg_hum, msg_press];",
        "outputs": 3,
        "noerr": 0,
        "x": 460,
        "y": 120,
        "wires": [
            [
                "51b57ef4.80809"
            ],
            [
                "be7cac56.7bcc4"
            ],
            [
                "f36e338d.e77e6"
            ]
        ],
        "inputLabels": [
            "BME280データ"
        ],
        "outputLabels": [
            "温度",
            "湿度",
            "気圧"
        ]
    },
    {
        "id": "107d62cf.5a6d8d",
        "type": "websocket-listener",
        "z": "",
        "path": "/ws/bme280",
        "wholemsg": "false"
    },
    {
        "id": "34e8ddba.6c67fa",
        "type": "ui_group",
        "z": "",
        "name": "グループ2",
        "tab": "9c00695d.f96b1",
        "disp": true,
        "width": "6",
        "collapse": false
    },
    {
        "id": "9c00695d.f96b1",
        "type": "ui_tab",
        "z": "",
        "name": "タブ2",
        "icon": "dashboard",
        "disabled": false,
        "hidden": false
    }
]