Detecting offline Things in a less stupid way

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.

Youā€™ll want to read up on

@fullmoonguru I use the switch to start another piece of code that parse every event in the linked queue. I decided to use the timer to start parsing after some seconds because in this way I can manage better when I receive several offline errors together, for example when Hue hub goes offline with every connected light.

@rossko57 thanks for replying I DID read up on this before trying it. Hereā€™s what I tried:

First I did:

Rule ID = Device_Offline_Alert

When > Thing Event > Log Reader > A Trigger Channel Fired > Channel > New Error Event > Event = COMMUNICATION_ERROR

Then > Execute a script (my email script)

That didnā€™t work so then I tried:

When > Thing Event > Log Reader > Trigger Channel Fires > Channel: newCustomEvent > Event = COMMUNICATION_ERROR

No luck there either.

I also tried Status change to OFFLINE and Status updated to OFFLINE, in the Logreader thing which didnā€™t work (I assume thatā€™s because itā€™s referring to the Logreader thing itself).

I can see the alerts in the logfile so I know thatā€™s working, and I know the email is working because I can get successful results by making the trigger an offline alert from a specific Thing.

@rossko57 do you have any ideas why this isnā€™t working for me?

is difficult to diagnose. Did the rule run, is it your email that doesnā€™t work? Itā€™s up to you to find out. You could add a log at the start of your rule to say when itā€™s triggered.

Do you see that in your events.log ? May we see? Does it match your rule trigger? May we see that? ('codeā€™if you are using UI rules)

Are there any new options in OH3 to support ā€œmonitoring a group of thingsā€
a) thing status changed
b) certain channels are triggered

This should be based on groups. I just started to implement a rule monitoring my Shellys in an alarm is triggered. After the 15h ā€œChanel xxx triggered oirā€ I stopped and looking for other options. For me it doesnā€™t make sense that I need to list every channel and hard code the thing uid. Why is it not possible to build a group of channels?
or could I

  • build a group of things (possible with the OH3 model)
  • have a periodic rule (once a minute)
  • iterate on the group, get thing status and check for OFFLINE

That would be ok for checking the thing status, but even doesnā€™t cover the aspect that things raise an alarm by triggering a channel.

As with pretty much all ā€œwhyā€ questions in openHAB, the answer is because no one has implemented it. Adding support for something like this will require changes to the core API and rule engine triggers since the ThingRegistry doesnā€™t have any way to get all Things or all Things with a given tag or anything like that.

Not possible, The model is just a way to organize your Items.

This is very much possible.

Given you canā€™t use the ThingRegistry to get the list of all Things and you canā€™t create a Group of Things about the only remaining thing you can do is to query the REST API for all the Things and parse through the returned JSON. You could put that into a once a minute triggered rule or the like.

@rossko57 I had a logfile running and could see the Thing drop. I just never saw the rule trigger. The email is the same script I use for other alerts and I also manually triggered the rule and got the email. I am working on some other issues right now but Iā€™ll get back on this and try to give more detailed info. I appreciate the help.