PID Mixing Valve Temperature control

Hello
I need help controling a mixsing valve so that the output temperature is around 60 C

Welcome to the forum.
We need some more information to be able to help:

  1. Which hardware are you running node-red on?
  2. Which types valves are you trying to control?
  3. How are you interfacing your valves with node-red?

This is the hardware part.

Everything is connected to node red and controlled over MQTT.
First I send the ON message to turn on the power to servo motor, then to move the valve to 0 position (full open) I send number 21 to nodemcu, now only cold water comes in from the buffer tank in to the heating unit.
To move the valve to position 10(full closed) I send 64
The DS18B20 returns the temperature of the water going in. (Usually takes 1 minute so see the difference because the sensor is on the outside of the pipe)

I was planning to use node red as pid controller to get 60C water going in the heating unit. But don't know how to implement the code

1 Like

I have node red, Mosquitto and Grafana running on RP3, for nodeMCU I use Tasmota

Have a look at node-red-contrib-pid for the PID aspect of the problem. That node provides a value in the range 0 to 1 for zero to full output, you will need to scale that to the numbers you need to drive the valve. The readme for the node links to a blog post that should provide help in tuning the pid parameters.
Note that I am the author of that node so do not consider this an unbiased recommendation.

1 Like

Colin Thanks for info :+1:
This is my Flow for now. Suggestions for improvement are welcome.

