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 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?
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.
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).