[semantic,ui] Using enriched semantic to ease UI creation

Hi Folks,
I鈥檇 like to share the idea of improving custom widget configuration by leveraging yet-existing information which can be derived from the semantics model.

Currently, this is not yet a turn-key solution but more a proof of concept to be discussed and improved. So yes: feel invited to adopt and suggest improvements 鈥 :wink:

Let鈥檚 start鈥

The Problem

In a setup, we may have multiple equipment with similar points. So all battery-powered room sensors may have channels for

  • temperature
  • humidity
  • battery low

When linking them to items, what *label* should we use?

  • 鈥淭emperature Kitchen Ground Floor鈥
  • 鈥淗umidity Kitchen Ground Floor鈥
  • 鈥淏attery Low Kitchen Ground Floor鈥

or simply:

  • 鈥淭emperature鈥
  • 鈥淗umidity鈥
  • 鈥淏attery Low鈥

With openHAB3 the latter would be sufficent since we have the semantic model.
We put the relevant items into the hierarchy. So from the model鈥檚 perspective it鈥檚 clear to what equipment in which room a temperature reading belongs to.

But what if we query 鈥渁ll temperatures鈥 throught the models鈥檚 temperature property? We鈥檒l get something like that:

Wouldn鈥檛 it be nice to leverage the model鈥檚 semantic relations here too?
Shouldn鈥檛 we be able to show the room and the equipment where these readings came from in our custom widgets?

Answer: Not out of the box (yet).

Solution idea

We already have all the needed information, the association to a equipment and the position in a location in the semantics metadata:

  • isPointOf
  • hasLocation

Unfortunately, in custom widgets we can鈥檛 follow these 鈥減ointers鈥 to derive information from the parent groups.

But in rules: We can!
So why not collecting the label information from the parent groups (equipment and location) and simply store them as config date in an additional metadata namespace?

Let us start鈥


We want to create a UI widget which shows us empty batteries which shoul be replaced. Since all items tagged with LowBattery have the same label, we like to add the Information to our widget where these batteries belong to.

As said above, we introduce a custom namespace 鈥渦iSemantics鈥 and put that information there:

"metadata": {
    "uiSemantics": {
      "config": {
        "preposition": " im ",
        "equipment": "Bewegungsmelder",
        "location": "B眉ro"
    "semantics": {
      "value": "Point_Status_LowBattery",
      "config": {
        "relatesTo": "Property_Energy",
        "isPointOf": "ZIGTIO1PhilipsSML001"

As you see, I even opted for putting the correct German preposition ("im" vs. "in der") to form correct expressions later in the UI like:

"Bewegungsmelder im B眉ro"
"K眉hlschrank in der K眉che"

Since we don鈥檛 want to add all those metadata by hand, I鈥檝e created a tiny script to derive the needed parent labels and put them into 鈥渦iSemantics鈥. This script may run daily (with adopted to MetadataRegistry.update鈥 :wink: )

var UI_NAMESPACE = "uiSemantics";    
var enrichMetadata = function(item) {
      logger.info("Item enriched: "+item)
      var prepositionFor = ["K眉che", "Werkstatt", "Waschk眉che", "Bibliothek", "Garage"]
      var uiSemanticsKeys = { "equipment" : "" , "location" : "", "prepositon" : ""}
      var equipmentItem=ir.getItem(getValue(item, "semantics", "isPointOf"));
      var locationItem=ir.getItem(getValue(equipmentItem.name, "semantics", "hasLocation"));
  uiSemanticsKeys.equipment = equipmentItem.label;
  uiSemanticsKeys.location = locationItem.label;
  uiSemanticsKeys.preposition = (prepositionFor.indexOf( uiSemanticsKeys.location) > -1 ) ? " in der " : " im ";
  logger.info( "Item: "+ item +" Equipment: "+ uiSemanticsKeys.equipment  + uiSemanticsKeys.preposition + uiSemanticsKeys.location );
  MetadataRegistry.add(new Metadata(new MetadataKey( UI_NAMESPACE, item), null , uiSemanticsKeys ));
  return null;

logger.info("Starting: Collecting semantics ...")
var lowBattItems = ir.getItem("gLowBattery").getMembers();

  // Check the metadata for all the relevant Items
  for each (var item in lowBattItems) {

Now that we have have the labels of the parents in the metadata, we can simply use them e.g. in the beloved oh-repeater (A couple of simple oh-repeater examples):

- component: oh-repeater
        for: i
        sourceType: itemsWithTags
        itemTags: LowBattery
        fetchMetadata: semantics,widgetOrder,uiSemantics
        filter: loop.i.state != "OFF"
          - component: oh-label-card
              icon: f7:battery_25
              title: =loop.i.label
              footer: =loop.i.metadata.uiSemantics.config.equipment + loop.i.metadata.uiSemantics.config.preposition + loop.i.metadata.uiSemantics.config.location
              item: =loop.i.name

This snippet iterates through all items tagged with LowBattery if the state not 鈥淥FF鈥. Notice the fetchMetadata line: There we query our label information to be used in the footer below.

This gives:

As I said: The equipment information as well as the location is available in the model and accessible from the rules - but currently simply not accessible from within widget code eg. via

= items[loop.i.metadata.semantics.config.isPointOf].label

Therefore I came up with this concept 鈥 :wink:


Clever :slight_smile:
And when I get around to implementing my idea here: [MainUI] See and edit Item metadata on the Item page 路 Issue #397 路 openhab/openhab-webui 路 GitHub you鈥檒l be able to describe your own metadata namespaces in the developer tools, so they will appear in the available namespaces in the UI (you won鈥檛 have to type it) and you鈥檒l get a nice config sheet like the widget props to set values.


BTW: Is there another way in widgets than just oh-repeater 's fetchMetadata to access item鈥檚 metadata?

If so, this could be leveraged to derive the presentation of an item (e.g. choose a matching icon based on tags)鈥

hmm sounds very interesting but I can鈥檛 get the script to work鈥
I鈥檝e placed it in the script folder and named it *.script
Did you add the script via the UI?

Crreate a rule of type javascript 鈥

Run it once or periodically :wink:

hmm already tried that but it throws a lot of errors:

Script execution of rule with UID '6244517efe' failed: ReferenceError: "logger" is not defined in <eval> at line number 18

and if I remove the logger part another error etc.

I think I miss something else

This is what I use, ofc you need to adjust the parts you are after 鈥

var logger = Java.type("org.slf4j.LoggerFactory").getLogger("org.openhab.model.script.Rules.Experiments");

var FrameworkUtil = Java.type("org.osgi.framework.FrameworkUtil");
var _bundle = FrameworkUtil.getBundle(scriptExtension.class);
var bundle_context = _bundle.getBundleContext()
var MetadataRegistry_Ref = bundle_context.getServiceReference("org.openhab.core.items.MetadataRegistry");
var MetadataRegistry = bundle_context.getService(MetadataRegistry_Ref);
var MetadataKey = Java.type("org.openhab.core.items.MetadataKey");
var Metadata = Java.type("org.openhab.core.items.Metadata");

function getMetadata(itemName, namespace) {
  return MetadataRegistry.get(new MetadataKey(namespace, itemName));

function enrichMetadata(item) {
  logger.info("Item enriched: " + item)

  var semantics = getMetadata(item, "semantics");
  var isPointOf = semantics.configuration["isPointOf"]
  var isPointOfItem = ir.getItem(isPointOf)
  var uiSemantics = { equipment: { label: isPointOfItem.getLabel() } };
  logger.info("Item: "+ item + " uiSemantics = " + uiSemantics.equipment.label);
  var key = new MetadataKey("uiSemantics", item);
  var metadata = new Metadata(key, null , uiSemantics)
  if (MetadataRegistry.get(key)) {
  } else {

function enrichMetadataForGroup(group) {
  var items = ir.getItem(group).getMembers();
  for each (var item in items) {

logger.info("Starting: Collecting semantics ...")


hmm ok thx but still same error

logger" is not defined

Miss-formatted my post so first line was missing, sorry :slight_smile:


Nice thx that was the solution

1 Like

Hi mates,
I鈥檓 happy that you gave the concept a try :slight_smile:
I鈥檝e just evolved the above a tiny bit even more 鈥

Some of you may have seen my oh-repeater stuff for timestamps here:

To not spread related content via multiple threads, I鈥檇 like to share it here:

Dynamic warning timeouts

In the thread linked above, I鈥檝e created a list which hilights outstanding heartbeats from wireles sensors:

The initial attempt used a fixed timeout (1h) for warnings and another (1day) for errors.

This leads to some false negativ presentation:
While most wireless sensors report something every some seconds/minutes, I do have some sensors too which need quite very long periods of deep sleep to reach an acceptable battery lifetime:

  • Xiaomi Lumi Zigbee temp/hum/pressure sensors
  • Shelly Flood water sensors
  • Shelly Door Window sensors

See them in the above picture shown with orange or red badges鈥

To overcome this limitation, I鈥檝e extended my uiSemantics namespace by additional keys for warning/failure timeouts:

  "metadata": {
    "uiSemantics": {
      "config": {
        "warn": -1440,
        "fail": -2160,
        "icon": "f7:waveform_path_ecg",
        "preposition": " im ",
        "equipment": "Wassermelder",
        "location": "Technikraum"

Notice the warn/fail keys here. There I鈥檝e added the specific timeout in minutes for that item.

Sure, you鈥檒l certainly ask for the negative values there - right?
Answer: in the dayjs() calculation in the widget I was using negative offsets. So putting negatives here already saved me some rewrite in my widgets. :wink:

As in the OP, I don鈥檛 populate the key鈥檚 by hand. It鈥檚 again in a supporting script:

var enrichMetadata = function(item, icon) {
  logger.info("Item tested: "+item)
  var prepositionFor = ["K眉che", "Werkstatt", "Waschk眉che", "Bibliothek", "Garage"]
  var uiSemanticsKeys = { "equipment" : "" , "location" : "", "prepositon" : "" , "icon" : "" , "warn" : -30, "fail" : -45}
  var isPointOf = getValue(item, "semantics", "isPointOf");
  logger.info("Item isPointOf: "+ isPointOf );
  var equipmentItem = isPointOf === undefined ? ir.getItem(item) : ir.getItem( isPointOf ) ;
  var locationItem=ir.getItem(getValue(equipmentItem.name, "semantics", "hasLocation"));
  uiSemanticsKeys.equipment = equipmentItem.label;
  uiSemanticsKeys.location = locationItem.label;
  uiSemanticsKeys.preposition = (prepositionFor.indexOf( uiSemanticsKeys.location) > -1 ) ? " in der " : " im ";
  uiSemanticsKeys.icon = icon;
  var warnTimeoutFor = {"LUMITH" : -720 , "SHELLYDW" : -240 , "SHELLYFLOOD" : -1440 };
  for (var s in warnTimeoutFor) {
    if (item.toUpperCase().indexOf(s) > -1 ) { 
      uiSemanticsKeys.warn = warnTimeoutFor[s];
  uiSemanticsKeys.fail= uiSemanticsKeys.warn * 1.5;
  if ( getValue(item, UI_NAMESPACE, "location" ) !== null ) {
        logger.info( "Item: "+ item +" UPDATE Metadata in "+ UI_NAMESPACE + ": " + uiSemanticsKeys.equipment  + uiSemanticsKeys.preposition + uiSemanticsKeys.location );
        MetadataRegistry.update(new Metadata(new MetadataKey( UI_NAMESPACE, item), null , uiSemanticsKeys ));
  } else {
        logger.info( "Item: "+ item +" ADD Metadata in "+ UI_NAMESPACE + ": " + uiSemanticsKeys.equipment  + uiSemanticsKeys.preposition + uiSemanticsKeys.location );
        MetadataRegistry.add(new Metadata(new MetadataKey( UI_NAMESPACE, item), null , uiSemanticsKeys ));
  return null;

The scripts makes some assumption on the item name to filter the long-sleeping deivices (items). I did this because I didn鈥檛 found a way to determine, what thing type an item is linked to from within a rule.
IF SOMEONE HAS GOT AN IDEA ON THIS, please let me know :wink:

Once we have the additional timeout set in our metadata, we can change the oh-repeater loop to use the specific timeouts instead of the fixed for filtering and coloring:

  - component: oh-repeater
      for: i
      sourceType: itemsInGroup
      groupItem: =props.mainItem
      fetchMetadata: semantics,widgetOrder,uiSemantics
      filter: '( loop.i.state < dayjs().add(loop.i.metadata.uiSemantics.config.warn,"m").format() ) ? true : vars.detailsOn '
        highlightColor: blue
        - component: oh-list-item
            icon: ="f7:"+props.icon
            iconColor: '=dayjs(items[loop.i.name].state).isAfter(dayjs().add(loop.i.metadata.uiSemantics.config.warn,"m")) ? "green" : dayjs(items[loop.i.name].state).isAfter(dayjs().add(loop.i.metadata.uiSemantics.config.fail,"m")) ? "orange" : "red"'
            footer: =loop.i.metadata.uiSemantics.config.preposition + loop.i.metadata.uiSemantics.config.location
            title: =loop.i.metadata.uiSemantics.config.equipment
            item: =loop.i.name
            badge: =dayjs(items[loop.i.name].state).fromNow()
            badgeColor: '=dayjs(items[loop.i.name].state).isAfter(dayjs().add(loop.i.metadata.uiSemantics.config.warn,"m")) ? "green" : dayjs(items[loop.i.name].state).isAfter(dayjs().add(loop.i.metadata.uiSemantics.config.fail,"m")) ? "orange" : "red"'

This brings us:

Notice the Shelly Flood 鈥淲assermelder im Technikraum鈥. This guy was marked orange formerly. BUT: Since it is absolutely fine, if it sleeps for a whole day if no water alarm is to be issued, the widget should not complain about it鈥

Have fun 鈥 :wink:

1 Like

thx for all the infos.
Now I have a question to your model:
You鈥檙e model must look something like this regarding the Feuchtigkeit example:
So each SensorType (e.g. Feuchtigkeit, Temperatur) has its own group even though they might come from the same Device/Sensor that measures both values, is this correct?

I ask because currently I have combined them in one Sensor like this:
And therefore your script gives me wired results.

That鈥檚 like my model is organized:

I see, that you have a kind of 鈥渟ub-equipment鈥 for battery inside of 鈥淩aumf眉hler鈥.
If this is how you really want it to be, you certainly have to adopt the script to gather the information (e.g. the location) not just from the direct parent but form the parents parent in case of your equipment-in-equipment. :wink:

I鈥檓 still figuring out what the best way is but sure I will change it.

Hmm strange you鈥檙e model looks very similar to mine but still I gate strange results.
So in the script when you call the function 鈥渆nrichMetadata鈥 would get you 鈥淩aumf眉hler B眉ro im B眉ro鈥 or not?

Yes. This deserves some cleanup - indeed! :wink:

ok got it.
And for a custom UI Widget that describes the temperature of a sensor like 鈥淭emperatur im B眉ro鈥 you use a different 鈥渆nrichMetadata鈥 script?

No, not neccessarily. For now, I鈥檝e just apply the metadata to items belonging to certain groups or have certain tags (e.g. LowBattery) attached.

In general, this just makes sense for items appearing in the model. So I had to limit the scope of the script to the items I#m currently sure they have the needed semantic metadata stuff set.

Potentially, you could evolve the script to make it runover all your items and just populate the uiSemantics for items, where the information is available from the parent groups.

Once you did this, feel free to share the result here, so tht others can benefit from it :wink:

One suggestion I would make to your script is to have the option to remove metadata from those Items that have been removed from the model. Managing Item metadata is a little awkward (though will get better with the solution to issue 397) but given that the script can potentially generate a massive amount of metadata, it would be nice if it could clean up after itself too.

Beyond that this is a really nice use of metadata. Thanks for posting!

1 Like

Currently I limit the scope to specific groups of items but already had the need for cleanup in mind 鈥 :wink:

Happy to hear that :slight_smile:

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