Rule which iterates through group(s) of similar items

Thank you, i understand now.

I have another question:

How can i make this shorter with only one rule for all of my 10 Temp-Sensors… I have now 10 rules, one for each sensor…

rule "Xiaomi Temp 1 Status Changed"
when
		Item Xiaomi_Temp_1 received update
then
		var SimpleDateFormat df = new SimpleDateFormat( "dd.MM., HH:mm" )
		var String timestamp = df.format( new Date() )
		Xiaomi_Temp_1_komplett.postUpdate(String::format("%.1f", (Xiaomi_Temp_1.state as DecimalType).floatValue()) + " °C / " + String::format("%.0f", (Xiaomi_Humidity_1.state as DecimalType).floatValue()) + " % (" + timestamp + ")")
end

Put them all in the same Group, I’ll call it Xiaomi_Temps.

rule "Xiaomi Temp Status Changed"
when
    Member of Xiaomi_Temp changed // assuming you named the Rule appropriately and you care about changes not updates
then
    postUpdate(triggeringItem.name+"_komplett", String::format("%1$.1f °C / %2$.0f % ( %3$td.%3$tm., %3tH:%3$tM)", Xiaomi_Temp_1.state as Number, Xiaomi_Humidity_1.state as Number, new Date() )
end

I’m not certain whether the format will work with Number or not so you might still need to call toFloat.

See Design Pattern: Associated Items for further explanation of the overall approach. The big thing is make sure your Items are all names so you can construct the name of on from the name of it’s assocaited Items. Then it is just some simple String manipulation to build the name of the associated Item and send it commands or post updates or pull it out of a Group or the Item registry.

I must say I didn’t fully realise that either.

VSCode editor is smart enough to realize that it won’t work in rules with command or updated triggers - and complains vaguely.

"Invalid number of arguments. The method previousState(Item) is not applicable without arguments"

OpenHAB fails with a runtime error if you do try it anyway in a rule, and that’s the source of the obscure error message seen earlier -

Rule 'testing': An error occurred during the script execution: index=0, size=0

So you CANNOT even include implicit previousState in an ‘update’ rule and test for null.


But there is a way to “cheat the system” …

//   ITEMS FILE
   Number testX
//   SITEMAP test tool so you generate change or same commands
    Switch item=testX mappings=[0="0",1="1",2="2"]

Demo rule

rule "testing previousState"
when
	Item testX received update or
	Item testX changed
then
	if (previousState !== null) {
		logInfo("updtest", "Changed from " + previousState.toString + " to " + testX.state.toString)
	} else {
		logInfo("updtest", "This time it is null, no change " + testX.state.toString)
	}
end

By including changed in the list of triggers, we are allowed to use previousState within the rule. But - not all the triggers need to actually be changed!

This rule will also run for an update when there is no change. Of course, previousState has no value in the case of no change. But the rule can now test it to see if it is null.

There is a big pitfall here.
When the item updates without change - the rule runs once (with previousState null)
When the item updates with a change - the rule runs twice, once for each trigger. Once with previousState null (as it does not get populated by the update trigger, even though it has really changed) and once with previousState valid as the old state, set by the changed trigger.
The effect of that is that an ‘update’ run of the rule will always occur and it cannot tell on that pass if the state really changed or not.

It’s a curiosity - I can’t see any real use for the cheat though.

I think i have to change the real item names in the end of the rule to triggeringItem or something like this? This rule has towork for all Temp_Sensors, not only for Sensor_1.

Will this work with my naming sheme or do i have to change the humidity-items to something else?

Maybe split the name of the temp item at first, then get the number of the sensor and then build the xiaomi_humidity_number item-name?

Look at the Design Pattern: Associated Items for a full description for how to access these other Items by name.

This should work with your naming scheme. You just need code like this:

val nameParts = triggeringItem.name.split("_")
val humidityName = nameParts.get(0) +"_Humidity_"+nameParts.get(2)

Then follow one of the techniques in the DP to access the item.

as long as you as you split the parts of the name consistently it doesn’t matter what order the parts of the name are.

It doesn´t work… I don´t know how to fix this:

rule "Xiaomi Temp Status Changed - 1 rule for all"
when
		Member of gXiaomiTemp received update
then
		val nameParts = triggeringItem.name.split("_")
		val humidityName = nameParts.get(0) +"_Humidity_"+nameParts.get(2)
		postUpdate(triggeringItem.name+"_komplett", String::format("%1$.1f °C / %2$.0f % ( %3$td.%3$tm., %3tH:%3$tM)", triggeringItem.state as Number, humidityName, new Date() ))
end

Error in Log:

2018-09-03 10:48:35.940 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'Xiaomi Temp Status Changed - 1 rule for all': Flags = ' '

Error is in the postUpdate-Line.


I added float value, but i get errors when typing:

(humidityName.floatValue())
or
(humidityName as Number).floatValue())

humidityName is a string. You will need to use that string to get the item, in order to use that item’s state…

import org.eclipse.smarthome.model.script.ScriptServiceUtil
...
val humidityItem = ScriptServiceUtil.getItemRegistry.getItem(humidityName)

Thanks, now i get another information in my logging.

