TPI thermostat rule

Hi

I thought I would share my code for an Openhab based TPI (Time Proportional Integral) thermostat. I use a MySensors temperature and humidity sensor (arduino pro mini based), wall mounted and connected to Openhab using RS485 > a Mysensors MQTT gateway > Mosquitto broker in Openhabian (OH3).

The boiler switch is one half of a z-wave Secure thermostat / switch combo. I no longer use the thermostat part of course. In fact the Secure thermostat has a very good TPI algorithm (which they do not disclose!). I wanted to stop using this because I was getting fed up with the delay inherent in using a battery operated z-wave thermostat. So I thought I’d try and get something home built that works as well.

MySensors links:

The code uses a basic PID approach (without the “D” factor). It calculates an “error” factor by adding up an instantaneous proportional § error and cumulative “integral” (I) error and then uses that to calculate how long the boiler should fire. The duty cycle is 10 mins. So if the result is more than 600 seconds the boiler will simply stay on. Otherwise it will come on for as long as it is needed to keep the temperature as close to the set point as possible.

The values of kp and ki need to be tweaked so that it does approach the set point but also not overshoot too much.

HallSetPoint is just a locally defined Number item attached to a stepper widget.

Because there is no cooling function in the system I have to set the integral error to zero each time the set point is changed. Also IntegalError is not changed if the current temperature is above the set point.

This is the code:

var Number SetPoint = 1.00
var Number CurrentTemp = 1.00
var Number Error = 1.00
var Timer LoopTimer = null
var Timer HeatTimer = null
val Number kp = 1.3
val Number ki = 0.02
var Number IntegralError = 0
var Number ProportionalError = 1.00


rule "TPIRuleHall"
when
    Item HallSetPoint changed
then
    if (HallHeatingLogic.state == ON)
    {
        IntegralError = 0
        if (LoopTimer !== null) LoopTimer.cancel
        LoopTimer = createTimer(now, [ |
            if(HallHeatingLogic.state == OFF) LoopTimer = null
            else 
            {
                CurrentTemp = GenericMQTTThing_HallTemperature.state as Number
                SetPoint = HallSetPoint.state as Number
                ProportionalError = ((SetPoint - CurrentTemp) * kp) as Number
                if (SetPoint > CurrentTemp) IntegralError = IntegralError + ((SetPoint - CurrentTemp) * ki)
                Error = ProportionalError + IntegralError
                logInfo ("TPIHall  ", "Current temp = " + CurrentTemp + " Setpoint = " + SetPoint + " PErr = " + ProportionalError + " IErr = " + IntegralError + " Error = " + Error)
                if (Error > 0.2){
                    logInfo ("TPIHall  ", "Heat on")
                    BoilerSwitch.sendCommand(ON)
                    if (HeatTimer !== null) HeatTimer.cancel
                    if (Error > 0.9) Error = 1
                    HeatTimer = createTimer(now.plusSeconds((Error * 600).intValue), [ |
                        logInfo ("TPIHall  ", "Heat off")
                        BoilerSwitch.sendCommand(OFF)
                        HeatTimer.cancel
                    ])
                }
                LoopTimer.reschedule(now.plusMinutes(10))
            }
        ])
    }
end