Offline Things Display

alert-svgrepo-com

This template supervises not ignored Things. When a Thing is offline, it changes the item Things online item to off and creates a new item under Parent item with the label set to the label of the Thing that is offline. Then it switches that item to off.
When the Thing is online again, the created item is not deleted but switched to on. That way offline/online history is kept and you can analyse, when Things were offline to find potential problems.

To setup this template, follow these steps.

Prepare the item:

  • Click on Settings → Model
  • On the right, click on Add Equipment
  • Name: OnlineThings, Label: Online Things
  • Click Create
  • Click Add Metadata → State Description
  • Toggle on Read only and Save (top right)
  • Click on Online Things to open it
  • Click Edit on top right
  • Set Members Base Type to Switch
  • Click Save (top right)

Install the template:

  • Click on Settings and then on Automation
  • Scroll to Rule Templates and click on Show xx more
  • Click the ADD button on Offline Things Display
  • Click the large ADD button on the pop up

Setup the rule:

  • Go to Rules and create a rule from Offline Things display.
  • Select the above item Online Things as Parent item, Ignored Things item and Things online item.
  • Force a Thing to be offline by e.g. removing power from it
  • Check the item Online Things, it should go to off and an item named after the offline Thing should be created inside.

Ignore a Thing:

  • Go to Settings → Things
  • In the list, on the Thing you want to ignore, click the square Copy UID
  • Click on Settings → Model
  • Click on Online Things
  • On the right, click on Online Things to open it
  • Click Edit on top right
  • Paste into Add tag and press enter
  • Click Save (top right)

Show it on a page:

  • Click on Settings → Pages
  • Click on Overview (or an other page you want)
  • If you don’t have rows/columns, click Add Row and on the row Add Column
  • Click on it and choose List card
  • Click on the + and choose Label List Item
  • Click on it and Edit YAML
  • Paste the code YAML for Label List Item below into it
  • Click Done and Save (top right)

Note:
Then you use a Group as Things online item, the state on and off is only visible if at least one item was created within the group.
Inputs, corrections and suggestions are very welcome.

Detection of offline Things is based on the template Thing Status Reporting. My first approach was to use it to implement this template. But it turned out more complicated than implementing that part myself.

YAML for Label List Item:

component: oh-label-item
config:
  action: group
  actionGroupPopupItem: OnlineThings
  item: OnlineThings
  style:
    background: =items.OnlineThings.state == 'OFF'?'red':''
  title: Online Things

Language: ECMAScript (ECMAScript 262 Edition 11)
Dependencies: JavaScript Scripting

Changelog

Version 0.1

  • initial release

Version 0.2

  • do not delete online items in other groups

Version 0.3

  • fix item deletion

Resources

uid: prosenb:offline_things_display
label: Offline Things display
description: Queries all Things about offline state and displays the offline ones.
configDescriptions:
  - name: PARENT_ITEM
    label: Parent item
    description: Online items for Things will be created under this item. It must be of type `Group`.
    type: TEXT
    context: item
    required: true
  - name: IGNORE_THINGS_ITEM
    label: Ignored Things item
    description: Create a tag on this item for each Thing to be ignore.
    type: TEXT
    context: item
    required: false
  - name: THINGS_ONLINE_ITEM
    label: Things online item
    description: This items shows if any Thing is offline. It is `on` when all Things are online and `off` otherwise. It must be of type `Switch` or `Group`. If it's `Group`, the  `Members Base Type`  must be set to `Switch`.
    type: TEXT
    context: item
    required: false
  - name: ITEM_LIST_WIDGET
    label: Online items list widget
    description: The list widget of the items representing the online state of Things.
    type: TEXT
    required: false
    defaultValue: oh-label-item
triggers:
  - id: "1"
    label: A Thing Changes Status
    description: Triggers when any Thing changes status
    configuration:
      types: ThingStatusInfoChangedEvent
      payload: ""
      topic: openhab/things/**
      source: ""
    type: core.GenericEventTrigger
conditions: []
actions:
  - inputs: {}
    id: "1"
    configuration:
      type: application/javascript;version=ECMAScript-2021
      script: >-
        /* global Java, event, items, actions */
        (function () {
          // Arguments
          const parentItemName = '{{PARENT_ITEM}}';
          const ignoreThingsItemName = '{{IGNORE_THINGS_ITEM}}';
          const thingsOnlineItemName = '{{THINGS_ONLINE_ITEM}}';
          const itemListWidget = '{{ITEM_LIST_WIDGET}}';
          
          const parentItem = items.getItem(parentItemName, true);
          const ignoreThingsItem = items.getItem(ignoreThingsItemName, true);
          const thingsOnlineItem = items.getItem(thingsOnlineItemName, true);
        
          if (parentItem == null) {
            console.info("parentItem not found, stop rule.")
            return;
          }
        
          var notProcessedThingOnlineItems = parentItem.members;
        
          var atLeastOneOffline = false;
          things.getThings().forEach(thing => {
            const onlineItemName = generateOnlineItemName(thing);
            // remove this item from notProcessedThingOnlineItems
            notProcessedThingOnlineItems = notProcessedThingOnlineItems.filter(item => item.name !== onlineItemName);
            
            if (ignoreThingsItem != null && ignoreThingsItem.tags.includes(thing.uid)) {
              if (items.getItem(onlineItemName, true) != null) {
                items.removeItem(onlineItemName);
                console.info("OfflineThingsDisplay", "ignored, remove onlineItem: " + onlineItemName);
              }
            } else if (thing.status == "OFFLINE") {
              atLeastOneOffline = true;
              var onlineItem = items.getItem(onlineItemName, true);
              if (onlineItem == null) {
                console.info("OfflineThingsDisplay", "add onlineItem: " + onlineItemName);
                addItem(onlineItemName, thing.label);
                onlineItem = items.getItem(onlineItemName);
              }
              if (onlineItem.state == "ON") {
                console.info("OfflineThingsDisplay", "offline: " + onlineItemName);
              }
              onlineItem.sendCommandIfDifferent("OFF");
              replaceMetadata(onlineItem, "widgetOrder", "1");
            } else { // ONLINE
              const onlineItem = items.getItem(onlineItemName, true);
              if (onlineItem != null) {
                if (onlineItem.state == "OFF") {
                  console.info("OfflineThingsDisplay", "online: " + onlineItemName);
                }
                onlineItem.sendCommandIfDifferent("ON");              
                replaceMetadata(onlineItem, "widgetOrder", "100");
              }
            }
          });
        
          if (thingsOnlineItem != null) {
            if (atLeastOneOffline) {
              thingsOnlineItem.sendCommandIfDifferent("OFF");   
            } else {
              thingsOnlineItem.sendCommandIfDifferent("ON");
            }
          }
          
          // remove ThingOnlineItems that related to deleted Things
          notProcessedThingOnlineItems.forEach(item => {
            if (item.tags.find(tag => tag == "ThingOnlineItem") == null) {
              console.debug("Not removed item " + item.name 
                           + " because it does not have the tag ThingOnlineItem but: " + item.tags);      
            } else if (item.groupNames.length > 1) {
              console.debug("Not removed item " + item.name 
                           + " because it is also in other groups: " + item.groupNames);      
            } else {
              items.removeItem(item.name);
              console.info("Removed item: " + item.name);
            }
          });
        
          function addItem(name, label) {
            items.addItem({
              type: 'Switch',
              name: name,
              label: label,
              groups: [parentItemName],
              tags: ['Point', 'ThingOnlineItem'],
              metadata: {
                listWidget: itemListWidget,
                stateDescription: {
                  config: {
                    readOnly: true
                  }
                }
              }
            });
          }
          function generateOnlineItemName(thing) {
            const newName = 'Online_' + thing.uid;
            return newName.replaceAll(":", "_").replaceAll("-", "_");
          }
          function replaceMetadata(item, namespace, value) {
            // OPENHAB_JS_VERSION before 4.5.0 are null
            if (utils.OPENHAB_JS_VERSION == null) {
              item.upsertMetadataValue(namespace, value);
            } else {
              item.replaceMetadata(namespace, value);
            }
          }
        })()

    type: script.ScriptAction
1 Like

There is a good deal of overlap here with Thing Status Reporting [4.0.0.0;4.9.9.9] but I won’t contest it unless it starts causing problems. See About the Add-on Marketplace category

  • Your contribution must be significantly different from other submissions (no copycats with minor changes). You are encouraged to work together as a community instead of proliferating multiple confusingly similar versions of the same thing.

I would be happy to work to modify my rule to address this use case. I already have it on my to list to make that rule template semantic model aware. Thus, if one adds a status Item to an Equipment the rule template will detect and set it based on the Thing’s status. I already do this other ways already.

One big improvement I can see for this template is to use the generic event trigger (see the trigger on my template) rather than an every second poll.

Thanks for your feedback @rlkoshak, I wanted to ask for your opinion anyway after refining it a bit more.

I’m aware of the template Thing Status Reporting and first used it to implement this template. I then came to the conclusion, that it’s easier to implement that part myself. I feel the two templates only overlap as far as online/offline detection is concerned, is that right?

My aim is by no means to copy anyone and I just added a reference to your template in the description.
The goal of this template is to make it as easy as possible for users to supervise their Things without having to code themselves or creating complicated structures.

That’s a great input to use the generic event trigger, I wasn’t aware that’s possible and will change it accordingly.

The two templates I contributed are a direct response to our discussion in order to make it easier for users to create and use rules.

That’s all I want, to improve openHAB and make it better accessible.

I appreciate your feedback, please let me know what you think.

They pretty much do the same thing except my template leaves it up to the end user what to do when an Thing changes state (in the called rule) while this one only sets the ON/OFF state of a Switch Item. So while this one is more complete, mine is more flexible.

I don’t think you are copying me or anyone else. But in order for the marketplace to work, we have to forge a balance between being open to new ideas and new templates and having a collection of quality rule templates/bindings/widgets without multiple iterations of the same thing with slightly different features.

This particular one is in a bit of a gray area. Overall I do think it would be better to not have two Thing Status related rule templates, but this one’s approach is different enough that it’s probably OK.

The required Item config is kind of a complicated structure though. That’s not a knock against that approach. If you are going to use Items for this, it’s going to be a complicated structure.

With the following Script, using your same overall approach as above (with one minor tweak) you get the same result.

var itemState = (newStatus == 'ONLINE') ? 'ON' : 'OFF';
items[('Online_'+thingID).replaceAll('-', '_')].sendCommandIfDifferent(itemState);

The one tweak is instead of having a separate THINGS_ONLINE_ITEM, put all the Online Items into a Group like you are now but also add an “all on then on else off” aggregation function. Then the state of the OnlineThings Group becomes the the Item that shows whether all Things are online or not.

The above script gets called when ever any Thing changes it’s status. If the new status of the Thing is ONLINE we set the corresponding Item to ON. All other status result in OFF. Everything else is managed through the Group.

I don’t think that’s all that complicated of a structure.

The addItem/remove Item stuff could be added to that Script or some other script as written above. If added to the above:

var itemName = ('Online_'+thingID).replaceAll('-', '_');

// Delete the status item if it exists and the Thing changed to REMOVED
if(items[itemName] !== undefined && newStatus == 'REMOVED') {
  items.removeItem(itemName);
} 
else {

  // Create the status Item if it doesn't exist
  if(items[itemName] === undefined) {
    items. addItem( {
      type: 'Switch',
      name: itemName,
      label: thing.label,
      groups: ['OnlineThings'],
      tags: [], // I don't think semantic tags are appropriate here as this Item is not a part of an Equipment
      metadata: {
        listWidget: itemListWidget,
        stateDescription: {
          config: {
            readOnly: true
          }
        }
      }
    });
  }

  // Command the status Item based on the status of the Thing
  var itemState = (newStatus == 'ONLINE') ? 'ON' : 'OFF';
  items[itemName].sendCommandIfDifferent(itemState);
}

That’s a bit more complicated but no more so than this rule template. When a Thing changes status to “REMOVED”, that means the Thing has been deleted so remove the status Item. When a Thing changes to any other status, if the Item doesn’t exist create it. Finally command the Item to the correspond with the Thing’s status.

I guess I don’t understand what complicated structure you were thinking you needed to build.

I definitely welcome and encourage others to create more rule templates. And I really appreciate that you create this one. I just need to weight that against the overall usability of the marketplace.

Just to be clear, I not going to raise any issue to the moderators or try to get this template taken down. I’m not even asking you to take it down. I just want to raise a warning that this template is walking up to the edge in duplicating an existing template and to proceed with caution.

Or, even better, we can work together to add or modify my template so that the reason you felt you needed to create this rule template is addressed. If we all work together to improve what’s already published, everyone benefits.

I feel the two templates have overlap in functionality but target two different audiences.
Yours is best for more tech savvy users which want full control over what happens when it calls their rule. Mine on the other hand tries to allow users access to Thing supervision who like to set it up only with clicking through the UI.
I did quite some thinking how this can be achieved within the boundaries of openHAB and came up with the approach this template implements. With your in depth knowledge of openHAB I’m hoping to simplify it more.

That said do I think that both templates should be on the marketplace to satisfy the needs of the two different types of users. Combining the two and trying to optimise it for the needs of both user group would make it less modular and less ideal for both. I suggest that we amend their titles and descriptions so that the users can easily distinguish them and choose the one that fits their needs.

The complicated structures did not refer to your template but to other approaches I had used before creating this template.

In relation to the structure this template currently uses did I consider many different approaches. A lot of functionality in openHAB is based on Items and after exploring different options, also with widgets did I came to conclusion, that using Items to represent the online/offline state of Things is the best approach within openHAB. They provide the required functionality of grouping, states, list widgets and so on that e.g. Things don’t do.

My own configuration in fact first used the group functionality All ON then ON else OFF but I felt, that it would be easier for users to set it up the way I described it now. But that’s certainly something that could be improved if we feel it would be a better differently.

I also consciously decided to separate the three Items instead of letting the users configure one only, in order to allow different types of configurations and make it more modular. This e.g. also allows to use the template without configuring THINGS_ONLINE_ITEM and using the group functionality instead.

Naturally, I wanted to place an ignore tag on the Thing, to have the template ignore it. But unfortunately do Things not support tags. For this reason, I decided to configure a dedicated Item with the names of the ignored Things as tags on it.

Absolutely, let’s work together to improve openHAB and make it better accessible for everyone.

I’ve installed the rule and the list on the card is being populated, but I keep seeing this error in the logs:

2024-04-14 12:26:56.334 [ERROR] [b.automation.script.javascript.stack] - Failed to execute script:
org.graalvm.polyglot.PolyglotException: TypeError: item.groupNames.size is not a function
	at <js>.:=>(<eval>:68) ~[?:?]
	at <js>.:anonymous(<eval>:64) ~[?:?]
	at <js>.:program(<eval>:1) ~[?:?]
	at org.graalvm.polyglot.Context.eval(Context.java:399) ~[?:?]
	at com.oracle.truffle.js.scriptengine.GraalJSScriptEngine.eval(GraalJSScriptEngine.java:458) ~[?:?]
	at com.oracle.truffle.js.scriptengine.GraalJSScriptEngine.eval(GraalJSScriptEngine.java:426) ~[?:?]
	at javax.script.AbstractScriptEngine.eval(AbstractScriptEngine.java:262) ~[java.scripting:?]
	at org.openhab.automation.jsscripting.internal.scriptengine.DelegatingScriptEngineWithInvocableAndAutocloseable.eval(DelegatingScriptEngineWithInvocableAndAutocloseable.java:53) ~[?:?]
	at org.openhab.automation.jsscripting.internal.scriptengine.InvocationInterceptingScriptEngineWithInvocableAndAutoCloseable.eval(InvocationInterceptingScriptEngineWithInvocableAndAutoCloseable.java:78) ~[?:?]
	at org.openhab.automation.jsscripting.internal.scriptengine.DelegatingScriptEngineWithInvocableAndAutocloseable.eval(DelegatingScriptEngineWithInvocableAndAutocloseable.java:53) ~[?:?]
	at org.openhab.automation.jsscripting.internal.scriptengine.InvocationInterceptingScriptEngineWithInvocableAndAutoCloseable.eval(InvocationInterceptingScriptEngineWithInvocableAndAutoCloseable.java:78) ~[?:?]
	at org.openhab.core.automation.module.script.internal.handler.ScriptActionHandler.lambda$0(ScriptActionHandler.java:71) ~[?:?]
	at java.util.Optional.ifPresent(Optional.java:178) [?:?]
	at org.openhab.core.automation.module.script.internal.handler.ScriptActionHandler.execute(ScriptActionHandler.java:68) [bundleFile:?]
	at org.openhab.core.automation.internal.RuleEngineImpl.executeActions(RuleEngineImpl.java:1188) [bundleFile:?]
	at org.openhab.core.automation.internal.RuleEngineImpl.runRule(RuleEngineImpl.java:997) [bundleFile:?]
	at org.openhab.core.automation.internal.TriggerHandlerCallbackImpl$TriggerData.run(TriggerHandlerCallbackImpl.java:87) [bundleFile:?]
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:539) [?:?]
	at java.util.concurrent.FutureTask.run(FutureTask.java:264) [?:?]
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304) [?:?]
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136) [?:?]
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635) [?:?]
	at java.lang.Thread.run(Thread.java:840) [?:?]
2024-04-14 12:26:56.336 [ERROR] [internal.handler.ScriptActionHandler] - Script execution of rule with UID '09f40b9af3' failed: org.graalvm.polyglot.PolyglotException: TypeError: item.groupNames.size is not a function

Is this an error on my side or is there a bug? (I’m on OH4)

Hi @tophee, nice are you giving the rule a go.
It looks like there might be an issue with the configured PARENT_ITEM. Which item did you configure as PARENT_ITEM? Is it an empty group or are the existing items in it? If so what items?
It would be great to find out what’s exactly happening in order to handle this case and at least print a more useful error message. Cheers

I believe I followed the instructions to the point, i.e. I put the newly created group as parent iem for all three whatever-it’s-called.

Here are the first lines of the resulting script:

/* global Java, event, items, actions */ (function () {
  // Arguments
  const parentItemName = 'OnlineThings';
  const ignoreThingsItemName = 'OnlineThings';
  const thingsOnlineItemName = 'OnlineThings';
  const itemListWidget = 'oh-label-item';
  
  const parentItem = items.getItem(parentItemName, true);
  const ignoreThingsItem = items.getItem(ignoreThingsItemName, true);
  const thingsOnlineItem = items.getItem(thingsOnlineItemName, true);

This is the code of that group-Item:

label: Online Things
type: Group
category: ""
groupNames: []
tags:
  - Equipment
groupType: Switch
function:
  name: EQUALITY

Thank you @tophee ,
The template had a small bug which I fixed but for some unknown reason does it not install anymore after that. I hope it can be resolved soon.