Device attribute syntax from within a device type


(Gary D) #1

I’m playing around with writing a device type with the capability of “door control.” Within that handler, I’m unable to use the following syntax to get the current state of the door:

doorState // this turns out to be null

I’ve also tried “device.doorState” with no luck. // also null

I’m having to resort to using the following to get the state:

def lastValue = device.latestValue(“door”)

According to the documentation here (https://graph.api.smartthings.com/ide/doc/device), I was under the impression that just “doorState” would work. What am I doing wrong? Why would “device.latestValue(“door”)” work perfectly when “device.doorState” doesn’t?

(Note to moderators: if this belongs in the device section instead of smart apps, please move it.)

Thank you
Gary


Device attribute syntax from within a device type - revisited
(ActionTiles.com co-founder Terry @ActionTiles; GitHub: @cosmicpuppy) #2

EDITED
[First of all, NB: This response is not going to directly answer your question, but it may get us on the same page; and I can follow up with further examples or resources if necessary, and we can chat about it through private message or whatever. Thanks for indugling me.]


This is a great question, @garyd9; and it is a topic I am in the process of actively discussing with @Jim (the new “documentation guy”). The documentation of Capabilities needs expansion (and, I think the implementation of Capabilities actually needs to be more rigorously enforced.

[And this confusion that I would like @Jim and @Ben to understand so that he and I (and all Device Type developers) to be consistently synced up.]

In the meantime, let’s break this down according to what I know:

Per the Capabilities Taxonomy Reference ( https://graph.api.smartthings.com/ide/doc/capabilities ), a Device of Device Type that has capability.doorControl must have the attribute: door with possible values ["open", "opening", "unknown", "closed", "closing"], and commands open() and close().

I found one example implementation of this in the Device Type Template: SmartSense Garage Door Sensor Button.

IMHO, it does not properly use the attribute door (though the “inconsistency” that I have a problem with is probably pretty common across many Device Types, unfortunately)…

Instead, it sets and uses the value of a variable called "status" (or device.status).

Some snips:

        standardTile("status", "device.status", width: 2, height: 2) {
            state("closed", label:'${name}', icon:"st.doors.garage.garage-closed", action: "actuate", backgroundColor:"#79b821", nextState:"opening")
            state("open", label:'${name}', icon:"st.doors.garage.garage-open", action: "actuate", backgroundColor:"#ffa81e", nextState:"closing")
            state("opening", label:'${name}', icon:"st.doors.garage.garage-opening", backgroundColor:"#ffe71e")
            state("closing", label:'${name}', icon:"st.doors.garage.garage-closing", backgroundColor:"#ffe71e")
        }
def open() {
    if (device.currentValue("status") != "open") {
        log.debug "Sending button press event to open door"
        sendEvent(name: "buttonPress", value: "true", isStateChange: true, unit: "")
    }
    else {
        log.debug "Not opening door since it is already open"
    }
}

def close() {
    if (device.currentValue("status") != "closed") {
        log.debug "Sending button press event to close door"
        sendEvent(name: "buttonPress", value: "true", isStateChange: true, unit: "")
    }
    else {
        log.debug "Not closing door since it is already closed"
    }
}

Note: he code does not have to define attribute "door" in the metadata{} section (after the capabilities list and before the command list), because door is a standard attribute for this capability. similarly, the command list consists only of command "actuate", and does NOT include the standard mandated open and close: The methods open() and close() must exist, but they are not needed in the metadata{}.

However…
This example is even inconsistent within itself. In the method private List parseOrientationMessage(String description), you will find the following calls – notice the use/creation of the Event "name: door".

    if (absValueZ > 825 && absValueXY < 175) {
        results << createEvent(name: "contact", value: "open", unit: "")
        results << createEvent(name: "status", value: "open", unit: "")
        results << createEvent(name: "door", value: "open", unit: "")
        log.debug "STATUS: open"
    }
    else if (absValueZ < 75 && absValueXY > 825) {
        results << createEvent(name: "contact", value: "closed", unit: "")
        results << createEvent(name: "status", value: "closed", unit: "")
        results << createEvent(name: "door", value: "closed", unit: "")
        log.debug "STATUS: closed"
    }

What does this imply about how to write and use a Device Handler?

It means that there is no intuitive way to read the attributes that we expect should exist (mandatorily) for a Device Type that supplies a particular Capability.

I tried, briefly, to find a sample/tempate/shared SmartApp that actually uses (selects in preferences) a Device with “capability.doorControl”, but did not find one. If I get a chance, I can write a little test one from scratch, or, well, you’re in the process of doing so anyway.

So… In the meantime, consider the code of Ridiculously Automated Garage Door and other SmartApps that sometimes use attributes correctly, perhaps, and sometimes don’t (?!?) … Sometimes they utilize values like so: (notice the use of doorSensor.contactState)

section("Garage door") {
     input "doorSensor", "capability.contactSensor", title: "Which sensor?"
...

def doorOpenCheck()
{
final thresholdMinutes = openThreshold
	if (thresholdMinutes) {
		def currentState = doorSensor.contactState
		log.debug "doorOpenCheck"
		if (currentState?.value == "open") {

Well – again, confusing, since capability.contactSensor is supposed to have attribute: contact -- ["closed", "open"] per the Reference, whereas there is no attribute called “contactState”.

Whatever: The sample template code for “SmartSense Open/Closed Sensor” (look it up…) actually uses this to set its, ummm, value… My confusion here? I don’t see any calls to createEvent() or sendEvent() … instead, this Device Handler seems to use private getter methods ("getTemperatureResult(value)", “getContactResults(value)” and puts everything in the variable “resultMap[ name, values, descriptionText ]”.

    case '0x0024': // Supervision Report
        resultMap = getContactResult('closed')
        break

    case '0x0025': // Restore Report
        resultMap = getContactResult('open')
        break
...
private Map getContactResult(value) {
    log.debug 'Contact Status'
    def linkText = getLinkText(device)
    def descriptionText = "${linkText} was ${value == 'open' ? 'opened' : 'closed'}"
    return [
        name: 'contact',
        value: value,
        descriptionText: descriptionText
    ]
}

I’m going to pause here for a break while I read through a few more examples and references to see if there is a “simple answer” for the time being…

…CP / Terry.


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

Back from break, and here’s what I conclude at the moment after reading the official Device Type Developer’s Guide and browsing just a few Device Handlers and SmartApps

I will make “generalized” assumption statements below, so I don’t need to prefer each one with “I assume”, “as far as I can tell”, or “generally…”, even if I say “only” or “must”, etc…


Attributes

…can be used internally in the Device Type. For example they are used for the GUI (tiles).
The Device Type Example (Centralite Switch), has capability.powerMeter, so that implies it has the standard attribute: "power".

This example displays the value of power on a sub-tile: (Where we can note, from the syntax, that power is actually an object (or structure), that is a part of the Device’s state, and power has sub-attributes, including currentValue, name, etc.). I would love to see the full syntax used somewhere and be as simple as “device.state.power.currentValue”, but can’t assume that is available syntax.

        valueTile("power", "device.power", decoration: "flat") {
            state "power", label:'${currentValue} W'
        }

Events

… are the mechanism by which “status” (?) can be passed to SmartApps. I fear (or dislike?) that – “status events” do not have to be the same as as “state.attributes”), though in the Device Type Example (Centralite Switch) it is: (notice that name is set to “switch”, which is a valid standard attribute of capability.switch).

        log.debug "Switch command"
        name = "switch"
        value = description?.endsWith(" 1") ? "on" : "off"
    }

    def result = createEvent(name: name, value: value)

The “createEvent()” method is only one way to return the event datatype (which is just a list of parameters to sendEvent() or must be returned from parse().

The Parse Method

… is used to update state / attributes without requiring an explicit createEvent() or sendEvent().

It does this by use of the return statement to return a list structure that can be built using createEvent() or just by populating a simple Groovy list structure with the required parameters:

i.e., I believe that these two following snips are equivalent:

def parse( description ) {
...
   results = getTempResult( ..., description )
   return results
}
...
private getTempResult(part, description) {
	def name = "temperature"
	def temperatureScale = getTemperatureScale()
	def value = zigbee.parseSmartThingsTemperatureValue(part, "temp: ", temperatureScale)
	if (tempOffset) {
		def offset = tempOffset as int
		def v = value as int
		value = v + offset
	}
	def linkText = getLinkText(device)
	def descriptionText = "$linkText was $value°$temperatureScale"
	def isStateChange = isTemperatureStateChange(device, name, value.toString())

	[
		name: name,
		value: value,
		unit: temperatureScale,
		linkText: linkText,
		descriptionText: descriptionText,
		handlerName: name,
		isStateChange: isStateChange,
		displayed: displayed(description, isStateChange)
	]
}

is basically equivalent to: (due to the magic of Groovy)

def parse( description ) {

resultList = getTempResult( …, description )
results = createEvent( resultList.name, resultList.value, …, resultlist.displayed )
return results
}

sendEvent()

… is used to update additional status values that are not restricted to the attribute list (but, I think, should be!), and can be called anywhere in the Device Handler, rather than depending on the “`parse()” method.


So to answer your original question…

As long as your Device Type sets the value for an attribute (or any arbitrary "name", I think), in either the event type result of parse() or in a sendEvent( name: "door", value: "x" ), then you can access this attribute using the device.currentValue("door") or `device.latestValue(“door”) method, as documented Event-Handler SmartApps.

The problem in your original post; you agree that the methods currentValue() and latestValue() work, but “device.doorState” does not work…

Several SmartApps that, indeed, use the shortcut form device.attributeState, without explicitly calling either of those methods.

The documentation page only gives this example, which, does call latestState():

def foo(evt) {
    def latestState = device1.latestState("attributeName")
    def latestStateDate = latestState.dateCreated
}

The (untested) Answer:
The shortcut syntax “device.attributeState” and similarly, “device.currentAttribute” is set up with the use of the subscribe() method.

Example (from the Ridiculously Automated Garage Door):

subscribe(doorSensor, "contact", garageDoorContact)

Magically provides such variables as:

if (doorSensor.currentContact == "closed") {
...
def currentState = doorSensor.contactState
if (currentState?.value == "open") {
...

In summary:

I can’t find any Documentation or Reference that describes the behavior we’re discussing.
But I also can’t find or explain any magic in the code of Ridiculously Automated Garage Door that makes it work, except for my guess … some hidden magic of the subscribe() method.

What do you think? Time to test my theory, I guess, or wait for SmartThings’s updated documentation.
Or am I missing some documentation or reference that you can point me to?


I believe this is the most relevant “specification level” reference documentation; note that there is no mention of “device.currentAttribute” or “device.attributeState” … but the Event specification must be considered in that context, perhaps.
:arrow_right: https://graph.api.smartthings.com/ide/doc/deviceType


…CP / Terry.


(Gary D) #4

Same page? You must use a REALLY tiny font to get all this on a page. :wink:

There’s also the “z-wave garage door opener” This is the one I was (am) working with when encountering the problem mentioned in the original post. In fact, it might be a better example, as it uses the “door” attribute more properly: standardTile("toggle", "device.door", width: 2, height: 2) {
In fact, most of what I’m reading in your first reply is sorted out with the z-wave garage door opener device type. Perhaps use that device as an example instead?

It’s obvious that the whole garage door opener via sensor+relay is a hack that was taken wayyyy too far and actually became ST’s “standard” for garage door control… to the point that using a REAL z-wave garage door control requires hacking to get it to work with ST’s standard modules.

BTW, here’s my “fixes” to the above mentioned z-wave garage door to make it work more like the hack that’s the ST standard: https://github.com/garyd9/smartthings/blob/master/my_z-wave_garage_door_opener.groovy

I had to add a “momentary” capability (for push()) and a “switch” capability (in which on() and off() are never called.) (I also added a polling cap, but that wasn’t required.) Anyway, in the definition of “push()”, you can see my need for finding the current state of the door. (I don’t know how to toggle it if I don’t know the current state.)

Also, I suppose I should re-write my original question. I wasn’t really wondering why doorState doesn’t work when latestValue(“door”) does. That was just me trying to be polite. The more blunt version is: Either the docs are wrong or the parser is broken. Docs say that “doorState” should work, but it doesn’t. :grinning:

[quote=“tgauchat, post:3, topic:9388”]
I can’t find any Documentation or Reference that describes the behavior we’re discussing.
[/quote]Not sure what your referring to… That describes “attributeState”? It’s documented here: https://graph.api.smartthings.com/ide/doc/device

<attribute name>State Returns the latest device state record for the specified attribute. For example sensor.temperatureState would return the temperature state record of a multi-sensor, from which you can access its value and various metadata such as the acquisition time and units of measure.
The same class definition documents the latestValue() method that works fine…

Take care
Gary


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

Aha…!
Perhaps it has to do with the difference between the Device Class vs. Smart Device?

There is a comment in the specs for Smart Device that simply says “this object is of a difference [sic] class than the Device object…”.

Most relevantly, “<attribute name>State” exists in class Device, but not in class Smart Device, while “latestState()” exists in both of these classes.

Darned if I know which class applies in which circumstances … I would think in the code for a Device Handler you get all the attributes and methods of Device, but perhaps you only get Smart Device?

I personally think this is a bit of a eureka moment, but still a big :question: !!!

…CP / Terry.


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

@garyd9 … I just wrote:

but, geesh, it’s right there in black & white (or black on grey, whatever)…

  • In the code of a Device Type Handler; you are within an instance of SmartDevice Type Class (therefore, no “<attribute name>State” variable for you)!

  • In the code of a SmartApp, in which we typically request a Device or list of Devices from the user in preferences{}; those are instances of Device Class (thus, here we have the “<attribute name>State” and similar variables available).

Two different classes that are similar, but with significant differences.

The Methods and Attributes Available to SmartDevice Type Handlers says “at some point these [Device and SmartDevice Type] will be merged” … I wonder if @Ben or @Jim has any idea when or if this will ever occur … it’s been a few years already :wink:.

…CP/Terry.


(Gary D) #7

ugh… what a subtle difference. Well, I guess that answers this question. Thank you

Gary


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

You’re very welcome – especially if you forgive the very verbose-roundabout way we got here!

Cheers,
…Terry.