Skip to content

Instantly share code, notes, and snippets.

@colinl

colinl/README.md Secret

Last active September 25, 2021 08:54
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save colinl/42f125b56a00dd5d1433c2f8023263e9 to your computer and use it in GitHub Desktop.
Save colinl/42f125b56a00dd5d1433c2f8023263e9 to your computer and use it in GitHub Desktop.
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 https://blog.clanlaw.org.uk/pid-loop-tuning.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"
}
]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment