Yet Another PID loop controller node

1,573 views
Skip to first unread message

Colin Law

unread,
Nov 14, 2016, 10:02:48 AM11/14/16
to node...@googlegroups.com
I believe that current PID controllers available for node-red [2] [3]
do not provide appropriate features for real world applications such
as (but in no way limited to) room heating. Also I feel it would not
be practical to extend either of those as the underlying technique and
configuration parameters are not ideal.

So I have written my own and committed it to github [1]. Any feedback
will be gratefully received.

Colin

[1] https://github.com/colinl/node-red-contrib-pid
[2] http://flows.nodered.org/node/node-red-node-pidcontrol
[3] https://github.com/tyrrellsystems/node-red-contrib-pid-controller

Dave C-J

unread,
Nov 14, 2016, 10:14:23 AM11/14/16
to node...@googlegroups.com
Nice,

can it handle devices that can only be on or off ? (eg my boiler ?)

Colin Law

unread,
Nov 14, 2016, 11:58:05 AM11/14/16
to node...@googlegroups.com
The technique to use for that is to use a time proportioned output. So
that the power cycles on and off over a defined period. For my boiler
and power operated radiator valves I use a cycle time of ten minutes
so that if, for example, 20% power is required it is on for 2 minutes
and off for 8 every ten minutes. There are some subtleties that might
not be thought of initially, such as allowing for the time the valve
takes to open/close or the boiler takes to light. The timeprop
algorithm must allow for that to be specified in order to ensure that,
for example, on low power the node does not ask for the valve to open
and then close it again before it is fully open.

I have a function node which does that which I plan to turn into a
contrib-node but if there is interest I can publish it as a flow for
the moment until I get round to doing it properly.

I have just published a flow that exercises the pid node at
http://flows.nodered.org/flow/42f125b56a00dd5d1433c2f8023263e9

Colin

On 14 November 2016 at 15:14, Dave C-J <dce...@gmail.com> wrote:
> Nice,
>
> can it handle devices that can only be on or off ? (eg my boiler ?)
>
> --
> http://nodered.org
>
> Join us on Slack to continue the conversation: http://nodered.org/slack
> ---
> You received this message because you are subscribed to the Google Groups
> "Node-RED" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to node-red+u...@googlegroups.com.
> To post to this group, send email to node...@googlegroups.com.
> Visit this group at https://groups.google.com/group/node-red.
> To view this discussion on the web, visit
> https://groups.google.com/d/msgid/node-red/CACXWFwK8c6gn8TgCid%3DMkQ3bb-0zdC3ufbB-Q%3DTMBGXNE%3DMn0w%40mail.gmail.com.
> For more options, visit https://groups.google.com/d/optout.

Julian Knight

unread,
Nov 14, 2016, 12:19:09 PM11/14/16
to Node-RED
Sounds like at least a flow would be helpful as the decisions are reasonably complex for people to make. You can always update the flow once you release the node but I'd suggest including enough information so that people understand what variables they need to think about then perhaps an example with some sensible variable choices.

Colin Law

unread,
Nov 14, 2016, 4:09:02 PM11/14/16
to node...@googlegroups.com

Lawrence Griffiths

unread,
Nov 14, 2016, 4:43:49 PM11/14/16
to Node-RED
Colin, thanks for this really like the Initial integral & Derivative smoothing factor features.
Any plans for manual mode eg. drive output 0-100% always good feature for commissioning sticky valves. 

Lawrence

Colin Law

unread,
Nov 14, 2016, 4:52:40 PM11/14/16
to node...@googlegroups.com
Disabled mode is effectively the same as manual, so give it a msg with topic enable and payload false or zero, then the output can be controlled by msg with topic disabled_op and payload between 0.0 and 1.0

Perhaps manual would have been a better name, I might add an alias.

Colin

Colin Law

unread,
Nov 14, 2016, 4:54:55 PM11/14/16
to node...@googlegroups.com
I have published node-red-contrib-pid to npm. It is my first published node so hopefully I have done it correctly.

Colin

Steven Daglish

unread,
Nov 15, 2016, 4:12:22 AM11/15/16
to Node-RED
Snap 

Been working on creating a PID control system to control my boiler: https://mydiyelectronics.wordpress.com/2016/10/19/room-temperature-control-system-pid-and-pwm/

Blog out of date at the moment, but I've now integrated P and D control (not had much need of I at the moment). I also include a PWM system to allow the boiler to switch on / off with a minimum time period of 1 minute.

Just looked into the area of creating a node-red node for this as I agree the current ones available are quite limited. Will look into your node soon and can hopefully come back with some useful idea :) (or at least some good thanks ;) )

Steven

Colin Law

unread,
Nov 15, 2016, 12:24:54 PM11/15/16
to node...@googlegroups.com
Without integral it will never converge accurately onto the setpoint. There will always be an error once it has settled out.  Without I then when the error is zero (and stable) the the power will be zero.

Colin

--
http://nodered.org
 
Join us on Slack to continue the conversation: http://nodered.org/slack
---
You received this message because you are subscribed to the Google Groups "Node-RED" group.
To unsubscribe from this group and stop receiving emails from it, send an email to node-red+unsubscribe@googlegroups.com.

To post to this group, send email to node...@googlegroups.com.
Visit this group at https://groups.google.com/group/node-red.

Steven Daglish

unread,
Nov 16, 2016, 4:04:51 AM11/16/16
to Node-RED
Hi Colin,

While I understand the theory behind what you've wrote, I've yet to find it an issue in my heating system. Although I'm still playing with my heating controller, I've yet to find an off-set problem. Instead, I'm getting a ripple of about +-0.2C.

While I could probably reduce the ripple by changing P and D, +- 0.2C is small enough that I'm not that worried about it.

Another thing to bear in mind is that when the error is 0, it doesn't necessarily mean that there is zero heat being put into the system (house  / room). During the evening while I'm in the house, there will be cookers, people, kettles, electrics, and pets all contributing heat to the system, which could be enough off-set any inaccurate settling point.

One of the bigger problems I'm having in my system is due to the non-simple nature of heating a house: the temperature outside has a large effect on my heating system, meaning when it's warmer outside, I need a smaller P and larger D, otherwise I get a larger-overshoot. However, if I always keep the P low and D high, the system becomes very slow when it is cold outside. I can't decide if I want to create a temperature sensor outside or just some kind of temperature API like Dark Sky to estimate the current (near future) temperature in my location.

Happy to discuss more and see if you've had any similar observations.

Steven

On Tuesday, 15 November 2016 17:24:54 UTC, Colin Law wrote:
Without integral it will never converge accurately onto the setpoint. There will always be an error once it has settled out.  Without I then when the error is zero (and stable) the the power will be zero.

Colin
On 15 November 2016 at 09:12, Steven Daglish <s.c.d...@gmail.com> wrote:
Snap 

Been working on creating a PID control system to control my boiler: https://mydiyelectronics.wordpress.com/2016/10/19/room-temperature-control-system-pid-and-pwm/

Blog out of date at the moment, but I've now integrated P and D control (not had much need of I at the moment). I also include a PWM system to allow the boiler to switch on / off with a minimum time period of 1 minute.

Just looked into the area of creating a node-red node for this as I agree the current ones available are quite limited. Will look into your node soon and can hopefully come back with some useful idea :) (or at least some good thanks ;) )

Steven

--
http://nodered.org
 
Join us on Slack to continue the conversation: http://nodered.org/slack
---
You received this message because you are subscribed to the Google Groups "Node-RED" group.
To unsubscribe from this group and stop receiving emails from it, send an email to node-red+u...@googlegroups.com.

To post to this group, send email to node...@googlegroups.com.
Visit this group at https://groups.google.com/group/node-red.

Colin Law

unread,
Nov 16, 2016, 4:26:31 AM11/16/16
to node...@googlegroups.com
On 16 November 2016 at 09:04, Steven Daglish <s.c.d...@gmail.com> wrote:
Hi Colin,

While I understand the theory behind what you've wrote, I've yet to find it an issue in my heating system. Although I'm still playing with my heating controller, I've yet to find an off-set problem. Instead, I'm getting a ripple of about +-0.2C.

While I could probably reduce the ripple by changing P and D, +- 0.2C is small enough that I'm not that worried about it.

Another thing to bear in mind is that when the error is 0, it doesn't necessarily mean that there is zero heat being put into the system (house  / room). During the evening while I'm in the house, there will be cookers, people, kettles, electrics, and pets all contributing heat to the system, which could be enough off-set any inaccurate settling point.

That is precisely the point. The steady state power you need changes dependant on ambient conditions. In the evening or in warm weather you will need virtually no heat, at other times or cold weather you need more. The only way a P+D controller can do that is by increasing the error. Now it may well be that you are able to  have the P gain high enough that the steady state error does not matter or is below the noise level you are seeing in which case fair enough.
 

One of the bigger problems I'm having in my system is due to the non-simple nature of heating a house: the temperature outside has a large effect on my heating system, meaning when it's warmer outside, I need a smaller P and larger D, otherwise I get a larger-overshoot. However, if I always keep the P low and D high, the system becomes very slow when it is cold outside.

It sounds as if you need some Integral to me :)
Why don't you try node-red-contrib-pid and see how it performs?

Colin

To unsubscribe from this group and stop receiving emails from it, send an email to node-red+unsubscribe@googlegroups.com.

To post to this group, send email to node...@googlegroups.com.
Visit this group at https://groups.google.com/group/node-red.

Steven Daglish

unread,
Nov 16, 2016, 5:25:26 AM11/16/16
to Node-RED
Hi Colin,

I've had a look into the code and have a few comments / questions.
  • I'm currently using the following node.js PID controller https://github.com/wilberforce/pid-controller which seems to be working well so far.
    • One of the things I really like about it is the ability to choose a "sample time". Using this, I'm able to call the function multiple times, and only have the PID controller calculate the output at sample time (once a minute for me).
    • Is this something that could be included in this node? I'm happy to help writing up the code, but I'm new to npm and github, so not sure the best way I could help (though open to suggestions).
  • Would it be possible to add a min / max output value? Since I'm connecting the output of the PID controller to a PWM controller, I only need output values between 0(%) and 100(%). This could be added to another node, but it would be great if it was included.
  • I'm also wondering if it's possible to change the way the PID parameters can be changed?
    • At the moment, it looks like each parameter can be changed by selecting "setpoint" in "msg.topic", and that you would need multiple calls to change different parameters.
    • Would it be possible to use a slightly different system where the parameters each have a different name?
    • For example, to change setpoint, you'd change "msg.setpoint", and to change the system input you'd change "msg.input".
    • That way you could change multiple parameters in one go, rather than needing multiple calls to the node.
I hope that makes sense and I'm happy to discuss this more. If you'd like, I'm happy to try and figure out how to make these changes locally and then send you any changes I make (sorry, but very good at this whole collaborative code stuff :) )

Steven

Steven Daglish

unread,
Nov 16, 2016, 6:58:51 AM11/16/16
to Node-RED
Just did some more reading and realised that you're output is set between 0 and 1. Therefore no max limits need setting :)

Colin Law

unread,
Nov 16, 2016, 7:26:54 AM11/16/16
to node...@googlegroups.com
On 16 November 2016 at 10:25, Steven Daglish <s.c.d...@gmail.com> wrote:
Hi Colin,

I've had a look into the code and have a few comments / questions.
  • I'm currently using the following node.js PID controller https://github.com/wilberforce/pid-controller which seems to be working well so far.
    • One of the things I really like about it is the ability to choose a "sample time". Using this, I'm able to call the function multiple times, and only have the PID controller calculate the output at sample time (once a minute for me).

Can you explain why this is desirable? Why would you provide a new process value and not want to recalc the output?
 
    • Is this something that could be included in this node? I'm happy to help writing up the code, but I'm new to npm and github, so not sure the best way I could help (though open to suggestions).
  • Would it be possible to add a min / max output value? Since I'm connecting the output of the PID controller to a PWM controller, I only need output values between 0(%) and 100(%). This could be added to another node, but it would be great if it was included.
  • I'm also wondering if it's possible to change the way the PID parameters can be changed?
    • At the moment, it looks like each parameter can be changed by selecting "setpoint" in "msg.topic", and that you would need multiple calls to change different parameters.
    • Would it be possible to use a slightly different system where the parameters each have a different name?
    • For example, to change setpoint, you'd change "msg.setpoint", and to change the system input you'd change "msg.input".
    • That way you could change multiple parameters in one go, rather than needing multiple calls to the node.

In fact I had it that way originally, but changed it to individual messages, as I found that when I used it I had to put in a node to combine the current values into one message.  In my case values are being changed via mqtt, switches, dropdown lists, sliders etc so they all come separately.  Can you explain your use case for me?

 
I hope that makes sense and I'm happy to discuss this more. If you'd like, I'm happy to try and figure out how to make these changes locally and then send you any changes I make (sorry, but very good at this whole collaborative code stuff :) )


If you do decide to get involved then the technique to use is to fork from my github repository, make the changes, and then send a Pull Request to me to implement.  Best to do as you have first though, to check whether I think the changes are a good idea.

Cheers

Colin


Steven Daglish

unread,
Nov 16, 2016, 8:12:24 AM11/16/16
to Node-RED


On Wednesday, 16 November 2016 12:26:54 UTC, Colin Law wrote:
On 16 November 2016 at 10:25, Steven Daglish <s.c.d...@gmail.com> wrote:
Hi Colin,

I've had a look into the code and have a few comments / questions.
  • I'm currently using the following node.js PID controller https://github.com/wilberforce/pid-controller which seems to be working well so far.
    • One of the things I really like about it is the ability to choose a "sample time". Using this, I'm able to call the function multiple times, and only have the PID controller calculate the output at sample time (once a minute for me).

Can you explain why this is desirable? Why would you provide a new process value and not want to recalc the output?

It's not so much that I want to ignore new values that are coming in, but more that I would like to control when the values are calculated. I have a number of sensor values coming in via MQTT, which aren't synced, so end up coming in at different times (for example more than one temperature sensor in the same room). Rather than recalculate the output when the temperature readings come in, I can have the PID controller only calculate the output say once a minute. It's a small thing and I could easily implement it using another node, so it's no big deal.

 
 
    • Is this something that could be included in this node? I'm happy to help writing up the code, but I'm new to npm and github, so not sure the best way I could help (though open to suggestions).
  • Would it be possible to add a min / max output value? Since I'm connecting the output of the PID controller to a PWM controller, I only need output values between 0(%) and 100(%). This could be added to another node, but it would be great if it was included.
  • I'm also wondering if it's possible to change the way the PID parameters can be changed?
    • At the moment, it looks like each parameter can be changed by selecting "setpoint" in "msg.topic", and that you would need multiple calls to change different parameters.
    • Would it be possible to use a slightly different system where the parameters each have a different name?
    • For example, to change setpoint, you'd change "msg.setpoint", and to change the system input you'd change "msg.input".
    • That way you could change multiple parameters in one go, rather than needing multiple calls to the node.

In fact I had it that way originally, but changed it to individual messages, as I found that when I used it I had to put in a node to combine the current values into one message.  In my case values are being changed via mqtt, switches, dropdown lists, sliders etc so they all come separately.  Can you explain your use case for me?


For me, my current system sends the setpoint and current temperature to the PID system at the same time. Therefore, I have something like msg.setpoint = ... and msg.payload = temperature. I then just send both of these values to the PID controller in one go. For my system, all the commands for the PID function come from a single function node, so it was simple enough for me to combine all the required values into a single package. For me tom use the system as it is, I would need to create use function node to find the current setpoint and send that to the PID controller before sending the current temperature.

