Detecting offline Things in a less stupid way

The main reason I didn’t recommend that to OP in the first place is because anything based on the Expire binding is going to require that the Item linked to the channel to receive an update or command on a regular schedule (e.g. always posts update once per hour whether there is a change in state or not). I wasn’t sure if all of the Things op was interested in do that and I didn’t want to waste time if they don’t.

For completeness, here is a generic approach that will work if the Items are periodically updated whether or not there is a change.

Look at the bottom of the top post for the Expire based example.

When one is using the MQTT binding (subscribe to the topics the device reports on and set the online switch to ON for any message) or devices that have a heartbeat channel (some ZWave devices, e.g zcombo smoke/co alarms) or online status channel (e.g. Nest) you don’t need most of the code in that example as the Expire binding handles everything but the reporting.

But can you iterate over all the Things to get their status? That is the core of OP’s problem.

I agree with this statement and OH itself really isn’t built that well to support this type of monitoring. However, often in a Rule one will want to do something different if OH can know that something is down so whatever system you are using needs to report this status to OH which kind of puts you back in the same situation of OH monitoring itself.

But I think of it like this. OH really isn’t monitoring itself in this case, it is monitoring the status of the actuators and sensors that it interacts with and their online status can be very important. I suppose it all depends on where do you draw the boundaries between OH and Not OH.

I believe so.

1 Like

Just upgraded to 2.4, and ThingAction is no longer working.

This is my rule

rule "TV Binding State Changed"
when
    Thing "samsungtv:tv:familyroom" changed
then
    var status = ThingAction.getThingStatusInfo("samsungtv:tv:familyroom").getStatus()
    logInfo("Family Room TV", "TV Thing status changed to: " + status)
    postUpdate(TV_Status, status.toString)
end

This throws the error

23-Dec-2018 14:30:46.570 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'TV Binding State Changed': The name 'ThingAction' cannot be resolved to an item or type; line 3586, column 18, length 11
23-Dec-2018 14:30:46.570 [DEBUG]

Has anyone else seen this since upgrading to 2.4?

Thanks,

Just found that. Another breaking change.
Been fighting them for 4 days since the upgrade, quite a lot in 2.4.

Just tracking down the last few lurking breaking changes now though…

Thanks for the link, I was having a hard time finding a list of breaking changes in 2.4.

Thanks for inspiring me with this discussion! I added some Thing monitoring to my rules and optimized logging to suppress some unavailable or unhelpful strings from getThingStatusInfo():

rule "TV"
when
    Thing 'samsungtv:tv:14dc9380_XXX' changed
then
    val ThingStatusInfo = getThingStatusInfo("samsungtv:tv:14dc9380_XXX")
    if(ThingStatusInfo===null) return;
    var String thingStatusInfo_Msg = ""
    if(ThingStatusInfo.statusDetail.toString!="NONE")	thingStatusInfo_Msg = thingStatusInfo_Msg + ThingStatusInfo.statusDetail
    if(ThingStatusInfo.description!==null)				thingStatusInfo_Msg = thingStatusInfo_Msg + ThingStatusInfo.description
    if(thingStatusInfo_Msg!="")	logInfo("RULE","@R TV: {} [{}]", ThingStatusInfo.status, thingStatusInfo_Msg)
    else						logInfo("RULE","@R TV: {}", ThingStatusInfo.status)
end

I agree with @rlkoshak that adding a rule for each Thing is suboptimal.
It would be easier if something like triggeringItem exists for Things as you may find in this non-working example:

rule "TV"
when
    Thing 'samsungtv:tv:livingroom' changed or
    Thing 'samsungtv:tv:bedroom' changed or
    Thing 'samsungtv:tv:kitchen' changed
then
    val TriggeredThing = triggeringThing
    val ThingStatusInfo = getThingStatusInfo(TriggeredThing)

Would it make sense to open an issue for this?

Cheers,
Alex

The new rule engine handles this, so IMO it is doubtful much effort would go into adding features to the old rule engine, but you never know!

