All "important" OFFLINE things

  • Platform information:
    • openHAB version:5.0.1

My openhub setup is getting bigger and bigger. More bindings, more things, more items. Sometimes I feel I am loosing overview about “is everything still working”.

Of course, the are multiple aspects in this question but the first one - i want to tackle - is “are all the things (and items) still connected and reachable. Some things run out of battery, some things might loose connection (hub not working, softwareupdate failed, e.g). And I see, that I have 2 different of things/(items). For some it is “normal”, that connection is lost (e.g. my TV, because this one just goes offline, if it is switche off, or a zigbee lamp, which is sometime really switched of at a physical switch) and for some it is “not normal” (e.g. a permanent sending temperature sensor, a shelly actuator).

And for those things (so, not all of them and i manually need to tag them) I want to create

a) an overview page (which of those things are not available) and

b) react on change in a rule on it.

…AND I AM REALLY WANDERING, WHY I DO NOT FIND A GOOD SOLUTION PATTERN FOR THIS PROBLEM - I FELL I MIGHT NOT BE THE ONLY ONE WHO HAS THIS IDEA.

What are my hopes:

  • find something which is less manual effort
  • find something which is good maintainable in future

What are the options I have in mind:

a) Thing status - item - rule group

  • Use the thing status as indicator
  • Create an new item (*-status) per relevant thing (so not all of them)
  • Create for each a new rule, which reacts on thing status change and populate the item
  • Create a group containing all new created status items
  • Use the group to show its members
  • Create a rule on the group, react,…

This sounds very straight forward, but this means, I have to create a lots of items and a lots of the same rules. Effort high, maintainability bad.

b) Generic rule - check against defined list

  • Use a rule which react generic on any thing status update - found somewhere that this should be possible
  • Check in the rule if the thing is inside a defined list (static list in the script)
  • And then somehow populate a string with the offline ones (add, remove,…)
  • This string could somehow be then sent to ONE item (string).
  • Show this one item somewhere
  • Create a rule which reacts on change,….

Drawbacks: What happens on startup/shutdown (initial prefilling), gets messy with lots of offline devices as the string gets long, the “react on any thing status update” rule trigger seems not available over main ui (where for maintainability reasons I like to have everything on one place).

c) Generic rule - check against meta attribute

  • in principle as b) but
  • instead of checking against a defined list, checking meta attribute I would add to all the “relevant” things.

Drawbacks: Same as b, I am still not sure how to really add and implement this meta attribute handling - found the one or other post near to that

d) Use items, bind to channels - ignore thing state

  • Look in every thing, if there is a channel, which is simmilar to the offline state (e.g. zigbee bindings might have/add that, Homematic items sometimes have connection, shelly have a heartbeat)
  • Interprete the value, maybe specific binding.
  • Use the items
  • Create a group,
  • ….

Drawback: Very specific and I have the feeling I am re-implementing a logic. High effort, low maintenance

Before I start I would really like to discuss with you your thoughts on that! I am really wandering if there is not a better and proved pattern out there.

Thanks!

1 Like

to c) I guess this is not possible. it is not possible to add metadata on Things. Did not find any possibility.

Some hints to the topic:

  • At least in DSL there is no way to check the thing state easily, but maybe other scripting engine may do the trick.
  • you could write a (maybe complex) rule which does an REST API call to get all things (see API Explorer in Developer tools). This will create a json object with all things.
    You can filter this json object to a list of uids: jsonpath:$[?(@.statusInfo.status!='ONLINE')].UID to get all things which are not ONLINE.
    With the list you can check every thing if it’s of interest for you (maybe all things of a specific binding?)
  • you can use the “location” of a thing for “metadata” as this property is not really required :slight_smile:
  • jsonpath:$[?(@.UID==‘aSpecificUID’)].label` will give the label of the thing.

I can tell you how I do it.

First of all, OH is not a great generic IT and network monitoring system so I only do this for the home automation relevant stuff.

Setting up the Items

I use the semantic model to organize my Items. This is important because everything that follows depends on that.

Each equipment that I care about has a status Item as a member of the Group. It doesn’t really matter what the name is, but it is semantically tagged with “Status”. Here’s an example from the Settings → Model page:

Toggling the Status Item when the device goes OFFLINE/ONLINE

This is going to depend on the device, Thing, and bindings. Some Things will report the online status of a device as a Channel on the Thing. Others will reliably detect when a device goes offline and set the status of the Thing to OFFLINE. Still others won’t do anything at all, and you need something else to tell you if the device went offline such as the Network binding to ping the device or just monitor the Items and you know it’s offline when the Item(s) stop updating.

This is why there is no generic solution to the problem. There is a huge variability in how one detects when a device has gone offline.

  1. For Things that offer a Channel, simply link the Channel to the Status Item. You may need to do a transform to convert the status to ON/OFF.
  2. For Things which change their own status use Thing Status Reporting [4.0.0.0;5.9.9.9]. The rule I pair with this rule template is as follows. Notice how I use a map to identify the Thing with the status Item. But were I to write this today I’d get to the Item through the Thing and the Links.:
configuration: {}
triggers: []
conditions:
  - inputs: {}
    id: "1"
    configuration:
      type: application/javascript
      script: >
        console.debug('Processing a Thing status change ' + ruleUID);

        const key = ruleUID+'_thingToItem';


        if(!cache.shared.exists(key)) {
          console.info('Creating Thing to Status Item map');
          cache.shared.put(key, { 'zwave:serial_zstick:zw_controller'      : 'Zwave_Status',
                                  'zigbee:coordinator_ember:zg_coordinator': 'Zigbee_Status',
                                  'ipcamera:generic:garage_camera'         : 'GarageCamera_Raw',
                                  'mqtt:topic:broker:family'               : 'FamilyRoomWaveplus_Status',
                                  'mqtt:topic:broker:basement_waveplus'    : 'BasementAirthingsWavePlus_Status'
                                });
        }


        const rval = cache.shared.get(key)[thingID] !== undefined;


        console.debug('Run the actions? ' + rval);


        return rval;
    type: script.ScriptCondition
actions:
  - inputs: {}
    id: "2"
    configuration:
      type: application/javascript
      script: >
        console.debug('Thing process change running...');

        console.debug('Thing ' + thingID + ' changed from ' + oldStatus + ' (' +
        oldDetail + ') to ' 
                      + newStatus + '(' + newDetail + ')');

        const itemName = cache.shared.get(ruleUID+'_thingToItem')[thingID];

        const newState = (newStatus == 'ONLINE') ? 'ON' : 'OFF';


        console.debug(cache.shared.get(ruleUID+'_thingToItem'));

        console.debug('Thing status update: ' + itemName + ' ' + newState);

        items[itemName].postUpdate(newState);
    type: script.ScriptAction

  1. For devices that you just have to monitor and set to OFF when it stops updating the Items you can use Basic Profiles - Transformation Services | openHAB or Threshold Alert and Open Reminder [4.0.0.0;5.9.9.9]. I use the latter and the rule I pair with the rule template is as follows. Notice how I navigate the semantic model to find the status Item.
configuration: {}
triggers: []
conditions:
  - inputs: {}
    id: "2"
    configuration:
      type: application/javascript
      script: >
        const equipment = items[alertItem].semantics.equipment;

        const statusItem = equipment.members.find(i =>
        i.tags.includes['Status']);

        if(equipment === null || statusItem === null) {
          console.warn(alertItem + ' does not belong to an equipment or equipment doesn\'t have a Status Item!');
          return false;
        }

        else {
          const statusItem = items[equipment.name+'_Status'];
          console.info('Sensor status reporting called for ' + alertItem + ', equipment ' + equipment.label + ', is alerting ' + isAlerting + ', and is initial alert ' + isInitialAlert 
                       + ', current equipment state is ' + statusItem.state);
          //         Sensor is offline                             Sensor back online
          return (isAlerting && statusItem.state != 'OFF') || (!isAlerting && statusItem.state != 'ON');
        }
    type: script.ScriptCondition
actions:
  - inputs: {}
    id: "1"
    configuration:
      type: application/javascript
      script: >
        const equipment = items[alertItem].semantics.equipment;

        const statusItem = equipment.members.find(i =>
        i.tags.includes('Status'));

        const refid = statusItem+'_offline_alert';


        if(isAlerting && statusItem.state != 'OFF') {
          statusItem.postUpdate('OFF');
          console.info(equipment.label + ' is offline!')
          actions.notificationBuilder(equipment.label + ' has stopped reporting!')
                 .addUserId('rlkoshak@gmail.com')
                 .withTag('Alarm')
                 .withTitle(equipment.label + 'Stopped!')
                 .withIcon('if:wpf:online')
                 .withReferenceId(refid)                              
                 .send();
        }

        else if(!isAlerting && statusItem.state != 'ON') {
          statusItem.postUpdate('ON');
          console.info(equipment.label + ' is back online');
          actions.notificationBuilder("hide notification").withReferenceId(refid).hide().send();
        }

        else {
          console.info('Sensor status update alerting ' + isAlerting + ' initial ' + isInitialAlert + ' equipment ' + equipment.label + ' status ' + statusItem.state);
        }
    type: script.ScriptAction

I don’t have a great approach to dealing with restarts but where needed I use Restart Expire [4.0.0.0;5.9.9.9] to update the Items being monitored by Threshold Alert with whatever state they got restored to from persistence during startup. I’ve modified my Threshold Alert rule to trigger on state updates instead of changes, so this update causes Threshold Alert to start monitoring the Item again. If it doesn’t get updated again the status will be set to OFF.

Monitoring the status from the UI

I’m a strong proponent of not showing stuff that doesn’t need to be shown. So I have a widget that only shows those services/equipment that are offline. If it’s online, there’s nothing to show.

Like the rule templates above, I’ve posted this to the marketplace as well: Service Status Standalone Widget

The widget assumes that any Item tagged with “Status” represents the online status of a service and it will only show it if that Item happens to be OFF.

All the rule templates and the widget can be installed through the Add-on Store. Read each for any additional requirements (e.g. the rule templates posted above require JS Scripting and openHAB-rules-tools to be installed (you can install OHRT from openhabian-config, using Install openHAB Rules Tools [5.0.0.0;5.9.9.9] or manually using npm).

Reacting

As you can see above, all I really do when a device goes offline is to publish a notification and cancel the notification when it comes back online. But you can do anything you want in the rule called by Threshold Alert.

You can use a GenericTrigger or the rule template I posted above (which uses a GenericTrigger). I believe for some cases they added an ability to trigger one rule with a wild card ThingStatusEvent but I don’t know the details.

Items have metadata, but Things do not.

Yes, in JS Scripting and Blockly and jRuby it’s easy to get a Thing’s status in a rule.

The rule template I posted above triggers on all ThingStatusInfo events for all things. So you can use that trigger as an example or just install and use that rule template.

2 Likes

It is possible, just had your configuration data Things will hold thé payload you need

Well, you have battery monitoring solutions readily available…

We run under the assumption that 99% of the cases something went offline is the battery…

I have all my batteries in a gBattery, and I get email notification if some of them drops below X.

Another more useful tactic I use, is I have set my temp/humidity sensors items to expire in 20min (simple by expire binding, or nowadays expire is builtin).

So, if any of the sensors do not update in a timely manner, I will just see temp/humidity as “-” on the sitemap/dashboard. Otherwise, if it stays 25.2C it can be like that for days before I notice room temp sensor is off.

As for power connected things, I do not monitor those as I do not have any outages on them, so not worth the effort for me (this again confirms the theory for others that there is no generic solution).

In my case battery level is not reliable enough. It happens much to often that a battery level of 25% (or sometimes even 40%) stops working the next day….where a similar thinks works several weeks at 0%.

But in principle I agree, that this would be the way to go for battery devices (if they would deliver reliable - where I do not have any influence).

I am thinking about your idea with the expire binding/option. For all the battery devices this could be really an idea. There a fast reaction often not needed. And in principle it is even very “right” that you set the related item (e.g. temparature) to UNDEF if you did not receive an update for a longer (or shorter) time period. But this is very context and device/thing specific (and this is the reason why I am still not 100% convinced).

BUT 99%: At my side it is not 99%, more 90%, and the more important devices (which influence other things in something like controller loops or alarm systems) do not have the battery problem.

Still need to continue thinking…but the expire option is a new idea! thanks!

The more I think about the topic, the more I realize, that even to understand the problem space (what do i really want) is complex and my tendency, how I want so solve it switches between “clean/right and easy”.

For “as an operator a have a simple overview which shows my if my important devices are online” this is really an easy solution (if you do not location for other purposes). Tag location“very_important_thing” and “import_thing”. There is an an already available view in the existing UI which list them grouped by location and filters the non locations. Definitly easy….not so clean :wink:

…and I recognice, that as my things do not have so speaking names (i did the semantic naming on the item level), I then need to do some navigation to find out, what is really behind an offline thing. But this does not happen so often.

yeah, same here, I mostly ignore alerts about battery because some devices keep working for months on 0%….its just a general overview :slight_smile:

but expire is good yes, you notice it immediately, or if many sensors suddenly expire simultaneously, then you know its not the battery, its the bridge :slight_smile:

I’ve created an automation that checks the status of ‘important’ devices and sends a notification to a Telegram bot with a configurable delay in seconds (since a device might go offline and then come back - that’s not an issue).
There’s also a group of devices that aren’t connected to backup power, and to avoid spam when the main power grid goes down, I monitor them in combination with the presence of electricity.
There are a couple of my own libraries here, but overall I think the idea is clear.

const bot = require('openhab-telegram').dovgodko;
const history = require('openhab-history').Device;
const alerts = require('openhab-alerts');

// thing = debounce (secs)
const devices = {
    'netatmo:home-coach:home:coach1': 360,
    'netatmo:home-coach:home:coach2': 360,
    'netatmo:weather-station:home:inside': 360,
    'netatmo:outdoor:home:inside:outside': 360,
    'modbus:tcp:nze': 60,
    'modbus:poller:N4DAC02_1:holding': 360,
    'mqtt:topic:ot': 60,

    'mqtt:topic:aircon_bedroom': 60,
    'mqtt:topic:aircon_kate': 60,

    'mqtt:broker:solar': 15,

    'mqtt:topic:zigbee_0x00be44fffe0ed00b': 60,
    'shelly:shellyuni:34945478fbbd': 60, // ворота
    'mqtt:topic:sv9EF830':60, // хвіртка
    'mqtt:topic:zigbee_bridge':15, // z2m
    'mqtt:broker:main':15, //mqtt
};

const nopower = [
    'mqtt:topic:aircon_bedroom',
    'mqtt:topic:aircon_kate'
];

let problems = {};

const h = function(status, uuid) {
    // offline
    let message = things.getThing(uuid).label + ' - відсутній зв\'язок ('+status+')';

    history.warning(message);

    const is_grid = !alerts.ElectricityLost.fired;
    if (is_grid || !nopower.has(uuid)) {
        bot.alert(message);
    
        problems[uuid] = things.getThing(uuid).label;

        alerts.EquipmentAlarm.fire('Відсутній зв\'язок: ' + Object.values(problems).join(', '), 0);
    }
}

rules.JSRule({
    name: "Devices offline",
    triggers: [
        ...Object.keys(devices).map(uuid => triggers.ThingStatusChangeTrigger(uuid)),
        triggers.GenericCronTrigger(`0 0/1 * ? * * *`, 'timer')
    ],
    execute: (event) => {

        if (event.eventType === 'time') {

            for (const [uuid, secs] of Object.entries(devices)) {
                if (!(uuid in problems) && things.getThing(uuid).status != 'ONLINE') {
                    let timer_key = `thing-offline-timer:${uuid}`;
                    if (!cache.private.exists(timer_key)) {
                        cache.private.put(timer_key, setTimeout(() => {
                            try {
                                h(things.getThing(uuid).status, uuid);
                            } finally {
                                cache.private.remove(timer_key);
                            }
                        }, secs*1000, things.getThing(uuid).status, uuid));
                    }
                }
            };
            return;
        }

        const uuid = event.thingUID;
        const key = `thing-offline-timer:${uuid}`;

        if (!(uuid in devices)) return;

        if (!(uuid in problems) && event.newStatus != 'ONLINE' && event.oldStatus == 'ONLINE') {
            const timerId = setTimeout(() => {
                try {
                    h(event.newStatus, uuid);
                } finally {
                    cache.private.remove(key);
                }
            }, devices[uuid]*1000, event.newStatus, uuid);
            cache.private.put(key, timerId);
        } else if (event.newStatus == 'ONLINE') {
            const prev = cache.private.remove(key);
            if (prev) clearTimeout(prev);
            else {
                // online
                let message = things.getThing(uuid).label + ' - відновлено зв\'язок ('+event.newStatus+')';

                history.recover(message);
                if (uuid in problems) bot.message(message);
            }
            delete problems[uuid];

            if (Object.keys(problems).length == 0) {
                alerts.EquipmentAlarm.restore();
            } else {
                alerts.EquipmentAlarm.fire('Відсутній зв\'язок: ' + Object.values(problems).join(', '));
            }
        }
    }
});
1 Like

Thanks for all the input. For who ever might be interested in future about my solution:

It took my a while until i did fully understand my system. If I analyze the availability of my things I have 3 different kind:

  1. Those which are really always ONLINE (permanent bulbs, permanent switches, battery driven temperature sensors, shellys,….) - here I want to be informed, when they are not anymore
  2. Those who are really not always ONLINE, more sporadic (e.g. a bulb which is behind a mechanical switch, a TV where the binding switches to offline, when it is off,….) - here I do not want to be informed (because I guess, I will identify the issue anyway in daily usecases)
  3. And those which are normally ONLINE, but there are the cases, when the get regularly OFFLINE (e.g. a solar inverter which get OFFLINE in the night) - here I would like to be informed, but maybe not immediately (but the rule might be use case specific)

Solution for 1)

So my solution to 1) is (it is pretty unclean, but simple):

  • I misuse the location property of the device to mark things of category 1 (in my case location:permanent)

  • I wrote a JS Rule, which watches generically all thing status changes. In case of a change, I send a notification - in my case a telegram message. The trigger for this (if you add the rule via MainUI is a bit tricky.) You can add wildcards, but not full ones. So my config roughly looks like this:

    triggers:
      - id: "1"
        configuration:
          thingUID: mqtt:*
        type: core.ThingStatusChangeTrigger
      - id: "3"
        configuration:
          thingUID: homematic:*
        type: core.ThingStatusChangeTrigger
      - id: "4"
        configuration:
          thingUID: hue:*
        type: core.ThingStatusChangeTrigger
    ...
    
    
    
    // The event object is automatically provided
    var thingUID = String(event.thingUID);    // e.g. "mqtt:topic:mybroker:tempsensor"
    
    // for unknown reasons the below does not work, so need to get new status via thing (more below)
    // var newStatus = String(event.newStatus);     // e.g. "ONLINE", "OFFLINE", "UNKNOWN"
    var thing = things.getThing(thingUID);
    
    var newStatus = thing.status;   // e.g. "ONLINE", "OFFLINE", "UNKNOWN"
    var label = thing.label;
    
    // console.log(`Thing '${label}' [${thingUID}] changed status to ${newStatus}`);
    
    var location = thing.location;
    
    //i misuse location here as a marker, if this is a relevant thing
    if (location) {
      
      // console.log(`Thing '${thingUID}', location: ${location}`);
      const message = `Thing '${thingUID}', '${label}' , location: ${location} got ${newStatus}`;  
      
      if (newStatus != "ONLINE") {
        console.log(`ALERT: ${message}`);
        // Hier kann weitere Logik folgen, z.B. Benachrichtigung
      }
      
      const telegramAction = actions.get("telegram", "telegram:telegramBot:telegram_bot1");  
      telegramAction.sendTelegram(message);   
    }
    
  • And I use the main UI - Thing - By location to have an overview and show just the relevant ones.

  • Inspired by and thanks to @Udo_Hartmann

Solution for 3)

  • The approach here is pretty simple and already described by @rlkoshak above**.** Thanks!
  • Create an item and map it to a channel where you can derive your info from (maybe you have a flag, maybe something like a last update time). The expire option of openhab can help here as well.
  • Create a rule which reacts “adequately” on the change.
    • Sometimes immediate reaction might be ok (e.g. last upate time gets to high)
    • Sometimes a delayed action (with a timer) might be ok (e.g. you want to set an alarm if offline for 24h)
  • Write the result into a *_alarm item (one item per thing)
  • Create an All_Alarms group, add all the individual ones.
  • Show this All_Alarms somewhere
  • Write a rule which reacts on a change in all_alarms and do a notificatoin (in my case telegram)

I do not have so many of them, so for me it is ok, to do some “handdrawn” config + rules here.

And an other kind of different categorization - items in controller loops

After really understanding my needs, I understand, that I have lots of things and items in my system where fast reaction (in case of offline) is not necessary, but there are rare ones, where just notification (as described above) is not enough.

In my case this are in principle sensor values which are more or less included in medium fast controller loops. Think of a temperature sensor which reads the temperature and based on it a valve is opened or a high level controlling of “Nulleinspeisung”.

In this case the pure “Signaling” of OFFLINE is not enough. In this case (at least for me) the “Expiration Timer” is a good choice. If I know that I should get a value e.g. every 1 minute, you can set an expiration timer at 3 minutes and set the value to UNDEFINED. This can be used in your controller loop automatically. And of course you can create an alarm (as in 3) ). Thanks @vanja

Thanks for all the input and lets see, if things develop further!

This is a very generic solution as well. Like it. :+1:

What I miss here is somewhere a UI, where I have an overview about all (and just) the relevant things. I use telegram as well, but sometimes it is nice (for me), to just look into a list and have a good overview.

I´m using an approach with items that hold the Online or Offline status as switches.
The JSRules also sends notifications when a thing goes offline and when it comes back online.
With the items you could build a dashboard.

Group gThingMonitor

// Monitored things
// Enter the uid of the bridge thing as tag
Switch mAmazonKonto "Amazon Konto [%s]" (gThingMonitor, gPersist) ["amazonechocontrol:account:User"]
Switch mHomematic "CCU3 [%s]" (gThingMonitor, gPersist) ["homematic:bridge:ccu3"]
Switch mHue "Hue Bridge [%s]" (gThingMonitor, gPersist) ["hue:bridge:Gen2"]
Switch miCloud "iCloud Account [%s]" (gThingMonitor, gPersist) ["icloud:account:openhab"]
Switch mBMW "BMW MQTT Broker [%s]" (gThingMonitor, gPersist) ["mqtt:broker:bmw"]
Switch mZWave "Z-Wave Controller [%s]" (gThingMonitor, gPersist) ["zwave:serial_zstick:master"]

I use two rules, one that triggers every minute and one that answers to a telegram message with the text ThingStatus.
The second rule for the telegram status can be deleted and so the two parameters in line 6 and 7.

/**
 * Thing Monitor Regel
 * Überwacht den Status von Things und meldet Offline-Zustände per Log und Telegram
 */
// Global configuration
var bot1 = 1234; // Enter your telegram bot ID
var tgAddOnId = 'telegram:telegramBot:XYZ'; // Enter your telegram binding ID
var monitorGroupName = 'gThingMonitor';
var recheckDelayMs = 30000; // Delay in milliseconds before rechecking offline things (30 seconds)

//------------------------------------------------------------------------------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------------------------------------------------------------------------------
// please don't change the code from here
//------------------------------------------------------------------------------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------------------------------------------------------------------------------
var logger = log("Thing_Status_Monitor");
var telegramAction = actions.get('telegram', tgAddOnId);
// Cache for offline things
var offlineThings = {};

rules.JSRule({
  name: "Thing Status Monitor",
  description: "Monitors the status of Things and reports offline states",
  triggers: [
    triggers.GenericCronTrigger("0 * * * * ?") // Every minute
  ],
  execute: function(event) {
    try {
      var thingRegistry = osgi.getService("org.openhab.core.thing.ThingRegistry");

      // Get all items from the monitor group
      var monitorGroup = items.getItem(monitorGroupName);
      var members = monitorGroup.members;
      
      members.forEach(function(item) {
        // Get the Thing UID from tags
        var tags = item.tags;
        var thingUID = null;
        
        tags.forEach(function(tag) {
          if (tag.indexOf(":") > -1) {
            thingUID = tag;
          }
        });
        
        if (thingUID === null) {
          logger.warn("Item {} has no Thing UID as tag", item.name);
          return;
        }
        
        // Get the Thing from ThingRegistry
        var thing = thingRegistry.get(new org.openhab.core.thing.ThingUID(thingUID));
        
        if (thing === null) {
          logger.warn("Thing with UID {} not found", thingUID);
          return;
        }
        
        // First status check
        var status = thing.getStatus();
        
        // Get current item state (with null-check)
        var currentState = item.state === null ? "NULL" : item.state.toString();
        
        // Check if Thing is online
        if (status.toString() === "ONLINE") {
          // Check if Thing was offline before (for recovery message)
          var wasOffline = offlineThings[thingUID] === true;
          
          // Thing is online - set switch to ON
          if (currentState !== "ON") {
            item.sendCommand("ON");
          }
          
          // If Thing was offline before, send recovery message
          if (wasOffline) {
            logger.debug("Thing ONLINE: {} [{}] is reachable again", item.label, thingUID);
            
            var telegramMessage = "✅ Thing Online\n\n" +
                                 "📍 " + item.label + "\n" +
                                 "🔧 UID: " + thingUID + "\n" +
                                 "📊 Status: Thing is reachable again";
            
            telegramAction.sendTelegram(bot1, telegramMessage);
            
            // Remove from offline list
            delete offlineThings[thingUID];
          }
        } else {
          // Thing is offline - schedule recheck after delay
          logger.debug("Thing {} is offline, scheduling recheck after {}ms", thingUID, recheckDelayMs);
          
          // Schedule delayed recheck using timer
          setTimeout(function() {
            try {
              // Recheck status after delay
              var recheckThing = thingRegistry.get(new org.openhab.core.thing.ThingUID(thingUID));
              var recheckStatus = recheckThing.getStatus();
              
              // If Thing is online now, abort
              if (recheckStatus.toString() === "ONLINE") {
                logger.debug("Thing {} is online after recheck", thingUID);
                var recheckState = item.state === null ? "NULL" : item.state.toString();
                if (recheckState !== "ON") {
                  item.sendCommand("ON");
                }
                return;
              }
              
              // Thing is still offline - get status details and report
              var statusDetail = recheckThing.getStatusInfo().getStatusDetail();
              var description = recheckThing.getStatusInfo().getDescription();
              
              // Thing is offline - set switch to OFF
              var recheckState = item.state === null ? "NULL" : item.state.toString();
              if (recheckState !== "OFF") {
                item.sendCommand("OFF");
              }
              
              // Check if Thing was already reported as offline
              var alreadyReported = offlineThings[thingUID] === true;
              
              if (!alreadyReported) {
                // First offline report - create log entry
                var logMessage = "Thing OFFLINE: " + item.label + " [" + thingUID + "] - Status: " + recheckStatus;
                if (description !== null && description !== "") {
                  logMessage += " - " + description;
                } else if (statusDetail !== null) {
                  logMessage += " - " + statusDetail;
                }
                logger.warn(logMessage);
                
                // Send Telegram message
                var telegramMessage = "⚠️ Thing Offline\n\n" +
                                     "📍 " + item.label + "\n" +
                                     "🔧 UID: " + thingUID + "\n" +
                                     "📊 Status: " + recheckStatus;
                
                if (description !== null && description !== "") {
                  telegramMessage += "\n💬 Details: " + description;
                } else if (statusDetail !== null) {
                  telegramMessage += "\n💬 Details: " + statusDetail;
                }
                
                telegramAction.sendTelegram(bot1, telegramMessage);
                
                // Mark Thing as offline reported
                offlineThings[thingUID] = true;
              } else {
                // Thing is still offline but already reported
                logger.debug("Thing {} is still offline (already reported)", thingUID);
              }
            } catch (e) {
              logger.error("Error during Thing recheck for {}: {}", thingUID, e.message);
            }
          }, recheckDelayMs);
        }
      });
      
    } catch (e) {
      logger.error("Error in Thing Monitor Rule: {}", e.message);
      if (e.stack) {
        logger.error("Stacktrace: {}", e.stack);
      }
    }
  }
});

rules.JSRule({
  name: "Thing Status Report",
  description: "Sends an overview of all monitored Things via Telegram",
  triggers: [
    triggers.ItemStateUpdateTrigger("tgLastMessageText", "ThingStatus")
  ],
  execute: function(event) {
    try {
      var thingRegistry = osgi.getService("org.openhab.core.thing.ThingRegistry");
      
      // Get all items from the monitor group
      var monitorGroup = items.getItem(monitorGroupName);
      var members = monitorGroup.members;
      
      var statusReport = "📊 Thing Status Overview\n";
      statusReport += "━━━━━━━━━━━━━━━━━━━━\n\n";
      
      var onlineCount = 0;
      var offlineCount = 0;
      var statusList = [];
      
      members.forEach(function(item) {
        // Get the Thing UID from tags
        var tags = item.tags;
        var thingUID = null;
        
        tags.forEach(function(tag) {
          if (tag.indexOf(":") > -1) {
            thingUID = tag;
          }
        });
        
        if (thingUID === null) {
          return;
        }
        
        // Get the Thing from ThingRegistry
        var thing = thingRegistry.get(new org.openhab.core.thing.ThingUID(thingUID));
        
        if (thing === null) {
          return;
        }
        
        var status = thing.getStatus();
        var statusDetail = thing.getStatusInfo().getStatusDetail();
        var description = thing.getStatusInfo().getDescription();
        
        // Create status entry
        var statusEntry = {
          label: item.label,
          status: status.toString(),
          detail: description !== null && description !== "" ? description : (statusDetail !== null ? statusDetail.toString() : "")
        };
        
        if (status.toString() === "ONLINE") {
          statusEntry.icon = "✅";
          onlineCount++;
        } else {
          statusEntry.icon = "❌";
          offlineCount++;
        }
        
        statusList.push(statusEntry);
      });
      
      // Summary
      statusReport += "✅ Online: " + onlineCount + "\n";
      statusReport += "❌ Offline: " + offlineCount + "\n";
      statusReport += "━━━━━━━━━━━━━━━━━━━━\n\n";
      
      // Detailed list
      statusList.forEach(function(entry) {
        statusReport += entry.icon + " " + entry.label + "\n";
        statusReport += "   Status: " + entry.status;
        // Only show details if Thing is NOT online
        if (entry.status !== "ONLINE" && entry.detail !== "") {
          statusReport += "\n   Details: " + entry.detail;
        }
        statusReport += "\n\n";
      });
      
      // Timestamp
      var now = new java.util.Date();
      var dateFormat = new java.text.SimpleDateFormat("dd.MM.yyyy HH:mm:ss");
      statusReport += "🕒 " + dateFormat.format(now);
      
      // Send Telegram message
      telegramAction.sendTelegram(bot1, statusReport);
      
      logger.debug("Thing Status Report sent via Telegram");
      
    } catch (e) {
      logger.error("Error in Thing Status Report Rule: {}", e.message);
      if (e.stack) {
        logger.error("Stacktrace: {}", e.stack);
      }
    }
  }
});
1 Like