OH3 Group Aggregation Widget? (Avg, Sum, And/Or)

Tags: #<Tag:0x00007fc8f62d12b8> #<Tag:0x00007fc8f62d11f0>

Hi all,

I’m still in the process of migrating my OH2 instance to OH3. Along with the semantics, I’ve restructured the item groups accordingly.

In OH2, my groups were mostly used to display aggregated values (e.g. Average, Sum, And/Or) in the UI. In OH3, all items are already in the semantic groups, meaning all items, especially e.g. all temperature sensors, are somewhere in the gHome group (or subgroups of gHome).

I was thus wondering if it possible to show e.g. the average of all temperature-items in a group with a widget in MainUI?
As an ideal solution, I’d image a generic widget:
Input parameters:

  • a group item
  • an aggregation type (AVG/SUM/AND/OR/…)
  • a tag/item type to filter(Temperature/Humidity/Pressure/…)

Output:

  • a single value dynamically calculated based on the parameters

Of course, I could add additional groups for that again, but it seems so redundant as all location/metadata information is already available.
I’m not afraid of writing my own widget, but couldn’t find any information about doing aggregation on groups in widgets so far. Similar functionality is already available in the predefined location-widgets, where e.g. the average temperature of all sensors is shown, so it seems to be possible in general.

Is there anybody who could help me implementing this in a custom widget?

Thanks in advance,
Chris

I don’t think this is feasible as the moment. There is some ability to do calculations in custom-built widgets, but I don’t think there’s a way to get access to the data array that you would need to calculate over.

Within a widget you can use the oh-repeater element to get access to various lists of items including items that are members of a group or items with a particular tag, but I do not think there is, at the moment, a way to combine those sets even with the filter property. Even if you can get the exact list of items you want, the oh-repeater iterates over that list in subsequent components without, to my knowledge, providing access to the complete list.

The predefined location widgets have quite a bit of special underlying code that the custom widgets do not have access to, so replicating some of those features (such as this aggregate info) is not currently possible.

Thanks for providing your insights. In the meantime, I’ve found some additional information scattered in the forums and gitlab:

This post is stating that filters are implemented. Using the filter parameter, I could at least get all items tagged with “Temperature” and apply an additional filter for group membership, which is indeed giving me the correct subset of items:

- component: oh-repeater
            config:
              fragment: true
              for: item
              sourceType: itemsWithTags
              itemTags: Temperature
              filter: loop.item.groupNames.indexOf('<group-name>') >= 0

The full array is given in an auto-generated variable, see the same post as linked above: you can use {name}_source.length where {name} is your iteration variable (the for parameter)

Another option that sounds interesting (and could be a potential solution to this problem) is the map-parameter of oh-repeater. This obviously allows to do some kind of mapping on the complete array before it gets handed over to the child components.
However, I couldn’t find any examples for this parameter yet. Can we do a mapping from multiple array elements to a single one? Is it possible to define custom mapping functions?

@ysc: Would you be so kind to give some further explanation on the map parameter? May I also ask what you think about solving this problem with a widget rather than with an additional items-group?

Those function are already there:

The question is about showing aggregated values of groups in widgets only.

As stated in my initial post, I especially do not want to create additional items groups every time I need an aggregated value!

Ultimately it’s going to be much more flexible to create the Groups and use the aggregation functions. You can then use the aggregation in Persistence and Rules too.

First of all, all of the temp sensors and similar measurements directly in a given location are already going to be presented to you as an average as a badge on the Location card. But as JustinG points out, those cards have and use capabilities that are not exposed to us.

oh-repeater won’t do anything for you because it’s a way to create one widget per Item, not to aggregate a bunch of Items into one widget. There’s no way to build up a variable that you add to each time through the loop (plus the math for calculating a running average is pretty ugly).

Except for oh-repeater I know of no way to get at all the members of a Group. And even in oh-repeater I know of no way to get all the members of the subgroups too. So you’d need to create your functional Groups anyway.

Based on my current knowledge, any approach that doesn’t use Groups with an aggregation function is going to be more work, not less. And it looks very much like it’s not possible.

The map parameter lets you specify an expression, the result of which being what will actually be in the loop.<for> variable in the descendants (default slot).
Note that the filter is applied before the map, in other words, the filter is applied to your unmapped source collection, then the map is applied.
The last of the examples here (to move to the docs): Widgets: add oh-repeater, dayjs to expression context by ghys · Pull Request #581 · openhab/openhab-webui · GitHub includes both a filter and a map with expressions also involving dayjs. The result is a collection of dayjs dates excluding Saturdays and Sundays, and with the time component set at midnight.

component: f7-list
config:
  mediaList: true
slots:
  default:
    - component: oh-repeater
      config:
        sourceType: range
        for: day
        rangeStart: 0
        rangeStop: 9
        filter: dayjs().add(loop.day, 'day').isoWeekday() < 6
        map: dayjs().add(loop.day, 'day').startOf('day')
        containerStyle:
          --f7-list-item-after-font-size: 20px
          --f7-list-item-after-font-weight: bold
          --f7-list-item-after-text-color: var(--f7-text-color)
      slots:
        default:
          - component: oh-list-item
            config:
              mediaItem: true
              title: =loop.day.format('dddd DD MMMM')
              footer: =loop.day.fromNow()
              after: =(Math.round(Math.random() * 50) + 100) / 10 + ' °C'
              icon: "=(loop.day_idx % 2 === 0) ? 'f7:cloud_sun' : 'f7:cloud_drizzle'"

image

Regarding aggregations, they are often performed with the reduce method in JS:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce

Could be something worth adding to the oh-repeater with expressions, like map. However as @rlkoshak said don’t expect too much from these, usually it’s better/more reliable to set aggregation functions on group items, or use rules to perform the aggregations, than doing aggregations on the client.

1 Like

Thanks for providing these insights!

In my opinion, not having the possibility to aggregate “on-the-fly” wastes a lot of the benefit of the semantic tags in everyday use.

Lets have a common example:
A flat with two floors and three rooms on each floor. Each room has

  • a temperature sensor, tagged with Measurement, Temperature
  • a humidity sensor, tagged with Measurement, Humidity
  • a brightness sensor, tagged with Measurement, Light

According to the semantic docs, all sensors are structured like this:

  • gHome
    • gFloor
      • gRoom
        • gEquipment
          • Measurement point

The sitemap should show the (average) value per room, per floor, per “home”

With dedicated item groups:
I’d need to setup

  • 3 item groups (temperature, humidity, brightness) for the home itself
    • 3 item groups per floor
      • depending on how the equipment group is modeled, 3 additional groups per room
  • If I want more than one aggregation, e.g. AVG and MAX, all of this is even required twice.
    => In this example: 3+(23)+(33)= 18 additional groups (for a fixed aggregation structure)

With semantic modeling:
Aggregations can be done dynamically, e.g.

  • “show average of all items tagged with [Measurement, Temperature] located in gHome (recursively)”
  • “show maximum of all items tagged with [Measurement, Humidity] in gSecondFloor (recursively)”

All required meta-data is available, no additional groups are required. (+ flexible aggregation)

This is already done on the default home screen and presumably seen as one of the major advantages there.

I’d therefor highly appreciate having a solution for this in the UI!