hmmm…I was warned to revive the topic after one year, but I think it might be quite of interest and want to contribute a solution that was not mentioned here before (I hope) and did do the trick for me.
I dont want to really monitor all my things but for sure also not create a rule for every single thing. But creating an item to hold the status for every thing I want to monitor seemed ok for me. So I use http binding with REST API and very simply created an item:

String OnlineAstroMoon "Online AstroMoon [%s]" (gOnline) { http="<[http://localhost:8080/rest/things/astro:moon:gg:30000:JSONPATH($.statusInfo.status)]"}

You can have than a rule for gOnline group changed if you want to alert getting the triggering item.
You could do some more transform in JS to use a switch item (having JS return ON/OFF)
You can also do more transform in JS to get more out of the Json returned by the REST api (thing label or thing UID…)
And through http binding you can obviously set the timing how often you want to check the thing.
And also nice in my experience: You can monitor via persistance (using Grafana or similar)

2 Likes

I can contribute something for all the HABApp users, too.
HABApp listens to the Thing events and it is really easy to trigger on a change of the status.

import HABApp
from HABApp import Rule
from HABApp.openhab.events import ThingStatusInfoChangedEvent
from HABApp.openhab.items import Thing


class CheckAllThings(Rule):
    def __init__(self):
        super().__init__()
        
        for thing in HABApp.core.Items.get_all_items():
            if isinstance(thing, Thing):
                thing.listen_event(self.thing_status_changed, ThingStatusInfoChangedEvent)
                print(f'{thing.name}: {thing.status}')
    
    def thing_status_changed(self, event: ThingStatusInfoChangedEvent):
        print(f'{event.name} changed from {event.old_status} to {event.status}')


CheckAllThings()

Sorry for my ignorance, but what is HABApp? Cannot find anything in the docs … :joy:

grafik

Python3 Rule engine which you can easily spin up next to openhab, normally I link it but I guess I forgot:

1 Like

Cool solution @haesslo many thinks!

I’m not an expert in JS, so can you please give some tips about how to do that? I assume you would add a script to the rest api url?

I tried and what should work is :
Define item as switch with http binding and transform JS:
Switch OnlineAstroMoon2 "Online AstroMoon [%s]" (gOnline) { http="<[http://localhost:8080/rest/things/astro:moon:gg:30000:JS(online.js)]"}

Then in your transform directory you create a fiel “online.js” which should contain:

(function(i) {
	var obj = JSON.parse(i); 
	var status	= obj.statusInfo.status;
	if (status == "ONLINE") {
			return "ON"
	} else {
			return "OFF"
	}		
})(input)
2 Likes

Hi @haesslo many thanks for the suggestion. I am not sure if it is really much simpler than creating a rule though. :slight_smile:

Maybe question of taste, but just to clarify:
The rules proposed here were one rule per Thing
My solution is one item per thing and one generic JS for all, which is only needed if you want to have the ON/OFF feature.
I feel managing items is much easier than managing rules…

Just for information, this is how I do it (in a rule)…

// lambda to update an (Alarm) Item to reflect a Thing's Offline state
val Functions$Function2<String, SwitchItem, Boolean> setOfflineAlarm = 
[ String thingId, SwitchItem alarmItem |
    val thingStatus = getThingStatusInfo(thingId)
    if ((thingStatus !== null) && (thingStatus.getStatus().toString() == "ONLINE")) {
        alarmItem.postUpdate(OFF)
    } else {
        alarmItem.postUpdate(ON)
    }
    true
]

rule "Hub Offline Status Checks (at *:*:5)"
when
    Time cron "5 1/1 * * * ?"
