New Automation: PID Controller

@aitorforero
You could probably do something like only switch the heater on if the PID output is indicating at least, say, 2 minutes of heat are required for the cycle. You would ignore shorter activations. The error would increase and so would the output time on following cycles until the activation time becomes > 2 mins and the heater would be turned on.
Maybe what you are doing (PID control) is slightly unusual for an on/off style heater or not a good fit? Did you try a simpler ‘bang/bang’ temperature control with hysteresis?

@Morgano is right. You have number as an output of calculation. It’s “kind of” infinite number. In my case anything above 100 means that heater is still opened fully. Below 0 means that i need to push the dimmer to 0 - between I can adjust the dimmer. In your case you could wait till output is some number like 100 and the leave heating working until calculated value will drop below 100.

I read that Electric heaters are also controlled by PID but there is this param calculated how much time it should stay ON to reach desired temperature.
Stove for ceramics for example. But don’t know the implementation details.

My intention is to use a time-proportional PID control. Maybe I have missunderstud how often the cycle must be calculated.

Till now what I have been doing is reading the output and calculate de proportinal time of the new cycle when the switch must be ON. Then wait until whole cycle ends and I reacalculate it again.

What you are saying is that this calculation has to be done at evey change of temperature?

@aitorforero
No, the PID cycle time (loop time) is normally based on the characteristics of the system. If it is a ‘slow’ system, like a heating system then the adjustment time will be in the ‘minutes’ realm as control changes take a while to have any noticeable effects.
On the other hand, if you were controlling something like the flight stability of a drone (fast system), the PID cycle would be in the ‘milliseconds’ realm as control changes would take effect quicker, and are needed much more often.

There is a mention of this is the README: openhab-addons/README.md at main · openhab/openhab-addons · GitHub

“The loopTime should be max a tenth of the system response. E.g. the heating needs 10 min to heat up the room, the loop time should be max 1 min. Lower values won’t harm, but need more calculation resources”

@Dominik_Jeziorski , @Morgano

I have added a second action to the rule that is where I set ON or OFF the heater:

this.logger = this.logger || Java.type('org.slf4j.LoggerFactory').getLogger('org.openhab.rule.' + ctx.ruleUID);
this.ZonedDateTime = this.ZonedDateTime || Java.type("java.time.ZonedDateTime");
this.ChronoUnit = this.ChronoUnit || Java.type("java.time.temporal.ChronoUnit");

var HeaterSwitchItem = "CalefaccionCaldera";
var PIDOutputItem = "CalefaccionOutput";
var CycleLengthItem = "CalefaccionIntervalo";

function calculate() {
  this.onTime = interval * ir.getItem(PIDOutputItem).getState();
  this.lastTime = now;
  this.logger.trace("New interval calculated {} at {}. ON Time: {}", [interval, this.lastTime, this.onTime])
}

function turnOn() {
    utils.mandarSoloSiCambia(HeaterSwitchItem, ON);
}

function turnOff() {
    utils.mandarSoloSiCambia(HeaterSwitchItem, OFF);  
}

try {
  var now = ZonedDateTime.now();
  var interval = ir.getItem(CycleLengthItem).getState() * 60;


  if(!this.onTime) {
    calculate()
  }

  var dt = this.lastTime.until(this.now , ChronoUnit.SECONDS);
 
  
  if(dt >= interval) {
    this.logger.info("Loop!!!");
    calculate();
  }

  if(dt > this.onTime) {
    turnOff();
  }  else {
    turnOn();
  }
  
} catch(e) {
  this.logger.error(e.message);
}

It’s kind of a bad timing as the heating period is coming to an end on the northern hemisphere, but I released a PWM module to control valves more easily in conjunction with a PID controller.

Hi

I have a loop were I need to change the PID controller to work Reversed

How can this be obtained?

@Nanna_Agesen I imagine you would want to set a negative target or multiply the PID output by -1.

1 Like

@Morgano I did as you suggested, easy fix thanks :slight_smile:

1 Like

@fwolter
The PID controller seems to be ignoring the ‘Loop Time’ setting. I have it set to 480000 but it still appears to be triggering and creating an output every minute, rather than every 8 minutes. Is this something you fixed in a later version?

The output is also updated when the input or the setpoint changes.

1 Like

Ah - OK , that will explain it then! It might be useful if there was a setting in the controller to reduce the frequency of triggers on a time basis, although maybe this would compromise the ‘purity’ of the PID controller. I can achieve a similar effect by making changes to my rules.

By the way, for a bit of background, I am using the PID controller to control battery-powered TRVs so wanted to reduce the frequency of small valve position changes to improve battery life. I’ve also been experimenting with a ‘minimum percentage change’ figure in my rules and ignoring small control output changes this way too.

Many PID controllers have a dead time, exactly as you describing. That might be a useful feature for this PID, too.

Ok, new to this PID thing…
I have a Eurotronic TRV that does not work very well (its own PID does overheat often), so I am experimenting with this automation addon.

First, thank you for providing it :slight_smile:

So I installed the addon, and created items for it.


triggers:
  - id: "1"
    configuration:
      input: gTemp_living
      setpoint: pid_LRM_step
      kp: 50
      kd: 0
      kdTimeConstant: 1
      commandItem: pid_LRM_RESET
      ki: 0.1
      loopTime: 60000
    type: pidcontroller.trigger
conditions: []
actions:
  - inputs: {}
    id: "2"
    configuration:
      output: pid_LRM_o
      pInspector: pid_LRM_p
      iInspector: pid_LRM_i
      dInspector: pid_LRM_d
    type: pidcontroller.action

Then I have two rules to complement the PID:


rule "PID Workaround"
when
	Item pid_LRM_o changed
then
   var Number oldValue = (trv_n2_valve_aperture.state as Number)
   var Number newValue = (pid_LRM_o.state as Number)
   if (newValue < 0) {
      newValue = 0
      pid_LRM_RESET.sendCommand("RESET")
   }
   else if (newValue > 100) newValue = 100
   else newValue = (newValue + 0.5).intValue
   if (newValue != oldValue) {
      trv_n2_valve_aperture.sendCommand(newValue)
   }
end

rule "PID Reset"
when
    Item pid_LRM_step changed
then
    // reset PID controller
    pid_LRM_RESET.sendCommand("RESET")
end

I will see overtime.

Since this heating PID is only controlling heating and not cooling, I also put in the workaround rule a test to reset the PID when the input temperature is equal or greater than the setpoint (negative PID output). If not, it builds up a negative integral value that IMO is not accurate here.

2 Likes

Yes, I had been wondering about that; as a radiator is unable to cool the room when there is an external influence heating it (e.g. it warms up outside). A large negative integral builds up which then delays the valve from opening as it should when the room cools again (e.g. when it gets colder outside).

Hello to all,

I have updated to openHAB 3.2.0.M1 and I have checked the rules, there I got the problem that the status is “uninitilized”.
There seems a problem with “pidcontroller.action”.

Does somebody have the same behaviour?

image

@Morgano @bolemo I’m able to relate concerning the negative integral part. Honestly, I’m using an old version of the PID controller where the limits on the integral part can still be applied, because I don’t know how to use the reset method in my use case (PV zero export). I then removed the limits as they seem hackish from a controlling point of view. On the other side you can achieve results fast by limiting the i-part, if you aren’t a control expert, which I guess is the majority of the users. I think I will add them again.

@Koechi The custom action module was dropped because the OH core already provides a module for that purpose. Simply add an “Item Action” instead:

See PID Controller Automation - Automation | openHAB

To configure a rule, you need to add a Trigger (“PID controller triggers”) and an Action (“Item Action”). Select the Item you like to control in the “Item Action” and leave the command empty.

@fwolter Thank you for your feedback.
I have replaced in all rules type: pidcontroller.action with core.ItemCommandAction.

Now the rule is active again.

triggers:
  - id: "1"
    configuration:
      input: KnxEGWohnzimmerIstTemp
      setpoint: KnxEGWohnzimmerSollTemp
      kp: 65
      kd: 0
      kdTimeConstant: 1
      commandItem: cInspectorLivingRoom
      ki: 0.2
      loopTime: 600000
    type: pidcontroller.trigger
conditions: []
actions:
  - inputs: {}
    id: "2"
    configuration:
      output: outputLivingRoom
      pInspector: pInspectorLivingRoom
      iInspector: iInspectorLivingRoom
      dInspector: dInspectorLivingRoom
      eInspector: eInspectorLivingRoom
    type: core.ItemCommandAction
1 Like

@george.erhan There’s a use case for battery powered heating valve actuators to update the output less frequently, when the setpoint is reached to save battery. Maybe there are other use cases where minor changes to the output should be avoided. Are there any PID mechanisms we can implement in this PID controller to support this use case? It seems I’m missing the correct search terms to find anything useful on this topic concerning control theory. Can you point me in the right direction?

Hi, I was just upgrading my whole solution to new instance of OH 3.1 - I saw that PID is now added to default plugin list but there seem to be changes compared to previous implementation.

It took me some time to figure it out - AFAIK:

  • output changed to itemName
  • pidcontroller.action → core.ItemCommandAction

Where there any other changes?

PS. Next time it would be nice to add it somewhere to the official documentation. Probably rule sample (as text would also be a nice starting point)

PS2. Addon is not throwing any error if items for debugging are not existing but are placed in the rule (at least I think the rule was not executed at all)

Sample:

triggers:
  - id: "1"
    configuration:
      input: Filip_Virtual_Temperature
      setpoint: Filip_Desired_SetPoint
      kp: 65
      kd: 0
      kdTimeConstant: 1
      ki: 0.1
      loopTime: 60000
    type: pidcontroller.trigger
conditions: []
actions:
  - inputs: {}
    id: "2"
    configuration:
      output: Filip_Desired_Dimmer
    type: pidcontroller.action

Would become

triggers:
  - id: "1"
    configuration:
      input: Filip_Virtual_Temperature
      setpoint: Filip_Desired_SetPoint
      kp: 65
      kd: 0
      kdTimeConstant: 1
      ki: 0.1
      loopTime: 60000
    type: pidcontroller.trigger
conditions: []
actions:
  - inputs: {}
    id: "2"
    configuration:
      itemName: Filip_Desired_Dimmer
    type: core.ItemCommandAction
1 Like