Rule to calculate pump filtration time depending on temperature

Hi

I am trying to create a rule for the pump of my pool to adapt the filtration time depending on the temperature of the water.
It is by far, the most advanced rule I have created and would like to know if someone could have a look at it, and maybe make it simplyer?

rule "Calculate the pump running time based on temperature daily at 8:45"
when
	Time cron "0 45 8 * * ?"
then
	var int durationHrInt
	var Timer poolOffTimer
	var DateTime stopTmr = parse(now.getYear() + "-" + now.getMonthOfYear() + "-" + now.getDayOfMonth() + "T" + startHrInt + ":00").plusHours(durationHrInt)
    var int temperature = (temperature_pool.state as DecimalType).intValue
	if((temperature.state as DecimalType) > 24)	{
		postUpdate(durationHrInt == 12)
		poolOffTimer = createTimer(stopTmr) [| 
			sendCommand(poolPumpSwitch, OFF)
		]
	}
	else {
		postUpdate(durationHrInt == 0.5*temperature)
		poolOffTimer = createTimer(stopTmr) [| 
			sendCommand(poolPumpSwitch, OFF)
		]
	}
end

Also, I am not sure if the calculation of 0.5*temperature will return a number which can be used for the duration. Should it be rounded?

Thanks

I think I would combine you if and else. Basically:

If temp > 24 then duration = 12
Else duration = .5*temp
The rest of the stuff here.

Sorry on my phone could not format with exact code. I have not used timers yet to comment if other things in rule are correct. But from reading I believe modifying the if statement could eliminate some duplicated code.

  • Because the poolOffTimer is a local variable (i.e. only exists inside the Rule) it goes away when the Rule exits. That may be fine. If you don’t care in any of your other Rules that there is a Timer this is perfectly fine. Only if that is the case, just eliminate the poolOffTimer variable. It doesn’t do anything for you. If you do need to check if there is a poolOffTimer active, you need to move the var Timer poolOffTimer to the top of your .rules file so it is a global.

  • durationHrInt is never set to anything. I’ve no idea what it would be set to but I’m pretty sure we can’t count on it being set to anything reasonable.

  • startHrInt is never defined anywhere. Given this Rule gets triggered at a certain time I would assume that the pump starts at that time (08:45). If it doesn’t run when the pump starts, why does this Rule run at a different time?

  • Assuming that the start time is when this Rule runs, then stopTmr can easily be set using val stopTmr = now.plusHours(durationHrInt) assuming durationHrInt is set to something reasonable. If not, then where does startHrInt come from? Anyway, using startHrInt, the line would still be simpler as val stopTmr = now.withTimeAtStartOfDay.plusHours(startHrInt + durationHrInt).

  • Avoid primitives unless avoiding it is impossible. The use of primitives can cause extended load time compile times (i.e. OH will take a lot longer to load your .rules file) and it limits some flexibility of the language and can cause unexpected type exceptions if you don’t use it just properly the right way. There is no reason to use .intValue for temperature.

  • postUpdate(durationHrInt == 12) makes no sense. durationHrInt isn’t an Item, its a local variable. You can’t post update to a variable. And even if you could, this is posting the result of durationHrInt == 12 which is either true (durationHrInt) is equal to 12, or false.

  • As Danny suggested, don’t duplicate the creation of the Timer.

  • Use the method on the Item, not the Action for postUpdate and sendCommand when you know the Item you are updating or commanding. https://www.openhab.org/docs/configuration/rules-dsl.html#manipulating-item-states

So assuming the Rule runs when the pump starts it would simplify and be corrected to:

rule "Calculate the pump running time based on temperature daily at 8:45"
when
    Time cron "0 45 8 * * ?"
then
    val stopPumpTime = if(temperature.state > 24) now.plusHours(12) else now.plusHours(Math::round(0.5*temperature.state)) 
    createTimer(stopPumpTime, [ | poolPumpSwitch.sendCommand(OFF) ]
end

If the Rule doesn’t start at 08:45 then you can use

when
    val startPumpTime = now.withTimeAtStartOfDay.plusHours(startHrInt) // assumes startHrInt is defined elsewhere 
    val stopPumpTime = if(temperature.state > 24) startPumpTime.plusHours(12) else startPumpTime.plusHours(Math::round(0.5*temperature.state))
    createTimer(stopPumpTime, [ | poolPumpSwitch.sendCommand(OFF) ]
end
1 Like

Thank you @Thedannymullen and @rlkoshak!

@rlkoshak that’s a clear explanation you gave!
And now my rule is so simple!

To explain a bit more the situation:

  • no I don’t use the timer anywhere else. The pump is started by a Time cron daily at 8am. Then I want to wait some time for the temperature of the circulating water to stabilize before counting how long it should run.
  • I thought that setting the durationHrInt within the rule by a postupdate would allow me to use it as the calculated running time.
  • startHrInt was defined before in another rule which allowed me to change the starting time and running time with presets. It was before I installed a thermometer to determine the temperature and thus the running time.
  • The temperature was defined as intValue to avoid it from being used in other rules. Good to know that it is better to avoid.
  • OK for the postupdate, so if I want to change the value of a local variable in a rule what should I use?

Your first example works perfectly for me! I just updated it with the corresponding values and everything is running as expected.

One last question, if I want the pump to run for half the time in the morning and half in the afternoon (from 8am and then from 2pm) should I just duplicate this rule? Or there is a smater way?

Thanks again for the answers, and the time.

rule "Calculate the pump running time based on temperature daily at 8:00 and 14:00 for half the time"
when
    Time cron "0 0 8 * * ?" or
    Time crom "0 0 14 * * ?"
then
    val stopPumpTime = if(temperature.state > 24) now.plusHours(6) else now.plusHours(Math::round(0.25*temperature.state)) 
    createTimer(stopPumpTime, [ | poolPumpSwitch.sendCommand(OFF) ])
end

Thanks @vzorglub !

Why am I always thinking about the most complicated solutions first? :slight_smile:

Again, I am sure that someone can find someway easier:
I have three sensors (temperature, Cl and pH) on the pool which I would like that they update only when the pump is running. So at the moment for each I have the following rule:

rule "Update water temperature when pump running"
when
    Number temperature_pool changed
then
    if(poolPumpSwitch.state == ON)
	{
	temperature_updated.postUpdate(temperature_pool)
	}
end

Is there someway to group the three?

Your explanations belie some fundamental misunderstandings about how Rules work.

postUpdate is one way to set the state of Items. That is all. Variables are set using =.

If you want to set a variable that keeps it’s value across multiple runs off the rule or across multiple different rules the variable needs to be declared as a global.

Presumably as a global? Otherwise this rule would not be able to use it.

Using intValue had nothing to do with whether a variable can be used in other rules. Where you declare it (i.e. within the Rule it as a global) controls that.

=

First of all, you can’t send an Item in a postUpdate. You must use

temperature_updated.postUpdate(temperature_pool.state)

To answer your question, eventually you will want to do something like Design Pattern: Associated Items. But I would recommend waiting until you are a bit more comfortable with some of the Rules DSL basics first. In particular you need more experience with variables, Items, and the difference between the two.