then
    setOfflineAlarm.apply("siemensrds:climatixic:cloud", Siemens_Cloud_Server_Offline_Alarm)
    setOfflineAlarm.apply("neohub:neohub:g24", Heatmiser_NeoHub_Offline_Alarm)
    setOfflineAlarm.apply("tado:home:g24", Tado_Home_Hub_Offline_Alarm)
    setOfflineAlarm.apply("hdpowerview:hub:g24", Luxaflex_Hub_Offline_Alarm)
    setOfflineAlarm.apply("hue:bridge:g24", Philips_Hue_Hub_Offline_Alarm)
    setOfflineAlarm.apply("harmonyhub:hub:g24", Logitech_Harmony_Hub_Offline_Alarm)
    setOfflineAlarm.apply("velux:klf200:g24", Velux_KLF200_Hub_Offline_Alarm)
    setOfflineAlarm.apply("zwave:serial_zstick:g24", ZWave_USB_Stick_Offline_Alarm)
    setOfflineAlarm.apply("astro:sun:g24", Astro_Offline_Alarm)
    setOfflineAlarm.apply("homeconnect:api_bridge:g24", Bosch_Home_Connect_Offline_Alarm)
    setOfflineAlarm.apply("darksky:weather-api:g24", DarkSky_Weather_API_Offline_Alarm)
end
1 Like

You can simplify the lambda definition a bit:

val setOfflineAlarm = [ String thingId, SwitchItem alarmItem |
    val thingStatus = getThingStatusInfo(thingId)
    if ((thingStatus !== null) && (thingStatus.getStatus().toString() == "ONLINE")) {
        alarmItem.postUpdate(OFF)
    } else {
        alarmItem.postUpdate(ON)
    }
]

It’s smart enough to figure out what type setOfflineAlarm needs to be on it’s own and since you don’t actually use the return value, it will generate a Procedure instead of a Function.

3 Likes

Had to implement Things monitoring as well, however in my case quite often the Thing only goes offline for a couple seconds, before the Binding reconnects (ChromeCasts). Here are the two rules I use for this:

// use a timer to avoid notifications for short outages

rule "ChromeCast Living Room offline - timer"
when
    Thing "chromecast:chromecast:xyz123" changed from ONLINE to OFFLINE
then
    logInfo("ChromeCast Status", "ChromeCast Living Room offline")
    if (timerLivingRoom === null) {
        timerLivingRoom = createTimer(now.plusMinutes(1), [ |
            // there was no cancel because the Thing never came online in the past minute
            // ... send notification here
            logInfo("ChromeCast Status", "ChromeCast Living Room is offline")
        ])
    }
end

rule "ChromeCast Living Room online - timer"
when
    Thing "chromecast:chromecast:xyz123" changed from OFFLINE to ONLINE
then
    logInfo("ChromeCast Status", "ChromeCast Living Room is online")
    if (timerLivingRoom === null) {
        // recovered from longer downtime, > 1 minute
        logInfo("ChromeCast Status", "Timer for chromecast:chromecast:xyz123 is already stopped")
    } else {
        logInfo("ChromeCast Status Debug", "Timer for chromecast:chromecast:xyz123 is running, stopping it")
        timerLivingRoom.cancel()
        timerLivingRoom = null
        // no further notification required, was a short downtime
    }
end

Need to pour that with more details into a blog post as well …

The same thing with HABApp:

from HABApp import Rule
from HABApp.core.events import ItemNoChangeEvent
from HABApp.openhab.items import Thing


class CheckThing(Rule):
    def __init__(self, name: str):
        super().__init__()
        
        self.thing = Thing.get_item(name)
        watcher = self.thing.watch_change(60)
        self.thing.listen_event(self.thing_no_change, watcher.EVENT)
    
    def thing_no_change(self, event: ItemNoChangeEvent):
        print(f'Thing {event.name} constant for {event.seconds}')
        print(f'Status: {self.thing.status}')


CheckThing('chromecast:chromecast:xyz123')
1 Like

I was looking for the same thing but I didn’t want to write a rule for every thing so I choose to use the logreader binding to trap ONLINE/OFFLINE events and an hashmap to associate things to items.

You might be able to put all ChromeCasts into a group, and then react on changes in the group.

In the end I decided to write them with a loop in the Ansible template. This way I change the template and when it is deployed, it created a rule for every CC.