Dear all,
I have been working for many years with PID controlled sytems for climate control and I need some help in order to build an action for a PID controller. My work up to now was in rules in OH 1. Basically, I have written PID’s for cooling and heating modes of HVAC installations (from my experience, cooling and heating are different in terms of constants for proportional, integral and derivative sub functions).
Concept:
The rules are built based on the items previously created with a code of room/zone in the item name
Each room has a number of constants set as items Kp, Ki, Kd, errors, outputs, minimum, maximum outputs, etc. Each set of variables used is assigned to a group. The code searches inside the group for the zone.
This is what I have achieved up to now:
Heating PID
import org.openhab.core.library.types.*
import java.lang.Math
import java.util.Date
import org.joda.time
import java.util.Map
import org.joda.time.*
import org.joda.time.DateTime
import org.openhab.core.library.types.DateTimeType
var double HpreviousIntegral = 0
var double HpreviousError = 0
var double HpreviousErrorI = 0
var double Hepsilon
var double HlastHourChange
var double HlastMinuteChange
var double HlastSecondsChange
var double HlastChange
var double HPIDdt
var double HPIDSetpoint
var double HPIDTemperature
var double HPIDError
var double Hkp
var double Hki
var double Hkd
var double Hproportional=0
var double Hintegral=0
var double Hderivative=0
var int HPIDOutput
/*array of rooms - this should be included in the item names */
var String[] PIDZones=newArrayList("P01", "P02", "P03", "P04", "P05", "P07", "P08", "P09", "P10", "P11", "P12", "P13", "P14", "P15", "P16", "P17", "P18", "P19", "P20", "P21", "M02", "M03", "M04", "M05", "M07", "M08", "M09", "M10")
var double HcorrectingIntegral
var int HminPIDOutput
var int HmaxPIDOutput
var double maxDerivativeError = (-0.3).doubleValue
var String Hhcinput
rule HPIDController
when
Time cron "0 * * * * ?"
or
Time cron "30 * * * * ?"
then
if (delayRules.state.toString=="ON")
{
for (String i:PIDZones)
{
print("\n STARTED HEATING PID CONTROLLER RULE:" + i + " ---->" + "\n")
heatingCoolingInput.allMembers.forEach(myMember|
if (myMember.name.contains(i))
{
Hhcinput=myMember.state.toString
}
)
if (Hhcinput=="ON")
{
measuredTemperatures.allMembers.forEach(myMember|
if (myMember.name.contains(i))
{
HlastHourChange = myMember.lastUpdate.hours
HlastMinuteChange = myMember.lastUpdate.minutes
HlastSecondsChange=myMember.lastUpdate.seconds
HlastChange= ((((HlastHourChange*60)+HlastMinuteChange)*60)+HlastSecondsChange).doubleValue
HPIDdt=now.getSecondOfDay - HlastChange
HPIDTemperature=(myMember.state as DecimalType).doubleValue
if (HPIDdt<0)
{
HPIDdt=(86399-HlastChange+now.getSecondOfDay).doubleValue
}
}
)
thermostatSetpoints.allMembers.forEach(myMember|
if (myMember.name.contains(i))
{
HPIDSetpoint=(myMember.state as DecimalType).doubleValue
HPIDError=(HPIDSetpoint.doubleValue-HPIDTemperature.doubleValue).doubleValue
}
)
Hkps.allMembers.forEach(myMember|
if (myMember.name.contains(i))
{
/*myMember.sendCommand(myMember.historicState(now.minusSeconds(20)).state.toString)*/
/*print("\n HKPS:" + i + " ---->" + "\n")*/
Hkp=(myMember.state as DecimalType).doubleValue
/*print("\n HKPS:" + i + " ---->" + Hkp + "\n")*/
}
)
Hkis.allMembers.forEach(myMember|
if (myMember.name.contains(i))
{
/*myMember.sendCommand(myMember.historicState(now.minusSeconds(20)).state.toString)*/
/*print("\n HKIS:" + i + " ---->" + "\n")*/
Hki=(myMember.state as DecimalType).doubleValue
/*print("\n HKIS:" + i + " ---->" + Hki + "\n")*/
}
)
Hkds.allMembers.forEach(myMember|
if (myMember.name.contains(i))
{
/*myMember.sendCommand(myMember.historicState(now.minusSeconds(20)).state.toString)*/
/*print("\n HKDS:" + i + " ---->" + "\n")*/
Hkd=(myMember.state as DecimalType).doubleValue
/*print("\n HKDS:" + i + " ---->" + Hkd + "\n")*/
}
)
HcorrectingIntegrals.allMembers.forEach(myMember|
if (myMember.name.contains(i))
{
/*myMember.sendCommand(myMember.historicState(now.minusSeconds(20)).state.toString)*/
/*print("\n H CORRECTING INTEGRALS:" + i + " ---->" + "\n")*/
HcorrectingIntegral=(myMember.state as DecimalType).doubleValue
/*print("\n H CORRECTING INTEGRALS:" + i + " ---->" + HcorrectingIntegral + "\n")*/
}
)
HminPIDOutputs.allMembers.forEach(myMember|
if (myMember.name.contains(i))
{
/*myMember.sendCommand(myMember.historicState(now.minusSeconds(20)).state.toString)*/
/*print("\n H MIN PID OUTPUTS:" + i + " ---->" + "\n")*/
HminPIDOutput=(myMember.state as DecimalType).intValue
/*print("\n H MIN PID OUTPUTS:" + i + " ---->" + HminPIDOutput + "\n")*/
}
)
HmaxPIDOutputs.allMembers.forEach(myMember|
if (myMember.name.contains(i))
{
/*myMember.sendCommand(myMember.historicState(now.minusSeconds(20)).state.toString)*/
/*print("\n H PREVIOUS INTEGRALS:" + i + " ---->" + "\n")*/
HmaxPIDOutput=(myMember.state as DecimalType).intValue
/*print("\n H MAX PID OUTPUTS:" + i + " ---->" + HmaxPIDOutput + "\n")*/
}
)
HpreviousIntegrals.allMembers.forEach(myMember|
if (myMember.name.contains(i))
{
/*print("\n HKPS:" + i + " ---->" + "\n")*/
HpreviousIntegral=(myMember.state as DecimalType).doubleValue
/*print("\n H PREVIOUS INTEGRALS:" + i + " ---->" + HpreviousIntegral + "\n")*/
}
)
HpreviousErrors.allMembers.forEach(myMember|
if (myMember.name.contains(i))
{
/*print("\n H PREVIOUS ERRORS:" + i + " ---->" + "\n")*/
HpreviousError=(myMember.state as DecimalType).doubleValue
/*print("\n H PREVIOUS ERRORS:" + i + " ---->" + HpreviousError + "\n")*/
}
)
HpreviousErrorIs.allMembers.forEach(myMember|
if (myMember.name.contains(i))
{
/*print("\n H PREVIOUS ERRORS I :" + i + " ---->" + "\n")*/
HpreviousErrorI=(myMember.state as DecimalType).doubleValue
/*print("\n H PREVIOUS ERRORS I:" + i + " ---->" + HpreviousErrorI + "\n")*/
}
)
Hepsilons.allMembers.forEach(myMember|
if (myMember.name.contains(i))
{
/*myMember.sendCommand(myMember.historicState(now.minusSeconds(20)).state.toString)*/
/*print("\n H EPSILONS:" + i + " ---->" + "\n")*/
Hepsilon=(myMember.state as DecimalType).doubleValue
/*print("\n H EPSILONS :" + i + " ---->" + Hepsilon + "\n")*/
}
)
Hproportional=HPIDError
/*Hintegral= HpreviousIntegral*/
/*print("\n ALL CONSTANTS RETRIEVED AND ASSIGNED \n")*/
if (HPIDError.doubleValue<=0.doubleValue)
{
Hintegral=((HpreviousIntegral+HPIDError*HPIDdt)-HcorrectingIntegral).doubleValue
}
if ((HPIDError.doubleValue<=-Hepsilon.doubleValue) || (HPIDError.doubleValue>=Hepsilon.doubleValue))
{
Hintegral=((HpreviousIntegral+HPIDError*HPIDdt)).doubleValue
}
if (Hintegral>7800)
{
Hintegral=7800
}
else if (Hintegral<0)
{
Hintegral=0
}
if (HPIDError.doubleValue!=HpreviousErrorI)
{
HpreviousError=HpreviousErrorI
HpreviousErrors.allMembers.forEach(myMember|
if (myMember.name.contains(i))
{
myMember.sendCommand(HpreviousError)
}
)
}
if (HPIDError > maxDerivativeError)
{
/*print("\n Inainte de impartire --- "+ i+ "\n")*/
if (HPIDdt==0)
{
Hderivative=0
}
else
{
Hderivative=((HPIDError-HpreviousError)/HPIDdt).doubleValue
}
}
else
{
Hderivative=0
}
if (Hderivative>0.004)
{
Hderivative=0.004
}
else if (Hderivative<0)
{
Hderivative=0
}
HpreviousErrorI=HPIDError.doubleValue
HpreviousErrorIs.allMembers.forEach(myMember|
if (myMember.name.contains(i))
{
myMember.sendCommand(HpreviousErrorI)
}
)
HPIDOutput=(Hkp*Hproportional+Hki*Hintegral+Hkd*Hderivative).intValue
if (HPIDOutput>0)
{
HPIDOutput=HPIDOutput+HminPIDOutput
}
HpreviousIntegral=Hintegral
HpreviousIntegrals.allMembers.forEach(myMember|
if (myMember.name.contains(i))
{
myMember.sendCommand(HpreviousIntegral)
}
)
if (HPIDOutput<=0)
{
HPIDOutput=0
}
else if (HPIDOutput>0)
{
if (HPIDOutput<HminPIDOutput)
{
HPIDOutput=HminPIDOutput
}
else if (HPIDOutput>HmaxPIDOutput)
{
HPIDOutput=HmaxPIDOutput
}
}
print("\n HEATING PID OUTPUT:" + i + " ---->" + HPIDOutput + "\n")
HPIDOutputs.allMembers.forEach(myMember|
if (myMember.name.contains(i))
{
myMember.sendCommand(HPIDOutput)
}
)
}
else
{
HPIDOutputs.allMembers.forEach(myMember|
if (myMember.name.contains(i))
{
myMember.sendCommand(0)
}
)
}
}
}
end
Cooling PID:
import org.openhab.core.library.types.*
import java.lang.Math
import java.util.Date
import org.joda.time
import java.util.Map
import org.joda.time.*
import org.joda.time.DateTime
import org.openhab.core.library.types.DateTimeType
var double ClastHourChange
var double ClastMinuteChange
var double ClastSecondsChange
var double ClastChange
var double CPIDdt
var double CPIDSetpoint
var double CPIDTemperature
var double CPIDError
var double Cproportional=0
var double Cintegral=0
var double Cderivative=0
/*var String[] PIDZones=newArrayList("S01", "P01", "P02", "P03", "P04", "P05", "P07", "P16", "P18", "P20", "M02", "M03", "M04", "M07", "M08")*/
var String[] PIDZones=newArrayList("P01", "P02", "P03", "P04", "P05", "P07", "P08", "P09", "P10", "P11", "P12", "P13", "P14", "P15", "P16", "P17", "P18", "P19", "P20", "P21", "M02", "M03", "M04", "M05", "M07", "M08", "M09", "M10")
var double Ckp
var double Cki
var double Ckd
var double Cepsilon
var double CpreviousIntegral = 0
var double CpreviousError = 0
var double CpreviousErrorI = 0
var double CcorrectingIntegral
var int CminPIDOutput
var int CmaxPIDOutput
var int CPIDOutput
var double maxDerivativeError = 0.3
var String Chcinput
rule CPIDController
when
/*Item thermostatSPchanged changed
or
Item measuredTemperatures changed
or*/
Time cron "5 * * * * ?"
or
Time cron "35 * * * * ?"
then
if (delayRules.state.toString=="ON")
{
for (String i:PIDZones)
{
/*print("\n STARED COLLING PID CONTROLLER RULE \n")*/
heatingCoolingInput.allMembers.forEach(myMember|
if (myMember.name.contains(i))
{
Chcinput=myMember.state.toString
}
)
if (Chcinput=="OFF")
{
measuredTemperatures.allMembers.forEach(myMember|
if (myMember.name.contains(i))
{
print("\n FOUND TEMPERATURE \n")
ClastHourChange = myMember.lastUpdate.hours
ClastMinuteChange = myMember.lastUpdate.minutes
ClastSecondsChange=myMember.lastUpdate.seconds
ClastChange= ((((ClastHourChange*60)+ClastMinuteChange)*60)+ClastSecondsChange).doubleValue
CPIDdt=now.getSecondOfDay - ClastChange
CPIDTemperature=(myMember.state as DecimalType).doubleValue
print("\n CPIDTEMPERATURE:" + CPIDTemperature +"\n")
if (CPIDdt<0)
{
CPIDdt=(86399-ClastChange+now.getSecondOfDay).doubleValue
}
}
)
thermostatSetpoints.allMembers.forEach(myMember|
if (myMember.name.contains(i))
{
print("\n FOUND SETPOINT \n")
CPIDSetpoint=(myMember.state as DecimalType).doubleValue
print("\n CPIDSETPOINT:" + CPIDSetpoint +"\n")
CPIDError=(CPIDSetpoint.doubleValue-CPIDTemperature.doubleValue).doubleValue
}
)
Ckps.allMembers.forEach(myMember|
if (myMember.name.contains(i))
{
/*myMember.sendCommand(myMember.historicState(now.minusSeconds(20)).state.toString)*/
Ckp=(myMember.state as DecimalType).doubleValue
}
)
Ckis.allMembers.forEach(myMember|
if (myMember.name.contains(i))
{
/*myMember.sendCommand(myMember.historicState(now.minusSeconds(20)).state.toString)*/
Cki=(myMember.state as DecimalType).doubleValue
}
)
Ckds.allMembers.forEach(myMember|
if (myMember.name.contains(i))
{
/*myMember.sendCommand(myMember.historicState(now.minusSeconds(20)).state.toString)*/
Ckd=(myMember.state as DecimalType).doubleValue
}
)
CcorrectingIntegrals.allMembers.forEach(myMember|
if (myMember.name.contains(i))
{
/*myMember.sendCommand(myMember.historicState(now.minusSeconds(20)).state.toString)*/
CcorrectingIntegral=(myMember.state as DecimalType).doubleValue
}
)
CminPIDOutputs.allMembers.forEach(myMember|
if (myMember.name.contains(i))
{
/*myMember.sendCommand(myMember.historicState(now.minusSeconds(20)).state.toString)*/
CminPIDOutput=(myMember.state as DecimalType).intValue
}
)
CmaxPIDOutputs.allMembers.forEach(myMember|
if (myMember.name.contains(i))
{
/*myMember.sendCommand(myMember.historicState(now.minusSeconds(20)).state.toString)*/
CmaxPIDOutput=(myMember.state as DecimalType).intValue
}
)
CpreviousIntegrals.allMembers.forEach(myMember|
if (myMember.name.contains(i))
{
CpreviousIntegral=(myMember.state as DecimalType).doubleValue
}
)
CpreviousErrors.allMembers.forEach(myMember|
if (myMember.name.contains(i))
{
CpreviousError=(myMember.state as DecimalType).doubleValue
}
)
CpreviousErrorIs.allMembers.forEach(myMember|
if (myMember.name.contains(i))
{
CpreviousErrorI=(myMember.state as DecimalType).doubleValue
}
)
Cepsilons.allMembers.forEach(myMember|
if (myMember.name.contains(i))
{
/*myMember.sendCommand(myMember.historicState(now.minusSeconds(20)).state.toString)*/
Cepsilon=(myMember.state as DecimalType).doubleValue
}
)
/*print("\n RETRIEVED ALL PARAMETERS \n")
print("\n CPROPORTIONAL:" + Cproportional +"\n")
print("\n CPIDERROR:" + CPIDError +"\n")*/
Cproportional=CPIDError
/*Cintegral= CpreviousIntegral*/
if (CPIDError.doubleValue>=0.doubleValue)
{
Cintegral=((CpreviousIntegral+CPIDError*CPIDdt)-CcorrectingIntegral).doubleValue
}
if ((CPIDError.doubleValue<=-Cepsilon.doubleValue) || (CPIDError.doubleValue>=Cepsilon.doubleValue))
{
Cintegral=((CpreviousIntegral+CPIDError*CPIDdt)).doubleValue
}
if (Cintegral<-7800)
{
Cintegral=-7800
}
else if (Cintegral>0)
{
Cintegral=0
}
/*print("\n CINTEGRAL:" + Cintegral +"\n")*/
if (CPIDError.doubleValue!=CpreviousErrorI)
{
CpreviousError=CpreviousErrorI
CpreviousErrors.allMembers.forEach(myMember|
if (myMember.name.contains(i))
{
myMember.sendCommand(CpreviousError)
}
)
}
if (CPIDError < maxDerivativeError)
{
if (CPIDdt==0)
{
Cderivative=0
}
else
{
Cderivative=((CPIDError-CpreviousError)/CPIDdt).doubleValue
}
}
else
{
Cderivative=0
}
if (Cderivative<-0.004)
{
Cderivative=(-0.004).doubleValue
}
else if (Cderivative>0)
{
Cderivative=0
}
/*print("\n CDERIVATIVE:" + Cderivative +"\n")*/
CpreviousErrorI=CPIDError.doubleValue
CpreviousErrorIs.allMembers.forEach(myMember|
if (myMember.name.contains(i))
{
myMember.sendCommand(CpreviousErrorI)
}
)
CPIDOutput=(Ckp*Cproportional+Cki*Cintegral+Ckd*Cderivative).intValue
if (CPIDOutput<0)
{
CPIDOutput=CPIDOutput+CminPIDOutput
}
CpreviousIntegral=Cintegral
CpreviousIntegrals.allMembers.forEach(myMember|
if (myMember.name.contains(i))
{
myMember.sendCommand(CpreviousIntegral)
}
)
if (CPIDOutput>=0)
{
CPIDOutput=0
}
else if (CPIDOutput<0)
{
if (CPIDOutput>CminPIDOutput)
{
CPIDOutput=CminPIDOutput
}
else if (CPIDOutput<CmaxPIDOutput)
{
CPIDOutput=CmaxPIDOutput
}
}
print("\n COOLING PID OUTPUT:" + i + " ---->" + CPIDOutput + "\n")
CPIDOutputs.allMembers.forEach(myMember|
if (myMember.name.contains(i))
{
myMember.sendCommand(CPIDOutput)
}
)
}
else
{
CPIDOutputs.allMembers.forEach(myMember|
if (myMember.name.contains(i))
{
myMember.sendCommand(0)
}
)
}
}
}
end
The idea is that I would like to create an action with all these parameters. I already started something, do you think this work is of any help turning it in to an action considering the amount of variables that will be required as parameters for the action?
Thanks,
George