I was able to adjust your code to allow me to do what I need, so it's no big deal :)

    this.on('input', function(msg) {
      var newMsg = null;
      if (msg.hasOwnProperty('setpoint')) {
        node.setpoint = Number(msg.setpoint);
      }
      if (msg.hasOwnProperty('enable')) {
        node.enable = Number(msg.enable);
      }
      if (msg.hasOwnProperty('prop_band')) {
        node.prop_band = Number(msg.prop_band);
      }
      if (msg.hasOwnProperty('t_integral')) {
        node.t_integral = Number(msg.t_integral);
      }
      if (msg.hasOwnProperty('t_derivative')) {
        node.t_derivative = Number(msg.t_derivative);
      }
      if (msg.hasOwnProperty('smooth_factor')) {
        node.smooth_factor = Number(msg.smooth_factor);
      }
      if (msg.hasOwnProperty('max_interval')) {
        node.max_interval = Number(msg.max_interval);
      }
      if (msg.hasOwnProperty('disabled_op')) {
        node.disabled_op = Number(msg.disabled_op);
      }
      if (!isNaN(msg.payload)){
        // anything else is assumed to be a process value
        node.pv = Number(msg.payload);   // this may give NaN which is handled in runControlLoop
        newMsg = runControlLoop();
      }
      node.send(newMsg);
    });

I've loaded it and it seems to work well. I like the node, but have had to spend a long time figuring out the Proportional Band instead of Proportional Gain (which is what I'm more used to), but it does seem to work well (with the adjustments above).
 
 
I hope that makes sense and I'm happy to discuss this more. If you'd like, I'm happy to try and figure out how to make these changes locally and then send you any changes I make (sorry, but very good at this whole collaborative code stuff :) )


If you do decide to get involved then the technique to use is to fork from my github repository, make the changes, and then send a Pull Request to me to implement.  Best to do as you have first though, to check whether I think the changes are a good idea.

Cheers

Colin



Keep up the great work :) 

Colin Law

unread,
Nov 16, 2016, 8:55:00 AM11/16/16
to node...@googlegroups.com
On 16 November 2016 at 13:12, Steven Daglish <s.c.d...@gmail.com> wrote:


On Wednesday, 16 November 2016 12:26:54 UTC, Colin Law wrote:
On 16 November 2016 at 10:25, Steven Daglish <s.c.d...@gmail.com> wrote:
Hi Colin,

I've had a look into the code and have a few comments / questions.
  • I'm currently using the following node.js PID controller https://github.com/wilberforce/pid-controller which seems to be working well so far.
    • One of the things I really like about it is the ability to choose a "sample time". Using this, I'm able to call the function multiple times, and only have the PID controller calculate the output at sample time (once a minute for me).

Can you explain why this is desirable? Why would you provide a new process value and not want to recalc the output?

It's not so much that I want to ignore new values that are coming in, but more that I would like to control when the values are calculated. I have a number of sensor values coming in via MQTT, which aren't synced, so end up coming in at different times (for example more than one temperature sensor in the same room). Rather than recalculate the output when the temperature readings come in, I can have the PID controller only calculate the output say once a minute. It's a small thing and I could easily implement it using another node, so it's no big deal.

Sorry, still don't get it.  The net result is that you are delaying the operation of the loop, which is always a bad thing for loop control.  What is advantage of delaying it?

> ....
I have no objection to including the ability to pass the parameters that way, but it would have to be in addition to being able to pass them the existing way. I will be happy to accept a Pull Request for that.  Don't do the test for NaN outside of runControlLoop though, firstly it is not a sufficient test (it needs to check for infinity also) and secondly it prevents the node status from showing bad pv as a status.
 
Colin

Steven Daglish

unread,
Nov 16, 2016, 9:22:51 AM11/16/16
to Node-RED


On Wednesday, 16 November 2016 13:55:00 UTC, Colin Law wrote:
On 16 November 2016 at 13:12, Steven Daglish <s.c.d...@gmail.com> wrote:


On Wednesday, 16 November 2016 12:26:54 UTC, Colin Law wrote:
On 16 November 2016 at 10:25, Steven Daglish <s.c.d...@gmail.com> wrote:
Hi Colin,

I've had a look into the code and have a few comments / questions.
  • I'm currently using the following node.js PID controller https://github.com/wilberforce/pid-controller which seems to be working well so far.
    • One of the things I really like about it is the ability to choose a "sample time". Using this, I'm able to call the function multiple times, and only have the PID controller calculate the output at sample time (once a minute for me).

Can you explain why this is desirable? Why would you provide a new process value and not want to recalc the output?

