How to implement custom states for tiles


(Mark MacDonald) #1

I am trying to create device handler for a custom device that has three states. To make this generic, I’m just going to call them A, B, and C.

In the device definition I included the following

  attribute "mode", "enum", ["A","B","C"]

Next in the UI defines:

tiles {
standardTile(“mydevice”, “device.mode”, width: 2, height: 2, canChangeBackground: true) {
state “A”, label: ‘${name}’, action: “modeB”, icon: “st.switches.switch.on”, backgroundColor: "#79b821"
state “B”, label: ‘${name}’, action: “modeC”, icon: “st.switches.switch.on”, backgroundColor: "#ffffff"
state “C”, label: ‘${name}’, action: “modeA”, icon: “st.switches.switch.off”, backgroundColor: “#ffffff
}

If my event handlers look like:

def modeB() {
zigbee.smartShield(text: “B”).format()
device.mode = “B”
}

Then they never even seem to get triggered from tapping the screen
If I remove the line attempting to assign device.mode=“B”, then at least the modeB event for the first standardTile state is created as expected for default behavior, but the state never changes to B or C.

Someone please enlighten me as to what I’m missing here.


(ActionTiles.com co-founder Terry @ActionTiles; GitHub: @cosmicpuppy) #2

Attributes are NOT variables! Assignment of a variable does not set the state of an Attribute!

The way to set an Attribute value is ONLY with the “sendEvent()” method (or as the return value in the “parse()” method which does an implied sendEvent().

See docs.SmartThings.com for information on the Event object, createEvent, and sendEvent.


(Kevin) #3

I think you have two issues:

  1. The zigbee line needs to be returned by the function. You can do this by either assigning it to a variable and returning that variable as the last line of the function or just swap the lines so that the last line of the function is the zigbee function.

  2. You can’t assign values directly to an attribute. You need to use something like:
    sendEvent(name: “mode”, value: “B”)


(Mark MacDonald) #4

Thanks guys. Using sendEvent() does the trick!
I’m an old C programmer and it’s taking me a little while to wrap my brain around the ST Groovy model. I can’t tell you how many changes I made that mysteriously compiled, but didn’t work. I’ve grown to like the strongly typed world of C Probably going to have to read the API docs a few more times before I start to understand it to the level of having more answers than questions when I look at example code and try to emulate.
Hopefully this post will end up helping someone else like me.

Now I get to move on to sorting out the json parsing for getting values back from my device.


(Kevin) #5

Using sendEvent() was the fix for your original question, but if you put the sendEvent line below the zigbee line, I’m pretty sure the zigbee line won’t get executed.


(Mark MacDonald) #6

Kevin, you are right. Examining the log even shows the event posted, but the zigbee data never gets transmitted. Which previously had me wondering why.

Quoting from the docs:

Commands may return any object, but typically do not return anything since they perform some type of action.

Can you please point me to where the need to have that statement last is explained?


(ActionTiles.com co-founder Terry @ActionTiles; GitHub: @cosmicpuppy) #7

I think it comes down to this, but pardon me if I’m guessing incorrectly which has got me in trouble here before…

This command just creates “formatted output”, which, in Groovy, is then the implicit return value of the calling method / command, if there are no following statements or an explicit return x statement.

All I know for certain is that syntax for Command methods, below, works fine for me and the Smart Shield:

def screenOn() {
	zigbee.smartShield(text: "screenOn").format()
}

def screenOff() {
    zigbee.smartShield(text: "screenOff").format()
}

def projecOn() {
    log.debug "Sending: projecOn."
	zigbee.smartShield(text: "projecOn").format()
}

def projecOff() {
    log.debug "Sending: projecOff."
    zigbee.smartShield(text: "projecOff").format()
}

(Kevin) #8

I know about this because I spent several frustrating hours doing a lot of trial and error while trying to figure out why some of my commands worked and others didn’t.

I wasn’t able to find anything that explicitly states this requirement in the zigbee DH section, but in the zwave DH section it says the following and the general concept is the same.

To send a Z-Wave command to the device, you must create the command object, call format on it to convert it to the encoded string representation, and return it from the command method.

I’m pretty sure the zwave/zigbee lines create the commands, but they need to be returned by the first method on the stack in order to be executed.

It’s not practical to always put the command last and you sometimes need to return more than one command so you can add the commands to a map and then put the map as the last line.

I usually split my code into a lot of methods so that each method only does one thing. You can still do this, but you have to be careful that the first method returns all of the zwave/zigbee commands that the other methods returned.

Example:

def methodA() {
  def result = []  
  
  //Inserts command into map.
  result << zigbee.smartShield(text: "A").format()
  
  //Inserts result of methodB into the map.
  result << methodB()
  
  //Inserts items from the result map of methodC.
  result += methodC()
  
  //returns all of the commands so they can be executed.
  result
}

def methodB() {
  //Returns a single command.
  zigbee.smartShield(text: "B").format()
}

def methodC() {
  // Returns a map of commands.
  [
    zigbee.smartShield(text: "C1").format(),
    zigbee.smartShield(text: "C2").format()
  ]
}

FYI, You have to do the same thing when using the createEvent command, but not when using the sendEvent command.