Parent and grandparent of an item

I have written a small widget which shows me the state of all batteries which are registered in a corresponding group.

uid: ud_batterie
tags: []
props:
  parameters:
    - context: item
      description: Die Gruppe der Batterie-Level
      label: Batterie Zustands Gruppe
      name: batLevel
      required: true
      type: TEXT
      filterCriteria:
        - value: Group
          name: type
  parameterGroups: []
timestamp: Dec 10, 2021, 2:45:33 PM
component: oh-list-card
config:
  title: '="Eine Batterie Leer: " + ((items[props.batLevel].state === "OFF") ? "Nein" : "Ja")'
slots:
  default:
    - component: oh-repeater
      config:
        fragment: true
        for: item
        sourceType: itemsInGroup
        groupItem: =props.batLevel
        fetchMetadata: widgetOrder,semantics,uiSemantics
      slots:
        default:
          - component: oh-list-item
            config:
              icon: '=(loop.item.state === "OFF") ? "f7:battery_100" : "f7:battery_0"'
              iconColor: '=(loop.item.state === "OFF") ? "green" :  "red"'
              title: = loop.item.name.split('_')[0]
              item: =loop.item.name
              badge: '=(loop.item.state === "OFF") ? "Ok" : "Wechseln"'
              badgeColor: '=(loop.item.state === "OFF") ? "green" : "red"'


Bildschirmfoto von 2021-12-10 17-47-54

This works also so far quite well, only I like the line

     title: = loop.item.name.split('_')[0]

no longer. Here the model is left and the knowledge about the Things behind it and their naming is accessed.
Besides, this makes the title like

FensterDachboden01

a compound information, which contains the equipment (parents) and the room (grandparents).
I would like to get the parent group with the equipment and the grandparent group with the room via the model.

My model looks like this:
Bildschirmfoto von 2021-12-10 17-45-11

I read in a thread that this could be done via something like.

loop.item.metadata.uiSemantics.config.location

is supposed to work.
Unfortunately I only get “undefined”.

I would like to compose a title like

Fenster im Raum Dachboden

Can someone tell me what I have forgotten or overlooked?

1 Like

This is not easy, but it is possible.

This does not work without other mechanisms. The semantic data is not stored in an item’s metadata. The thread where you found this should also describe the script that is necessary to convert an item’s semantic tags and groups to metadata. You’d have to also duplicate and run that script.

If you want to try and do it without the metadata script, then you have to use oh-repeaters. Several of the source types for the repeaters return object with an items full basic information, this would include the groupNames property which tells you the name of all the groups the item belongs to. In the case of some item that is a semantic point, one of those groups would be the equipment item. You could then repeat this step with that equipment item name and use groupNames again to get the location item that equipment is a member of.

If none of the items are members of any other groups, this isn’t too cumbersome, but groupNames will return all the groups an item is a member of, semantic and non-semantic. So, if your items are members of other groups as well, then you will have to figure out how to use the repeater’s filter property to extract only the semantic group you need.

I found, that

            footer: =loop.item.metadata.semantics.config.isPointOf

gives me the parent, the equipment. On the Items Pages of the equipment there is “Direct Parent Groups” wich shows me the room and “hasLocation” with the same information
Is this the element

I need one of these fields in the widget.
Could you help with the syntax?

The big issue here is that the object that gives you access to item all the item states only gives you access to the item’s state (and it state modified for display, if there is one). From within most of the widget expressions if all you have is the name of the item all you can get is the state. You can’t get the label, which it seems is what you would like to create your descriptive names. So you still need to go through the oh-repeater route to get the full item object back from the API.

First you need a way to get a list of all the potential equipment items you are likely to need. Repeaters can search for items on the basis of tags so you just need a list of the equipment types that might have battery values you are interested in. From your model image that’s probably at least Window, SmokeDetector, and RadiatorControl. So with an repeater that collects all those equipment items you just need to filter that list to use only the one that matches the item name you know you want from the semantics metadata:

- component: oh-repeater
  config:
    for: equipmentItem
    sourceType: itemsWithTags
    itemTags: Window, SmokeDetector, RadiatorControl
    fetchMetadata: semantics
    filter: loop.equipmentItem.name == loop.item.metadata.semantics.config.isPointOf

Now the variable loop.equipmentItem is just one item with all the information about the equipment item that loop.item is a point of and you can use loop.equipmentItem.label to return the nicely formatted label of the equipment item. To go all the way up to the room level, you can use the same reasoning, but instead of equipment tags, you would want another nested repeater which searches out location tags (Bathroom, Bedroom, etc.).

- component: oh-repeater
  config:
    for: equipmentItem
    ...all the equipment item configurations
  slots:
    default:
      - component: oh-repeater
        config:
          for: locationItem
          ...all the location item configurations
        slots:
          default:
            - component: oh-list-item
              config:
                ...this component can now use loop.equipmentItem.label and loop.locationItem.label to build a title string

try:

loop.item.metadata.semantics.config.hasLocation

Ok, the easiest way would be to map the model quasi in the naming of the equipments (as it is now with me partly already, in the name is also the room) and then with string manipulation to split the names into the components.

But then I don’t use the model at all but that’s what I wanted to try (I would then take the room off from the equipment name).

I tried to implement your suggestion, but for me the approaches seem different I tried to use the first one:

uid: ud_batterie
tags: []
props:
  parameters:
    - context: item
      description: Die Gruppe der Batterie-Level
      label: Batterie Zustands Gruppe
      name: batLevel
      required: true
      type: TEXT
      filterCriteria:
        - value: Group
          name: type
  parameterGroups: []
timestamp: Dec 11, 2021, 10:34:03 AM
component: oh-list-card
config:
  title: '="Eine Batterie Leer: " + ((items[props.batLevel].state === "OFF") ? "Nein" : "Ja")'
slots:
  default:
    - component: oh-repeater
      config:
        fragment: true
        for: item
        sourceType: itemsInGroup
        groupItem: =props.batLevel
        fetchMetadata: widgetOrder,semantics,uiSemantics
      slots:
        default:
          - component: oh-list-item
            config:
              icon: '=(loop.item.state === "OFF") ? "f7:battery_100" : "f7:battery_0"'
              iconColor: '=(loop.item.state === "OFF") ? "green" :  "red"'
              title: = loop.item.name.split('_')[0]
              item: =loop.item.name
              badge: '=(loop.item.state === "OFF") ? "Ok" : "Wechseln"'
              badgeColor: '=(loop.item.state === "OFF") ? "green" : "red"'
              footer: =loop.item.metadata.semantics.config.isPointOf
            slots:
              default:
                - component: oh-repeater
                  config:
                    for: equipmentItem
                    sourceType: itemsWithTags
                    itemTags: Window, SmokeDetektor, RadiatorControl
                    fetchMetadata: semantics
                    filter: loop.equipmentItem.name == loop.item.metadata.semantics.config.isPointOf
                  slots:
                    default:
                      - component: oh-list-item
                        config:
                          title: equipmentItem.label
                          footer: "Test"

But there is no additional list-item to see, it is an empty loop.

only

loop.item.metadata.semantics.config.isPointOf

gives an output.

then you do anything wrong.
I am using ‘hasLocation’ within an oh-repeater and I get all the locations.

There is a main difference between our situations. Your oh-repeater uses a group of equipment (class smokeDetector). The room is a parent element.
My repeating works on Points of Equipment (_LOWBAT). The room is grandparent.

loop.item.metadata.semantics.config.isPointOf

gives me the name of the parent, but I do not know to get the name of the grandparent (the location)

did you try it?
Your model looks the same as mine:


all group items which represent an equipment are also member of a “device” group called “All Rauchmelder”. This group does not even have to be part of the model.
the repeater loops through all member of “Alle Rauchmelder”.

In many ways, this is probably true. As I said initially, using the model information is feasible, it’s just not the most efficient way to do this. On the other hand it does have it’s advantages such as freeing you to use some other item naming convention.

There are a few errors in the widget code you posted. The most important is that with:

    - component: oh-repeater
      ...
      slots:
        default:
          - component: oh-list-item
            ...
            slots:
              default:
                - component: oh-repeater
                  ...
                  slots:
                    default:
                      - component: oh-list-item

You are trying to put the second list item inside the first. This isn’t going to render properly so even if the repeater were returning something, you likely wouldn’t see the expected results.

What you need to do, as in the snippet I gave above is to have the second repeater be the direct child of the first one and then have the oh-list-item be the child of the second repeater. Only in that way will the list item have access to both the loop variables (and, of course, if you wanted to go back another model level, you would have to have a third stacked repeater before the list item).

This needs an equals sign to make it an expression at the moment the title would just be the string equipmentItem.label.

Uwe is correct. Only equipment has the hasLocations property (with a few notable exceptions) and he is basing his widget around the points inside the equipment. Once he uses the repeater trick to get the equipment information then hasLocations becomes a possiblity for reaching the next level of the model.

Thank you for your patience, now it nearly works. This is the new version:

uid: ud_batterie
tags: []
props:
  parameters:
    - context: item
      description: Die Gruppe der Batterie-Level
      label: Batterie Zustands Gruppe
      name: batLevel
      required: true
      type: TEXT
      filterCriteria:
        - value: Group
          name: type
  parameterGroups: []
timestamp: Dec 11, 2021, 9:09:50 PM
component: oh-list-card
config:
  title: '="Eine Batterie Leer: " + ((items[props.batLevel].state === "OFF") ? "Nein" : "Ja")'
slots:
  default:
    - component: oh-repeater
      config:
        for: equipmentItem
        sourceType: itemsWithTags
        itemTags: SmokeDetector
        fetchMetadata: semantics
      slots:
        default:
          - component: oh-repeater
            config:
              for: locationItem
              sourceType: itemsInGroup
              groupItem: =props.batLevel
              fetchMetadata: semantics
              filter: loop.equipmentItem.name == loop.locationItem.metadata.semantics.config.isPointOf
            slots:
              default:
                - component: oh-list-item
                  config:
                    footer: =loop.equipmentItem.metadata.semantics.config.hasLocation
                    after: =loop.locationItem.metadata.semantics.config.isPointOf
                    title: =loop.equipmentItem.label

I get:
Bildschirmfoto von 2021-12-11 21-17-47

Nearly what I wanted. As “footer” you see the room, as “after” the equipment (I named it this way) and the “title” could be easily split with string-functions.

There is only a problem left with the first oh-repeater:

itemTags = ....

only works if I use exactly one Tag, SmokeDetector, or Window, or RadiatorControl. If I use two of them (komma seperated) no list-item is produced

itemTags: SmokeDetector

works

  itemTags: Window

works

but

    itemTags: Window,SmokeDetector

does not work.

That’s strange. You’re right. If I run a quick test, I get the same behavior. I wonder if the api has changed recently. What version of OH are you currently running?

Anyway, there is a sort of a cheat workaround here. If you set itemTags to just a comma,

itemTags: ,

it will return all items. This is overkill for what you need, and I don’t know if you will see a performance drop if that has to run several times on the widget, but it’s worth a try.

great, that’s how it works. The lack of restriction is not a problem for me, since almost all devices work with battery.

Of course, this is a bit slow, we iterate here through all equipments and then for each again through all equipments to find the device whose name corresponds to:

loop.locationItem.metadata.semantics.config.isPointOf

There should be a construct like (no valid YAML-Syntax)

item-with-name(loop.locationItem.metadata.semantics.config.isPointOf).metadata.semantics.config.hasLocation

But I’m afraid this metadata is not available.

In the meantime I have found an explanation for the lonely comma. The comma acts like an ‘‘and’’ and not like an ‘‘or’’. If you list multiple tags, then only items that have all tags are selected.
This is the reason why

itemTags: Window,SmokeDetector

does not work.

But it seems that the value must not be left empty either, hence the comma. An ‘‘And’’ with two empty entries.