Get Thing from Item to tell Item status (e.g. online or offline)

Hi,

I run OpenHAB 2.1, and I can now see the status of Things in rules (e.g. see http://docs.openhab.org/addons/actions.html#thing-status-action).
I am now trying to link Items to the status of the Thing they come from, so as I can tell in my rules if an Item is online or offline based on whether the underlying Thing is online or offline.

This is because in my rules I want to randomly pick one Item in a group, with the constraint that this Item (i.e. its underlying Thing) must be online.

I found a slightly convoluted way to achieve this, but I am hoping there is a neater way to do this.

Is there an API call to get the Thing an Item is linked to, and/or the list of Items linked to an Item?

Thanks,
Thib.

ps: my current way to do this (note I am by no mean a Xtend expert, so good-practices and improvement suggestions welcome!)

First I add a tag with the name of the thing the item is linked to:

    Switch sLight "My light" <light> (gBackLights) ["thing=zwave:device:controller:node5"] {channel="zwave:device:controller:node5:switch_binary"}

This tag is not ideal as it is redundant information (we could tell the Thing from the channel), and a typo is all too easy (e.g one might type ā€˜node6ā€™ instead of ā€˜node5ā€™ and get unexpected results).

Then I created a function to check if the item is online:

    // isOnline.apply(item): returns a boolean indicating if the item (and underlying thing) is online
    // Note: this function assumes that a tag "thing=your:thing:string:name" is set on the item, e.g.:
    // Switch sLight "my light" <light> (gLights) ["thing=zwave:device:controller:node5"] {channel="zwave:device:controller:node5:switch_binary"}
    // Note that we return true if we can't find that tag.
    val Functions.Function1<GenericItem,Boolean> isOnline = [
        GenericItem item |	
    	logDebug("isOnline", "Starting on item=" + item.name)
    	for (String tag: item.tags) {
    		if (tag.startsWith("thing=")) {
    			var thingName = tag.split("=").get(1)
    			
    			// Now check the status of that Thing
    			var thingStatusInfo = getThingStatusInfo(thingName)

    			if ((thingStatusInfo != null) && (thingStatusInfo.getStatus().toString() != "ONLINE")) {
    			    logDebug("isOnline", "item '{}' is *NOT* online.", item.name)
    			    return false
    			}
    			
    		}		
    	}
    	// Return true if we didn't find that channel 
    	return true
    ]

and finally an example rule using this function:

    rule "test item and thing status"
    when
            Item sLight changed
    then
        gBackLights.members.forEach[item|
                if (isOnline.apply(item as GenericItem)) {
                        logInfo("test item status", "item {} is online", item.name)
                }
                else {
                        logInfo("test item status", "item {} is offline", item.name)
                }
        ]
    end
2 Likes

You can get the list of Things through the REST API in JSON format and then search through that list to find the Thing(s) the Item is linked to. Iā€™m not so certain that will be any better that your current approach. The call would be something like:

val allThings = sendHttpGetRequest("https://openhabserver:8080/rest/things")

Install and play around with the REST API docs for details and to see the JSON you get back. You might be able to use a JSONPATH string to go straight to the Things you want.

A more Xtend way to implement your for loop in the lambda would be:

item.tags.filter[tag | tag.startsWith("thing=")].forEach[ tag |
    var thingName = tag.split("=").get(1)
    var thingStatusInfo = getThingsStatusInfo(thingName)
    if((thingStatusInfo != null) && (thingStatusInfo.getStatus.toString() != "ONLINE")) {
        return false
    }
]
return true

Thanks Rich. Interesting to know about the REST API, for this question about also more generally speaking. I may be able to replace my manual tags by calling the ā€˜linksā€™ REST endpoint and parsing with JSONPATH. Iā€™ll give that a try.

And thank you for the suggestion on the more ā€˜Xtend wayā€™ to write the lambda. I gave it a try (there was a little type with an extra ā€˜sā€™ in getThingStatusInfo): the IDE doesnā€™t like it, but it looks a bit nicer and seems to work!

Hello @all,

tried today a little bit an got some working code for that:

val Functions.Function1<GenericItem,Boolean> isOnline = [ GenericItem item |

    val allLinks = sendHttpGetRequest("http://localhost:8080/rest/links")
    var String filter = "$..[?(@.itemName=='"+item.name+"')].channelUID"
                                                                                                                                                                                                                   
    var output = transform("JSONPATH", filter, allLinks)
    output = output.split('"').get(1)
    var thingName = output.split(":").get(0)+":"+output.split(":").get(1)+":"+output.split(":").get(2)+":"+output.split(":").get(3)
                                                                                                                                                                                                                   
    var thingStatusInfo = getThingStatusInfo(thingName)
                                                                                                                                                                                                                   
    logInfo("test","Thing:"+thingName+" Status:"+thingStatusInfo)
                                                                                                                                                                                                                   
    if ((thingStatusInfo != null) && (thingStatusInfo.getStatus().toString() != "ONLINE")) {
        return false                                                                                                                                                                                               
    }                                                                                                                                                                                                              
                                                                                                                                                                                                                   
    return true                                                                                                                                                                                                    
]   

This functions takes over an Item and resolves via the Link List the resposible Thing. If it is ONLINE, TRUE will be returned, otherwise FALSE.
This works well for me, maybe this can be improved a little bit.

2 Likes

I think ESH should provide better possibilities to solve this use case. Opened https://github.com/eclipse/smarthome/issues/5149 as a base for further discussion.

My apologies for reviving an old threadā€¦

@Sebastian_Zimmermann @rlkoshak

I copy and pasted your isOnline function verbatim. The only change I made was regarding a warning regarding the use of ā€˜!=ā€™ instead of ā€˜!==ā€™ for ā€˜nullā€™. I made the change to eliminate that warning.

I am getting an error when my rule triggers. For now I have a dummy switch triggering the rule just to invoke the isOnline function.

rule "Test item and thing status"
when
    Item gReachable changed
then
    isOnline.apply(kitchen_coffeeMaker as GenericItem)
end

When the rule runs, it generates an error:

[ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule ā€˜Test item and thing statusā€™: 1

This is the list of Things returned by the REST API

[
{"channelUID":"nest:thermostat:DeviceToken:mode","configuration":{},"itemName":"hallway_thermostat_HVACMode"},
{"channelUID":"nest:thermostat:DeviceToken:min_set_point","configuration":{},"itemName":"hallway_thermostat_TargetTemperatureMin"},
{"channelUID":"nest:thermostat:DeviceToken:max_set_point","configuration":{},"itemName":"hallway_thermostat_TargetTemperatureMax"},
{"channelUID":"nest:thermostat:DeviceToken:mode","configuration":{},"itemName":"hallway_thermostat_HVACMode"},
{"channelUID":"nest:thermostat:DeviceToken:temperature","configuration":{},"itemName":"hallway_thermostat_AmbientTemperature"},
{"channelUID":"nest:thermostat:DeviceToken:humidity","configuration":{},"itemName":"hallway_thermostat_Humidity"},
{"channelUID":"nest:thermostat:DeviceToken:max_set_point","configuration":{},"itemName":"hallway_thermostat_TargetTemperatureMax"},
{"channelUID":"nest:thermostat:DeviceToken:time_to_target_mins","configuration":{},"itemName":"hallway_thermostat_TimeToTarget"},
{"channelUID":"nest:thermostat:DeviceToken:humidity","configuration":{},"itemName":"hallway_thermostat_Humidity"},
{"channelUID":"nest:thermostat:DeviceToken:temperature","configuration":{},"itemName":"hallway_thermostat_AmbientTemperature"},
{"channelUID":"nest:thermostat:DeviceToken:min_set_point","configuration":{},"itemName":"hallway_thermostat_TargetTemperatureMin"},
{"channelUID":"nest:structure:StructureToken:away","configuration":{},"itemName":"nestStructure_Away"},
{"channelUID":"nest:structure:StructureToken:rush_hour_rewards_enrollment","configuration":{},"itemName":"nestStructure_RHREnrollment"},
{"channelUID":"nest:structure:StructureToken:time_zone","configuration":{},"itemName":"nestStructure_TimeZone"},
{"channelUID":"tplinksmarthome:hs105:tpID:switch","configuration":{},"itemName":"kitchen_coffeeMaker"},
{"channelUID":"tplinksmarthome:hs105:tpID:switch","configuration":{},"itemName":"livingroom_clock"},
{"channelUID":"tplinksmarthome:hs105:tpID:switch","configuration":{},"itemName":"den_fireplace"},
{"channelUID":"tplinksmarthome:hs105:tpID:switch","configuration":{},"itemName":"livingroom_lamp"},
{"channelUID":"astro:sun:local:set#start","configuration":{},"itemName":"Sunset_Time"},
{"channelUID":"astro:sun:local:rise#end","configuration":{},"itemName":"Sunrise_Time"},
{"channelUID":"astro:sun:minus105:set#start","configuration":{},"itemName":"Evening_Time"},
{"channelUID":"astro:sun:local:phase#name","configuration":{},"itemName":"Day_Phase"},
{"channelUID":"astro:sun:local:season#name","configuration":{},"itemName":"Season_Name"},
{"channelUID":"astro:sun:local:zodiac#sign","configuration":{},"itemName":"Zodiac_Sign"},
{"channelUID":"astro:sun:local:position#elevation","configuration":{},"itemName":"Sun_Elevation"},
{"channelUID":"astro:moon:local:position#elevation","configuration":{},"itemName":"Moon_Elevation"},
{"channelUID":"astro:moon:local:phase#name","configuration":{},"itemName":"Moon_Phase"},
{"channelUID":"astro:moon:local:phase#full","configuration":{},"itemName":"Moon_Next_Full"},
{"channelUID":"astro:moon:local:phase#new","configuration":{},"itemName":"Moon_Next_New"},
{"channelUID":"ntp:ntp:local:dateTime","configuration":{},"itemName":"Current_DateTime"}
]

The Item I am testing is in the list of links. But it does not appear that the execution is getting that far.

Any light you can shed would be appreciated.

Regards.

Mike

Lambdas are a pain when error occur. Here is aversion of Sebastianā€™s lambda with some additions that hopefully will provide a bit better of an error message.

val isOnline = [ GenericItem item |

    try {
        val allLinks = sendHttpGetRequest("http://localhost:8080/rest/links")
        var String filter = "$..[?(@.itemName=='"+item.name+"')].channelUID"
                                                                                                                                                                                                                   
        var output = transform("JSONPATH", filter, allLinks)
        output = output.split('"').get(1)
        var thingName = output.split(":").get(0)+":"+output.split(":").get(1)+":"+output.split(":").get(2)+":"+output.split(":").get(3)
                                                                                                                                                                                                                   
        var thingStatusInfo = getThingStatusInfo(thingName)
                                                                                                                                                                                                                   
        logInfo("test","Thing:"+thingName+" Status:"+thingStatusInfo)
                                                                                                                                                                                                                   
        (thingStatusInfo != null) && (thingStatusInfo.getStatus().toString() != "ONLINE") // the result of the last line is what gets returned, no need to have 4 lines of code to do this.
    }
    catch(Exception e) {
        logError("Test", "Failure in isOnline: " + e.toString)
        false
    }
]

Thanks Rich. Now I can do a little debuggingā€¦

Failure in isOnline: java.lang.ArrayIndexOutOfBoundsException: 1

And the reasonā€¦ this statement:

output = output.split('"').get(1)

because the filtered result (output)

tplinksmarthome:hs105:tpID:switch

Does not have a double quote character in it.

Now that I know the resulting output string, my question to @Sebastian_Zimmermann is, why the extra effort to parse the string to remove and re-insert colons? The before and after is exactly the same.

var thingName = output.split(":").get(0)+":"+output.split(":").get(1)+":"+output.split(":").get(2)+":"+output.split(":").get(3)

In the end though, Iā€™m not entirely sure that getThingStatusInfo(thingName) is doing what is intended as the result is null. I know that the Thing Iā€™m testing the code with is ONLINE (in PaperUI).

Regards.

Mike

Iā€™m no expert but my first guess is channelUID is not the same as ThingUID

if you omit the last part of the channeluid, the getThingStatusInfo should return something else than NULL

for example getThingStatusInfo(ā€œnest:thermostat:DeviceToken:modeā€) returns NULL
and getThingStatusInfo(ā€œnest:thermostat:DeviceTokenā€) should do the trick

var thingName = output.split(":").get(0)+":"+output.split(":").get(1)+":"+output.split(":").get(2)

Also The JSONPath Transformation Addon is needed for var output = transform(ā€œJSONPATHā€, filter, allLinks) to return the correct output

That makes sense now. @Sebastian_Zimmermannā€™s code recreated the ChannelUID because he included the last segment - the Channel, i.e., get(3) . So, dropping the Channel would make it the Thing. Thanks for spotting that nuance! Iā€™ll give getThingStatusInfo a try again.

Mike

2 Likes

Can I ask the motivation for wanting this? Is it because there are cases where you canā€™t rely on an itemā€™s state being changed to UNDEF when the bound thing is offline?

I donā€™t think all bindings respect the convention to force a channel to UNDEF in the event of comms failure. Probably not sensible in the case of e.g. battery devices going for a long sleep.
And there are write-only channels as well.

Yes, basically this. Times when acting on what appears to be an Item in ā€œgood standingā€ when it is actually offline.

This is specifically for my TP-Link devices. Thus, I have a routine that checks the Thing status for these devices and sends me an alert when they go offline.

Interesting; in my experiments with a binding I developed, when transition a thing to offline, I donā€™t explicitly set the associated channels to UNDEF, but it happens anyway. Might be a OpenHab 2.4 change?

I was testing it because I was testing a change I was working on for HomeKit, and noticed this behavior, and was like ā€œoh geez I guess I donā€™t need to write all of this code afterallā€.

Can you provide some more insight related to the TP-Link devices? Because the binding does set the channels to UNDEF if itā€™s offline. At least that is intended.

My items were not turning ā€˜undefā€™ when my things were going offline, at least z-wave items on earlier releases of OpenHAB (I havenā€™t tested 2.4).

I cannot be absolutely certain that the Items were not set to UNDEF. All I know is that in my rules, they were not responding to ON/OFF requests. Now that I am aware of the UNDEF state, I suppose I could add that check to my rule before I try to set the Item state. However, what I require is a way to be alerted that a device is offline before I actually need to use it so that I could take action to get it back online before it was called upon in my automations. The approach I used was to check on the status of the Thing. When the Thing Status changes to offline, I send a notification. This gives me a chance to investigate (usually unplug and plug back in is all that is required). But having it operational when required rather than finding out at the time it was needed is what Iā€™m after. I suppose I could monitor the Item state changing to UNDEF instead?

Well, itā€™s a little more complicated than that; some devices have channels that donā€™t receive values until their state changes (things like Z-wave devices). So if you add a z-wave device alarm sensor, the device will be online but the value will be UNDEF. You have to uses persistence and trigger the device on/off in order for the UNDEF to clear.

Aside from that caveat, UNDEF seems to be pretty reliable. Like I said, this is OpenHab setting it in response to thing transitioning to offline, so at least you donā€™t need to rely on binding authors to do this.

Iā€™m wondering rather if an issue here is that the offline status isnā€™t surfaced through basic UI? It might be possible to do a conditional icon for UNDEF state in the sitemap, I donā€™t know, Iā€™ve not tried it, but seems like sometime like that should be possible.

I was having a problem with one of my devices not responding to my requests, I didnā€™t know what was causing it until I ā€œstumbledā€ on the OFFLINE status when I was poking around in Paper UI. So, Paper UI is reporting the status. I was wanting to be automatically alerted rather than having to manually inspect Paper UI. Thatā€™s why I coded a rule that runs every few minutes to check the status of my TP-Link Things. This way I get an e-mail so I am audibly notified within a very few minutes of when the problem arises.

thatā€™s pretty straightforward in fact e.g. myswitch.png , myswitch-on.png myswitch-off.png where you use a big red question mark for myswitch.png. Thatā€™ll show up for NULL or UNDEF