Device attribute syntax from within a device type - revisited


(John S) #1

This question was asked not long ago… and I now find myself asking it again!

Say I’ve defined a custom attribute in my meta section:

attribute "tamper", "string" //, enum ["clear", "detected"] actually, but we digress

Awesome. Now, I can set that attribute via sendEvent just dandy in the device handler. I can use it’s current value (sorta) in the tiles section as expected:

standardTile("tamper", "device.tamper") {
    state("clear", label:'secure',  icon:"st.security.alarm.clear",   backgroundColor:"#79b821")
    state("detected", label:'tampered',  action:"resetTamperAlert", icon:"st.security.alarm.alarm", backgroundColor:"#e3eb00")
}

Awesome. Now say I want to make a color method which returns one of two color codes based on the value in tamper. This doesn’t do it:

def color() {
    log.debug "current tamper value is ${this.tamper}"
   if (this.tamper != "detected") {log.debug "color green"; return "#79b821" }
   else { log.debug "color yellow"; return "#e3eb00" }
}

this.tamper, device.tamper, tamper, or currentValue/lastValue (“tamper”) all fail to give me the value of tamper (which, indeed, is set when looking at the device attributes for the device in the ide/console)

How should I be referring to a custom attribute in the device handler itself (or, do I not get to do that?)


Changing properties of standardTiles dynamically based on other state variables
(ActionTiles.com co-founder Terry @ActionTiles; GitHub: @cosmicpuppy) #2

Short Answer:
ATTRIBUTES ARE NOT VARIABLES.
They are a completely separate namespace, only updated by Events and only readable by a small set of methods, and, supposedly, a couple of “magic property names” of the Device object (NB: not the Device Type object… Completely different animals.).

Ahem… I apologize for shouting. :speak_no_evil:

I just went through this research for a recent question / response thread. Let me know if you’d like to chat about it.

Honestly, I was just as lost before I thought about it and wrote about it…


(John S) #3

I had a vague memory of that post :stuck_out_tongue: but could not find it.

