Switch/Group changed Rule not working as expected

This rule sort of works, nearly, it is intended to switch off the Central Heating when the maximum actual temperature of any TRV in a group is greater than the maximum desired temperature of any TRV in the group. In other words, the actual temp is higher than the highest setpoint for a TRV.

However, what happens when max actual > max setpoint , is that when I move a Switch widget (for Central Heating) on a Sitemap , from OFF to ON, widget immediately indicates ON then immediately goes to OFF (I suspect, the rule fires and correctly turns off the Switch). However the Sitemap widget then goes back to ON and only after I refresh the screen , does the widget now indicates OFF.

I have checked the openhab.log and the automated message is fired as soon as I select ON .So how do I refresh a sitemap in the rule? although I wouldn’t have thought I needed to do this because the widgets are linked to items linked to channels/groups so OH knows to refresh the ON/OFF widget?

Rule

rule 
    "HVAC_CH_Off"
when
    Member of g_TRV_Actual_Temperature changed 
    or Member of g_TRV_Set_Temperature changed 
    or Member of g_HVAC_Switch changed to ON
then 
    /*
    logInfo("HVAC_CH_Off.rules", "Checking ACTUAL temp {} against MAX temp {}", g_TRV_Actual_Temperature.state, g_TRV_Set_Temperature.state)
    */
    if (i_HVAC_CH_Switch.state == ON && (g_TRV_Actual_Temperature.state >= g_TRV_Set_Temperature.state)) {
    i_HVAC_CH_Switch.sendCommand(OFF)
    logInfo("HVAC_CH_Off.rules", "CENTRAL HEATING turned off by rule")
    }
end

Items

Group g_HVAC
Group g_HVAC_Switch
Group:Number:MAX g_TRV_Actual_Temperature 	"Max Temp TRV"
Group:Number:MAX g_TRV_Set_Temperature	 	"Max Temp TRV"
...
String i_TRV_Lounge_Get_Temperature			"Current temperature [%s]C"	<temperature> 	(g_HVAC, g_TRV, g_TRV_Actual_Temperature) {channel="mqtt:topic:b_MQTT_Broker:t_TRV_Lounge:c_Temperature"}
String i_TRV_Lounge_Set_Temperature			"Set temperature [%s]"		<temperature>	(g_HVAC, g_TRV, g_TRV_Set_Temperature) {channel="mqtt:topic:b_MQTT_Broker:t_TRV_Lounge:c_Set_Temperature"}
...
/* Danfoss RF Controller */
Switch	i_HVAC_CH_Switch "CH On/Off" <switch>	(g_HVAC, g_HVAC_Switch)	{channel = "zwave:4849599d:t_Zcontroller:node3:swtich_binary2"}
Switch	i_HVAC_HW_Switch "HW On/Off" <switch>	(g_HVAC, g_HVAC_Switch)	{channel = "zwave:4849599d:t_Zcontroller:node3:swtich_binary1"}

Sitemap

	Frame label="HEATING CONTROLS" {
		Switch 		item=i_HVAC_CH_Switch				label="Central heating On/Off" 
		Switch 		item=i_HVAC_HW_Switch				label="Hot water On/Off"
		Text 		item=g_TRV_Set_Temperature	    	label="Max desired temperature [%.1f]" icon="temperature"
		Text 		item=g_TRV_Actual_Temperature	    label="Max ACTUAL temperature [%.1f]" 	icon="fire"
	}

???

EDIT: Oh, and I’m on a LAN (i.e. not Cloud / WAN) , and , I am using the OH app on Android. I note a refresh must be done manually (swipe down on Android) and browser (F5).,

Events

2020-01-13 20:51:16.604 [GroupItemStateChangedEvent] - g_TRV_Actual_Temperature changed from 21.6992 to 21.5 through i_TRV_Lounge_Get_Temperature
2020-01-13 20:56:04.396 [ome.event.ItemCommandEvent] - Item 'i_HVAC_CH_Switch' received command ON
2020-01-13 20:56:04.470 [nt.ItemStatePredictedEvent] - i_HVAC_CH_Switch predicted to become ON
2020-01-13 20:56:04.521 [vent.ItemStateChangedEvent] - i_HVAC_CH_Switch changed from OFF to ON
2020-01-13 20:56:04.524 [ome.event.ItemCommandEvent] - Item 'i_HVAC_CH_Switch' received command OFF
2020-01-13 20:56:04.586 [nt.ItemStatePredictedEvent] - i_HVAC_CH_Switch predicted to become OFF
2020-01-13 20:56:04.607 [vent.ItemStateChangedEvent] - i_HVAC_CH_Switch changed from ON to OFF

You can’t.

That’s correct. The various UIs are more or less effective at auto-refreshing when states change. It is possible to misconfigure to make that worse.

Well, let’s try and nail it down it a bit.
May we see your openhab.log and events.log entries showing the sequence?

I don’t really understand your choice of rule trigger, in conjunction with comparison.

when
   Member of g_TRV_Actual_Temperature changed

okay 
 but your comparison

(g_TRV_Actual_Temperature.state >= g_TRV_Set_Temperature.state))

is about the states of the Groups (the MAX), not about states of members.
Frankly, you don’t care when a member changes at all, and you’re really interested in (and should trigger from) the group state changing.

Apologies, I edited the prev post to include the log.

mmm, ok, I may have done something wrong here, but 


(g_TRV_Actual_Temperature.state >= g_TRV_Set_Temperature.state))

So here, I intended to test whether the maximum value of any of the TRVs temperature reading (g_TRV_Actual_Temperature) is higher than the maximum value from the Setpoints (g_TRV_Set_Temperature) from all the TRVs. If so, then turn off the boiler because it’s not needed until a room cools down and the inverse holds true (i.e. Actual is less than Set)
The comparison is needed because the change in states of groups could be in the other direction where I need to turn ON the boiler automatically if a room cools down.

As I say, it appears to be working looking at the logs, the issue is that the sitemap isnt being refreshed - I think :slight_smile:

Yes. My comment is that you are running your rule for every change of every member device.
Pointless, as you are comparing group states.
Group states are calculated from MAX.
You can make a hundred member changes without changing MAX.
Trigger the rule when group state changes, who cares if a member Item changes and does not cause a group state change.

In other threads you are worrying about unnecessary overheads - here’s one, a trivial one, but they add up.

Yes. I’ve no experience with Android app, but do search your openhab.log for sitemap related messages.

Sorry , I am not following you. To recap:-

I have two groups aggregated by MAX which gives me the Max of both the actual temperate and the (desired) Set temperatures.

Group:Number:MAX g_TRV_Actual_Temperature 	"Max Temp TRV"
Group:Number:MAX g_TRV_Set_Temperature	 	"Max Set TRV"

TheI have to n compare max actual temp (g_TRV_Actual_Temperature) with max set temp (g_TRV_Set_Temperature) to test if the boiler can be turned off (i.e. max actual temp read by all TRVs is greater than the highest set point of all TRVs).

    if (i_HVAC_CH_Switch.state == ON && (g_TRV_Actual_Temperature.state >= g_TRV_Set_Temperature.state)) {
    i_HVAC_CH_Switch.sendCommand(OFF)
    logInfo("HVAC_CH_Off.rules", "CENTRAL HEATING turned off by rule")
    } 

I don’t think I follow you when you say it is not needed , can you elaborate by example please.

This rule triggers when Group state changes. If the group doesn’t change, the rule doesn’t trigger.

when
   Item myGroup changed

This rule triggers when any member of the Group changes. The Group itself may or may not change state.

when
   Member of myGroup changed

In the case of your rule with comparison, there’s no point running the rule unless the things you are comparing have changed.

oh hang on, I get, you mean remove “Member of” on the rule, but I still need the comparison?

Ok, so this is the new rule.

rule 
    "HVAC_CH_Off"
when
    Item g_TRV_Actual_Temperature changed 
    or
    Item g_TRV_Set_Temperature changed 
then 
    /*
    logInfo("HVAC_CH_Off.rules", "Checking ACTUAL temp {} against MAX temp {}", g_TRV_Actual_Temperature.state, g_TRV_Set_Temperature.state)
    */
    if (i_HVAC_CH_Switch.state == ON && (g_TRV_Actual_Temperature.state >= g_TRV_Set_Temperature.state)) {
    i_HVAC_CH_Switch.sendCommand(OFF)
    logInfo("HVAC_CH_Off.rules", "CENTRAL HEATING turned off by rule")
    } 
end

You were correct, Thank You. I was triggering the rule on member changes and not when the MAX of the Group value changes (up or down). I also fixed the on/off/on/off issue - I was sending the OFF command to the Switch and the rule had a trigger on the switch. So kind of recursive loop.

Anyway, all sorted now in terms of the Rule, AND, I also added rule code to turn heating ON for the inverse condition. But the refresh of the UI still isnt happening unless I refresh manually on the browser or android app. Any ideas on this?

EDIT: Just noted your initial comment on my question “how do I refresh a sitemap in a rule” , your reply “you can’t”. Mmmm, this sounds like a bug 
 I would imagine ., in any software, the GUI presentation should reflect a change in any of the underlying hierarchy. I mean Windows does this , most OS and apps do. Is this a bug or “feature request”.?

Solution for anyone visiting here as a search (credit to @rossko57)

Sitemap

Text 		item=i_HVAC_HW_Timer				label="Hot water off in [%d minutes]" visibility=[i_HVAC_HW_Switch!=OFF]

Items

Number i_HVAC_HW_Timer "Hot water timer" <clock-on> (g_HVAC)

Rule

rule 
    "HVAC_HW_Timer"
when
    Item i_HVAC_HW_Switch changed 
then 
    /* Initiate 60 minute timer if Hot Water demanded */
    if (i_HVAC_HW_Switch.state == ON) {
        HW_Timer = createTimer(now.plusMinutes(60), [|
            // code that should execute on expiry of timer
            i_HVAC_HW_Switch.sendCommand(OFF)
            logInfo("HVAC_HW.rules", "HOT WATER turned OFF by rule")
            ])          
        logInfo("HVAC_HW.rules", "HOT WATER countdown timer started by rule")
    }
end


rule 
    "HVAC_HW_Countdown"
when
    Item i_HVAC_HW_Switch changed to ON
then
    /* Display 60 minute countdown */
    var count = 60
    while(count >= 0) {
        i_HVAC_HW_Timer.postUpdate(count)
        count = count - 1
        Thread::sleep(60000)
    }
end

Nope.
By design, the UI refresh is autonomous. You can do nothing about it from rules, not least because you have no idea (and cannot influence) what UI the user is using.

The autonomous part can go wrong of course.

Have you searched your openhab.log for mention of ‘sitemap’ yet?

I explored Timers a bit more, and searching the forums came upon Expiry Timers from a great post from @rlkoshak here Design Pattern: Expire Binding Based Timers

But my timer isnt expiring (sending the OFF command)

Any ideas why?

items

/* HW Countdown Timer , even set to 1 minute to interrupt the 60 minute counter. Didnt work */
Switch i_HVAC_HW_Timer_Switch { expire="1m,command=OFF" }
Number i_HVAC_HW_Counter "Hot water timer" <clock-on> (g_HVAC) 
/* Sitemap switch */
Switch	i_HVAC_HW_Switch "HW On/Off" <switch>	(g_HVAC, g_HVAC_Switch)	{channel = "zwave:4849599d:t_Zcontroller:node3:swtich_binary1"}

Rule

// Global var count can be updated from any rule
var count = 0 

rule
    // Start timer when HW switch set to ON
    "HVAC_HW_Start_Timer"
when
    Item i_HVAC_HW_Switch changed to ON
then
    // Start expiry timer
    logInfo("HVAC_HW.rules", "HOT WATER countdown timer STARTED by rule")    
    i_HVAC_HW_Timer_Switch.postUpdate(ON)
    count = 60
    while (count >= 0) {
        i_HVAC_HW_Counter.postUpdate(count)
        count = count - 1
        // Sleep for one minute
        Thread::sleep(60000)
    }
    logInfo("HVAC_HW.rules", "HOT WATER countdown updates finished")    
    i_HVAC_HW_Timer_Switch.sendCommand(OFF)
end


rule 
    // When timer has expired
    "HVAC_HW_Timer_Expired"
when
    Item i_HVAC_HW_Timer_Switch received command OFF
then
    // Set counter to zero - will occult the timer value on the sietmap
    logInfo("HVAC_HW.rules", "HOT WATER countdown timer EXPIRED")    
    count = 0
    //Expiry on switch doesnt send the "OFF" command so force it ??
    i_HVAC_HW_Switch.postUpdate(OFF)
end


rule 
    // Cancel timer if HW switch set to OFF by user
    "HVAC_HW_Cancel_Timer"
when
    Item i_HVAC_HW_Switch changed to OFF
then
    if (i_HVAC_HW_Timer_Switch.state == ON) {
        // Interrupt timer and force expiry
        logInfo("HVAC_HW.rules", "HOT WATER countdown timer INTERRUPTED by rule")    
        count=0
        i_HVAC_HW_Timer_Switch.sendCommand(OFF)
    }
end



On the GUI, when I select HW (i_HVAC_HW_Switch) to ON, the events fire and the timer is started and the countdown appears in the GUI, but the expiry on the Switch i_HVAC_HW_Timer_Switch { expire=“1m,command=OFF” } does not “fire” the OFF command so that the rule
HVAC_HW_Timer_Expired is executed.

Any ideas?

Have you installed the expire binding?

Does item ever go ON? Inspect events.log
Does your rule send it an OFF before it expires? Inspect events.log
Does your rule send it another ON before it expires? This you won’t see in events.log, because updates-to-same are not logged.
But it’s easy to make a diagnostic rule to trigger and report updates.

Please don’t use while-sleep in a finished rule, it is horribly bad practice.

Here is a sleep free countdown technique.

1 Like

ok . I installed expiry binding and have this item:

Number i_HVAC_HW_Counter  <clock> {expire="60s,command=-1", autoupdate="false"} 

but my rule HVAC_HW_Countdown_Tick is fired every minute instead of every second . Is there anyway i can trap ticks every second, because it’s a 60 sec counter and i want to display the countdown in seconds ?


rule
    // Start timer when HW switch set to ON
    "HVAC_HW_Start_Timer"
when
    Item i_HVAC_HW_Switch changed to ON
then
    // Start expiry timer
    logInfo("HVAC_HW.rules", "HOT WATER countdown timer STARTED by rule")    
    i_HVAC_HW_Counter.sendCommand(60) // sixty seconds ??
end

rule
    // When counter is running
     "HVAC_HW_Countdown_Tick" 
when
	Item i_HVAC_HW_Counter received command
then
	var cmd = (receivedCommand as Number).intValue // integers only
	var count = 0

    logInfo("HVAC_HW.rules", "HW countdown tick")

    // 1st time run
	if (i_HVAC_HW_Counter.state != NULL) {  // avoid 1st time run error
		count = (i_HVAC_HW_Counter.state as Number).intValue
        logInfo("HVAC_HW.rules", "HOT WATER count set to {}",count) 
	}
end

That is the “tick” rate of your countdown timer, you got what you asked for.

I would not advise using this method for a seconds based counter.
Expire binding has a won’t-fix accuracy bug for times in seconds; this is detailed in the expire-counter thread comments.

The method is intended for “minutes” use e.g. lighting delays, it is not accurate nor particularly efficient for seconds based counting (and we’re back to why would you want to control hot water with a seconds based timer anyway??)

If you must go with seconds, explore alternative means

1 Like

@rossko57 you are - as always from my perspective - spot on and correct in your advice. however, I just want the 1sec tick so as to visualise my sitemap in an “accelerated” manner then I switch to 60s ticks.

Okay 
 presumably you’ve achieved that, then. The expire binding time setting is the tick rate for this countdown. 60s is one tick/count per minute. 1s is once per second (except that the binding bug will make it tick per two seconds)

When finished playing, note that expire 1m will be more accurate than 60s because of the binding bug.

1 Like

understood. Thank You

Final Solution for a 1 HOUR “Boost” timer for Hot Water boiler.
HVAC=Hot water, Ventilation and Air Conditioning.
HW=Hot Water

HW switch on the sitemap. When moved to ON , the countdown timer is initialised and “ticks” processed at 12 minute intervals. A countdown timer shows “minutes left to HW off”. Initial timer value is 60 (60 x 1 minute = 1 Hour). This can be modified in the rule by adjusting the val timeout

credits to: @rlkoshak and @rossko57

items

/* Danfoss RF Controller */
Switch	i_HVAC_CH_Switch "CH On/Off" <switch>	(g_HVAC, g_HVAC_Switch)	{channel = "zwave:4849599d:t_Zcontroller:node3:swtich_binary2"}
Switch	i_HVAC_HW_Switch "HW On/Off" <switch>	(g_HVAC, g_HVAC_Switch)	{channel = "zwave:4849599d:t_Zcontroller:node3:swtich_binary1"}

/* HW Countdown Timer */
Number i_HVAC_HW_Counter  <clock> {expire="1m,command=-1",autoupdate="false"} 

rules

rule
    // Start timer when HW switch set to ON
    "HVAC_HW_Start_Timer"
when
    Item i_HVAC_HW_Switch changed to ON
then
    // Start expiry timer for HW
    val timeout=60 // ONE HOUR
    //val timeout=5 // SECS for test. Set expiry item to "1s"

    logInfo("HVAC_HW.rules", "User turned on HW. Countdown set to {}",timeout)    
    i_HVAC_HW_Counter.sendCommand(timeout)
end

rule
    // When counter is running -
     "HVAC_HW_Countdown" 
when
	Item i_HVAC_HW_Counter received command
then
	var cmd = (receivedCommand as Number).intValue // integers only
	var count = 0

    logInfo("HVAC_HW.rules", "COUNTER TICK - Start")
    
    // Avoid decrementing when timer not initialised
	if (i_HVAC_HW_Counter.state != NULL) {  
		count = (i_HVAC_HW_Counter.state as Number).intValue
        logInfo("HVAC_HW.rules", "...Counter {} is not null",count)
	}

    // Counter running / timer tick so action on command received
	if (cmd == -1 && count > 0) {  

        // Command -1 received so decrement counter, do not go below zero
        logInfo("HVAC_HW.rules", "...Command=-1, Counter>0 ({}). Decrement",count)
        if (count == 1) {

			// Do actions for NORMAL counter expiry, as we about to zero it now.
            // Send Zero to counter to cancel it.
            logInfo("HVAC_HW.rules", "...Counter will expire normally.")
            //i_HVAC_HW_Counter.postUpdate(0)
            //i_HVAC_HW_Switch.sendCommand(OFF)
        }

        // Counter > 1 so Decrement counter
        i_HVAC_HW_Counter.postUpdate(count - 1)
        logInfo("HVAC_HW.rules", "...Counter now set to {}",count-1)
        
    // Check if Command to refresh/set Counter
    } else if (cmd >= count || cmd < -1) {  
    
        // New or refreshed counter value
        logInfo("HVAC_HW.rules", "...Command {} received. Set Counter to same",cmd)
        if (cmd < -1) {  
            // Force override
			cmd = 0 - cmd  
            logInfo("HVAC_HW.rules", "...Command={} so forcing override",cmd)
    	}

        // We still update even if equal value - resets expire binding
	    logInfo("HVAC_HW.rules", "...Counter reset to {} using Command val", cmd)
        i_HVAC_HW_Counter.postUpdate(cmd)  
		
        // Do startup/continue actions when HW not on.
		if (i_HVAC_HW_Switch.state != ON) {
            logInfo("HVAC_HW.rules", "...Command {} received when HW off so turning ON")
            i_HVAC_HW_Switch.sendCommand(ON)
		}

    // Test if Command 0 received to stop Counter early.
	} else if (cmd == 0)  {  

        // Command 0 received so interrupt countdown and turn off HW
        logInfo("HVAC_HW.rules", "...Command 0 received")
        //if (i_HVAC_HW_Switch.state != OFF) {
            // Force HW OFF
        logInfo("HVAC_HW.rules", "...Post 0 to Counter")
        i_HVAC_HW_Counter.postUpdate(0)
        //}
	} else if (count == 0) {

        // Expired. Turn off HW
        logInfo("HVAC_HW.rules", "...EXPIRED. Request HW Off")
        i_HVAC_HW_Switch.sendCommand(OFF)
    }

    // End of timer tick
    logInfo("HVAC_HW.rules", "COUNTER TICK - End")
end


rule 
    // HW turned off
    "HVAC_HW_Off"
when
    Item i_HVAC_HW_Switch changed to OFF
then
    logInfo("HVAC_HW.rules", "HW Switch OFF event received")
    if (i_HVAC_HW_Counter.state != 0) { 
        
        logInfo("HVAC_HW.rules", "User set HW to Off. Stop Counter")
        // Reset counter to Zero.
        // The "Tick" event will be fired that will request HW OFF.
        // However, we have already set HW to OFF above so this "changed to OFF" event will not fire.
        i_HVAC_HW_Counter.sendCommand(0)
    }
end

sitemap

Switch 		item=i_HVAC_HW_Switch				label="Hot water On/Off"
Text 		item=i_HVAC_HW_Counter				label="Hot water off in [%d minutes]"  	icon="clock-on"	visibility=[i_HVAC_HW_Switch == ON]