Max of multiple items?

TL;DR:

  • How do I get the maximum of multiple item (Dimmer) states? When searching, I am drowned in temporal maxima of a persisted single item, not multiple current ones - or questions about the maximal number of items in openHAB. Where would I look such a thing up without reading through examples (that look a little randomly chosen)?

Long story, for suggestions for improvements:
I have spotlights in my ceiling that are controlled in groups of. Some groups are cold white, some are warm white.
So I have Items
Spots_Warm1…Spots_WarmN
and
Spots_Cold1…Spots_ColdN
which send and receive MQTT commands to the controller. Works like a charm.
In addition, there are Items Spots_Warm, Spots_Cold, Spots which control all warm, cold, or both spots. The controller already has MQTT channels to receive commands. It does not send status updates, since determining what to display when different groups have different brightness values is more of a usability thing, so I want to do that on the OpenHAB level.
So, what I want is a rule that sets the supergroups to maximum value of the subgroups. Basically like this:

rule FirstFloor_Bedroom_Mainlight_Cold1_Rule
    when
        Item FirstFloor_Bedroom_Mainlight_Cold1 received update
    then
        postUpdate(FirstFloor_Bedroom_Mainlight_Cold,max(FirstFloor_Bedroom_Mainlight_Cold1.state, FirstFloor_Bedroom_Mainlight_Cold2.state))
        postUpdate(FirstFloor_Bedroom_Mainlight,max(FirstFloor_Bedroom_Mainlight_Cold1.state,FirstFloor_Bedroom_Mainlight_Cold2.state,FirstFloor_Bedroom_Mainlight_Warm1.state.state,FirstFloor_Bedroom_Mainlight_Warm2.state))
    end

the max statement doesn’t work, though, I just made it up. I could of course program a max function with pairwise comparisons myself, but I guess there is an easier way.

I have put in the following workaround. 1==1, because Cold2 and Warm1 are not connected yet and do not have values. Update of _Cold and _Warm group works, but FirstFloor_Bedroom_Mainlight never changes. Why?

rule FirstFloor_Bedroom_Mainlight_Rule
    when
        Item FirstFloor_Bedroom_Mainlight_Cold changed or
        Item FirstFloor_Bedroom_Mainlight_Warm changed
    then
        if( FirstFloor_Bedroom_Mainlight_Cold.state < FirstFloor_Bedroom_Mainlight_Warm  )
            postUpdate(FirstFloor_Bedroom_Mainlight,FirstFloor_Bedroom_Mainlight_Cold.state)
        else
            postUpdate(FirstFloor_Bedroom_Mainlight,FirstFloor_Bedroom_Mainlight_Warm.state)
    end

rule FirstFloor_Bedroom_Mainlight_Cold_Rule
    when
        Item FirstFloor_Bedroom_Mainlight_Cold1 received update or
        Item FirstFloor_Bedroom_Mainlight_Cold2 received update
    then
        if( 1==1 || FirstFloor_Bedroom_Mainlight_Cold1.state < FirstFloor_Bedroom_Mainlight_Cold2.state )
            postUpdate(FirstFloor_Bedroom_Mainlight_Cold,FirstFloor_Bedroom_Mainlight_Cold1.state)
        else
            postUpdate(FirstFloor_Bedroom_Mainlight_Cold,FirstFloor_Bedroom_Mainlight_Cold2.state)
    end

rule FirstFloor_Bedroom_Mainlight_Warm_Rule
    when
        Item FirstFloor_Bedroom_Mainlight_Warm1 received update or
        Item FirstFloor_Bedroom_Mainlight_Warm2 received update
    then
        if( 1==1 || FirstFloor_Bedroom_Mainlight_Warm1.state > FirstFloor_Bedroom_Mainlight_Warm2.state )
            postUpdate(FirstFloor_Bedroom_Mainlight_Warm,FirstFloor_Bedroom_Mainlight_Warm2.state)
        else
            postUpdate(FirstFloor_Bedroom_Mainlight_Warm,FirstFloor_Bedroom_Mainlight_Warm1.state)
    end

postUpdate() would only change the state ao an item (inside openHAB) but wouldn’t send a command to the corresponding hardware, you will have to use sendCommand() instead.

I don’t WANT to send a command to the corresponding hardware. That is the last thing I want to do. I just want to adjust the group sliders according to the items that correspond to actual devices. If that would update anything, it would either reset the one I just changed, or also alter the other devices.
But it SHOULD trigger the “… changed” condition, shouldn’t it?

Ok, I think I got closer to the problem. I set up the following workaround:

rule FirstFloor_Bedroom_Mainlight_Cold_Rulex
    when
        Item FirstFloor_Bedroom_Mainlight_Cold1 received update or
        Item FirstFloor_Bedroom_Mainlight_Cold2 received update
    then
        if( 1==1 )
            postUpdate(FirstFloor_Bedroom_Mainlight_Cold,FirstFloor_Bedroom_Mainlight_Cold1.state)
        else
            postUpdate(FirstFloor_Bedroom_Mainlight_Cold,FirstFloor_Bedroom_Mainlight_Cold2.state)

        var Number meanBrightness = (FirstFloor_Bedroom_Mainlight_Cold.state+FirstFloor_Bedroom_Mainlight_Cold.state)/2
        postUpdate(FirstFloor_Bedroom_Mainlight,meanBrightness)
    end

This gives the following error

Error during the execution of rule 'FirstFloor_Bedroom_Mainlight_Cold_Rulex': Could not invoke method: org.eclipse.xtext.xbase.lib.ObjectExtensions.operator_plus(java.lang.Object,java.lang.String) on instance: null

despite the fact that FirstFloor_Bedroom_Mainlight_Cold was updated right before. (In the final version, the second term is FirstFloor_Bedroom_Mainlight_Warm, of course, to get the mean, but I want to rule out unnecessary sources of error right now.)

Ah… :slight_smile:
So, FirstFloor_Bedroom_Mainlight_Cold1 and FirstFloor_Bedroom_Mainlight_Cold2 are of type Number? And what’s your point in doing a if (1==1)? this is the same as if(true), always true so only first option will take place.
Maybe this is, what you want:

rule "get minimum of two items"
when
    Item FirstFloor_Bedroom_Mainlight_Cold1 received update or
    Item FirstFloor_Bedroom_Mainlight_Cold2 received update
then
    if ((FirstFloor_Bedroom_Mainlight_Cold1.state instanceof DecimalType) && (FirstFloor_Bedroom_Mainlight_Cold2.state instanceof DecimalType)) {
        if ((FirstFloor_Bedroom_Mainlight_Cold1.state as DecimalType) < (FirstFloor_Bedroom_Mainlight_Cold2.state as DecimalType)) 
            FirstFloor_Bedroom_Mainlight_Cold.postUpdate(FirstFloor_Bedroom_Mainlight_Cold1.state as DecimalType) 
        else 
            FirstFloor_Bedroom_Mainlight_Cold.postUpdate(FirstFloor_Bedroom_Mainlight_Cold2.state as DecimalType)
    }
end

Pay attention especially to the if (item.state instanceof DecimalType) and the (item.state as DecimalType) which will ensure you are calculating with numbers, not strings.

I think the simplest solution is to use a Group.\

Group:Dimmer:Max AllCold "Cold Lights"
Group:Dimmer:Max AllWarm "Warm Lights"

Add all the Cold lights to the AllCold group and the warm ones to the AllWarm group.

Put AllCold and AllWarm on your sitemap as a Dimmer

Dimmer item=AllCold

The Dimmer element on your sitemap will be set to the dimmer value of the maximum set Dimmer in the group. Adjusting that dimmer will send that value to all the members of the group.

Hi Udo, I explained why I put 1==1 in my previous posting: because the other group is not connected yet. I also don’t understand why “instanceof DecimalType” would solve my problem. This would prevent errors in the case that the item is not decimal for some reason, but I don’t see how it would make anything work in the case it is not. My Items are of type Dimmer. So there is an integer value related to them in some way. I don’t know why you highlight .state, As far as I can see, I have that in my code.

I still don’t understand why the item that was updated right before is null.

Hi Rich,

thanks, I think that is the intended way to go. Thanks for pointing me at that. I have a working solution with that. There are a few issues I could resolve, and a few that I couldn’t though. First for the record:

  • It apparently NEEDS to be Group:Dimmer:MAX (upper case). Had to start the designer to find that, I think openhab has a problem with meaningful error messages in log in this regard.

  • There is no sitemap item Dimmer. To reproduce what you get from an automatically added group item, it needs to be

    Slider item=FirstFloor_Bedroom_Mainlight switchSupport