It's not so much that I want to ignore new values that are coming in, but more that I would like to control when the values are calculated. I have a number of sensor values coming in via MQTT, which aren't synced, so end up coming in at different times (for example more than one temperature sensor in the same room). Rather than recalculate the output when the temperature readings come in, I can have the PID controller only calculate the output say once a minute. It's a small thing and I could easily implement it using another node, so it's no big deal.

Sorry, still don't get it.  The net result is that you are delaying the operation of the loop, which is always a bad thing for loop control.  What is advantage of delaying it?

Happy to admit defeat on that one. Not something that fully needs implementing in the code, but more to do with the way my flows work. In case you're interested, I have two temperature senors in a room that send via MQTT. They aren't synced, so send their values at slightly different times. Rather than calculating the new PID value when each arrives, I wait for both to arrive, average them, and them and then do the calculation. In order to be a bit lazy, I simply wait one minute and assume both values have come. I need to improve this anyway, so this will help :) 
Thanks for the further information. I'll look into that soon.

I do have one other question though. I'm currently unsure where this 0.5 biasing is coming from [var power = -1.0/node.prop_band * (proportional + node.integral + node.derivative) + 0.5]

I can understand all the other parts of the PID equations, but I can't figure out if this is something that you've put in for your system, or if it's a part of a control mechanism I'm not sure about. As far as I can tell (and found in my testing). the output will be at 0.5 even when the error is zero (assuming Pd and Pi to be zero at the moment). I've tried to do some research on the subject, and as far as I can tell, it's a use-defined bias.

Is this correct?

Thanks,

Steven

Colin Law

unread,
Nov 16, 2016, 10:19:43 AM11/16/16
to node...@googlegroups.com
On 16 November 2016 at 14:22, Steven Daglish <s.c.d...@gmail.com> wrote:


On Wednesday, 16 November 2016 13:55:00 UTC, Colin Law wrote:
> ...
Sorry, still don't get it.  The net result is that you are delaying the operation of the loop, which is always a bad thing for loop control.  What is advantage of delaying it?
Happy to admit defeat on that one. Not something that fully needs implementing in the code, but more to do with the way my flows work. In case you're interested, I have two temperature senors in a room that send via MQTT. They aren't synced, so send their values at slightly different times. Rather than calculating the new PID value when each arrives, I wait for both to arrive, average them, and them and then do the calculation. In order to be a bit lazy, I simply wait one minute and assume both values have come. I need to improve this anyway, so this will help :) 

If you wait the minute, do the average, and then pass that on to the PID loop it will only get samples once a minute anyway and so will only run the control algorithm once a minute.

> ...


I do have one other question though. I'm currently unsure where this 0.5 biasing is coming from [var power = -1.0/node.prop_band * (proportional + node.integral + node.derivative) + 0.5]

I can understand all the other parts of the PID equations, but I can't figure out if this is something that you've put in for your system, or if it's a part of a control mechanism I'm not sure about. As far as I can tell (and found in my testing). the output will be at 0.5 even when the error is zero (assuming Pd and Pi to be zero at the moment). I've tried to do some research on the subject, and as far as I can tell, it's a use-defined bias.

Is this correct?

Yes, or to put it another way when the error is zero the power will be in the middle of its range. The integral term can then push it up to full power or down to 0 as appropriate.  Where the integral term starts on deploy is set by the value of Initial Integral.  If you set that to zero then when the error is zero the power will be 0, if you set it 1 then when the error is zero  the power will be 1.  Except that the integral term changes over time of course, so if it is correctly tuned the integral will settle out at the point where the power is exactly right to hold the temperature on the setpoint.  If the pv is a little high the integral will wind the power down to gently reduce the pv until it is on the setpoint, and vice versa. There is code to lock the integral when the pv is far from the setpoint so that it cannot push the proportional region completely away from the setpoint ( so it will not try to ask for more that full or less than zero power when the error is 0).

Cheers

Colin

Steven Daglish

unread,
Nov 16, 2016, 10:45:08 AM11/16/16
to Node-RED
Pull request made (hopefully...)

 I've also added the ability to change the bias value to make the node more flexible (set to 0.5 by default as per your current version).

Steven
Reply all
Reply to author
Forward
0 new messages