PID action

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

@george.erhan Great job !
I’m interesting by your solution to control my electrical heaters. Is it possible ?

Yes it is! Give me some details (what type of heater, how many stages/steps, etc) and I’ll tell you how to do it.

Wow - that would be awesome for such a solution - i also asked for such a solution earlier to control my underfloor heating…
Could be great to have a kind of function where you could do the PID in :slight_smile:

The amount of variables per room is pretty big (around 14), so having a function with 14 parameters is very hard to control. My approach on this is to add an auto tuning PID section! I’ll keep you updated on the progress!

Sounds very nice - but no, not nessecary to have it as a function - but just a “small” section in the program… Would be very nice with a PID an some kind of auto tuner…

Currently i’m trying to control my underfloor heating in a way so the tiles never get cold, so i never shut off the valve completely - but it’s very hard to program without the PID part…

Dear Rasmus,

You will need a PWM driver for the valve control. More than that, take care with the surface contact temperature (if there is no heating demand from the ambient sensor, the floor temperature should not be set above the measured ambient temperature). You can use the heating PID, but you will need more code specific to your setup for what is turned ON/OFF and when (the so called temperature dead zone).

Best regards,

George

Yes i know that… i habe sensors on outside temperature and for the water into the underfloor heating, where I can do some dynamically adjustments for the dead zone in order to “hit” this zone so the floor feels hot…

The pwm Modulation is build in my output module, so I only send a % value for the valve

Best way to control the water temperature is using a mixing valve (3 way valve). If there is a 2 way valve the accuracy of the set temperature is pretty low, moreover the pressure loss increases. For what you want to achieve the PID above is ok for sending signal to the PWM, but tune the Kp, Kd, Ki constants, now they are set for ambient temperature control.

Indeed, the best thing would be to put in a mixer valve… I’ve also thought about that… my problem is I only have one pump and also my preheater for ventilation and towel rails is on the same system, so if I set the temperature very low for the floor it also takes the other components… but maybe I need to change that and split it in 2 circuits

Hi @george.erhan,
Some details of my electrical heater installation:

  • Day zone: 2 x Sauter Bolero 2000w with integrated thermostat and a “pilot” wire with six modes (off, out of freezing, eco, comfort, comfort -1°C and comfort -2°C)
  • Night zone: 2x Sauter Bolero 1500w with integrated thermostat and a “pilot” wire with six modes (off, out of freezing, eco, comfort, comfort -1°C and comfort -2°C)
    N.B: these heaters have inertness…

For each zone, I would like to define the mode (comfort, etc.) time period by time period. E.g: between 12:30 and 14:00 -> comfort -1°C mode, 14:00 - 18:00 -> eco mode, 18:00 - 22:00 -> comfort mode, etc.

Hi @george.erhan,

Its some while since the last update of this post.
Can you share some working examples?
So for example take one room with only a temperature sensor that measures the room temperature, a setpoint and a valve to open and close the heating demand, so basically an on-off switch.

if for such a situation both the items file and the rule can be provided it is a more practical approach to work from.

Thanks,

Maarten

Hi @snowbird,

As lame as it may seem, the overall ambient temperature control is far more complicated. I shared these lines of code hoping someone will get in the same “boat”.
PID control is not something new, nor easy to understand!
So, please understand that I am willing to share knowledge, but not doing your homework! Thus said, please check the following links:
Not the best insight knowledge, but a very good start!,
then this one, with the enclosed not so very bright (but working!) initial contribution!
So, fair and square, if you want to switch a valve on and off, my topic is not for you. If you want to send some commands to devices that can handle the basic control (below 1-200ms), then you just hit the jackpot!

BR,
George

Hi George,

Thank you for your quick reply.
I am not sure if the question is weather I want to do homework or not, the
question is if something can be used in a modular way by a broader audience.
I do believe everybody in the community tries to make OpenHAB as easy to
use as possible.

If I buy a Honeywell thermostat I also don’t need to think about their
design logic, I can simply use the device.
Also there are plenty hardware based PID controllers which are more than
reasonable priced which simply require a temperature sensor to connect or
have an ADC input and a switching output.

I think the main question is, if you are able to provide some more or less
ready to use PID controller building block which can easily be used
within OpenHAB or not.
As of now there is no such building block so it could definitely be adding
value.

If your answer is:“you need to study it by yourself”, then of course it can
from your point of view be a valid answer, but it I am not sure if it is
really helping the community.

Maarten

1 Like

Yes I am (although you were not very polite)! Read the link provided previously, and put this in your openHAB addons folder!

If you already have this, why are you asking?

Any open source project requires studying (yes, by yourself)! IMHO helping the community means sharing knowledge!

1 Like

Hello George…
Finally i managed to get my 3-way valve… Do you know if i should mount it on the forward string or the return string? Basically i think it should be on the return string, then with a “one-way” valve on the forward string, so the return water won’t “cheat” and go retun in the forward string?

Hi George,

Thanks again for your quick response.

It is great to see you already have developed a PID contriller Thing which
can be used as an addon!
The intention is not to be rude, but my response was driven by your remark “you
need to study it by yourself”.
Like many people are using Z-Wave devices just a few know exactly how it
works under the hood and other contribute by testing it but not by studying
the protocol itself.

So sharing knowledge is for some people the die hard programming and for
others to contribute in testing the “in the field” solution, both sides
cant be successfull without the other side.

Further there are many many sensors that can do temperature readings and
provide OpenHAB witch such data, but as far as I know your addon is the
first PIDController that actually brings this logic directly in OpenHAB.

Once more thanks for your contribution.

Maarten

Hey @george.erhan. I’m trying to join in into helping test and standardize your binding, which is an awesome idea and a very important piece in a smart home.

I saw Issues are disabled in your github repository. Where do you want us to file issues and improvements?

I also would like to de-localize the debugging output to english. I try to use it and it kinda works, because I know how PID controllers work, but it’s confusing anyways :smiley: Can I reformat your README.md? I saw what is open in your merge request. I would totally help out getting these things done.

I also have problems with the functionality of the binding itself, which stops working as soon as the INPUT diverges from the SETPOINT, but that may be better put into an github issue.

AFAIK it should stop scheduling (looping) when setpoint and input are the same not when they are different.

Yes, for the moment, it will not be issued as a binding, but as an automation module, so things are going to change. I will release it in the near future (still working).

So, thank you for your attention to details, I will definitely keep in touch with you and make things happen.

Best regards,
George

1 Like

This looks like being just the perfectly fitting thing. Great, I am waiting to help out where I can as soon as you tell me. :+1: