How to get all items that are linked to OFFLINE things (to be used in widgets)

Hi folks,
after many hours of research and coding, I finally got a JavaScript that works and gets me all items that are linked to OFFLINE things. The use case is that I want to color oh-label-cells yellow for any cell that displays information that may be dated (since its linked think has goes offline). Here is my “piece of work”. Hope you can use it (and let me know how to improve):

// Script configuration: Configure two text items that hold your list of offline items and things for later search
var strItemOfflineThings    = "systemthingsnotonline";
var strItemOfflineItems     = "systemitemsnotonline";

// Script imports
var ItemChannelLinkRegistry = Java.type('org.openhab.core.thing.link.ItemChannelLinkRegistry');
var ThingRegistry           = Java.type('org.openhab.core.thing.ThingRegistry');
var ThingStatus             = Java.type('org.openhab.core.thing.ThingStatus');  // To compare Thing status
var Bundle                  = Java.type('org.osgi.framework.Bundle');           // OSGI
var FrameworkUtil           = Java.type('org.osgi.framework.FrameworkUtil');
var logger                  = Java.type('org.slf4j.LoggerFactory').getLogger('org.openhab.rule.GeneralTestRule');

// Since ItemChannelLinkRegistry & ThingRegistry are in the same bundle we can use either of them
var thingBundle = FrameworkUtil.getBundle(ItemChannelLinkRegistry.class);
var bundleContext = thingBundle.getBundleContext();
var thingRegistryRef = bundleContext.getServiceReference(ThingRegistry.class);
var thingRegistryImpl = bundleContext.getService(thingRegistryRef);
var itemChannelLinkRegistryRef = bundleContext.getServiceReference(ItemChannelLinkRegistry.class);
var itemChannelLinkRegistryImpl = bundleContext.getService(itemChannelLinkRegistryRef); // Instance of ItemChannelLinkRegistry

// Get all things ist list offline, also available getStatus(), getLabel()
var strListOfflineThings = "";
var numListOfflineThings = 0;
var strListOfflineItems = "";
var numListOfflineItems = 0;
thingRegistryImpl.getAll().forEach(function(objThing){
    if(objThing.getStatus() !== ThingStatus.ONLINE) { 
      if( strListOfflineThings != "" ) strListOfflineThings += ",";  
      strListOfflineThings += "[" + objThing.getUID() + "]";  
      numListOfflineThings += 1;
      // Get linked items for this thing
      objThing.getChannels().forEach(function(objChannel){
        // Get linked items for this thing
        itemChannelLinkRegistryImpl.getLinkedItems(objChannel.getUID()).forEach(function(objItem){
          if( strListOfflineItems != "" ) strListOfflineItems += ",";  
          strListOfflineItems += "{" + objItem.name + "}"; 
          numListOfflineItems += 1;
        })
      })
    } 
})
var strListOfflineThingsPrevious = itemRegistry.getItem(strItemOfflineThings).state;
//logger.info("DEBUG Previos List" + strListOfflineThingsPrevious.toString().split(",")[0]);
//logger.info("DEBUG Current List" + strListOfflineThings.toString().split(",")[0]);

// Here is still a little problem but it is not essential 
strListOfflineThingsPrevious.toString().split(",").forEach(function(strThing){ if ( strListOfflineThings.toString().search(strThing) < 0 ){ 
  logger.info("Thing back online: " + strThing); }})
strListOfflineThings.toString().split(",").forEach(function(strThing){ if ( strListOfflineThingsPrevious.toString().search(strThing) < 0 ){ 
  logger.info("Thing went offline: " + strThing); }})

//Update my custom system information items, so I can check withing widhgets (by searching through it) 
events.sendCommand(strItemOfflineThings, strListOfflineThings); 
events.sendCommand(strItemOfflineItems, strListOfflineItems); 
logger.info("Update system items with offline things (" + numListOfflineThings + ") and items (" + numListOfflineItems + ")!");
//logger.info("Thing(s) not online (UIDs): " + strListOfflineThings);
//logger.info("Item(s) not online (names): " + strListOfflineItems);

// Get linked Things from given Item via ItemChannelLinkRegistry
//itemChannelLinkRegistryImpl.getBoundThings(demoItemName).forEach(function(thing){
//  logger.info("Thing " + thing.getLabel() + " with UID " + thing.getUID() + " is " + thing.getStatus())
//})



… and this is how my systemthingsnotonline item looks like:

… and here is the widget that I use to show my energy consumption, smart meter reading and trend line. Besides using my OFFLINE item (s. above), I am also comparing the latest activity with an NTP read to detect if my energy updates are running late:

uid: widget_energyinfo_cell
tags: []
props:
  parameters:
    - default: Energieverbrauch Hausstrom
      description: A title for the energy widget
      label: Titel
      name: title
      required: false
      type: TEXT
    - default: oh:energy
      label: Icon
      name: icon
      required: false
      type: TEXT
    - default: "1"
      label: Idle minutes
      name: idle
      required: true
      type: INTEGER
    - default: "false"
      label: Show HT/LT
      name: showHTLT
      required: true
      type: BOOLEAN
    - default: "false"
      label: Show Feed
      name: showFeed
      required: true
      type: BOOLEAN
    - context: item
      default: SmartMeter
      description: Item for Meter data
      label: Meter Item
      name: itemMeter
      required: false
      type: TEXT
    - context: item
      default: ShellyEM3HausstromHAR_KumulierterVerbrauch
      description: Item for energy data (total)
      label: Energy Item
      name: itemEnergy
      required: false
      type: TEXT
    - context: item
      default: daily_energy_house
      description: Item for  energy average
      label: Energy Item (average)
      name: itemEnergyAvg
      required: false
      type: TEXT
  parameterGroups: []
timestamp: Oct 6, 2022, 4:31:45 PM
component: oh-label-cell
config:
  icon: =props.icon
  title: =props.title
  subtitle: "=((props.showHTLT == true) ? ('HT: ' + items[props.itemMeter.split('_').at(0)+'_10181'].state + ', LT: ' + items[props.itemMeter.split('_').at(0)+'_10182'].state) : ('Zählerstand: ' + items[props.itemMeter.split('_').at(0)+'_10180'].state)) + ((items.systemitemsnotonline.state.includes(props.itemMeter.split('_').at(0)+'_10180')) ? ' (OFFLINE)' : '')"
  footer: "= 'Mittel: ' + items[props.itemEnergyAvg].state + ' (' + props.itemEnergyAvg +')'"
  action: group
  item: =props.itemEnergy
  trendItem: "=(items.systemitemsnotonline.state.includes(props.itemEnergy) || (dayjs(items['NTPAccount_DateTime'].state).diff(dayjs(items[props.itemEnergy.split('_').at(0) + '_LetzteAktivitat'].state), 'minute') > props.idle)) ? '' : props.itemEnergy"
  trendGradient:
    - "#F00"
    - "#FF0"
    - "#0F0"
  color: yellow
  on: true
  stateAsHeader: true
  expandable: false
  actionGroupPopupItem: =props.itemEnergy.split("_").at(0)
  offlineItems: "=((items.systemitemsnotonline.state.includes(props.itemMeter.split('_').at(0)+'_10180')) ? '(OFFLINE)' : '')"
  visible: true

It could be helpful to show the full rule in context to see triggers and such. It also looks like it’s Nashorn JavaScript (i.e. ECMAScript 5.1) which is important to mention since we have the JS Scripting add-on which provides a more recent version of JavaScript (ECMAScript 11).

Finally, consider publishing this to the Marketplace. That will allow users to install this capability like an add-on instead of needing to copy/paste/edit it. You set properties for the user to fill in which might make this more generically useful (e.g. maybe get a list of those Items that are linked to Things that are ONLINE instead).

One useful design pattern I’ve used with rule templates is to have the rule call another rule with the list of Items (in this case). You can see an example of that in Nashorn in Threshold Alert. That gives the end user the ability to decide exactly what to do with the list.

Wow, that was a quick reply. I am not even finished uploading my story but thanks for the great hints. I was just curious getting this running first (which was quite some work as I hadn’t coded in centuries). Is there some guidance how to use and upload into the marketplace?

This is how my widgets look like:

Widgets

And finally the complete rule:

configuration: {}
triggers:
  - id: "1"
    configuration:
      cronExpression: 0 0/3 * * * ? *
    type: timer.GenericCronTrigger
conditions: []
actions:
  - inputs: {}
    id: "1"
    configuration:
      type: application/javascript
      script: >+
        // Script configuration

        var strItemOfflineThings    = "systemthingsnotonline";

        var strItemOfflineItems     = "systemitemsnotonline";


        // Script imports

        var ItemChannelLinkRegistry = Java.type('org.openhab.core.thing.link.ItemChannelLinkRegistry');

        var ThingRegistry           = Java.type('org.openhab.core.thing.ThingRegistry');

        var ThingStatus             = Java.type('org.openhab.core.thing.ThingStatus');  // To compare Thing status

        var Bundle                  = Java.type('org.osgi.framework.Bundle');           // OSGI

        var FrameworkUtil           = Java.type('org.osgi.framework.FrameworkUtil');

        var logger                  = Java.type('org.slf4j.LoggerFactory').getLogger('org.openhab.rule.GeneralTestRule');


        // Since ItemChannelLinkRegistry & ThingRegistry are in the same bundle we can use either of them

        var thingBundle = FrameworkUtil.getBundle(ItemChannelLinkRegistry.class);

        var bundleContext = thingBundle.getBundleContext();

        var thingRegistryRef = bundleContext.getServiceReference(ThingRegistry.class);

        var thingRegistryImpl = bundleContext.getService(thingRegistryRef);

        var itemChannelLinkRegistryRef = bundleContext.getServiceReference(ItemChannelLinkRegistry.class);

        var itemChannelLinkRegistryImpl = bundleContext.getService(itemChannelLinkRegistryRef); // Instance of ItemChannelLinkRegistry


        // Get all things ist list offline, also available getStatus(), getLabel()

        var strListOfflineThings = "";

        var numListOfflineThings = 0;

        var strListOfflineItems = "";

        var numListOfflineItems = 0;

        thingRegistryImpl.getAll().forEach(function(objThing){
            if(objThing.getStatus() !== ThingStatus.ONLINE) { 
              if( strListOfflineThings != "" ) strListOfflineThings += ",";  
              strListOfflineThings += "[" + objThing.getUID() + "]";  
              numListOfflineThings += 1;
              // Get linked items for this thing
              objThing.getChannels().forEach(function(objChannel){
                // Get linked items for this thing
                itemChannelLinkRegistryImpl.getLinkedItems(objChannel.getUID()).forEach(function(objItem){
                  if( strListOfflineItems != "" ) strListOfflineItems += ",";  
                  strListOfflineItems += "{" + objItem.name + "}"; 
                  numListOfflineItems += 1;
                })
              })
            } 
        })

        var strListOfflineThingsPrevious = itemRegistry.getItem(strItemOfflineThings).state;

        //logger.info("DEBUG Previos List" + strListOfflineThingsPrevious.toString().split(",")[0]);

        //logger.info("DEBUG Current List" + strListOfflineThings.toString().split(",")[0]);


        strListOfflineThingsPrevious.toString().split(",").forEach(function(strThing){ if ( strListOfflineThings.toString().search(strThing) < 0 ){ 
          logger.info("Thing back online: " + strThing); }})
        strListOfflineThings.toString().split(",").forEach(function(strThing){ if ( strListOfflineThingsPrevious.toString().search(strThing) < 0 ){ 
          logger.info("Thing went offline: " + strThing); }})
        events.sendCommand(strItemOfflineThings, strListOfflineThings); 

        events.sendCommand(strItemOfflineItems, strListOfflineItems); 

        logger.info("Update system items with offline things (" + numListOfflineThings + ") and items (" + numListOfflineItems + ")!");

        //logger.info("Thing(s) not online (UIDs): " + strListOfflineThings);

        //logger.info("Item(s) not online (names): " + strListOfflineItems);


        // Get linked Things from given Item via ItemChannelLinkRegistry

        //itemChannelLinkRegistryImpl.getBoundThings(demoItemName).forEach(function(thing){

        //  logger.info("Thing " + thing.getLabel() + " with UID " + thing.getUID() + " is " + thing.getStatus())

        //})


    type: script.ScriptAction

Create a topic in the Marketplace → Rule Template category and follow the instructions of the post. Look at other posts for how to format your code so it looks like a rule template and how to define and use properties. There is also How to write a rule template which I think is still up to date.

I guess this only works with your items, right?

see the following in your code:

_10180

which doesn’t seem to be generic…

Hi, sure you will need to update and adapt to your needs but the _10180 is from the standard smart meter binding and represents the OBIS-Code for total power consumption. In my case I am using a USB IR reader that communicates directly with my meter (provided by energy provieder).

This topic was automatically closed 41 days after the last reply. New replies are no longer allowed.