[Rules DSL] Get item from string name!

I’m curious to see an example where other means could not be used to ensure that the Item exists. Using the ItemRegistry may be the easiest, but it comes with a performance hit, so it would be best to find another way. That said, there are other useful ItemRegistry methods. In Jython, I use this to check for the existence of an Item…

if ir.getItems("Current_Timestamp") == []:

This is not used in a rule, but in a script that is creating Items if they do not already exist. This uses ItemRegistry.getItems() (note the ‘s’), which returns a collection of Items matching a regex. The regex being a literal string. The same could be done in a DSL rule using…

if (ScriptServiceUtil.getItemRegistry.getItems("item_name_to_check_for_existence") == newArrayList) {

This could potentially be used as another workaround for checking existence of an Item given an Item name as a string. For example…

val test = ScriptServiceUtil.getItemRegistry.getItems("bad_item_name")
if (test == newArrayList) {
    logInfo("Rules", "Bad item")
} else {
    logInfo("Rules", "Test [{}]", test.get(0).name)
    test.get(0).sendCommand(ON)
}
2 Likes

Oh, there’s always other means :smiley:

My point was just that “searching” for a non-existent Item name is not necessarily something terrible to be avoided, and that having methods to deal gracefully with the situation are useful.

You’ve provided just such a method already, so thankyou :heart:

The kind of task I was thinking about would be something like a bunch of lights, where only some have some extra “property”.

LampA
LampA_preferredColor
LampB
LampC
LampC_preferredColor

Curious about this, be interesting to compare with the “traditional” searching/filtering of a Group by name-strings. I imagined the underlying mechanism was much the same.
Group limits the scope of search, so maybe of more benefit in large OH system (many Items)

I agree. By performance hit, I mean unneeded processing. In your example where only some Items have associated Items, put them in a PreferredColor group and use that for when it’s time to change the color. I just do everything I can to make my rules run as fast and efficiently as they can. I had to point this out for others that are as obsessed as I am in the speed with which their lights turn on :wink:. We’re talking milliseconds!

You are (all) very welcome! Now go use JSR223 for scripting your rules and you won’t have to bother with ScriptServiceUtil any more! :stuck_out_tongue_winking_eye:

In Rules DSL I’m kind of with you. I’m all in favor of adding lots of error checking to Rules. But I’m not seeing the use case.

However, when you go to Rule Templates I can totally see where checking for the existence of an Item in the Rule would be more important. But I’ve no problem with using an exception in that case as appears to be the case now.

An alternative is

if (ScriptServiceUtil.getItemRegistry.getItems("item_name_to_check_for_existence").isEmpty) {

That way you don’t have to create a new empty array list to compare to. .size == 0 would also work.

2 Likes

How i solved it in the end can be found here, feel free to comment on it or even improove it

Did you get something on the hunt? I’m having trouble getting a State from a DateTimeItem coming from the ScriptServiceUtil.

In the rule I’ll loop through 6 different calendar entries and try to get their states (DateTimeItems and StringItems)

(...)
	var DateTime EmailTime = ScriptServiceUtil.getItemRegistry?.getItem("Calendar_GoogleFamily_Event"+i+"_EmailTime") as DateTimeItem
	var String Summary = ScriptServiceUtil.getItemRegistry?.getItem("Calendar_GoogleFamily_Event"+i+"_Summary") as StringItem
(...)
	logInfo("Familienkalender", "Item from DateItem-Definition: [{}]",Calendar_GoogleFamily_Event1_EmailTime)
	logInfo("Familienkalender", "State from Item-Definition [{}]",Calendar_GoogleFamily_Event1_EmailTime.state)
	logInfo("Familienkalender", "State from String-Definition [{}]",Calendar_GoogleFamily_Event1_Summary.state)
	logInfo("Familienkalender", "Item from DateScriptServiceUtil: [{}]",EmailTime)
	logInfo("Familienkalender", "State from DateScriptServiceUtil: [{}]",EmailTime.state)
	logInfo("Familienkalender", "State from StringScriptServiceUtil: [{}]",Summary.state)

I’ll then get in the logs:

2020-01-10 08:26:00.044 [INFO ] [rthome.model.script.Familienkalender] - Item from DateItem-Definition: [Calendar_GoogleFamily_Event1_EmailTime (Type=DateTimeItem, State=2020-01-10T07:46:00.000+0100, Label=FamKal Ev1 Email, Category=calendar, Groups=[gCalendar, gCalFamilyNotification])]
2020-01-10 08:26:00.050 [INFO ] [rthome.model.script.Familienkalender] - State from Item-Definition [2020-01-10T07:46:00.000+0100]
2020-01-10 08:26:00.055 [INFO ] [rthome.model.script.Familienkalender] - State from String-Definition [dingsbums anrufen]
2020-01-10 08:26:00.059 [INFO ] [rthome.model.script.Familienkalender] - Item from DateScriptServiceUtil: [Calendar_GoogleFamily_Event1_EmailTime (Type=DateTimeItem, State=2020-01-10T07:46:00.000+0100, Label=FamKal Ev1 Email, Category=calendar, Groups=[gCalendar, gCalFamilyNotification])]
2020-01-10 08:26:00.064 [ERROR] [ntime.internal.engine.ExecuteRuleJob] - Error during the execution of rule 'Binder Familienkalender': 'state' is not a member of 'org.joda.time.DateTime'; line 33, column 72, length 15
2020-01-10 08:23:03.134 [ERROR] [ntime.internal.engine.ExecuteRuleJob] - Error during the execution of rule 'Binder Familienkalender': 'state' is not a member of 'java.lang.String'; line 33, column 74, length 13

The items from the .items-file and the ones generated via ScriptServiceUtil seem to have the same attributes - but it seems I can’t access them…
I’d also like to make comparisons with the “EmailTime”-Date and stuff I can with the .items-generated items…

No. There is no way to access Item metadata from the rules DSL. The solution is to use scripted automation, which is the future of automation for OH and metadata can be easily accessed using the core helper libraries. My plan is to make this functionality available through a Scripting API, so that the core helper libraries are no longer needed. If you’re interested, it’s getting easier to get them setup…

There are some issues with your casting. In the DSL, it is best to be only as specific as you need to be. Try being less specific and let the DSL do the work for you…

var EmailTime = ScriptServiceUtil.getItemRegistry.getItem("Calendar_GoogleFamily_Event"+i+"_EmailTime")
var Summary = ScriptServiceUtil.getItemRegistry?.getItem("Calendar_GoogleFamily_Event"+i+"_Summary")
1 Like

That’s great! at least I now get:

2020-01-10 10:18:02.748 [INFO ] [rthome.model.script.Familienkalender] - Item from DateItem-Definition: [Calendar_GoogleFamily_Event1_EmailTime (Type=DateTimeItem, State=2020-01-10T07:46:00.000+0100, Label=FamKal Ev1 Email, Category=calendar, Groups=[gCalendar, gCalFamilyNotification])]
2020-01-10 10:18:02.750 [INFO ] [rthome.model.script.Familienkalender] - State from Item-Definition [2020-01-10T07:46:00.000+0100]
2020-01-10 10:18:02.752 [INFO ] [rthome.model.script.Familienkalender] - State from String-Definition [dingsbums anrufen]
2020-01-10 10:18:02.754 [INFO ] [rthome.model.script.Familienkalender] - Item from DateScriptServiceUtil: [Calendar_GoogleFamily_Event1_EmailTime (Type=DateTimeItem, State=2020-01-10T07:46:00.000+0100, Label=FamKal Ev1 Email, Category=calendar, Groups=[gCalendar, gCalFamilyNotification])]
2020-01-10 10:18:02.755 [INFO ] [rthome.model.script.Familienkalender] - State from DateScriptServiceUtil: [2020-01-10T07:46:00.000+0100]
2020-01-10 10:18:02.757 [INFO ] [rthome.model.script.Familienkalender] - State from StringScriptServiceUtil: [dingsbums anrufen]

Do you have an idea, how I can compare the corresponding DateTime?

	val jetzt = now.minusSeconds(20)
(line 37:)
	if (jetzt.isBefore(new DateTime((EmailTime as DateTimeType).getZonedDateTime.toInstant.toEpochMilli))) {
(...)

leads to:

2020-01-10 10:21:02.852 [ERROR] [ntime.internal.engine.ExecuteRuleJob] - Error during the execution of rule 'Binder Familienkalender': Could not cast Calendar_GoogleFamily_Event1_EmailTime (Type=DateTimeItem, State=2020-01-10T07:46:00.000+0100, Label=FamKal Ev1 Email, Category=calendar, Groups=[gCalendar, gCalFamilyNotification]) to org.eclipse.smarthome.core.library.types.DateTimeType; line 37, column 36, length 25

it seems, the “EmailTime”-defintion (without all those DateTime-references) gets the right values, but I’m still lacking understanding on how to cast accordingly… (I’m no devloper, casting and stuff is not in my blood :wink: ). The if-condition works with “real” items, as I use it elsewhere (also partly copied from the forum)

EmailTime is an Item. EmailTime.state is a DateTimeType. Your issue is that you are using EmailTime instead of EmailTime.state.

if (jetzt.isBefore(new DateTime((EmailTime as DateTimeType).getZonedDateTime.toInstant.toEpochMilli))) {

This should be…

if (jetzt.isBefore(new DateTime((EmailTime.state as DateTimeType).getZonedDateTime.toInstant.toEpochMilli))) {

This is much simpler as…

if (jetzt.isBefore(new DateTime(EmailTime.state.toString))) {

Personally, I’d stop using Joda (removed in OH 3.0) and use ZoneDateTime

val jetzt = new DateTimeType().zonedDateTime.minusSeconds(20)

if (jetzt.isBefore((EmailTime.state as DateTimeType).zonedDateTime)) {
1 Like

Thank you very much. Now it’s working as intended!
I’ll have to get my head around all that time-stuff and casting and whatnot… :wink:

I’d like to beta-test the Jython-thingy also, but first must move my OH2.5 to a new raspberryPi4…

1 Like

Can you use a ZoneDateTime in the call to createTimer?

In OH 2.5, createTimer uses org.joda.time.AbstractInstant, so to use a ZonedDateTime…

test_timer = createTimer(new DateTime(new DateTimeType().zonedDateTime.plusSeconds(5).toString()))[|

Although, I bet you were asking if a ZDT could be fed to createTimer without using Joda, and the answer to that is no.

The createTimer Action is part of the old rule engine, which will be removed in OH 3.0. Something similar will need to be created for the new rule engine, along with the other Core Actions. These will be made accessible for use within scripted automation through the scripting API. The mechanism will likely be a ScriptExtension, possibly through a default preset.

1 Like

My question was kind of self serving.

Does it make sense to push people to move to ZonedDateTime before exceptionally commonly used Actions like createTimer can accept it? When I look at my use of Joda DateTime in all of my rules, about 80% of the uses are related to calls to createTimer. Thus, it doesn’t make sense for me to migrate until I can use a ZonedDateTime in createTimer. It will be more work for me to switch now and then again when the replacement for createTimer comes along. I imagine I’m not unique in that.

3 Likes

Thanks to this post and little more digging, I’ve got this rule working.

In short, it keeps an eye on gMinute and gHour groups, so that minutes and hours are constrained, but also that when the minutes exceed the limits, it adjusts the coresponidng hour

I’ve left the logInfo lines in so you can see my working out.

import org.openhab.core.model.script.ScriptServiceUtil

rule "Confine Minute"

when	
	Member of gMinute changed
then
	
	   logInfo("Minute Limiting", "Minute Value of "+triggeringItem.name+" Currently "+triggeringItem.state)
	
	if (triggeringItem.state > 59) {
	
	sendCommand(triggeringItem.name,"0")
//	logInfo("Alarm Times",triggeringItem.name.split('Min').get(0)+"Hour")
	
	var HourName = ""
	HourName = triggeringItem.name.split('Min').get(0)+"Hour"
		logInfo("Alarm Times",HourName)
		
	var Number HourValue = 0
	HourValue = ScriptServiceUtil.getItemRegistry.getItem(HourName).state
	logInfo("Alarm Times","Hour is currently " + HourValue)
	
	
	var Number NewHour = 0
	NewHour=1 + HourValue
		logInfo("Alarm Times","New Hour value is " + NewHour)
		
		




						sendCommand(HourName, NewHour.toString)
	}
	
	
	
	
	
		if (triggeringItem.state < 0) {
	
	sendCommand(triggeringItem.name,"45")

	
	var HourName = ""
	HourName = triggeringItem.name.split('Min').get(0)+"Hour"
		logInfo("Alarm Times",HourName)
		
	var Number HourValue = 0
	HourValue = ScriptServiceUtil.getItemRegistry.getItem(HourName).state
	logInfo("Alarm Times","Hour is currently " + HourValue)
	
	
	var Number NewHour = 0
	NewHour=HourValue -1 
		logInfo("Alarm Times","New Hour value is " + NewHour)
		
		




						sendCommand(HourName, NewHour.toString)

	}
	
end


rule "Confine Hour"

when	
	Member of gHour changed
then
	
	   logInfo("Hour Limiting", "Hour Value of "+triggeringItem.name+" Currently "+triggeringItem.state)
	
	if (triggeringItem.state > 23) {
	
	sendCommand(triggeringItem.name,"0")
//		logInfo("Alarm Times",triggeringItem.name.split('Hou').get(0))
	}
	
		if (triggeringItem.state < 0) {
	
	sendCommand(triggeringItem.name,"23")
//		logInfo("Alarm Times",triggeringItem.name.split('Hou').get(0))
	}
	
end
1 Like

For anyone who stumbles on this with OH3, replace the import with org.openhab.core.model.script.ScriptServiceUtil and this works again.

6 Likes

Thank you… OP updated!

How do you add the import org.openhab.core.model.script.ScriptServiceUtil instruction in OH3, when the Rule script is not in a file, but created using Admin > Settings > Rules > Execute a given script

From Settings > Rules > Code I have

triggers:
  - id: "5"
    configuration:
      cronExpression: 0 0 0/1 * * ? *
    type: timer.GenericCronTrigger
conditions: []
actions:
  - inputs: {}
    id: "2"
    configuration:
      type: application/vnd.openhab.dsl.rule
      script: "
[lines deleted ]
          "
    type: script.ScriptAction

Can I add the import ... in the above?

PS: adding the import line at the top of the script gives me this error:

 The method or field import is undefined; line 1, column 0, length 6

do you found the right solution for import in GUI-based rules?

You cannot import in UI Rules DSL Script Actions and Script Conditions. You have to use the full name of the class you want to use. So instead of

import java.util.Map

val Map myMap = createHashMap

you need to use the full class name everywhere you use the class you would have imported.

val java.util.Map myMap = createHashMap

Is it the same situation in OH4 or is it still this way?

val testItem = ScriptServiceUtil.getItemRegistry.getItem("Virtual_Switch_1")

What is the best way to catch the error in OH4 to not loose performance if the item does not exist.
If “testItem” does not exist it should be null.