[Rules DSL] Get item from string name!

For a couple years now, I’ve been wanting/looking for a way to construct an item from its name as a string. There are other ways to do this, and the most popular requires the item to be in a group, which can have it’s challenges. I’ll keep this short and SWEET… I finally figured it out, and it is extremely simple!

For OH 2.x, you’ll need this import…

import org.eclipse.smarthome.model.script.ScriptServiceUtil

For OH 3.x, this import instead…

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

You can then access an item by its string name, like this…

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

In this example, testItem is returned as an Item. In most cases, you will want to use it as a GenericItem, so instead of the above, you’ll need to cast it…

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

If an Item cannot be found with a name matching the string used, you will get an error like…

2020-05-5 5:55:55.555 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'Generate error from bad Item name': Item 'bad_name' could not be found in the item registry

The getItems (note the s) method can be used to avoid this error. See this post below.

Happy HABing!!!

40 Likes

That gives you a fully functional Item like you get when using other more will known methods of gaining access to Items?

I only ask because I’ve noticed while browsing through the javadocs that the Switch item, for example, we get in Rules had all sorts of methods I can’t find on the Switch item class or any of its parents. In particular I’ve yet to figure out where the persistence methods are implemented.

So my questions are:

  • can I cast the result of this call to the various Item types (e.g SwitchItem)?
  • Are all the methods available and do they function as expected? Obviously sendCommand and postUpdate, but also members for Groups and the persistence methods.

If this provides a fully functional Item that behaves the same in Rules as the rest then this can be as useful as Member of and triggeringItem.

2 Likes

Yes… it appears to be a full blown Item! I found this while looking into the BusEvent.java to see how to properly add checkFirst methods to sendCommand and postUpdate. I should mention that I am using snapshot 1304, in case this was somehow recently exposed. I wonder how much more functionality is available but as of yet undocumented! It looks like there are some more goodies in there… ScriptServiceUtil.java.

I think you are looking for PersistenceExtensions.java.

Yes!

These tests have all been successful…

val GenericItem test = ScriptServiceUtil.getItemRegistry?.getItem("Virtual_Switch_2") as GenericItem
val SwitchItem testSwitch = ScriptServiceUtil.getItemRegistry?.getItem("Virtual_Switch_2") as SwitchItem
val GroupItem testGroup = ScriptServiceUtil.getItemRegistry?.getItem("gTest") as GenericItem as GroupItem
testSwitch.sendCommand(OFF)
testSwitch.postUpdate(ON)
sendCommand(testSwitch.name,"OFF")
postUpdate(testSwitch.name,"ON")

logDebug("Rules","Test1: item from registry=[{}]",test)
logDebug("Rules","Test1: class=[{}]",test.class)
logDebug("Rules","Test1: tags=[{}]",test.tags)
logDebug("Rules","Test1: category=[{}]",test.category)
logDebug("Rules","Test1: changedSince=[{}]",test.changedSince(now.minusHours(1)))
logDebug("Rules","Test1: lastUpdate=[{}]",test.lastUpdate)
logDebug("Rules","Test1: historicState=[{}]",test.historicState(now.minusHours(1)).state)
logDebug("Rules","Test1: groupNames=[{}]",test.groupNames)
logDebug("Rules","Test1: members=[{}]",testGroup.members)
logDebug("Rules","Test1: allmembers=[{}]",testGroup.allMembers)

I was completely amazed when I triggered a test rule with this in it… and I’m still giddy about it! In the last couple years, I have probably spent at least a few days trying similar ways to access the ItemRegistry. My hopes are very high that this is the DSL :unicorn: I’ve been seeking! I only have about 1000 lines left to migrate to Jython, but hopefully someone can put it to use! :wink:

I’m still on the hunt for a way to access item metadata… but item.getTags and item.hasTag are the closest I’ve come.

4 Likes

:+1:

:+1: :+1:

I’m going to have to play with this. I

I’m on 2.3 Release and VSCode didn’t complain about the import nor the call to getItem and it appears to work.

1 Like

Did you look/find a way to modify item tags? I cannot get item.addTag or addTags to work…

Wow, thank you for this posting @5iver
This makes a lot of rules so much easier.
Just refactored a few of my rules to make them more generic with this approach.

Cheers
Sebastian

1 Like

There are also methods for accessing the ThingRegistry and ModelRpository. I’m not sure what the latter does, but with the ThingRegistry, you can get Channels and check the state of a Thing. I have some examples somewhere, if interested.

2 Likes

Would that allow an indicator that a binding is down or that an external controller isn’t working. Say like the connection to the security panel?

1 Like

You can already do that with Thing based triggers. I’ve no doubt that such triggers are also supported in JSR223.

For example

when
   Thing "avmfritz:FRITZ_DECT_210:dect:116570021295" received update OFFLINE
then

Yes, would like to see that. I have some kind of monitoring for my Zwave Things on my todo list. Sounds like this can be a possible approach.

My memory was confused with JSR223… I had actually never played with this in the Rules DSL. But I took a quick look, and found a way to get a Thing status. I got excited about this, but there’s also the Thing Status Action.

You can also access a Things status by using…

// Rules DSL
import org.eclipse.smarthome.model.script.ScriptServiceUtil
import org.eclipse.smarthome.core.thing.ThingUID
logDebug("Rules", "Rules DSL: test=[{}]",ScriptServiceUtil.getInstance.thingRegistry.get(new ThingUID("kodi:kodi:familyroom")).statusInfo)

Bonus info… in JSR223, the Thing registry is already accessible through the predefined script variable things

# JSR223
from org.eclipse.smarthome.core.thing import ThingUID
log.debug("JSR223: test=[{}]".format(things.get(ThingUID("kodi:kodi:familyroom")).statusInfo))

There’s not much you can do with Channels, but you could potentially trigger them.

1 Like

This is great!

1 Like

I am trying to do the same however I have a problem with the syntax because it seems case sensitive, is there a way to find the item from the string regardless of case of the string?

import org.eclipse.smarthome.model.script.ScriptServiceUtil

rule "Update lights"
when
	Member of Group_Scenes changed 
then
	logInfo("Notification", "Scene changed: " + triggeringItem.name.split("_").get(0))
	val roomName= triggeringItem.name.split("_").get(0) //as SwitchItem
	val group =ScriptServiceUtil.getItemRegistry.getItem( "Group_"+roomName +"_Lights") as GroupItem

	if(group === null) {
        logError("admin", "Cannot find Item " + "Group_"+roomName +"_Lights")
        return;
    }

	
	if (triggeringItem.state ==0) {
		logInfo("Notification", "Group to set OFF: " + group.name)
		group.members.forEach[ i | i.sendCommand(OFF) ]
	}
	else if (triggeringItem.state ==1) {
		logInfo("Notification", "Group to set ON: " + group.name)
    	group.members.forEach[ i | i.sendCommand(ON) ]
	}
	else{
		val sceneGroup = ScriptServiceUtil.getItemRegistry.getItem( "Group_"+roomName +"_Lights_" +transform("MAP", "Livingroom.map", ""+triggeringItem.state) as GroupItem
		
		group.members.forEach[ i | 
			i.sendCommand(OFF)
		]
		sceneGroup.members.forEach[ i | 
			i.sendCommand(ON)
		]
		
		logInfo("Notification", "Scene to set: " + scene)
	}
end

This fails due to the fact the map file is in upper case:

val sceneGroup = ScriptServiceUtil.getItemRegistry.getItem( "Group_"+roomName +"_Lights_" +transform("MAP", "Livingroom.map", ""+triggeringItem.state) as GroupItem

Is there a way around this issue?

Could something like String toLowerCase() help?

No, it’s case sensitive and there is no way around that.

OK, i rewrote my map file to handle it. However I have another issue now:

val dimmer=ScriptServiceUtil.getItemRegistry.getItem(roomName +"_Dimmer") as DimmerItem

Makes my rules fails if the item does not exist:

2019-04-23 22:02:23.284 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'Update lights': Item 'Hallway_Dimmer' could not be found in the item registry

Is there aworkaround this issue? I just want to ignore the item if it does not exist:

if (triggeringItem.state ==0) {
		logInfo("Notification", "Group to set OFF: " + group.name)
		group.members.forEach[ i | i.sendCommand(OFF) ]
		dimmer.postUpdate(OFF)// ignore this if dimmer does not exiost
	}

You should create a new topic to request help with your rule, as it is not pertinent to this tutorial. Include your full rule and the functionlaity you are trying to achieve, as it looks like you may be making things more complicated than they need to be. Ping me on your new topic and I’ll be glad to assist.

@5iver Point taken,
However I have used ur tutorial with great succsess until today when an item does not exist in the register:

Rule 'Update lights': Item 'Hallway_Dimmer' could not be found in the item registry

So if there is no workaround, is it possible for you to update your original post with this line:

Note when using ItemRegistry it is important that the item EXIST otherwise your rules will FAIL!

You’re rule has a configuration error, as it’s trying to get a nonexistent Item from the ItemRegistry. If the Item does not exist, ItemRegistry.getItem() throws an ItemNotFoundException. To avoid or workaround the exception, you could wrap it in a try/catch…

var GenericItem sceneGroup
try {
    sceneGroup = ScriptServiceUtil.getItemRegistry.getItem("bad_item_name")
} catch (ItemNotFoundException infe) {
    logInfo("Rules", "bad Item")
}
if (sceneGroup !== null) {
    logInfo("Rules", "sceneGroup=[{}]", sceneGroup.name)
}

However, I do not see why this would even be needed for use in a rule, since this exception would only occur due to a rule configuration error. Fix the rule and you won’t get the error!

To be fair, there are uses for running a search that may “fail” and dealing with it tidily.
You may have a large set of Items to handle, only some of which have some associated Item - a related device say - so seeking the associated Item will sometimes fail.