Sample flow for exercising node-red-contrib-pid

A sample flow for demonstrating the PID loop controller node-red-contrib-pid using a process simulation consisting of a 3 second transport delay followed by 10 second and 30 second lags and an offset.

A guide to tuning a process controlled by a PID controller, which uses this flow as an example to demonstrate the prinicples can be found at http://blog.clanlaw.org.uk/2018/01/09/PID-tuning-with-node-red-contrib-pid.html

[
    {
        "id": "b4973237.46ab6",
        "type": "subflow",
        "name": "Process Simulation",
        "info": "",
        "in": [
            {
                "x": 37,
                "y": 103,
                "wires": [
                    {
                        "id": "ec719d4d.0d54f8"
                    }
                ]
            }
        ],
        "out": [
            {
                "x": 728.5,
                "y": 294,
                "wires": [
                    {
                        "id": "ae1a6e5.d4c0d9",
                        "port": 0
                    }
                ]
            }
        ]
    },
    {
        "id": "7fe4b5c3.32e58c",
        "type": "function",
        "z": "b4973237.46ab6",
        "name": "30 sec RC + 20",
        "func": "// Applies a simple RC low pass filter to incoming payload values\nvar tc = 30*1000;       // time constant in milliseconds\n\nvar lastValue = context.get('lastValue');\nif (typeof lastValue == \"undefined\") lastValue = msg.payload;\nvar lastTime = context.get('lastTime') || null;\nvar now = new Date();\nvar currentValue = msg.payload;\nif (lastTime === null) {\n    // first time through\n    newValue = currentValue;\n} else {\n    var dt = now - lastTime;\n    var newValue;\n    \n    if (dt > 0) {\n        var dtotc = dt / tc;\n        newValue = lastValue * (1 - dtotc) + currentValue * dtotc;\n    } else {\n        // no time has elapsed leave output the same as last time\n        newValue = lastValue;\n    }\n}\ncontext.set('lastValue', newValue);\ncontext.set('lastTime', now);\n\nmsg.payload = newValue + 20;\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "x": 626.5,
        "y": 207,
        "wires": [
            [
                "ae1a6e5.d4c0d9"
            ]
        ]
    },
    {
        "id": "1bacd004.9753c",
        "type": "inject",
        "z": "b4973237.46ab6",
        "name": "Inject -0.2 at start",
        "topic": "",
        "payload": "-0.2",
        "payloadType": "num",
        "repeat": "",
        "crontab": "",
        "once": true,
        "x": 134.5,
        "y": 30,
        "wires": [
            [
                "ec719d4d.0d54f8"
            ]
        ]
    },
    {
        "id": "999a52c2.f465f",
        "type": "function",
        "z": "b4973237.46ab6",
        "name": "10 sec RC",
        "func": "// Applies a simple RC low pass filter to incoming payload values\nvar tc = 10*1000;       // time constant in milliseconds\n\nvar lastValue = context.get('lastValue');\nif (typeof lastValue == \"undefined\") lastValue = msg.payload;\nvar lastTime = context.get('lastTime') || null;\nvar now = new Date();\nvar currentValue = msg.payload;\nif (lastTime === null) {\n    // first time through\n    newValue = currentValue;\n} else {\n    var dt = now - lastTime;\n    var newValue;\n    \n    if (dt > 0) {\n        var dtotc = dt / tc;\n        newValue = lastValue * (1 - dtotc) + currentValue * dtotc;\n    } else {\n        // no time has elapsed leave output the same as last time\n        newValue = lastValue;\n    }\n}\ncontext.set('lastValue', newValue);\ncontext.set('lastTime', now);\n\nmsg.payload = newValue;\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "x": 451,
        "y": 207,
        "wires": [
            [
                "7fe4b5c3.32e58c"
            ]
        ]
    },
    {
        "id": "ec719d4d.0d54f8",
        "type": "delay",
        "z": "b4973237.46ab6",
        "name": "",
        "pauseType": "delay",
        "timeout": "1",
        "timeoutUnits": "seconds",
        "rate": "1",
        "rateUnits": "second",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": false,
        "x": 268,
        "y": 104,
        "wires": [
            [
                "ede39236.1961f8"
            ]
        ]
    },
    {
        "id": "a823c9cf.2a6178",
        "type": "function",
        "z": "b4973237.46ab6",
        "name": "2 msg transport delay",
        "func": "// stores messages in a fifo until the specified number have been received, \n// then releases them as new messages are received.\n// during the filling phase the earliest message is passed on each time \n// a message is received, but it is also left in the fifo\nvar fifoMaxLength = 2;\nvar fifo = context.get('fifo') || [];\n// push the new message onto the top of the array, messages are shifted down and\n// drop off the front\nvar length = fifo.push(msg);  // returns new length\nif (length > fifoMaxLength) {\n    newMsg = fifo.shift();\n} else {\n    // not full yet, make a copy of the msg and pass it on\n    var newMsg = JSON.parse(JSON.stringify(fifo[0]));\n}\ncontext.set('fifo', fifo);\nreturn newMsg;",
        "outputs": 1,
        "noerr": 0,
        "x": 258,
        "y": 208,
        "wires": [
            [
                "999a52c2.f465f"
            ]
        ]
    },
    {
        "id": "ae1a6e5.d4c0d9",
        "type": "function",
        "z": "b4973237.46ab6",
        "name": "Clear all except payload",
        "func": "msg2 = {payload: msg.payload};\nreturn msg2;",
        "outputs": 1,
        "noerr": 0,
        "x": 545,
        "y": 293,
        "wires": [
            []
        ]
    },
    {
        "id": "ede39236.1961f8",
        "type": "range",
        "z": "b4973237.46ab6",
        "minin": "0",
        "maxin": "1",
        "minout": "0",
        "maxout": "100",
        "action": "scale",
        "round": false,
        "name": "",
        "x": 87,
        "y": 208,
        "wires": [
            [
                "a823c9cf.2a6178"
            ]
        ]
    },
    {
        "id": "e0cb2a8c.4402a8",
        "type": "PID",
        "z": "f0414dd0.dd514",
        "name": "",
        "setpoint": "50",
        "pb": "22",
        "ti": "24",
        "td": "6",
        "integral_default": "0",
        "smooth_factor": "0",
        "max_interval": 600,
        "enable": "1",
        "disabled_op": "0",
        "x": 362,
        "y": 175,
        "wires": [
            [
                "5ec4239b.34e174",
                "e94f888a.130a88"
            ]
        ]
    },
    {
        "id": "f70949c8.aab988",
        "type": "change",
        "z": "f0414dd0.dd514",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "topic",
                "pt": "msg",
                "to": "op",
                "tot": "str"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 601.5,
        "y": 250,
        "wires": [
            [
                "9f58a52d.076dd"
            ]
        ]
    },
    {
        "id": "2792f805.066c78",
        "type": "inject",
        "z": "f0414dd0.dd514",
        "name": "Setpoint 30",
        "topic": "setpoint",
        "payload": "30",
        "payloadType": "num",
        "repeat": "",
        "crontab": "",
        "once": false,
        "x": 104,
        "y": 198,
        "wires": [
            [
                "e0cb2a8c.4402a8"
            ]
        ]
    },
    {
        "id": "1bf3ce16.c3ae02",
        "type": "inject",
        "z": "f0414dd0.dd514",
        "name": "Setpoint 80",
        "topic": "setpoint",
        "payload": "80",
        "payloadType": "num",
        "repeat": "",
        "crontab": "",
        "once": false,
        "x": 102.5,
        "y": 247,
        "wires": [
            [
                "e0cb2a8c.4402a8"
            ]
        ]
    },
    {
        "id": "f69bbf05.bdd49",
        "type": "inject",
        "z": "f0414dd0.dd514",
        "name": "enable",
        "topic": "enable",
        "payload": "true",
        "payloadType": "bool",
        "repeat": "",
        "crontab": "",
        "once": false,
        "x": 95,
        "y": 83,
        "wires": [
            [
                "e0cb2a8c.4402a8"
            ]
        ]
    },
    {
        "id": "143523b2.2f165c",
        "type": "inject",
        "z": "f0414dd0.dd514",
        "name": "disable",
        "topic": "enable",
        "payload": "false",
        "payloadType": "bool",
        "repeat": "",
        "crontab": "",
        "once": false,
        "x": 95.5,
        "y": 133,
        "wires": [
            [
                "e0cb2a8c.4402a8"
            ]
        ]
    },
    {
        "id": "5ec4239b.34e174",
        "type": "subflow:b4973237.46ab6",
        "z": "f0414dd0.dd514",
        "x": 365,
        "y": 102,
        "wires": [
            [
                "de738fc5.75e4c8",
                "e0cb2a8c.4402a8"
            ]
        ]
    },
    {
        "id": "de738fc5.75e4c8",
        "type": "change",
        "z": "f0414dd0.dd514",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "topic",
                "pt": "msg",
                "to": "pv",
                "tot": "str"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 586,
        "y": 101,
        "wires": [
            [
                "9f58a52d.076dd"
            ]
        ]
    },
    {
        "id": "e94f888a.130a88",
        "type": "range",
        "z": "f0414dd0.dd514",
        "minin": "0",
        "maxin": "1",
        "minout": "0",
        "maxout": "25",
        "action": "scale",
        "round": false,
        "name": "Scale power",
        "x": 427,
        "y": 250,
        "wires": [
            [
                "f70949c8.aab988"
            ]
        ]
    },
    {
        "id": "9f58a52d.076dd",
        "type": "ui_chart",
        "z": "f0414dd0.dd514",
        "name": "",
        "group": "c45a83a3.d00908",
        "order": 0,
        "width": "6",
        "height": "6",
        "label": "chart",
        "chartType": "line",
        "legend": "false",
        "xformat": "HH:mm:ss",
        "interpolate": "linear",
        "nodata": "",
        "dot": false,
        "ymin": "0",
        "ymax": "100",
        "removeOlder": "3",
        "removeOlderPoints": "",
        "removeOlderUnit": "60",
        "cutout": 0,
        "useOneColor": false,
        "colors": [
            "#1f77b4",
            "#cf0005",
            "#ff7f0e",
            "#2ca02c",
            "#98df8a",
            "#d62728",
            "#ff9896",
            "#9467bd",
            "#c5b0d5"
        ],
        "useOldStyle": false,
        "x": 791,
        "y": 175,
        "wires": [
            [],
            []
        ]
    },
    {
        "id": "bef0a1f8.2e2cf",
        "type": "inject",
        "z": "f0414dd0.dd514",
        "name": "Clear chart on deploy",
        "topic": "",
        "payload": "{\"data\":[]}",
        "payloadType": "json",
        "repeat": "",
        "crontab": "",
        "once": true,
        "x": 352,
        "y": 318,
        "wires": [
            [
                "9079ffaf.4a096"
            ]
        ]
    },
    {
        "id": "9079ffaf.4a096",
        "type": "change",
        "z": "f0414dd0.dd514",
        "name": "",
        "rules": [
            {
                "t": "move",
                "p": "payload.data",
                "pt": "msg",
                "to": "payload",
                "tot": "msg"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 588,
        "y": 318,
        "wires": [
            [
                "9f58a52d.076dd"
            ]
        ]
    },
    {
        "id": "c45a83a3.d00908",
        "type": "ui_group",
        "z": "",
        "name": "PID",
        "tab": "80cd4062.93a5",
        "disp": true,
        "width": "6"
    },
    {
        "id": "80cd4062.93a5",
        "type": "ui_tab",
        "z": "",
        "name": "Home",
        "icon": "dashboard"
    }
]
colinl

Flow Info

created 1 year, 8 months ago
updated 5 months, 2 weeks ago

Node Types

Core
  • change (x3)
  • delay (x1)
  • function (x4)
  • inject (x6)
  • range (x2)
Other
  • PID (x1)
  • subflow (x1)
  • subflow:b4973237.46ab6 (x1)
  • ui_chart (x1)
  • ui_group (x1)
  • ui_tab (x1)

Tags

  • node-red
  • pid
  • loop
  • control
Copy this flow JSON to your clipboard and then import into Node-RED using the Import From > Clipboard (Ctrl-I) menu option