[
    {
        "id": "a6ee5f70.096cf",
        "type": "tab",
        "label": "PID Kotlarnica",
        "disabled": false,
        "info": ""
    },
    {
        "id": "ec330aa8.b59f88",
        "type": "PID",
        "z": "a6ee5f70.096cf",
        "name": "",
        "setpoint": "60",
        "pb": "22",
        "ti": "24",
        "td": "6",
        "integral_default": "0",
        "smooth_factor": "0",
        "max_interval": 600,
        "enable": "1",
        "disabled_op": "0",
        "x": 409,
        "y": 284,
        "wires": [
            [
                "9c0ca922.2658d8"
            ]
        ]
    },
    {
        "id": "9c0ca922.2658d8",
        "type": "range",
        "z": "a6ee5f70.096cf",
        "minin": "0",
        "maxin": "1",
        "minout": "64",
        "maxout": "21",
        "action": "scale",
        "round": true,
        "property": "payload",
        "name": "Scale power",
        "x": 566,
        "y": 284,
        "wires": [
            [
                "c261fe68.d81f6",
                "653cb467.8f3e6c",
                "1fed989e.973627"
            ]
        ]
    },
    {
        "id": "581c2099.5b745",
        "type": "mqtt out",
        "z": "a6ee5f70.096cf",
        "name": "MQTT",
        "topic": "",
        "qos": "1",
        "retain": "true",
        "broker": "ca0557c2.d52418",
        "x": 1203,
        "y": 218,
        "wires": []
    },
    {
        "id": "8e0f869d.3f1b88",
        "type": "mqtt in",
        "z": "a6ee5f70.096cf",
        "name": "Boiler Sensors",
        "topic": "sokomm/tele/kot/SENSOR",
        "qos": "1",
        "datatype": "auto",
        "broker": "ca0557c2.d52418",
        "x": 116,
        "y": 77,
        "wires": [
            [
                "25186461.aa728c"
            ]
        ]
    },
    {
        "id": "25186461.aa728c",
        "type": "json",
        "z": "a6ee5f70.096cf",
        "name": "",
        "property": "payload",
        "action": "",
        "pretty": false,
        "x": 296,
        "y": 80,
        "wires": [
            [
                "75a49fe7.e79cd"
            ]
        ]
    },
    {
        "id": "5ebccd82.767194",
        "type": "debug",
        "z": "a6ee5f70.096cf",
        "name": "",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "false",
        "x": 681,
        "y": 113,
        "wires": []
    },
    {
        "id": "a35ca21a.e5b58",
        "type": "ui_chart",
        "z": "a6ee5f70.096cf",
        "name": "",
        "group": "8938b4ef.518dd8",
        "order": 0,
        "width": "6",
        "height": "6",
        "label": "Boiler",
        "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": 981,
        "y": 109,
        "wires": [
            [],
            []
        ]
    },
    {
        "id": "557067c8.86f528",
        "type": "change",
        "z": "a6ee5f70.096cf",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "topic",
                "pt": "msg",
                "to": "Temperatura",
                "tot": "str"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 818,
        "y": 58,
        "wires": [
            [
                "a35ca21a.e5b58"
            ]
        ]
    },
    {
        "id": "c261fe68.d81f6",
        "type": "change",
        "z": "a6ee5f70.096cf",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "topic",
                "pt": "msg",
                "to": "Ventil",
                "tot": "str"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 821.5,
        "y": 165,
        "wires": [
            [
                "a35ca21a.e5b58"
            ]
        ]
    },
    {
        "id": "d0e8f90b.dc15c8",
        "type": "debug",
        "z": "a6ee5f70.096cf",
        "name": "",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "false",
        "x": 1232,
        "y": 290,
        "wires": []
    },
    {
        "id": "71d7491e.f1aad8",
        "type": "delay",
        "z": "a6ee5f70.096cf",
        "name": "",
        "pauseType": "rate",
        "timeout": "1",
        "timeoutUnits": "minutes",
        "rate": "1",
        "nbRateUnits": "1",
        "rateUnits": "minute",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": true,
        "x": 436,
        "y": 192,
        "wires": [
            [
                "ec330aa8.b59f88"
            ]
        ]
    },
    {
        "id": "4fcaaf1d.3e893",
        "type": "change",
        "z": "a6ee5f70.096cf",
        "name": "Position Topic",
        "rules": [
            {
                "t": "set",
                "p": "topic",
                "pt": "msg",
                "to": "sokomm/cmnd/mventil/Dimmer",
                "tot": "str"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 992,
        "y": 228,
        "wires": [
            [
                "d0e8f90b.dc15c8",
                "581c2099.5b745"
            ]
        ]
    },
    {
        "id": "1fed989e.973627",
        "type": "trigger",
        "z": "a6ee5f70.096cf",
        "op1": "OFF",
        "op2": "ON",
        "op1type": "str",
        "op2type": "str",
        "duration": "10",
        "extend": false,
        "units": "s",
        "reset": "",
        "bytopic": "all",
        "name": "ON and OFF are inverted",
        "x": 791,
        "y": 288,
        "wires": [
            [
                "4cd64d25.3c2c34"
            ]
        ]
    },
    {
        "id": "4cd64d25.3c2c34",
        "type": "change",
        "z": "a6ee5f70.096cf",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "topic",
                "pt": "msg",
                "to": "sokomm/cmnd/mventil/Power1",
                "tot": "str"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 989,
        "y": 288,
        "wires": [
            [
                "581c2099.5b745",
                "d0e8f90b.dc15c8"
            ]
        ]
    },
    {
        "id": "653cb467.8f3e6c",
        "type": "delay",
        "z": "a6ee5f70.096cf",
        "name": "",
        "pauseType": "delay",
        "timeout": "2",
        "timeoutUnits": "seconds",
        "rate": "1",
        "nbRateUnits": "1",
        "rateUnits": "minute",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": true,
        "x": 804,
        "y": 230,
        "wires": [
            [
                "4fcaaf1d.3e893",
                "829d2b30.c1e028"
            ]
        ]
    },
    {
        "id": "75a49fe7.e79cd",
        "type": "function",
        "z": "a6ee5f70.096cf",
        "name": "Split Data",
        "func": "var msgBoilerIN = {};\nvar msgBuffer = {};\nvar msgPump = {};\nvar msgPID = {};\nmsgBoilerIN.payload = msg.payload.DS18x20.S28FF3C29001704ED; //Temp Sensor on the Boiler Inlet\nmsgBuffer.payload = msg.payload.DS18x20.S28FF4BB7311703D3; // Buffer Tnak Bottom Temperatue\nmsgPump.payload = msg.payload.Switch1; //Reads the state of the Buffer Tnak Pump,ON and OFF are inverted\n\n\n\nif ((msgBuffer.payload > 60 && msgPump.payload === \"OFF\") || msgPump.payload === \"ON\") {msgPID.payload=false;\n    msgPID.topic = 'enable';\n    return [msgPID];\n}\nelse;\n    {msgPID.payload=true;\n    msgPID.topic = 'enable';\n    return [msgBoilerIN,msgPID];\n    }   \n\nnode.status({fill:\"black\",shape:\"dot\",text:msgPump.payload });\n\n\n\n\n\n",
        "outputs": 2,
        "noerr": 0,
        "x": 429,
        "y": 82,
        "wires": [
            [
                "71d7491e.f1aad8",
                "557067c8.86f528"
            ],
            [
                "5ebccd82.767194",
                "ec330aa8.b59f88",
                "d86764a8.9f8a18"
            ]
        ],
        "outputLabels": [
            "Temp Ventil",
            "PID on off"
        ]
    },
    {
        "id": "1dd0dd9c.130922",
        "type": "influxdb out",
        "z": "a6ee5f70.096cf",
        "influxdb": "373fa459.54e07c",
        "name": "KucaDB",
        "measurement": "senzori",
        "precision": "s",
        "retentionPolicy": "",
        "x": 1089,
        "y": 578,
        "wires": []
    },
    {
        "id": "76fc55a.36a85ac",
        "type": "change",
        "z": "a6ee5f70.096cf",
        "name": "Set Topic",
        "rules": [
            {
                "t": "set",
                "p": "topic",
                "pt": "msg",
                "to": "rflink",
                "tot": "str"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 926,
        "y": 572,
        "wires": [
            [
                "1dd0dd9c.130922"
            ]
        ]
    },
    {
        "id": "829d2b30.c1e028",
        "type": "function",
        "z": "a6ee5f70.096cf",
        "name": "PID",
        "func": "msg.payload = {\n    PID: msg.payload,\n    \n    \n}\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "x": 777,
        "y": 575,
        "wires": [
            [
                "76fc55a.36a85ac"
            ]
        ]
    },
    {
        "id": "d86764a8.9f8a18",
        "type": "function",
        "z": "a6ee5f70.096cf",
        "name": "pretvaranje false u 1",
        "func": "if(msg.payload === false)\n {\n\tmsg.payload = 1;\n\treturn msg;\n }\n\nif(msg.payload === true)\n {\n\tmsg.payload = 25;\n\treturn msg;\n }",
        "outputs": 1,
        "noerr": 0,
        "x": 571,
        "y": 627,
        "wires": [
            [
                "aa27e29d.9e8f1"
            ]
        ]
    },
    {
        "id": "aa27e29d.9e8f1",
        "type": "function",
        "z": "a6ee5f70.096cf",
        "name": "PID",
        "func": "msg.payload = {\n    PID_ONOFF: msg.payload,\n    \n    \n}\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "x": 771,
        "y": 642,
        "wires": [
            [
                "76fc55a.36a85ac"
            ]
        ]
    },
    {
        "id": "ca0557c2.d52418",
        "type": "mqtt-broker",
        "z": "",
        "name": "",
        "broker": "localhost",
        "port": "1883",
        "clientid": "",
        "usetls": false,
        "compatmode": true,
        "keepalive": "60",
        "cleansession": true,
        "birthTopic": "",
        "birthQos": "0",
        "birthPayload": "",
        "closeTopic": "",
        "closePayload": "",
        "willTopic": "",
        "willQos": "0",
        "willPayload": ""
    },
    {
        "id": "8938b4ef.518dd8",
        "type": "ui_group",
        "z": "",
        "name": "Kotlarnica",
        "tab": "11830169.4e96bf",
        "order": 2,
        "disp": true,
        "width": "16"
    },
    {
        "id": "373fa459.54e07c",
        "type": "influxdb",
        "z": "",
        "hostname": "127.0.0.1",
        "port": "8086",
        "protocol": "http",
        "database": "KOTAO",
        "name": "KucaDB",
        "usetls": false,
        "tls": ""
    },
    {
        "id": "11830169.4e96bf",
        "type": "ui_tab",
        "z": "",
        "name": "Kuca",
        "icon": "dashboard",
        "order": 1
    }
]

My first run of PID, oscillation is happening the first 30 min (30 measurements), then it settles and again starts oscillating. What should i change in the pid settings. This is the first time I use PID, the link for the tuning guide is dead.
http://blog.clanlaw.org.uk/2018/01/09/PID-tuning-with-node-red-contrib-pid.html

My PID Settings

I am not familiar with this particular PID, However I am very familiar with PID loops in general and the exact application you are attempting. Very rarely do do need derivative. I would make that 0.

Here where i recommend you start. Only use proportional band. Leave integral and derivative at 0. Get the prop where it is steady and not hunting a lot ( note you wont make setpoint , but that is normal.) once you get prop figured, start adding integral to get you to setpoint.

Thanks
I will try tomorrow when I startup the boiler again.

Did you follow the tuning technique described in my blog http://blog.clanlaw.org.uk/pid-loop-tuning.html? I am surprised at how small an integral you have, I would have expected it to be several minutes looking at the chart.
What rate are you feeding samples to the PID? I wonder whether you are not sampling at sufficient rate. Looking at the chart it doesn't look as if the temperature is charting at the same rate as the PID output, which seems strange.
@sokomm is right, you probably don't need derivative, at least initially leave it at zero.

Just read your previous post again, that appears to say that you are only measuring once a minute. If so, that is much too slow for that process. Try it at 5 seconds and run through the tuning procedure again. I would expect you to end up with an integral of a few minutes and a PB of maybe 1 or 2 degrees.

Before you do this - a couple of things

  1. What sort of boiler do you have ??
  2. What is the size of your buffer tank ?

I personally would be careful about having the boiler cycle too often as that can be quite inefficient and can cause condensation issues etc

As it appears you only have a simple on/off servo valve control i do not see what a PID node will give you as you do not have enought control to warrant its use - i think you would be better of with an averaging node

I assume the boiler has a pump in it - how do you control the on/off for that pump as that will be what controls the movement of water through the boiler - typically that would be on the cold inlet side of the boiler so it might not last long with cavitation by you turning off the incoming water to it

Craig

I have a Viessmann Vitolig 150 25kw boiler, the buffer tank is 1000L, I can set the valve position anywhere from 0 to 90 deg. I know about the condensation problem, that's why im doing this.
The buffer pump is controlled by the boiler, the pump is on the inlet of the boiler. The pump is turned on when the boiler reaches 60 C

Vitolig-150-slika
vitolig150a

@craigcurtin my understanding is that the pid is controlling the position of a rotary mixing valve to keep the return flow temp at about 60C.

@sokomm I think maybe 2C on the prob band will be too tight, I was thinking you were controlling the tank temperature not the flow temp, which will be rather more difficult to control I think. However, with the sample rate higher we should be able to see better what is going on.

@Colin I can increase the sample rate to 10 sec max but can I limit the output rate of the PID so that the servo run as little as possible? I want to prolong its life.

Once it is properly tuned it shouldn't move much, but if you want to control a fast process then you have to be able to respond quickly. Get it tuned first and see how it goes.

Todays tuning attempt
Sample rate 10s (that's the max I can go) Derivative set to 0, integral initali 0.

Found PB to be most stable at 4, temp variation 61-58 C.
Tried various integral values but could not get temperature to stabilize.
At the end the integral was set to 30 and it seemed to be stabilizing but the buffer temperature was close to 60C. Once it passed 60C the pid in turned off.

It is completely unstable so you are way out on the settings.
You didn't answer whether you tried the technique in the blog.
If for some reason you don't want to do that then set the integral Time to a large value compared to the oscillation period, which looks to be a bit less than one minute. So set it to 600 (ten minutes) for the moment.
If it is still unstable then keep doubling the Prop Band till it is stable.
If you are posting a graph again, it is necessary to see the details of a few cycles, not what happens over a long period so show a much shorter time range please.

@Colin I read your instructions. Their it says the to set all to 0 and and watch for the first overshoot (mine was 1C) and set PB to twice that value, so PB 2.
In your example the output stabilizes, but mine was still oscillating. So I went on increasing PB to get the over and undershoot to be as small as possible. I dont know by how much should i increase the values. As for the graph I don't know witch part of it is relevant so I posted it all, if you want I can zoom in just say witch time period.
Thanks

The tuning technique may have given false information because the sample time was to long.
Try the suggestions from my previous post, set the internal time larger then keep doubling the PB till it is stable.