But still the same error in the openhab.log.

I made a logging for the valuename.state and it shows the value, so it works.

But still the same error with adding .state

Maybe the error is not at this place, maybe the “new Date()” or the string at the beginning is the problem?


Another question:
How can i add a logging to the end of my rule with the changed item?

This isn´t working:

logInfo("RULES Xiaomi Temp", "changed item: " + triggeringItem.name+"_komplett")

Do i have to use another way of writing the triggeringItem.name + “_komplett” ???

Yes… if the variable points to an item. That is what I was suggesting in my previous post. The humidityName variable contains a string, not an item, but you can get the item by using that string. The DP Rich referenced explains how, and I gave a quick example.

Could you post your current rule?

I use something like this…

logDebug("Rules","Alert: Security alert and sound effects [{}]: {}",triggeringItem.name,triggeringItem.state.toString)

Here is my rule:

rule "Xiaomi Temp Status Changed - 1 rule for all"
when
		Member of gXiaomiTemp received update
then
		val nameParts = triggeringItem.name.split("_")
		val humidityName = nameParts.get(0) +"_Humidity_"+nameParts.get(2)
		val humidityItem = ScriptServiceUtil.getItemRegistry.getItem(humidityName)
		postUpdate(triggeringItem.name+"_komplett", String::format("%1$.1f °C / %2$.0f % (%3$td.%3$tm., %3tH:%3$tM)", triggeringItem.state as Number, humidityItem.state, new Date() ))
end

Another question with logging:
I want to get the whole log-information of the item, not only the name.

So i want to get the information from for example Xiaomi_Temp_3_komplett in this style:

Xiaomi_Temp_3 (Type=NumberItem, State=22.719999313354492, Label=Temp 3 Technik, Category=temperature, Groups=[gXiaomiTemp, gXiaomi_Temp_Sensors, gXiaomi_Temperatur])

Not only the name and the value of the item. But i have to build the item name out of triggeringItem and adding “_komplett” to the name. So in my try i get the triggeringItem and adding only the String “_komplett” to the log-output. So it is not building the new name, it only shows the string…

The ‘Associated Items’ design pattern shows you how to take a string, representing the name of an Item, and use it search through a Group of similar Items, to get the Item you want.
Slow down and give it a read.

The error is due to the string formatter. Try going back to how you had it originally…

postUpdate(triggeringItem.name + "_komplett", String::format("%.1f", (triggeringItem.state as DecimalType).floatValue) + " °C / " + String::format("%.0f", (humidityItem.state as DecimalType).floatValue) + " % (" + new java.util.Date + ")")

Empty parenthesis () are not needed in Xtend. If you have an import to java.util.Date, then you can just use new Date.

Then use the item and not item.name. I think you’ll need humidityItem.toString though.

Ok,i will try this.

EDIT:
It works, only new Date is giving me timestamp in a wrong format - so i´ll have to change that with a variable also at the beginning.

Thanks to all who helped me!


Logging:
I think i´m speaking of something other then you…

If i make a logging of:
triggeringItem.name + “_ABCDEFG”

I will get:
–> Itemthatwastriggered_ABCDEFG
—> this is the name of the item, followed by the string _ABCDEFG

But i want the state of the item with the name: itemthatwastriggered_ABCDEFG

So that i would get the output, maybe: 123 or ON or whatever

To stay with my example:
I have many items with the name:
itemthatwastriggered_1_ABCDEFG
itemthatwastriggered_2_ABCDEFG
and so on

You want the state of an item, but you only have a string that represents the item’s name. You need to get the item, like you did for humidityItem. Does that make sense?

1 Like

Ah, ok, now i understand.

Thanks.

1 Like

And now the last rule which iterates through a group. I want to change this to the new writing style too.

Is this also possible, when i have a cron as trigger of the rule?

When an item changes, i save the timestamp into a extra item. And every minute another rule calculates the time since the last update.

Here is the rule:

rule "Update Xiaomi Temp time since"
when
    Time cron "0 * * * * ?"
then
	if(SystemStarting.state == OFF) {
	        gXiaomiZeit.members.forEach[ DateTimeItem lastTime |
    	                 val lastMillis = (lastTime.state as DateTimeType).getZonedDateTime.toInstant.toEpochMilli
                         val mins = (now.millis - lastMillis) / 60000
		         val split = lastTime.name.split('_')
                         postUpdate(split.get(0)+"_"+split.get(1)+"_"+split.get(2)+"_time_since", mins.toString)
                ]
	}
end

You have no triggeringItem for a cron triggered Rule. You could make the code a little simpler but not enough to be worth doing. You are already using the Associated Items DP here.

The slight simplification would be:

I am assuming that the lastTime Item name just needs to have “_time_since” appended to it. If that isn’t the case you should work the names so that becomes the case.

    if(SystemStarting.state != OFF) return;

    gXiaomiZeit.members.forEach[ DateTimeItem lastTime |
        val lastMillis = new DateTime(lastTime.toString).millis
        val mins = (now.millis - lastMillis) / 60000
        postUpdate(lastTime.name+"_time_since", mins.toString)
    ]

Ever so slightly simpler. But not enough to make much of a difference.