I am trying to understand why, in THAT thread near the bottom, this seems to work:
${device.currentValue(‘reportASAP’) - assumung reportASAP is an attribute?

Setting that aside is the answer
make a custom attribute foo
also make a state variable state.foo
update state.foo whenever attribute change event sent.
refer to state.foo when performing decision logic in the custom device code itself

Seems complimicated.


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

That’s not the most accurate approach, unfortunately, I fear, but don’t know for sure.

The Attribute could be asynchronously changed by something (a SmartApp, or another process instance of the Device Handler, I think).

I think “currentValue()” and similar methods can give you more accuracy, but I don’t know enough about the Event queue an ST concurrency to say.

This is a curious area.


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

No… “reportASAP” is the name of an Attribute. LOL…

Seriously, that statement relies on the Attribute being updated by sendEvent(' <Attribute Name> ') previously.


(John S) #6

I have defined an attribute named tamper
I try to save the device type with this function

// for tamper
def color() {
    def val = device.currentValue("tamper")
    log.debug "current tamper value is ${val}"
   if (val != "detected") {log.debug "color green"; return "#79b821" }
   else { log.debug "color yellow"; return "#e3eb00" }
}

The IDE barfs:

java.lang.NullPointerException: Cannot invoke method currentValue() on null object @ line 641

Line 641 is def val = device.currentValue(“tamper”)

So… again, why is this not working? How would I read the current value of the tamper attribute?


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

This is in the Device Type Handler, right? So you don’t have access to the Device object or its methods (though I’d be tempted to try this. for the heck of it.

Let me get back to you in a few…


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

Check out the reference page for “SmartDevice Type”. It has that annoying paragraph that, dangit, says that the “device” Object exists, even though it is not the same as the Device Object fetched it SmartApps.

So you get these methods available in the Device Type Handler code…

https://graph.api.smartthings.com/ide/doc/deviceType#DeviceInstance

Stay tuned for edits or additions to this post.


(John S) #9

[quote=“tgauchat, post:7, topic:16398”]
I’d be tempted to try this. for the heck of it.
[/quote]Yep tried it. Tried naked currentValue(“tamper”)
Tried sacrificing a goat. Nothing. Weird, isn’t it?


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

Try…

http://docs.smartthings.com/en/latest/device-type-developers-guide/overview.html#attributes

And… I searched through some shared code (Geko’s … maybe I’ll search more), and found this syntax in use in a Device Type Handler:

if (device.currentState("fanState")?.value != value) ...

And another:
https://github.com/KristopherKubicki/device-plantlink-direct/blob/master/device-type-plantlink-direct.groovy

   if(device.currentValue("soilType") != settings.soilType) { 
    	sendEvent(name: "soilType", value: settings.soilType)
    }

And finally, a Topic about someone’s modified Switch device, with extra Attribute referenced with the device.currentValue() method many times.


Off Topic but relevant… This weird undocumented device Attribute update method, by the way…:
device.updateDataValue(name, value) ; where name and value are variables holding the Attribute Name and an appropriately typed value. Guessing that devce.updateDataValue("tamper", 1) should work … but does this fire an event. Who knows.


(John S) #11

It’s me using the system in ways not intended.

state "locked", label:'locked', action:"lock.unlock", icon:"st.locks.lock.locked", backgroundColor:"${(device.tamper != 'detected')? '#79b821':'#e3eb00'}", nextState:"unlocking"

This fails. Because device does not exist inside the eval context for backgroundColor. In fact, not much exists, but you can do some basic code in there… but you don’t have device or state… I was calling color() in there before, which worked (it called color()) but failed because there is no device object to get the attribute value. You CAN use device.currentValue(‘foo’) most anywhere. Just not there.

I can’t change the background color of two tiles, it seems. So I either need two states (lockedTampered and locked) which breaks the lock namespace, or I don’t try to change the color of the main tile.


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

Right… Your state is in the metadata{} section. You can’t do much in there at all – you can’t call any methods!

But you can create an extra redundant meta-Attribute, perhaps? … i.e., an Attribute just for the Main Tile? Something called lockAndTamperState or similar? Or is that what you just said with “lockTampered” (doh!?).

In other words, you would have to maintain “lockAndTamperState” yourself, with the various combinations of: locked-no_tamper, locked-tampered, unlocked-no_Tamper, unlocked-tampered.


(Bruce) #13

Did you try device.tamper? That’s how I access attributes in a device type, generally, device.attribute.


(John S) #14

I ended up going this way. It’s working fine, but man is that fugly.

For the lock - when in locked state (which is the only state the lock itself fires the tampered notification) I wanted the main tile to be green if all is well, and yellowish if someone had tried to guess the code and tripped the tamper. But I didn’t want to introduce new states for device.lock - that would potentially break/confuse other smart apps. So as Terry suggested I just made a second custom attribute with one additional state (lockedtampered) so my main tile could be yellowish if it was locked and tampered.

The main tile color resets to normal if the lock is successfully changed in state (to any normal state - unlocked, etc) by any means. But the tamper smaller tile remains tripped until you press the button in the device page to clear the tamper attribute.

I think that’s reasonable behavior. I think that’s gawd awful to implement.

Normal state:

Someone is being naughty

But then we successfully unlock the door

And finally we clear the tamper by tapping it

What might be more reasonable IMHO is to allow access to other attributes in the tiles/state section like I was trying to do…

You end up with something like this to make the shadow state work - code shown just to demonstrate technique, I’m not sure if these device types are terribly useful for others.

Add some metadata

command "resetTamperAlert" // reset tamper state
attribute "tamper", "String"      //, ["clear", "detected"] 
attribute "locktamper", "String"  //, ["locked", "lockedtamper"...] 

Change the main tile to use your custom attribute

standardTile("toggle", "device.locktamper", width: 2, height: 2) {
      state "locked", label:'locked', action:"lock.unlock", icon:"st.locks.lock.locked", backgroundColor:"#79b821", nextState:"unlocking"
      state "lockedtamper", label:'locked', action:"lock.unlock", icon:"st.locks.lock.locked", backgroundColor:"#e3eb00", nextState:"unlocking"
      state "unlocked", label:'unlocked', action:"lock.lock", icon:"st.locks.lock.unlocked", backgroundColor:"#ffffff", nextState:"locking"
      state "unknown", label:"unknown", action:"lock.lock", icon:"st.locks.lock.unknown", backgroundColor:"#ffffff", nextState:"locking"
      state "locking", label:'locking', icon:"st.locks.lock.locked", backgroundColor:"#79b821"
      state "unlocking", label:'unlocking', icon:"st.locks.lock.unlocked", backgroundColor:"#ffffff"
}

Add a tile to show tamper notification and tap it to clear

standardTile("tamper", "device.tamper") {
        state("clear", label:'secure',   icon:"st.security.alarm.clear",   backgroundColor:"#79b821")
        state("detected", label:'tampered',  action:"resetTamperAlert", icon:"st.security.alarm.alarm", backgroundColor:"#e3eb00")
}

Mirror state changes from the real attribute to the shadow attribute to maintain parallel state and make device behave as expected. In the zwave lock this happens in this function, so I add a second event which sets the shadow attribute here. If I didn’t want to “clear” the display for tamper in all states, I would need *tamper values for unlocked/etc. I also didn’t bother with the intermediate “unlocking” states. Pretty much the big tile will only show tamper/locked until anything changes it, but that’s good enough for me for at-a-glance seeing something is amiss.

def zwaveEvent(DoorLockOperationReport cmd) {
  def result = []
  def map = [ name: "lock" ]
  def maptamper = [ name: "locktamper" ]
  if (cmd.doorLockMode == 0xFF) {
    maptamper.value = map.value = "locked"  
  } else if (cmd.doorLockMode >= 0x40) {
    maptamper.value = map.value = "unknown"
  } else if (cmd.doorLockMode & 1) {
    maptamper.value = map.value = "unlocked with timeout"
  } else {
    maptamper.value = map.value = "unlocked"
    if (state.assoc != zwaveHubNodeId) {
      log.debug "setting association"
      result << response(secure(zwave.associationV1.associationSet(groupingIdentifier:1, nodeId:zwaveHubNodeId)))
      result << response(zwave.associationV1.associationSet(groupingIdentifier:2, nodeId:zwaveHubNodeId))
      result << response(secure(zwave.associationV1.associationGet(groupingIdentifier:1)))
    }
  }
  sendEvent(maptamper) // set the shadow attribute
  result ? [createEvent(map), *result] : createEvent(map)
}

Generate “extended” state to the custom attribute when the oddball state happens (tamper tripped) - this is buried inside the alarm zwave function - case 161 is the tamper message from the lock

    case 161:
      if (cmd.alarmLevel == 2) {
        map = [ descriptionText: "$device.displayName front escutcheon removed", isStateChange: true ]
      } else {
        // lock was tampered - set tampered state
        sendEvent(displayed: false, name: "locktamper", value: "lockedtamper")
        // trip alarm
        map = [ name: "tamper", value: "detected", isStateChange: true, descriptionText: "$device.displayName has had a failed code entered" ]
      }
      break

And give them a manual reset for the tamper tile

// for tamper
def resetTamperAlert() {
    // get lock display out of tamper state by setting it to whatever the lock state actually is
    // this should happen on successful unlock or lock as well
    sendEvent(displayed: true,  isStateChange: true, name: "locktamper", value: device.currentValue("lock"))
    sendEvent(displayed: true,  isStateChange: true, name: "tamper", value: "clear", descriptionText: "$device.displayName tamper state cleared")
}

(edit - bonus points for posting screenshots when my CoC troops were ready :stuck_out_tongue:)


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

Great work putting this all together creatively!

BTW: I think those Attributes can be defined as “enum” to limit their values to the specific list.


(John S) #16

Yep. Had them as enum in the beginning, switched to string when randomly trying things. Most definitely should be enum.


(John Rucker) #17

@tgauchat Terry, you just made my day with this old post. Thanks I was having a hard time with the correct syntax to get at my attributes.
def testv = device.currentState(“closeLightLevel”)?.value
is what I needed!


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

Thanks!!!

For those at the bottom of this thread, I this the gist is in this post: