Detecting offline Things in a less stupid way

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.

I use this

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.

Rickytr, can you share your rule using Log Reader? I’m trying this but can’t get it working.

@fullmoonguru I used the trigger to catch a new custom event and added every received event to a linked queue to be processed (because usually you receive more than one event if a thing goes offline…)

rule “Event log management”
when
Channel “logreader:reader:b211c4e3:newCustomEvent” triggered
then
if (logEvents.peek === null && evento.state.toString == “ON”)
evento.postUpdate (OFF)
logEvents.add(receivedEvent.toString())
if(timer === null && evento.state.toString != “ON”) {
timer = createTimer(now.plusSeconds(5), [ |
if (evento.state.toString != “ON”) {
evento.sendCommand(ON)
timer = null
}
])
}
end

I put in patterns for the logreader thing “to ONLINE|to OFFLINE” and filtered out ItemStateChangedEvent to avoid the loop.

1 Like

@Rickytr I’m pretty new at this. When you say:

I put in patterns for the logreader thing “to ONLINE|to OFFLINE” and filtered out ItemStateChangedEvent to avoid the loop.

How did you do that?

Then in the script, I’m not clear what it’s doing. It looks like it’s posting an event (a virtual switch you created?) as OFF if something is offline and ON if nothing is offline. Not clear what teh timer is doing.

Sorry for my noob questions & thanks for your help.