What I could not resolve:

  • The value is shown strangely. For the actual item, [%s %%] becomes something like 12%, which is correct. For the groups (both MAX and AVG), it becomes something like 0.12000000%, however. Is that a bug, or am I misunderstanding something? Is there a workaround?
    My items look like this:

    Group:Dimmer:AVG FirstFloor_Bedroom_Mainlight “Spots [%s %%]” (gFirstFloor_Bedroom)
    Group:Dimmer:MAX FirstFloor_Bedroom_Mainlight_Cold “Spots Kalt [%s %%]” (gFirstFloor_Bedroom,FirstFloor_Bedroom_Mainlight)
    Dimmer FirstFloor_Bedroom_Mainlight_Cold1 “Spots Kalt G1 [%s %%]” (gFirstFloor_Bedroom,FirstFloor_Bedroom_Mainlight_Cold) { autoupdate=“false”, sendFrequency=“200” }

  • This type of group isn’t automatically added to a group-based sitemap correctly. It acts as a normal group. I am unsure if that should be intended behaviour, since I defined the item type Dimmer for that group. I think it should be displayed like a Dimmer.

A dimmer holds a PercentType which is an integer between 0 and 100 and represents 0.0 to 1.0. Put another way, 12% (PercentType) == 0.12 (Number). I’m surprised that the MAX is treating this value as a Number, but I’m not surprised the AVG is.

It is not a bug. Maybe one could argue the MAX should work differently but its current behavior is not necessarily incorrect.

I think the only workaround would be to go back to using a separate Item to store the MAX and AVG values.

You can control the number of decimal places using [%.2f] instead of %s.

You need to put it on your sitemap using a Slider item=“FirstFloor_Bedroom_Mainlight”, not as a Group item="… . You cannot have it appear on your sitemap as a Dimmer if you are putting this Group on your sitemap as part of another group (e.g. you put Group gFirstFloor_Bedroom on your sitemap and rely on the fact that FirstFloor_Bedroom_Mainlight is a member of gFirstFloor_Bedroom).

A dimmer holds a PercentType which is an integer between 0 and 100 and represents 0.0 to 1.0. Put another way, 12% (PercentType) == 0.12 (Number). I’m surprised that the MAX is treating this value as a Number, but I’m not surprised the AVG is.

Well, then I would expect that [%s %%] also outputs “0.12000000 %”. Or “12%%”. I do not understand why a value that output 12% becomes 0.12% after applying a MAX function. I mean, I guess I might unterstand, there is something like a cast somewhere on the way, but I don’t think this should be expected behaviour. MAX should be equal to IDENTITY when one argument is supplied, so it shouldn’t alter the value.

You need to put it on your sitemap using a Slider item=“FirstFloor_Bedroom_Mainlight”, not as a Group item="… . You cannot have it appear on your sitemap as a Dimmer if you are putting this Group on your sitemap as part of another group (e.g. you put Group gFirstFloor_Bedroom on your sitemap and rely on the fact that FirstFloor_Bedroom_Mainlight is a member of gFirstFloor_Bedroom).

I understand that. I just don’t like it :slight_smile: When I specify a type for a group, it should be included like that type in a sidemap automatically.

MAX isn’t altering the value, PercentType is. Internally the value is actually stored as a 0.12. It is the PercentType that is basically multiplying by 100 when you call .toString. I suspect that if you changed your label for the Dimmer Item to [%.2f] you will see that the actual value, when it is treated as a NumberType instead of a PercentType, is also 0.12.

In other words, the “problem” is PercentType provides some convenience by multiplying by 100 when you call toString. The actual numbers stored is 0.12, not 12 though.

MAX isn’t altering the value, PercentType is and it is only doing so for display (i.e. toString).

Ok, I see. Unfortunately [%.2f] still results in 100.00. instead of 1.00

That is odd. Before I was making assumptions based on behavior. I just had a look at the code.

PercentType is a DecimalType. However, my assumption was incorrect. It is storing and making sure that the value is an int between 0 and 100.

I couldn’t find the code that implements the MAX quickly. But after having looked at the PercentType code and seeing that [%.2f] doesn’t work correctly I have no idea what is going on. I suspect someone implemented MAX, AVG, etc to work in such a way that you can combine DecimalTypes and PercentTypes in the same group and get a meaningful result. I’m not sure this is a valid approach or not.

@maintainer does anyone have a recommendation, advice, or explanation? Is this really a bug or is the behavior expected and intended?

In specific, if one has a Group:Dimmer:MAX, AVG, (presumably MIN) and all the members of the Group are Dimmers, the Group’s value is a float (e.g. 0.12) instead of the expected integer (e.g. 12).

It is an inconsistency but I can see arguments for why it would behave this way. So is this a bug and should an issue be reported or should the wiki be updated with an explanation of this behavior? Is this something that needs to be reviewed in OH 2’s implementation?