Change labels of child device programatically?


(Allan) #1

I have been racking my brain for a couple days, testing things back and forth trying to get labels in a child device to update programmatically instead of having to have another device type. I am using the ST_Anything program (https://community.smartthings.com/t/release-st-anything-v2-8-arduino-esp8266-esp32-to-st-via-thingshield-ethernet-or-wifi/) with a ESP32. I have a working sketch and got everything installed with no issues at all.

So here is what I’m trying to do. The child device right now is a simple contact sensor, open and closed (https://github.com/DanielOgorchock/ST_Anything/blob/master/devicetypes/ogiewon/child-contact-sensor.src/child-contact-sensor.groovy). I’m going to be using this for a garage heater (current sensor on its hot lead) so I want it to display “Running” or “Off”. Then I started thinking I would want to do the same with other things around my house, having a “On/Off” or “True/False”. More I thought about it the more I thought a custom device type with preferences would make the most sense. So I modified the default device and added a preferences section with two options, what to display when “open” and what to display when “closed”:

input(“openLabel”, “text”, title: “Label to display when Open”, defaultValue: “Open”, required: true, displayDuringSetup: true)
input(“closedLabel”, “text”, title: “Label to display when Closed”, defaultValue:“Closed”, required: true, displayDuringSetup: true)

However no matter what I did I could not get the labels to display in the MultiAttributeTile. I used the standard ‘{$openLabel}’ and every variation of it like so:

attributeState “open”, label:’{$openLabel}, icon:“st.contact.contact.open”, backgroundColor:"#e86d13"
attributeState “closed”, label:’{$closedLabel}, icon:“st.contact.contact.closed”, backgroundColor:"#00a0dc"

I kept getting the correct background color change but the text just said NULL. I would go back to the original code and put in "label: ‘${name}’ " and it would display OPEN or CLOSED like normal. After more reading I found something that said the metadata section can’t use the variables directly unless they were called from a function. I then found a post by @tslagle13 that seemed promising: https://community.smartthings.com/t/changing-properties-of-standardtiles-dynamically-based-on-other-state-variables/ . In that he called a function to determine backgroundColor in a tile and said that worked. Now that was on a stadard tile, I’m using a multi, but I didn’t think that would matter. So I tried the same for the label:

attributeState “open”, label:getLabelText(), icon:“st.contact.contact.open”, backgroundColor:"#e86d13"
attributeState “closed”, label:getLabelText(), icon:“st.contact.contact.closed”, backgroundColor:"#00a0dc"

along with a simple function:

def getLabelText(){
def myLabel = openLabel
if (device.currentState(“contact”) == “closed”){
myLabel = closedLabel
}
return myLabel
}

So it assigns the openLabel (Open or On or Running or Yes…whatever the user enters) then if the status is closed it switches to the closed label. The problem is two fold.

  • I cannot figure out a value that returns when closed, I’ve tried dozens and none evaluate. The one above (device.currentState(“contact”) doesn’t compile…gives me a “java.lang.NullPointerException: Cannot invoke method currentState() on null object”.
  • Even when I get something that compiles like ‘${name}’ which is what the label uses normally it still doesn’t evaluate and I’m still returning a empty string.

For testing I added some debug lines to the getLabelText() function and found that it doesn’t seem to actually get called on a status change. If I go into the device and hit the gear icon then hit Done it does run through it and I see my debug lines but on a normal status change it doesn’t. The odd thing about this is the background color changes so the tile is working…it’s just not calling the getLabelText() function at all.

Finally I tried setting state information like this:

def updated() {
log.debug(“Updated called…”)
state.myOpenLabel = openLabel
log.debug(“state.myOpenLabel set to $state.myOpenLabel”)
state.myClosedLabel = closedLabel
log.debug(“state.myClosedLabel set to $state.myClosedLabel”)
}

The state info did persist but I couldn’t find a way to use that within the tile label either.

So is this just how it is or is there a way I can programmatically change the labels? If I can then how? Or is it not working because its a child device and there needs to be more to it? I’m at a loss.

-Allan


How to show a dynamic label in my device handler?
Give a default value for a optional preference?
Need Help With Updating standardTile Label
(Jason "The Enabler" as deemed so by @Smart) #2

Hi Allen, no need to reinvent the wheel. Take a look at this.


(Allan) #3

No…thats not exactly what im looking for. I don’t want to create more devices, i already have the device. I simply want to change the open and closed labels based on user preferences instead of hard coding them in if thats possible. I don’t need to change types or anything else.


(Michael) #4

Have you tried ${currentValue}? Then you initialize the value of the tile with your default value and then set it to whatever you want. Example:

    valueTile("alarmMsg", "device.alarmMsg", width: 6, height:2, inactiveLabel: false, decoration: "flat") {
		state "default", label:'${currentValue}'
	}

(Allan) #5

That just gives the normal OPEN and CLOSED, same as ‘${name}’ which is what I’m trying to changed through programming. Unless I’m missing something you are saying.

Again I can make a dozen device types, one for Open/Closed, on for On/Off, one for Start/Stop, etc. But I don’t want to if I can just change two preferences and have it customized with a single device type. Maybe it’s not possible.


(Allan) #6

So I’ve tried State variables, I’ve tried attributes. I’ve tried regular variables. I’ve tried preferences. Everything works OUTSIDE of the tile label, debug info shows everything gets set correctly but nothing I put in there to call the value back for the label works…it’s always blank or Null. I think I’m gonna give it another hour or two of hacking and declare it’s not possible and just make multiple device handlers (which would have taken 5 minutes instead of 10+ hours…)

Edit: Dynamic label using settings on standardTile So can you change “currentValue” in the device? So if its Open I want to rewrite to itself the preference for open (On) and if Closed rewrite it to the preference for closed (off)? Probably not but worth asking.


(Stephan H.) #7

Not sure if this will work but I remember battling with something similar…

  • create a string attribute like “myLabel”
  • create a method that does a sendEvent to set this attribute to the value of the variable you use to capture the custom text.
  • have this method executed every time you refresh your device
  • set your tile label to be the current value of the attribute “myLabel”

Seems like it would work (in my head) but haven’t tried it myself. Of course you would have to refresh the device every time you change the custom variable.

EDIT:
Now that I think about it…probably won’t work…I don’t think it will know the value of any variable other that the one specified in the tile state…I would give it a shot anyway tho :slight_smile:


(Allan) #8

@stephack - Yeah…tried that too. The attribute works fine, gets my value from the preference, but there is no way to call it from the tile label. It either doesn’t compile or returns null. I tried that after I couldn’t get State to work.

Again I’m thinking it can’t be done which is unfortunate…it would save on a lot of custom device types that exist only to change labels or background colors or things like that. @tslagle13 example in the thread linked in the first post I think would work for a SmartApp (which is what he did) but I don’t think it works the same in a device which is what I’m trying to do. I kinda want to test it in a SmartApp but on the same token it’s not going to make my device handlers work.


(Rudi Prunzel) #9

Hi @vseven, I’m sorry to say but this is just not possible at this moment (with tiles) on a Device Handler.
I was waiting for ST to switch to HTML so we could have more customization but now I’m not sure if this will ever happen :frowning:


(Allan) #10

Well at least I can give up and know I tried really hard. :slight_smile:

I took the whole two minutes and made a copy of the current device type and updated it to “Running” and “Off” for my garage heater and since I was already changing the labels I used the st.thermostat.fan-off for off and st.thermostat.heating for on. Looks nice. Didn’t want to maintain multiple devices but at this point whatever.


(Barry) #11

The trick I use is this:

  • Create a new attribute named “displayValue” “string”, and use it in the multiAttribute tile as the attribute to be displayed
  • create attributeStates for the superset of atates you could send that tile (open, closed, off, on, running, etc.), each with the desired label, icon and color (dependent upon you multiAttirbuted tile settings)
  • in your event handler, when you handle the status change event, send both the actual value to the actual attribute, but also do a sendEvent(name: "displayValue", value: "${string you want to send}", isStateChange: true)

You can handle almost unlimited different icon/color/text displays in a single tile using this trick…check out my Ecobee thermostat device for several different examples:


(Allan) #12

The issue is it’s a child device that’s written to by a parent device (not SmartApp) using:

childDevice.sendEvent(name: namebase, value: value)

So I was hoping I could simply evaluate currentValue and change the label based on that but that doesn’t look possible. Also there isn’t any event that auto triggers in the child so I can’t based it on that either. It looks like the only option, for me in this situation, is multiple DTH’s which is fine as I doubt the child DTH’s are going to change much with the ST_Anything project.

Thanks for the suggestion though.


(cjcharles) #13

Another advantage of multiple DTHs (rather than a hacked multi device - which I tried aswell in order to serve as a contact and motion sensor in one project), is the integration with CoRE and other tools is much easier. With the DTH I hacked I found that sometimes CoRE failed to recognise the state change since it was expecting a DTH with more ‘standard’ performance…


(Barry) #14

I don’t think that matters - I actually have one child device send events to a peer child device by writing through the parent.

In the example you provided, make a couple of subtle changes:

  • Don’t do “childDevice.sendEvent”, change that to something like “childDevice.generateEvent(” with the same arguments.

  • In the childDevice, define generateEvent as in my referenced example of the EcobeeThermostat. To wit:

        def generateEvent(String name, String value) { // In my example, I actually pass a map of multiple name/value pairs
        def myType = parent.deviceType( this )
        if (myType == 'pump') {
            if (value == 'on') {
                sendEvent( name: name + "Display", value: "running") // this updates the tile that will be displayed
            } else {
                sendEvent(name: name + "Display", value: "stopped")
            }
            sendEvent( name: name, value: value)                        // this updates the actual switch state for this device
       } else if ( myType == "anotherDeviceType") {
            ...
    

You define a tile named “switchDisplay”, and have multiple states (attributeState if in a Multi-Tile), one for each string you want to display (which you base of of the type of this particular device).

Go back and look how the Ecobee (Connect) service manager SmartApp creates key/value pairs to send to the Thermostat and Sensor devices, and then how each “translates” those into specific delay values (good examples are thermostatOperatingStateDisplay and currentProgramName - and even temperatureDisplay vs. temperature

I’m pretty sure this will address what you are trying to do without resorting to individual DTH’s each that differ only in the attributeState definitions…

PM me if you’d like to discuss more…


(Allan) #15

Thank-you @storageanarchy - I forked @ogiewon code and will work on the changes. I would “think” it should be simple based on your example which is starting to make sense to me. I’ll PM you only if I’m pulling my hair out.


(Allan) #16

Think I got the basic framework laid out which is all I wanted to accomplish for now as there are a lot of child devices to update all at once. The original push of data to the device was:

childDevice.sendEvent(name: namebase, value: value)

Per your instructions I added a generateEvent in the child device like this:

def generateEvent(String name, String value) {
log.debug(“Passed values to routine generateEvent in device named $device: Name - $name - Value - $value”)
sendEvent(name: name,value: value)
}

and then I tried to modify the parent device to generateEvents like this:

childDevice.generateEvent(name: namebase, value: value)

But it threw errors. So I change it to just this:

childDevice.generateEvent(namebase, value)

and I was able to pass the values. The debug log showed the correct info being passed as well as the tile updated:

10:05:18 PM: debug Passed values to routine generateEvent in device named Outdoor Temperature: Name - temperature - Value - 10.24

Thank-you very much @storageanarchy for the guidance. Now that I have a routine that actually fires on value change I think I can change the labels based on preferences but I’ll save that for another day.

-Allan


(Barry) #17

Very nice - glad I could help.

Have fun!!!


(Allan) #18

Thanks to @storageanarchy further guidance I got this working so to hopefully help other people I’ll post my code. With it you can display anything for the labels while the underlying device still maintains it’s standard states. This is based on @ogiewon ST_Anything child contact sensor (comments, header, extra stuff not important removed):

metadata {
	definition (name: "Child Contact Sensor", namespace: "ogiewon", author: "Dan Ogorchock") {
		capability "Contact Sensor"
		capability "Sensor"        
	}
    
	preferences {
		section("prefs") {
			input(name: "openDisplayLabel", type: "text", title: "Enter the text to display when the contact is open.", multiple: false, required: true, default: "Open")
			input(name: "closedDisplayLabel", type: "text", title: "Enter the text to display when the contact is closed.", multiple: false, required: true, default: "Closed")
    		}
	}
    
	tiles(scale: 2) {
		multiAttributeTile(name:"contact", type: "generic"){
			tileAttribute ("device.contactDisplay", key: "PRIMARY_CONTROL") {
				attributeState "Open", label:'${currentValue}', icon:"st.contact.contact.open", backgroundColor:"#e86d13"
				attributeState "Closed", label:'${currentValue}', icon:"st.contact.contact.closed", backgroundColor:"#00a0dc"
            		}
            		
        	}
		main "contact"
	}
}

def updated() {
	//log.debug("Updated called.  Update labels if changed.")
	updateLabels(device.currentValue("contact"))
}

def generateEvent(String name, String value) {
	log.debug("Passed values to routine generateEvent in device named $device: Name - $name  -  Value - $value")
	// Update device
	sendEvent(name: name,value: value)
	updateLabels(value)
}

def updateLabels (String value) {
	//log.debug("updateLabels called.  Passed value is $value.  openDisplayLabel is $openDisplayLabel.  closedDisplayLabel is $closedDisplayLabel.")
	// Update tile with custom labels
    	if (value.equals("open")) {
       		sendEvent(name: "contactDisplay", value: openDisplayLabel, isStateChange: true);
	} else {
		sendEvent(name: "contactDisplay", value: closedDisplayLabel, isStateChange: true)
	}
}

Hopefully this makes sense. I personally have Open = Off and Closed = Running for a electric heater in my garage although for testing it was “Suck It Trebeck” and “Anal Bum Cover”. :sunglasses:

-Allan


Using an attribute as value for a tile
(Dan) #19

Allan,

Very interesting. I am going to need some time to digest and test these changes to ensure they are 100% backwards compatible. There are a lot of users who expect things to just work like “normal” SmartThings devices.

I definitely do like the idea of calling a child device specific function any time the device is updated. This would allow some simplification of the Parent DH, and allow some more interesting options with the child DH.

Give me some time to think this through. I see the pull-requests in GitHub, so I’ll get back to you soon.

Glad you were able to find a solution that meets your needs!

Dan


(Allan) #20

Yeah, I only was able to test my devices: temperature, humidity, and contact. Technically they all should work fine…the generateEvent is just calling the sendEvent but now you have something that you can call other things for. Like in this example the ability to change the status labels. It would need some full testing but I submitted all the pull requests so you can copy and paste it on your side and test. I make no guarantees…I just hack at stuff until it does what I want. :slight_smile:

With that said the end result could be reducing the number of DTH’s overall based on @storageanarchy 's code. So for your sensors that are status only you can probably consolidate if you wanted to. Since I know people using your code might be using all the different DTH’s I just updated all of them and put in pull requests instead of trying to reduce the number thinking exactly what you said…wouldn’t want to mess anyone up but still allow some new stuff to be used and/or allow for future expansion.

PM me or comment on the pull requests if there are any questions. Once you wrap your head around the changes it makes sense and should make things easier in the future (I hope).

-Allan