Http call not sent to all end points (Set room temperature)

  • Platform information:
    • Hardware: Raspberry Pi 4
    • OS: Raspbian Bullseye
    • Java Runtime Environment: openjdk 11.0.14 2022-01-18
    • openHAB version: 3.2.0
  • Issue of the topic:
    Every morning and evening I switch all my thermostates from “day” to “night” and back which basically means: Night temp: 18 °C and day temperature the last user set temperature.
    Sometimes openHAB forgets to set the temperature of one thermostate. I.e. it still is set to 18 °C although we have reached the “day” program.
  • Please post configurations (if applicable):
    I tried to shorten the rules as much as possible but in order to give you the full picture I had to post nearly all rules.

The init:

import java.util.Map

val Map<String, String> shellies = newHashMap

rule "IP Adressen festlegen"
when
    System started
then    
	shellies.put("Keller", "192.168.188.66")
	shellies.put("WC", "192.168.188.70")
	shellies.put("Wohnzimmer", "192.168.188.68")
	shellies.put("Kinderzimmer", "192.168.188.74")
	shellies.put("Treppenhaus", "192.168.188.67")
	shellies.put("Buero", "192.168.188.72")
	shellies.put("BadDG", "192.168.188.69")
	shellies.put("BadOG", "192.168.188.71")
	shellies.put("Spielzimmer", "192.168.188.73")
	shellies.put("Schlafzimmer", "192.168.188.75")	
end

Day/Night switch:

rule "Fussbodenheizung: Nachtabsenkung"
when
	Time cron "0 30 22 1/1 * ? *"
then
	if(vIt_HZ_AusserHaus.state==OFF) {
		vIt_HZ_Tag.sendCommand('OFF')
		logInfo("Fussbodenheizung: Nachtabsenkung", "Nachtabsenkung aktiviert")
	}
end

rule "Fussbodenheizung: Tagesprogramm"
when
	Time cron "0 30 5 1/1 * ? *"
then
	if(vIt_HZ_AusserHaus.state==OFF) {
		vIt_HZ_Tag.sendCommand('ON')
		logInfo("Fussbodenheizung: Tagesprogramm", "Tagesprogramm gestartet")
	}
end

Now let us iterate through all thermostates:

rule "Fussbodenheizung: Tagesprogramm Temperaturen ändern"
when
	Item vIt_HZ_Tag changed
then		
	gSollTemperaturen.members.forEach[ v_Heizung_Soll | 
		var String raumName = v_Heizung_Soll.name.split("_").get(3)
		var v_Heizung_SollAnwender = gAnwenderSollTemperaturen.members.findFirst[ i | i.name.toString.contains(raumName) ] as NumberItem
		if(vIt_HZ_Tag.state == ON) {
			v_Heizung_Soll.sendCommand(v_Heizung_SollAnwender.state as Number)
		} else {
			v_Heizung_Soll.sendCommand(vIt_HZ_SollTempNacht.state as Number)
		}
	]	
	logInfo("Fussbodenheizung: Tagesprogramm Temperaturen ändern", "Alle Thermostate haben die Temperatur für das " + vIt_HZ_Tag.state + "-Programm erhalten")
end

And now the temperature settings “cascade” until they get sent to the shellies:

rule "Fussbodenheizung: Abschalt-/Einschaltwert neu berechnen"
when	
	Member of gSollTemperaturen received update
then
	var v_Heizung_Soll = triggeringItem
	var Double Hysterese = (vIt_HZ_Hysterese.state as Number).doubleValue()	
	var Double Abschaltwert = (v_Heizung_Soll.state as Number).doubleValue() + Hysterese	
	var Double Einschaltwert = Abschaltwert - 2 * Hysterese	
	
	var String roomName = v_Heizung_Soll.name.split("_").get(3)	
	val v_Heizung_Abschalt = gAbschaltTemperaturen.members.findFirst[ i | i.name.toString.contains(roomName) ] as NumberItem	
	val v_Heizung_Einschalt = gEinschaltTemperaturen.members.findFirst[ i | i.name.toString.contains(roomName) ] as NumberItem
	logInfo("Fussbodenheizung: Abschalt-/Einschaltwert neu berechnen", "Solltemperatur geändert für " + roomName + ": " + v_Heizung_Soll.state + " °C")
	
	// Heizungstimer abbrechen, falls Temperatur manuell geandert wird, bevor der Timer abgelaufen war
	val GroupItem a_Heizung_Timer = gHeizungsTimer.members.findFirst[ timer | timer.name.toString.contains(roomName)] as SwitchItem	
	if(a_Heizung_Timer !== null) {
		// Dieser Raum hat einen Ruecksetztimer (wahrscheinlich alle ausser das Treppenhaus)
		if(a_Heizung_Timer.state == ON) {
			a_Heizung_Timer.postUpdate(OFF)
			logInfo("Fussbodenheizung: Abschalt-/Einschaltwert neu berechnen", "Timer " + a_Heizung_Timer.name + " wurde abgebrochen aufgrund manueller Temperaturaenderung.")
		}
	}
	v_Heizung_Abschalt.sendCommand(Abschaltwert)
	v_Heizung_Einschalt.sendCommand(Einschaltwert)
end

rule "Fussbodenheizung: Abschalt-/Einschaltwert an ein Shelly versenden"
when
	Member of gEinschaltTemperaturen changed
then
	var v_Heizung_Einschalt = triggeringItem	
	var String roomName = v_Heizung_Einschalt.name.split("_").get(3)
	val v_Heizung_Abschalt = gAbschaltTemperaturen.members.findFirst[ i | i.name.toString.contains(roomName) ] as NumberItem	
	if(v_Heizung_Einschalt.state == NULL || v_Heizung_Abschalt.state == NULL) {
			return;
	}
	
	var String ipAddress = shellies.get(roomName)
		
	// http Befehl an Shelly senden
	var String httpString = ""	
	httpString = "http://"+ipAddress+"/settings/ext_temperature/0?undertemp_threshold_tC=" + String::format("%.2f",(v_Heizung_Einschalt.state as Number).doubleValue())
	sendHttpGetRequest(httpString)	
	httpString = "http://"+ipAddress+"/settings/ext_temperature/0?overtemp_threshold_tC=" + String::format("%.2f",(v_Heizung_Abschalt.state as Number).doubleValue())
	sendHttpGetRequest(httpString)
	
	logInfo("Fussbodenheizung: Abschalt-/Einschaltwert an ein Shelly versenden", "Neue Einschalttemperatur im Raum " + roomName + ": " + v_Heizung_Einschalt.state)
	logInfo("Fussbodenheizung: Abschalt-/Einschaltwert an ein Shelly versenden", "Neue Abschalttemperatur im Raum " + roomName + ": " + v_Heizung_Abschalt.state)
	Thread::sleep(1000)
end
  • If logs where generated please post these here using code fences:
    The log is very important here now. The temperature was not set for “Keller”.
2022-05-27 05:30:00.999 [INFO ] [ript.Fussbodenheizung: Tagesprogramm] - Tagesprogramm gestartet
2022-05-27 05:30:01.045 [INFO ] [bschalt-/Einschaltwert neu berechnen] - Solltemperatur geändert für Keller: 20.5 °C
2022-05-27 05:30:01.071 [INFO ] [g: Tagesprogramm Temperaturen ändern] - Alle Thermostate haben die Temperatur für das ON-Programm erhalten
2022-05-27 05:30:01.072 [INFO ] [bschalt-/Einschaltwert neu berechnen] - Solltemperatur geändert für Treppenhaus: 19.5 °C
2022-05-27 05:30:01.101 [INFO ] [bschalt-/Einschaltwert neu berechnen] - Solltemperatur geändert für Wohnzimmer: 21.0 °C
2022-05-27 05:30:01.126 [INFO ] [bschalt-/Einschaltwert neu berechnen] - Solltemperatur geändert für BadDG: 21.0 °C
2022-05-27 05:30:01.155 [INFO ] [bschalt-/Einschaltwert neu berechnen] - Solltemperatur geändert für BadOG: 21.0 °C
2022-05-27 05:30:01.180 [INFO ] [bschalt-/Einschaltwert neu berechnen] - Solltemperatur geändert für WC: 21.0 °C
2022-05-27 05:30:01.208 [INFO ] [bschalt-/Einschaltwert neu berechnen] - Solltemperatur geändert für Buero: 21.0 °C
2022-05-27 05:30:01.233 [INFO ] [bschalt-/Einschaltwert neu berechnen] - Solltemperatur geändert für Spielzimmer: 21.0 °C
2022-05-27 05:30:01.251 [INFO ] [bschalt-/Einschaltwert neu berechnen] - Solltemperatur geändert für Kinderzimmer: 21.0 °C
2022-05-27 05:30:01.269 [INFO ] [bschalt-/Einschaltwert neu berechnen] - Solltemperatur geändert für Schlafzimmer: 21.0 °C
2022-05-27 05:30:01.918 [INFO ] [inschaltwert an ein Shelly versenden] - Neue Einschalttemperatur im Raum Treppenhaus: 19.2
2022-05-27 05:30:01.921 [INFO ] [inschaltwert an ein Shelly versenden] - Neue Abschalttemperatur im Raum Treppenhaus: 19.8
2022-05-27 05:30:04.730 [INFO ] [inschaltwert an ein Shelly versenden] - Neue Einschalttemperatur im Raum Wohnzimmer: 20.7
2022-05-27 05:30:04.732 [INFO ] [inschaltwert an ein Shelly versenden] - Neue Abschalttemperatur im Raum Wohnzimmer: 21.3
2022-05-27 05:30:06.532 [INFO ] [inschaltwert an ein Shelly versenden] - Neue Einschalttemperatur im Raum BadDG: 20.7
2022-05-27 05:30:06.551 [INFO ] [inschaltwert an ein Shelly versenden] - Neue Abschalttemperatur im Raum BadDG: 21.3
2022-05-27 05:30:08.291 [INFO ] [inschaltwert an ein Shelly versenden] - Neue Einschalttemperatur im Raum BadOG: 20.7
2022-05-27 05:30:08.295 [INFO ] [inschaltwert an ein Shelly versenden] - Neue Abschalttemperatur im Raum BadOG: 21.3
2022-05-27 05:30:10.133 [INFO ] [inschaltwert an ein Shelly versenden] - Neue Einschalttemperatur im Raum WC: 20.7
2022-05-27 05:30:10.136 [INFO ] [inschaltwert an ein Shelly versenden] - Neue Abschalttemperatur im Raum WC: 21.3
2022-05-27 05:30:12.918 [INFO ] [inschaltwert an ein Shelly versenden] - Neue Einschalttemperatur im Raum Buero: 20.7
2022-05-27 05:30:12.921 [INFO ] [inschaltwert an ein Shelly versenden] - Neue Abschalttemperatur im Raum Buero: 21.3
2022-05-27 05:30:14.633 [INFO ] [inschaltwert an ein Shelly versenden] - Neue Einschalttemperatur im Raum Spielzimmer: 20.7
2022-05-27 05:30:14.636 [INFO ] [inschaltwert an ein Shelly versenden] - Neue Abschalttemperatur im Raum Spielzimmer: 21.3
2022-05-27 05:30:16.456 [INFO ] [inschaltwert an ein Shelly versenden] - Neue Einschalttemperatur im Raum Kinderzimmer: 20.7
2022-05-27 05:30:16.465 [INFO ] [inschaltwert an ein Shelly versenden] - Neue Abschalttemperatur im Raum Kinderzimmer: 21.3
2022-05-27 05:30:18.296 [INFO ] [inschaltwert an ein Shelly versenden] - Neue Einschalttemperatur im Raum Schlafzimmer: 20.7
2022-05-27 05:30:18.298 [INFO ] [inschaltwert an ein Shelly versenden] - Neue Abschalttemperatur im Raum Schlafzimmer: 21.3

We see that the lower/upper temperatures were calculated for the room “Keller” as we can see in the message at 2022-05-27 05:30:01.045 but this information is not further propagated. The rule Einschaltwert an ein Shelly versenden is not calculated for this room.

Is this probably a threading issue? I hoped to workaround this issue with the Thread:sleep(1000) command in the rule Fussbodenheizung: Abschalt-/Einschaltwert neu berechnen but it did not help.

Any ideas?
Best,
Oliver

PS. Parts of the code are inspired from this project Thermostatsteuerung by kriwanek

First, why HTTP. Shellies support MQTT as well as having their own binding. Either one would simplify the code significantly which might address the problem. In the end you’d just need a Group item with all the setpoints and a ruke to send the new value as a command to the Group and that’s all.

It’s likely not a threading issue. Only one instance of a given rule can run at a time. Subsequent triggers will queue and run in turn. I don’t see any global variables being written to so there shouldn’t be any interface caused by one rule updating a variable while another one is reading it.

Are you certain that the one that was missed changed state? You should see that in events.log.

Note, that action will not necessarily trigger this rule -

Commands may or may not produce a state change.
If you want to trigger on every command, use received command trigger.
If you want to trigger only on changes - keep the changed trigger, but you don’t need the command, just send the Item a postUpdate. It’s less overhead. I’m assuming this is a “virtual” Item attached to no real binding.

You’ve not reported seeing any ‘null’ errors in your log, but -

using this technique to find an Item usually works - but it can mess up when the Group has a lot of activity going on, sometimes resulting in an expected match not being returned. Something in OH core is not so good at handling multiple member updates.

If you can build the full string name of your target Item, use
x = ScriptServiceUtil.getItemRegistry.getItem("your_name_string")
which doesn’t involve Groups and so doesn’t care if the Group is thrashing about with multiple updates.

That should do away with this sort of thing as well -

which worries me, because ‘contains’ is not the same as uniquely equal.

It is because you cannot set the upper/lower threshold temperature for switching the relais via MQTT. At least I did not find anything about it in the binding nor in the shelly docs. And yes, this makes this code a pain in the ***.

At least I found something odd in the events.log. Check this out:

2022-05-27 05:30:01.000 [INFO ] [openhab.event.ItemCommandEvent      ] - Item 'vIt_HZ_Tag' received command ON
2022-05-27 05:30:01.016 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item 'vIt_HZ_Tag' changed from OFF to ON
2022-05-27 05:30:01.026 [INFO ] [openhab.event.ItemCommandEvent      ] - Item 'v_Heizung_Soll_Keller' received command 20.5
(...)
'v_Heizung_Soll_Keller' changed from 18.0 to 20.5
2022-05-27 05:30:01.058 [INFO ] [hab.event.GroupItemStateChangedEvent] - Item 'gSollTemperaturen' changed from 18.0 to UNDEF through v_Heizung_Soll_Keller
(...)
2022-05-27 05:30:01.081 [INFO ] [openhab.event.ItemCommandEvent      ] - Item 'v_Heizung_Abschalt_Keller' received command 18.3
(...)
2022-05-27 05:30:01.088 [INFO ] [openhab.event.ItemCommandEvent      ] - Item 'v_Heizung_Einschalt_Keller' received command 17.7

The actual temperature is set to 20.5. However, the rule Fussbodenheizung: Abschalt-/Einschaltwert neu berechnen calculates 18.3 and 17.7 as upper/lower threshold although I would have expected 20.2 and 20.8 due to these lines in that rule

	var Double Hysterese = (vIt_HZ_Hysterese.state as Number).doubleValue()	
	var Double Abschaltwert = (v_Heizung_Soll.state as Number).doubleValue() + Hysterese	
	var Double Einschaltwert = Abschaltwert - 2 * Hysterese

v_Heizung_Soll.state should contain 20.5 as we can see in the events.log, shouldn’t it?

Your assumption is correct. But thanks for this clarification! I will correct this.

It is doable. I will give it a try. But it means that I always have to update the rule here in case I rename my items. With the current technique I can rename it as I want and do not have to take care of it in the rules as long as the room name is on the 3rd position.

With the current technique I cannot use “equals” as the timer items are called like this: a_Heizung_Keller_Timer and I only have the room name for the same reasons as above.

I think you are saying you’ve got something like the the string “Heizung_Keller”.
You can concatanate strings
getItem("a_" + your-string-variable + "_Timer")

Are your Items (and Group sub-type) really just Number types? Doesn’t Shelly provide Number:Temperature type channels? The group going UNDEF prompts this question.

I can’t follow the cascading path through your rules very well, but let me point out an effect you may not be aware of. Both command and postUpdate are asynchronous.
That means -

itemX.postUpdate(some-value)
logInfo("test", "State is now " + itemX.state)
// this will report the OLD value, the update has not happened yet.
// it takes some milliseconds, the rule will NOT stop and wait.

I think you’ve got something like this spread accross your rules.

Add some more logging to more closely follow who is doing what. Reduce your Groups to just two members for testing so that you can better follow what happens in your logs.

Oh wow, this is a completely new information to me. I did not expect this and it breaks with my current way how I write usually code.
Indeed, I think this is the issue here.

Given your example above: In case I want to have the new state in the logs, how would you do that? Create a new rule that listens for changes and then creates the log entry?

The example with logging added

itemX.postUpdate(some-value)
logInfo("test", "State was "+ itemX.state)
// this will report the OLD value, the update has not happened yet.
//
// but - we already know what we sent it
logInfo("test", "State will become " + some-value)

It is very rare that you need to code any “wait” for this; just think clearly about what you need and choose rule triggers carefully.

Items are not simple variables.

1 Like

Thank you! I will refactor the system. It looks like I need to work with less rules (no cascading) so that I can work with variables inside a rule instead of item states.