Sort Attribute List

I'm trying to add a list type attribute in my app/driver. I've successfully implemented creating the list but am struggling figuring out how to sort the list alphabetically (I presume the list would do this by default).

App Function
def getCurrentCount() {
	def device = getChildDevice(state.contactDevice)
	def totalOpen = 0
    def totalClosed = 0
    def openList = []
	contactSensors.each { it ->
		if (it.currentValue("contact") == "open")
		{
			totalOpen++
                        openList.add(it.name)
		}
		else if (it.currentValue("contact") == "closed")
		{
			totalClosed++
                }
    }
    state.totalClosed = totalClosed
    logDebug "There are ${totalClosed} sensors closed"
    logDebug "There are ${totalOpen} sensors open"
    logDebug "Open sensors are ${openList}"
    device.sendEvent(name: "TotalClosed", value: totalClosed)
    device.sendEvent(name: "TotalOpen", value: totalOpen)
    device.sendEvent(name: "OpenList", value: openList)
}

Resulting list:

These should be alphabetical, at least if they were all there when the page loaded (anything added after the fact will appear at the end, as far as I can tell--but any attribute that is declared should have some value, or apps might crash, an issue that can be worked around but may not always be; this would be the only case where they wouldn't appear).

If this isn't happening for you, my guess is that the sorter is treating all capital letters as coming before any lowercase letter, an issue that isn't normally encountered since Hubitat attribute names normally begin with lower case (I'm not aware of any strict rule for this, but it is a convention for sure; there are the "convenience" .currentX properties for an attribute named x where it might).

2 Likes

This :point_up:. It's a simple ASCII sort.

2 Likes

I should have been clearer. I'm not looking to sort the device attributes themselves but the values inside of the OpenList attribute.

Ah, in that case, you can just use the sort() method on the List. If you call it exactly as sort(), it will use the default/natural ordering and mutate the list itself (as opposed to returning a new object that is sorted and leaving the original one alone). This will probably work. If not, there are more varieties of this call you can use, including one that takes a closure as a parameter where you can specify custom sorting: Iterable (Groovy JDK enhancements)

Also, what is the type of this attribute? A list is not a valid attribute type. If it's a String, I think Groovy will automatically convert, though it can't hurt to be specific. If you want something that an app can work with more easily (though it is a bit more work for you in the driver), a JSON_OBJECT might be best. Groovy has methods to convert lists to JSON. This is very nearly it above except you'd need quotes around the items in the list (but I'd use the methods if you go this route).

2 Likes

Well, for now, adding this line seems to have done the trick:

openList = openList.sort()

image

The attribute type is set to string. For now, unless someone tells me otherwise, I think this will be OK. The desire is to have a list of devices that trigger a state change for the virtual device. For example, the app can monitor a group of motion sensors with a user defined threshold for how many devices in the group will need to be active before the child virtual device will update to active. This way, users can add the attribute to a tile in a dashboard or (once I get it added) the description text from the event to push a notification.

Welp...using a string list finally bit me. I was trying to combine the lists in an aggregate app and kept ending up with "[[value1,value2], [value3,value4]]". After spending more time than I care to admit on trial and error, I ended up coming back around to this post. I'm sure that I'm not doing this the most efficiently way possible, but it's working.

Child app method that creates the initial lists in the child devices
def getCurrentCount() {
	def device = getChildDevice(state.contactDevice)
	def totalOpen = 0
    def totalClosed = 0
	def openWindowList = []
	contactSensors.each { it ->
		if (it.currentValue("contact") == "open")
		{
            totalOpen++
			if (it.label) {
            openWindowList.add(it.label)
            }
            else if (!it.label) {
                openWindowList.add(it.name)
            }
		}
		else if (it.currentValue("contact") == "closed")
		{
			totalClosed++
		}
    }
    state.totalOpen = totalOpen
	if (openWindowList.size() == 0) {
        openWindowList.add("None")
    }
	openWindowList = openWindowList.sort()
    openWindowList = groovy.json.JsonOutput.toJson(openWindowList)
    logDebug "There are ${totalClosed} sensors closed"
    logDebug "There are ${totalOpen} sensors open"
    device.sendEvent(name: "TotalClosed", value: totalClosed)
    device.sendEvent(name: "TotalOpen", value: totalOpen)
	device.sendEvent(name: "OpenWindowList", value: openWindowList)
}
Aggregate app that combines the lists from any selected child devices
def getCurrentAggregateCount() {
	def device = getChildDevice(state.contactDevice)
	def aggregateOpen = 0
    def aggregateClosed = 0
	def aggregateTotal = 0
	def aggregateOpenWindowsList = []
	def aggregateClosedDoorList = []
    contactSensors.each { it ->
        totalOpen = it.currentValue("TotalOpen")
		aggregateOpen = (aggregateOpen + totalOpen)
        totalClosed = it.currentValue("TotalClosed")
		aggregateClosed = (aggregateClosed + totalClosed)
        totalCount = it.currentValue("TotalCount")
		aggregateTotal = (aggregateTotal + totalCount)
        def openWindows = new groovy.json.JsonSlurper().parseText(it.currentValue("OpenWindowList"))
		aggregateOpenWindowsList.addAll(openWindows)
    }
    logDebug "There are ${aggregateClosed} sensors closed."
    logDebug "There are ${aggregateOpen} sensors open."
	logDebug "There are ${aggregateTotal} sensors in total."
	logDebug "These windows are open: ${aggregateOpenWindowsList}"
    device.sendEvent(name: "AggregateTotalClosed", value: aggregateClosed)
    device.sendEvent(name: "AggregateTotalCount", value: aggregateTotal)
	device.sendEvent(name: "AggregateTotalOpen", value: aggregateOpen)
	device.sendEvent(name: "AggregateOpenWindowList", value: aggregateOpenWindowsList)
}

Yeah, JsonOutput.toJson() and parseJson() are probably what I'd use, too, though I'm sure there's more than one way! (In your case, you could also just store the list internally and then only convert to JSON when needed for the event/attribute, though I'm not sure there's a reason to prefer one approach over the other, so probably just whatever is easiest...)

1 Like

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