Troubleshooting my heating rule

  • Platform information:
    • Hardware: Raspi 4B / 4GB RAM / External SSD
    • OS: Raspbian Lite / Docker
    • openHAB version: 2.5.0

Hi guys,

I’m running repeatedly, but not reliable, into a not so funny problem with my heating rule - and I might need some help from you to resolve that issue…

At nightfall all thermostats should be set to a lower temperature for the night, in the morning all thermostats should be reset to the target temperature they had before. What actually happens sometimes, but not always, is the opposite: at nightfall the thermostats are reset to their target temperature for the day and in the morning the temperature is lowered to the target temperature for the night…

As far as I can see the logic of my rules is fine and it doesn’t seem to be a Homematic related problem - but maybe I’m just overlooking something:

Lets start with the items

Number VItem_targetTemperature_Night
    "Zieltemperatur Nacht [%.1f]"
    <temperature>
    (VItems, GHeating, Items, GRestoreOnStartup)
Number VItem_targetTemperature_Absence
    "Zieltemperatur bei Abwesenheit [%.1f]"
    <temperature>
    (VItems, GHeating, Items, GRestoreOnStartup)

[...]
Group Thermo_LR
    "Heizung Wohnzimmer"
    <radiator>
    (Livingroom, GHeating, Items)
[...]
Number Thermo_LR_TargetTemperature
    "Zieltemperatur Heizung Wohnzimmer [%.1f]"
    <heating>
    (Thermo_LR, VItems, GRestoreOnStartup)
[...]
Number Thermo_LR_TargetTemperature_Day
    "Zieltemperatur Heizung Wohnzimmer Tagsüber [%.1f]"
    (Thermo_LR, VItems, GRestoreOnStartup)
Number Thermo_LR_TargetTemperature_Shadow
    { channel="homematic:HM-CC-RT-DN:ccu:OEQxxxxxxx:4#SET_TEMPERATURE" }
Switch LowBatt_LR_Thermostat
    "WZ Batterie Thermostat"
    <battery>
    (Thermo_LR, GBatteries)
    { channel="homematic:HM-CC-RT-DN:ccu:OEQxxxxxxx:0#LOWBAT" }
[...]

I’m using Homematic thermostats to control the heating in each room. I’m using

  • VItem_targetTemperature_Night to store the temperature to which all thermostats should be set at nightfall,
  • Thermo_XX_TargetTemperature_Day to store the temperature to which the individual thermostat should be reset at morning,
  • Thermo_XX_TargetTemperature is exposed on the sitemap to set a new target temperature,
  • and Thermo_XX_TargetTemperature_Shadow is sending the new target temperature to my thermostat after confirming that the windows in the room are closed

And lets have a look at the relevant rules:

// trigger all thermostats to reset heating to default target temperatures
rule "Good Morning / Returning - Heating"
when
	Item State_Daytime changed from OFF to ON
	or Item State_Presence changed from OFF to ON
then
	// 1. Check to see if the Rule has to run at all, if not exit.
	logInfo(rulesDomaine, "Rule \"Good Morning / Returning - Heating\" triggered")

	// 2. Calculate what needs to be done and create necessary variables
	var Number temp_Bath = 0
	var Number temp_BR = 0
	var Number temp_CR = 0
	var Number temp_LR = 0

	// only for debugging:
	var String msg = "Rule \"Good Morning / Returning - Heating\" triggered"

	if (State_Daytime.state == ON && State_Presence.state == ON) {
		// if it is daytime and if someone is home reset the target temperature from yesterday
		temp_Bath = Thermo_Bath_TargetTemperature_Day.state as Number
		temp_BR = Thermo_BR_TargetTemperature_Day.state as Number
		temp_CR = Thermo_CR_TargetTemperature_Day.state as Number
		temp_LR = Thermo_LR_TargetTemperature_Day.state as Number
		msg += "\n State_Daytime.state == ON && State_Presence.state == ON, set TT to saved temp daytime"
	} else if (State_Daytime.state == ON) {
		// if it is daytime but nobody is home set the TT to temperature for absence
		temp_Bath = VItem_targetTemperature_Absence.state as Number
		temp_BR = VItem_targetTemperature_Absence.state as Number
		temp_CR = VItem_targetTemperature_Absence.state as Number
		temp_LR = VItem_targetTemperature_Absence.state as Number
		msg += "\n State_Daytime.state == ON && State_Presence.state == OFF, set TT to VItem_targetTemperature_Absence"
	} else if (State_Presence.state == ON) {
		// if it is nighttime and somebody is home set the TT to temperature for nighttime
		temp_Bath = VItem_targetTemperature_Night.state as Number
		temp_BR = VItem_targetTemperature_Night.state as Number
		temp_CR = VItem_targetTemperature_Night.state as Number
		temp_LR = VItem_targetTemperature_Night.state as Number
		msg += "\n State_Daytime.state == OFF && State_Presence.state == ON, set TT to VItem_targetTemperature_Night"
	}

	// 3. Do it. Only do actions in like sendCommand in one place at the end of the Rule.
	Thermo_Bath_TargetTemperature.sendCommand(temp_Bath)
	Thermo_BR_TargetTemperature.sendCommand(temp_BR)
	Thermo_CR_TargetTemperature.sendCommand(temp_CR)
	Thermo_LR_TargetTemperature.sendCommand(temp_LR)
	msg += "\n TT is set -> Rule \"Good Morning / Returning - Heating\" finished"
	Message_Debugging.sendCommand(msg)
end

// trigger all thermostats to reset heating to target temperatures for the night
rule "Good Night - Heating"
when
	Item State_Daytime changed from ON to OFF
then
	// 1. Check to see if the Rule has to run at all, if not exit.
	logInfo(rulesDomaine, "Rule \"Good Night - Heating\" triggered")

	// 2. Calculate what needs to be done and create necessary variables

	// only for debugging
	var String msg = "Rule \"Good Night - Heating\" triggered"

	// 3. Do it. Only do actions in like sendCommand in one place at the end of the Rule.
	// Save the current target temperature, but only if it is the daytime 
	// & being present temperature
	if (State_Presence.state == ON) {
		Thermo_Bath_TargetTemperature_Day.sendCommand(
			Thermo_Bath_TargetTemperature.state as Number)
		Thermo_BR_TargetTemperature_Day.sendCommand(
			Thermo_BR_TargetTemperature.state as Number)
		Thermo_CR_TargetTemperature_Day.sendCommand(
			Thermo_CR_TargetTemperature.state as Number)
		Thermo_LR_TargetTemperature_Day.sendCommand(
			Thermo_LR_TargetTemperature.state as Number)
		msg += "\n State_Presence.state == ON, save target temperature"
	}
	Thermo_Bath_TargetTemperature.sendCommand(VItem_targetTemperature_Night.state as Number)
	Thermo_BR_TargetTemperature.sendCommand(VItem_targetTemperature_Night.state as Number)
	Thermo_CR_TargetTemperature.sendCommand(VItem_targetTemperature_Night.state as Number)
	Thermo_LR_TargetTemperature.sendCommand(VItem_targetTemperature_Night.state as Number)
	msg += "\n target temperature set to VItem_targetTemperature_Night -> Rule \"Good Night - Heating\" finished"
	Message_Debugging.sendCommand(msg)
end

// Check for open windows or other modifiers before you change the targetTemperature
rule "Heating Living Room"
when
	Item Thermo_LR_TargetTemperature received command
	or Item Contact_LR_Balcony_Door changed from OPEN to CLOSED
	or Item Contact_LR_Window_Left changed from OPEN to CLOSED
	or Item Contact_LR_Window_Middle changed from OPEN to CLOSED
	or Item Contact_LR_Window_Right changed from OPEN to CLOSED
then
	// 1. Check to see if the Rule has to run at all, if not exit.
	if (Contact_LR_Balcony_Door.state == OPEN 
			|| Contact_LR_Window_Left.state == OPEN 
			|| Contact_LR_Window_Middle.state == OPEN 
			|| Contact_LR_Window_Right.state == OPEN) {
		return
	}

	// Only log if rule applies
	logInfo(rulesDomaine, "Rule \"Heating Living Room\" triggered")

	// 2. Calculate what needs to be done and create necessary variables

        // only for debugging
	Message_Debugging.sendCommand("Heating LR will change from " + Thermo_LR_TargetTemperature_Shadow.state +
		" to " + Thermo_LR_TargetTemperature.state + ". Is it daytime? " + State_Daytime.state +
		", Is somebody at home? " + State_Presence.state)

	// 3. Do it. Only do actions in like sendCommand in one place at the end of the Rule.
	Thermo_LR_TargetTemperature_Shadow.sendCommand(
		Thermo_LR_TargetTemperature.state as Number)
end

And now lets have a look at the debugging messages I get at 23:00 when State_Daytime change from ON to OFF:

  • state of VItem_targetTemperature_Night: 18
  • from rule Good Night - Heating: “Rule “Good Night - Heating” triggered
    State_Presence.state == ON, save target temperature
    target temperature set to VItem_targetTemperature_Night -> Rule “Good Night - Heating” finished”
  • from rule Heating Living Room: “Heating LR will change from 22.00 to 22. Is it daytime? OFF, Is somebody at home? ON”

And the same in the morning:

  • state of Thermo_LR_TargetTemperature_Day: 22
  • from rule Good Morning / Returning - Heating: “Rule “Good Morning / Returning - Heating” triggered
    State_Daytime.state == ON && State_Presence.state == ON, set TT to saved temp daytime
    TT is set -> Rule “Good Morning / Returning - Heating” finished”
  • from rule Heating Living Room: “Heating LR will change from 22.00 to 18.0. Is it daytime? ON, Is somebody at home? ON”

So wtf is going on?? And to make it even more confusing - just sometimes the rules are working as intended, especially if I trigger State_Daytime on purpose to debug the problem…

The events.log that goes with the rule process might be instructive.

I haven’t unravelled it, but observe you’re commanding a lot of Items with other Item’s states. There is risk there …

Item commands and updates are asynchronous.
myItem.sendCommand("X")
posts a command event on to the event bus, and the rule moves on to execute the next line. The rule does not stop and wait for the outcome. The Item state is unchanged.

Some time later, a binding might act on the command. Some time after that, a device might respond with a new status … and at last the binding will update the Item state.
Or very often, autoupdate acting as a psuedo binding will act on the command, guess a likely outcome, and update the Item state with its guess, so you get a nice snappy UI without noticeable delay. But it still takes a finite time. And that rule is not waiting for it.

So in short
myItem.sendCommand("X")
logInfo("test", myItem.state.toString)
will almost certainly produce the old state from before command X … because the command hasn’t been actioned yet.
Asynchronous.

I think parts of your rules boil down to
myItem.sendCommand("X")
otherItem.sendCommand(myItem.state)
which is doomed to inconsistent behaviour

In a real rule, you don’t need to read back the state of the Item to find out what it will become - you know you’ve just posted “X” to it.

An alternative approach, if you need to do some action after the state has really changed, then trigger another rule off the change to do that action.
Do not be frightened to have a cascade of small rules to replace a monster.
It does take a bit to get your head around events-driven rule writing.

You might also consider if you need to send commands to your interim Items, or just post updates. They are not the same thing, and chosen carefully, have different effects.

2 Likes

In regard to logging: I filter / disable most of the event.log to avoid the endless stream of ‘sensor data changed’ messages and to keep the log readable… But yes, it might be a good idea to reverse that to find the underlying problem :sob:

In regard to to asynchronous state changes: Sounds exactly like that could be my problem - but I where thinking / hoping that rules triggered by

rule "Good Night - Heating"
when
	Item State_Daytime changed from ON to OFF
[...]

would only trigger after the change was completed. :thinking:

And will I’m writing that I might have found the culprit - my rule Heating Living Room is triggered by received command instead of changed :scream:

@rossko57 big thx for your input, lets put that idea to the test :grin:

hmm, readable but containing no info when you need it doesn’t help. Turn it around - why would you read it unless lookiing for a problem? If looking for a problem, why hide info?

That’s fine if you use the receivedCommand implicit variable, and don’t expect whatever the Item’s state is to reflect the command.

1 Like

With filtering out the ‘sensor data changed’ messages I’m seeing the messages I’ more interested in - received command and logInfo messages. Means just stuff that was triggered by my rules and not hundreds of sensor data changes :slight_smile:

Yes, that should also work :thinking: