Dynamic ordering of items in sitemap

Apologies if I’ve missed this in the docs.

I have a number of items in a group called gBatteries. In the sitemap I want to list them by the item with the lowest battery level first.

I understand that you cannot control ordering of the items when having one group item (gBatteries) in the sitemap (https://www.openhab.org/v2.3/docs/configuration/sitemaps.html#element-types)

If I listed out each battery item in my sitemap, is there a way of listing them so they are ordered dynamically?

Thanks.

Nope :slight_smile: (that I know of)

You could try to use https://www.openhab.org/v2.3/docs/configuration/sitemaps.html#visibility (but this doesn’t do what you are looking for: dynamic ordering)

Thanks Dim, I can work with visibility to get something similar. I think I can define multiple frames e.g. “Less than 10%”, “Less than 25%”, listing all batteries, and then use visibility to display batteries as appropriate.

I think I’ll end up with batteries listed multiple times as I can’t AND the visibility conditions (only OR). I’d like to do visibility for “less than 20% AND greater than 10%”

I’ll have a look into it, thanks.

1 Like

Did you ever figure this out Andy? I’ve just started trying to do this myself as I move away from using Node-Red to OpenHab for home automation.

I didn’t sorry, but I think it’d work in theory. Did you manage to do it?

No not yet, I’ve been trying to get my setup to work a bit more reliably, as soon as one issue goes away a couple of others pop up to replace it so this has slipped down the chart. I suspect I might wind up using an external programme to build a sorted sitemap as a cron job or something.

The following is just thinking out loud without testing, but what you could do in theory is:
Define for each BatteryLevel_n item a BatteryPosition_n item, and whenever any of the BatteryLevel_n items changes (ideally by putting them in a Group in my opinion), you can use a rule to determine what the desired position of each item is, and store that in the BatteryPosition_n.
So for example you will have BatteryLevel_1 with a value of 25%, BatteryLevel_2 with a value of 62%, BatteryLevel_3 with a value of 75% and BatteryLevel_4 with a value of 17%.
So then the desired values of BatteryPositon_n are: 1 for n=4, 2 for n=1, 3 for n=3 and 4 for n=3. Let’s suppose you have a rule to achieve that (and I am quite positive that is doable), you could then do this: (in short)

Text item=BatteryLevel_1 label="Battery 1 [%d %%]" visibility=[BatteryPosition_1==1]
Text item=BatteryLevel_2 label="Battery 2 [%d %%]" visibility=[BatteryPosition_2==1]
Text item=BatteryLevel_3 label="Battery 3 [%d %%]" visibility=[BatteryPosition_3==1]
Text item=BatteryLevel_4 label="Battery 4 [%d %%]" visibility=[BatteryPosition_4==1]
Text item=BatteryLevel_1 label="Battery 1 [%d %%]" visibility=[BatteryPosition_1==2]
Text item=BatteryLevel_2 label="Battery 2 [%d %%]" visibility=[BatteryPosition_2==2]
Text item=BatteryLevel_3 label="Battery 3 [%d %%]" visibility=[BatteryPosition_3==2]
Text item=BatteryLevel_4 label="Battery 4 [%d %%]" visibility=[BatteryPosition_4==2]
Text item=BatteryLevel_1 label="Battery 1 [%d %%]" visibility=[BatteryPosition_1==3]
Text item=BatteryLevel_2 label="Battery 2 [%d %%]" visibility=[BatteryPosition_2==3]
Text item=BatteryLevel_3 label="Battery 3 [%d %%]" visibility=[BatteryPosition_3==3]
Text item=BatteryLevel_4 label="Battery 4 [%d %%]" visibility=[BatteryPosition_4==3]
Text item=BatteryLevel_1 label="Battery 1 [%d %%]" visibility=[BatteryPosition_1==4]
Text item=BatteryLevel_2 label="Battery 2 [%d %%]" visibility=[BatteryPosition_2==4]
Text item=BatteryLevel_3 label="Battery 3 [%d %%]" visibility=[BatteryPosition_3==4]
Text item=BatteryLevel_4 label="Battery 4 [%d %%]" visibility=[BatteryPosition_4==4]

I decided to see if it could be done as I imagined, and it seems to work fine.
For testing I just made up these items, where all items that represent a battery level go in a group called gBatteryLevels, and each of these items has a corresponding item with the suffix _Pos, which goes in the group gBatteryPos (although that last group is not strictly needed).

Group gBatteryLevels
Group gBatteryPos

Number DoorSensor_Back_BatteryLevel "Back Door Battery Level [%d%%]" (gBatteryLevels)
Number DoorSensor_Front_BatteryLevel "Front Door Battery Level [%d%%]" (gBatteryLevels)
Number Remote1_Battery "Remote 1 Battery Level [%d%%]" (gBatteryLevels)
Number Remote2_Battery "Remote 2 Battery Level [%d%%]" (gBatteryLevels)

Number DoorSensor_Back_BatteryLevel_Pos "Back door Battery Pos [%d]" (gBatteryPos)
Number DoorSensor_Front_BatteryLevel_Pos "Front door Battery Pos [%d]"(gBatteryPos)
Number Remote1_Battery_Pos "Remote 1 Battery Pos [%d]"(gBatteryPos)
Number Remote2_Battery_Pos "Remote 2 Battery Pos [%d]"(gBatteryPos)

Of course you need to make sure that these made up items are in reality linked to a channel and get a value from that channel before moving on.
Assuming that is all done, you can make the rule that does the sorting whenever one of the battery level items changes:

import org.eclipse.smarthome.model.script.ScriptServiceUtil
import java.util.HashMap
import java.util.Map
import java.util.TreeMap

rule "Order battery levels"
when
    Member of gBatteryLevels changed
then
    // This section is only needed for first time, to get some values in the Position items: 
    // (can be removed after first time running this rule)
    DoorSensor_Back_BatteryLevel_Pos.postUpdate(1)
    DoorSensor_Front_BatteryLevel_Pos.postUpdate(2)
    Remote1_Battery_Pos.postUpdate(3)
    Remote2_Battery_Pos.postUpdate(4)
    
    var HashMap<Number, String> TestMap = new HashMap<Number, String>();
    for(var i=0; i<gBatteryLevels.members.length; i++){
        var LevelItem = gBatteryLevels.members.get(i)
        var PositionItem = ScriptServiceUtil.getItemRegistry.getItem(LevelItem.name + "_Pos")
        TestMap.put(LevelItem.state as Number, PositionItem.name)
    }
    var Map<Number, String> map = new TreeMap<Number, String>(TestMap)

    var i = 1
    for (String ItemName : map.values()) {
        ScriptServiceUtil.getItemRegistry.getItem(ItemName).postUpdate(i)
        i++
    }
end

Please note that this is something that I just made up in a relatively limited amount of time, so there might be more efficient ways to do this.

And as described in my previous post, then add the following to your sitemap:

Default item=DoorSensor_Back_BatteryLevel   visibility=[DoorSensor_Back_BatteryLevel_Pos    ==1]
Default item=DoorSensor_Front_BatteryLevel  visibility=[DoorSensor_Front_BatteryLevel_Pos   ==1]
Default item=Remote1_Battery                visibility=[Remote1_Battery_Pos                 ==1]
Default item=Remote2_Battery                visibility=[Remote2_Battery_Pos                 ==1]
Default item=DoorSensor_Back_BatteryLevel   visibility=[DoorSensor_Back_BatteryLevel_Pos    ==2]
Default item=DoorSensor_Front_BatteryLevel  visibility=[DoorSensor_Front_BatteryLevel_Pos   ==2]
Default item=Remote1_Battery                visibility=[Remote1_Battery_Pos                 ==2]
Default item=Remote2_Battery                visibility=[Remote2_Battery_Pos                 ==2]
Default item=DoorSensor_Back_BatteryLevel   visibility=[DoorSensor_Back_BatteryLevel_Pos    ==3]
Default item=DoorSensor_Front_BatteryLevel  visibility=[DoorSensor_Front_BatteryLevel_Pos   ==3]
Default item=Remote1_Battery                visibility=[Remote1_Battery_Pos                 ==3]
Default item=Remote2_Battery                visibility=[Remote2_Battery_Pos                 ==3]
Default item=DoorSensor_Back_BatteryLevel   visibility=[DoorSensor_Back_BatteryLevel_Pos    ==4]
Default item=DoorSensor_Front_BatteryLevel  visibility=[DoorSensor_Front_BatteryLevel_Pos   ==4]
Default item=Remote1_Battery                visibility=[Remote1_Battery_Pos                 ==4]
Default item=Remote2_Battery                visibility=[Remote2_Battery_Pos                 ==4]

Of course you will need to repeat this pattern if you have more than 4 battery level items, so the number of definitions in your sitemap will increase quadratically with the number of battery level items.

I hope this helps.

Great stuff RolfV, thanks a lot.

Have a look into this thread, the OP shows fuelprices sorted!

No problem, it was a nice exercise.
Would you mind selection my answer as solution, that way it will be easier to find for others as well.

I think I’ve just come up with an alternative, given @opus’s link which I think might be easier to maintain.

I now have two groups gBatteries and gBatteriesSorted. All my battery items belong to gBatteries.

Then I have a rule that will fire whenever a battery changes or system is started:

rule "sort batteries"
when
  System started or
  Member of gBatteries changed
then
  
  // Remove all batteries from sorted group.
  gBatteries.members.forEach[ item |
    if (gBatteriesSorted.members.contains(item)) {
      gBatteriesSorted.removeMember(item)
    }
  ]
  
  // Sort the batteries and add them to sorted group in order
  gBatteries.members.filter[ item | item.state !== NULL ].sortBy[state as DecimalType].forEach[ item |
    gBatteriesSorted.addMember(item)
  ]
  
  // Add batteries with no value to the end
  gBatteries.members.filter[ item | item.state === NULL ].forEach[ item |
    gBatteriesSorted.addMember(item)
  ]
  
  gBatteriesSorted.members.forEach[ item |
    logDebug("sortbatteries", "Sorted: {} {}", item.name, item.state)
  ]
end

And I display gBatteriesSorted in my sitemap.

This seems to work so far - I’ve not done much testing. There might be easier ways to clear the gBatteriesSorted members before re-adding, but I was getting exceptions using clear() or removeAll() but may have just missed something.

I don’t know if there’s any downsides/gotchas to this…

Edit: Just tweaked this, as adding a new battery would break. Code is a little more defensive now.

2 Likes

There’s no guarantee as to what order group members get displayed in. No reason to think it’ll change from happening to be creation order anytime soon, but it might. Neat solution for existing system though!