Device Handler

Device Handlers are the virtual representation of a physical device.

A Device Handler defines a metadata() method that defines the device’s definition, UX information, as well as how it should behave in the IDE simulator.

A Device Handler typically also defines a parse() method that is responsible for transforming raw messages from the device into Events for the SmartThings platform.

Device Handlers must also define methods for any supported commands, either through its supported capabilities, or device-specific commands.

For more information about the structure of Device Handlers, refer to the Device Handler’s Guide.

Tip

Writing a Device Handler is considered a somewhat advanced topic. Understanding of how a Device Handler is organized and operates is assumed in this reference documentation. You should be familiar with the contents of the Device Handler’s Guide to get the most out of this documentation.


Methods expected to be defined by Device Handlers:

<command name>()

Note

This method is expected to be defined by Device Handlers.

The definition for a Command supported by this Device Handler. Every Command that a Device Handler supports, either through its capabilities or custom commands, must have a corresponding command method defined.

Commands are the things that a device can do. For example, the “Switch” capability defines the commands “on” and “off”. Every Device that supports the “Switch” capability must define an implementation of these commands. This is done by defining methods with the name of the command. For example, def on() {} and def off().

The exact implementation of a command method will vary greatly depending upon the device. The command method is responsible for sending protocol and device-specific commands to the physical device.

Signature:
Object <command name([arguments])>
Returns:
Object - Commands may return any object, but typically do not return anything since they perform some type of action.

Example:

metadata {
    // Automatically generated. Make future change here.
    definition (name: "CentraLite Switch", namespace: "smartthings",    author: "SmartThings") {
        ...
        capability "Switch"
        ...
}
...
// capability "Switch" declared, so all supported commands
// of "Switch" must be implemented:
def on() {
    // device-specific commands to turn the switch on
}

def off() {
    // device-specific commands to turn the switch off
}
...

parse()

Note

This method is expected to be defined by Device Handlers.

Called when messages from a device are received from the Hub. The parse method is responsible for interpreting those messages and returning Event definitions. Event definitions are maps that contain, at a minimum, name and value entries. They may also contain unit, displayText, displayed, isStateChange, and linkText entries if the default, automatically generated values of these Event properties are to be overridden. See the createEvent() documentation for a description of these properties.

Because the parse() method is responsible for handling raw device messages, their implementations vary greatly across different Device Handlers.

The parse() method may return a map defining the Event to create and propagate through the SmartThings platform, or a list of Events if multiple Events should be created. It may also return a HubAction or list of HubAction objects in the case of LAN-connected devices.

Signature:

Map parse(String description)

List<Map> parse(String description)

HubAction parse(String description)

List<HubAction> parse(String description)

Example:

def parse(String description) {
    log.debug "Parse description $description"
    def name = null
    def value = null
    if (description?.startsWith("read attr -")) {
        def descMap = parseDescriptionAsMap(description)
        log.debug "Read attr: $description"
        if (descMap.cluster == "0006" && descMap.attrId == "0000") {
            name = "switch"
            value = descMap.value.endsWith("01") ? "on" : "off"
        } else {
            def reportValue = description.split(",").find {it.split(":")[0].trim() == "value"}?.split(":")[1].trim()
            name = "power"
            // assume 16 bit signed for encoding and power divisor is 10
            value = Integer.parseInt(reportValue, 16).intdiv(10)
        }
    } else if (description?.startsWith("on/off:")) {
        log.debug "Switch command"
        name = "switch"
        value = description?.endsWith(" 1") ? "on" : "off"
    }

    // createEvent returns a Map that defines an Event
    def result = createEvent(name: name, value: value)
    log.debug "Parse returned ${result?.descriptionText}"

    // returning the Event definition map creates an Event
    // in the SmartThings platform, and propagates it to
    // SmartApps subscribed to the device events.
    return result
}

addChildDevice()

Adds a child device to a Device Handler. An example use is in a composite device Device Handler.

A parent may have multiple children, but only one level of children is allowed (i.e., if a device has a parent, it may not have children itself).

Warning

A parent may have at most 500 children.

Signature:

DeviceWrapper addChildDevice(String typeName, String deviceNetworkId, hubId, Map properties)

DeviceWrapper addChildDevice(String namespace, String typeName, String deviceNetworkId, hubId, Map properties)

Parameters:

String namespace - the namespace for the device. If not specified, defaults to the namespace of the current Device Handler executing the call.

String typeName - the device type name

String deviceNetworkId - the device network id of the device

hubId - (optional) The hub id. Defaults to null

Map properties (optional) - A map with device properties. Available options are:

Option Description
isComponent Allowed values are true and false. When true hides the device from the Things view and doesn’t let it be separately deleted. (Example: This value is true for the ZooZ ZEN 20 and false for Hue bridge.)
componentName A way to refer to this particular child. It should be a Java Bean name (i.e. no spaces). It is used to refer to the device in the parent’s detail view. This option is only needed when isComponent is true.
componentLabel The plain-english name (or i18n key) to be used by the UX.
completedSetup Specify true to complete the setup for the child device; false to have the user complete the installation. It should be true if isComponent is true. Defaults to false.
label The label for the device.
Returns:
Device - The device that was created.
Throws:

UnknownDeviceTypeException - If a Device Handler with the specified name and namespace is not found.

IllegalArgumentException - If the deviceNetworkId is not specified.

ValidationException - If the this device already has a parent.

SizeLimitExceededException - If this device already has the maximum number of children allowed (500).

Example:

// on installation, create child devices
def installed() {
    createChildDevices()
}

def createChildDevices() {

    // This device (power strip) has five outlets
    for (i in 1..5) {
        // can omit namespace (first arg) if it is the same as this device
        addChildDevice("smartthings", "Zooz Power Strip Outlet", "${device.deviceNetworkId}-ep${i}", null,
                            [completedSetup: true, label: "${device.displayName} (CH${i})",
                             isComponent: true, componentName: "ch$i", componentLabel: "Channel $i"])
    }
}

apiServerUrl()

Returns the URL of the server where this Device Handler can be reached for API calls, along with the specified path appended to it. Use this instead of hard-coding a URL to ensure that the correct server URL for this installed instance is returned.

Signature:
String apiServerUrl(String path)
Parameters:
String path - the path to append to the URL
Returns:
The URL of the server for this installed instance of the Device Handler.

Example:

// logs <server url>/my/path
log.debug "apiServerUrl: ${apiServerUrl("/my/path")}"

// The leading "/" will be added if you don't specify it
// logs <server url>/my/path
log.debug "apiServerUrl: ${apiServerUrl("my/path")}"

attribute()

Called within the definition() method to declare that this Device Handler supports an attribute not defined by any of its declared capabilities.

For any supported attribute, it is expected that the Device Handler creates and sends Events with the name of the attribute in the parse() method.

Signature:
void attribute(String attributeName, String attributeType [, List possibleValues])
Parameter:

String attributeName - the name of the attribute

String attributeType - the type of the attribute. Available types are “string”, “number”, and “enum”

List possibleValues (optional) - the possible values for this attribute. Only valid with the "enum" attributeType.

Returns:
void

Example:

metadata {
    definition (name: "Some Device Name", namespace: "somenamespace",
                author: "Some Author") {
        capability "Switch"
        capability "Polling"
        capability "Refresh"

        // also support the attribute "myCustomAttriute" - not defined by supported capabilities.
        attribute "myCustomAttribute", "number"

        // enum attribute with possible values "light" and "dark"
        attribute "someOtherName", "enum", ["light", "dark"]
     }
     ...
}

capability()

Called in the definition() method to define that this device supports the specified capability.

Important

Whatever commands and attributes defined by that capability should be implemented by the Device Handler. For example, the “Switch” capability specifies support for the “switch” attribute and the “on” and “off” commands - any Device Handler supporting the “Switch” capability must define methods for the commands, and support the “switch” attribute by creating the appropriate Events (with the name of the attribute, e.g., “switch”)

Signature:
void capability(String capabilityName)
Parameters:
String capabilityName - the name of the capability. This is the long-form name of the Capability name, not the “preferences reference”.
Returns:
void

Example:

metadata {
    definition (name: "Cerbco Light Switch", namespace: "lennyv62",
                author: "Len Veil") {
        capability "Switch"
        ...
    }
    ...
}

def parse(description) {
    // handle device messages, determine what value of the Event is
    return createEvent(name: "switch", value: someValue)
}

// need to define the on and off commands, since those
// are supported by "Switch" capability
def on() {
    ...
}

def off() {

}

carouselTile()

Called within the tiles() method to define a tile often used in conjunction with the Image Capture capability, to allow users to scroll through recent pictures.

Signature:
void carouselTile(String tileName, String attributeName [,Map options, Closure closure])
Parameters:

String tileName - the name of the tile. This is used to identify the tile when specifying the tile layout.

String attributeName - the attribute this tile is associated with. Each tile is associated with an attribute of the device. The typical pattern is to prefix the attribute name with "device." - e.g., "device.water".

Map options (optional) - Various options for this tile. Valid options are found in the table below:

option type description
width Integer controls how wide the tile is. Default is 1.
height Integer controls how tall this tile is. Default is 1.
canChangeIcon Boolean true to allow the user to pick their own icon. Defaults to false.
canChangeBackground Boolean true to allow a user to choose their own background image for the tile. Defaults to false.
decoration String specify "flat" for the tile to render without a ring.
range String used to specify a custom range. In the form of "(<lower bound>..<upper bound>)"

Closure closure (optional) - a closure that defines any states for the tile.

Returns:
void

Example:

tiles {
    carouselTile("cameraDetails", "device.image", width: 3, height: 2) { }
}

command()

Called within the definition() method to declare that this Device Handler supports a command not defined by any of its declared capabilities.

For any supported command, it is expected that the Device Handler define a <command name>() method with a corresponding name.

Signature:
void command(String commandName [, List parameterTypes])
Parameter:

String commandName - the name of the command.

List parameterTypes (optional) - a list of strings that defines the types of the parameters the command requires (in order), if any. Typical values are “string”, “number”, and “enum”.

Returns:
void

Example:

metadata {
    definition (name: "Some Device Name", namespace: "somenamespace",
                author: "Some Author") {
        capability "Switch"
        capability "Polling"
        capability "Refresh"

        // also support the attribute "myCustomCommand" - not defined by supported capabilities.
        command "myCustomCommand"

        // commands can take parameters
        command "myCustomCommandWithParams", ["string", "number"]

     }
     ...
}

def myCustomCommand() {
    ...
}

def myCustomCommandWithParams(def stringArg, def numArg) {
    ...
}

controlTile()

Called within the tiles() method to define a tile that allows the user to input a value within a range. A common use case for a control tile is a light dimmer.

Signature:
void controlTile(String tileName, String attributeName, String controlType [, Map options, Closure closure])
Returns:
void
Parameters:

String tileName - the name of the tile. This is used to identify the tile when specifying the tile layout.

String attributeName - the attribute this tile is associated with. Each tile is associated with an attribute of the device. The typical pattern is to prefix the attribute name with "device." - e.g., "device.water".

String controlType - the type of control. Either "slider" or "control".

Map options (optional) - Various options for this tile. Valid options are found in the table below:

option type description
width Integer controls how wide the tile is. Default is 1.
height Integer controls how tall this tile is. Default is 1.
canChangeIcon Boolean true to allow the user to pick their own icon. Defaults to false.
canChangeBackground Boolean true to allow a user to choose their own background image for the tile. Defaults to false.
decoration String specify "flat" for the tile to render without a ring.
range String used to specify a custom range. In the form of "(<lower bound>..<upper bound>)"

Closure closure (optional) - A closure that calls any state() methods to define how the tile should appear for various attribute values.

Example:

tiles {
    controlTile("levelSliderControl", "device.level", "slider", height: 1,
                 width: 2, inactiveLabel: false, range:"(0..100)") {
        state "level", action:"switch level.setLevel"
    }
}

createEvent()

Creates a Map that represents an Event object. Typically used in the parse() method to define Events for particular attributes. The resulting map is then returned from the parse() method. The SmartThings platform will then create an Event object and propagate it through the system.

Signature:
Map createEvent(Map options)
Parameters:

Map options - The various properties that define this Event. The available options are listed below. It is not necessary, or typical, to define all the available options. Typically only the name and value options are required.

Property Type Description
name (required) String the name of the Event. Typically corresponds to an attribute name of a capability.
value (required) Object the value of the Event. The value is stored as a string, but you can pass numbers or other objects.
descriptionText String the description of this Event. This appears in the mobile application activity for the device. If not specified, this will be created using the Event name and value.
displayed Boolean specify true to display this Event in the mobile application activity feed, false to not display. Defaults to true.
linkText String name of the Event to show in the mobile application activity feed.
isStateChange Boolean specify true if this Event caused a device attribute to change state. Typically not used, since it will be set automatically.
unit String a unit string, if desired. This will be used to create the descriptionText if it (the descriptionText option) is not specified.
data Map A map of additional information to store with the Event

Example:

def parse(String description) {
    ...

    def evt1 = createEvent(name: "someName", value: "someValue")
    def evt2 = createEvent(name: "someOtherName", value: "someOtherValue")

    return [evt1, evt2]
}

definition()

Called within the metadata() method, and defines some basic information about the device, as well as the supported capabilities, commands, and attributes.

Signature:
void definition(Map definitionData, Closure closure)
Parameters:

Map definitionData - defines various metadata about this Device Handler. Valid options are:

option type description
name String the name of this Device Handler
namespace String the namespace for this Device Handler. Typically the same as the author’s github user name.
author String the name of the author.

Closure closure - A closure with method calls to capability() , command() , or attribute() .

Returns:
void

Example:

metadata {
    definition (name: "My Device Name", namespace: "mynamespace",
                author: "My Name") {
        capability "Switch"
        capability "Polling"
        capability "Refresh"

        command "someCustomCommand"

        attribute "someCustomAttribute", "number"
    }
    ...
}

details()

Used within the tiles() method to define the order that the tiles should appear in.

Signature:
void details(List<String> tileDefinitions)
Parameters:
List < String > tileDefinitions - A list of tile names that defines the order of the tiles (left-to-right, top-to-bottom)
Returns:
void

Example:

tiles {
    standardTile("switchTile", "device.switch", width: 2, height: 2,
                 canChangeIcon: true) {
        state "off", label: '${name}', action: "switch.on",
              icon: "st.switches.switch.off", backgroundColor: "#ffffff"
        state "on", label: '${name}', action: "switch.off",
              icon: "st.switches.switch.on", backgroundColor: "#E60000"
    }
    valueTile("powerTile", "device.power", decoration: "flat") {
        state "power", label:'${currentValue} W'
    }
    standardTile("refreshTile", "device.power", decoration: "ring") {
        state "default", label:'', action:"refresh.refresh",
              icon:"st.secondary.refresh",
    }

    main "switchTile"

    // defines what order the tiles are defined in
    details(["switchTile","powerTile","refreshTile"])
}

device

The Device object, from which its current properties and history can be accessed. As of now this object is a different type than the Device object available in SmartApps. At some point these will be merged, but for now the properties and methods of the device object available to the Device Handler are discussed in the example below:

...
// Gets the most recent State for the given attribute
def state1 = device.currentState("someAttribute")
def state2 = device.latestState("someOtherAttribute")

// Gets the current value for the given attribute
// Return type will vary depending on the device
def curVal1 = device.currentValue("someAttribute")
def curVal2 = device.latestValue("someOtherAttribute")

// gets the display name of the device
def displayName = device.displayName

// gets the internal unique system identifier for this device
def thisId = device.id

// gets the internal name for this device
def thisName = device.name

// gets the user-defined label for this device
def thisLabel = device.label

fingerprint()

Called within the definition() method to define the information necessary to pair this device to the Hub.

See the Fingerprinting Section of the Device Handler guide for more information.


getApiServerUrl()

Returns the URL of the server where this Device Handler can be reached for API calls. Use this instead of hard-coding a URL to ensure that the correct server URL for this installed instance is returned.

Signature:
String getApiServerUrl()
Returns:
String - the URL of the server where this Device Handler can be reached.

getChildDevices()

Gets a list of all child devices for this device.

Signature:
List<ChildDeviceWrapper> getChildDevices()
Returns:
List <Device> - a list of child devices for this device

Example:

def children = getChildDevices()

log.debug "device has ${children.size()} children"
children.each { child ->
    log.debug "child ${child.displayName} has deviceNetworkId ${child.deviceNetworkId}"
}

getColorUtil()

Returns the ColorUtilities object.

Signature:
ColorUtilities getColorUtil()
Returns:
ColorUtilities

getImage()

Returns a ByteArrayInputStream for the image stored using storeImage() or storeTemporaryImage() with the specified name.

An exception is thrown if the requested image does not exist for this device.

Signature:
ByteArrayInputStream getImage(String name)
Parameters:
String name - The name of the image to retrieve.
Returns:
ByteArrayInputStream - The input stream of bytes for this image.

Example:

ByteArrayInputStream imgStream = getImage("some-existing-image-name")

httpDelete()

Executes an HTTP DELETE request and passes control to the specified closure. The closure is passed one HttpResponseDecorator argument from which the response content and header information can be extracted.

Signature:

void httpDelete(String uri, Closure closure)

void httpDelete(Map params, Closure closure)

Parameters:

String uri - The URI to make the HTTP DELETE call to.

Map params - A map of parameters for configuring the request. The valid parameters are:

Parameter Description
uri Either a URI or URL of of the endpoint to make a request from.
path Request path that is merged with the URI.
query Map of URL query parameters.
headers Map of HTTP headers.
contentType Request content type and Accept header.
requestContentType Content type for the request, if it is different from the expected response content-type.
body Request body that will be encoded based on the given contentType.

Closure closure - The closure that will be called with the response of the request.

Returns:
void

httpGet()

Executes an HTTP GET request and passes control to the specified closure. The closure is passed one HttpResponseDecorator argument from which the response content and header information can be extracted.

If the response content type is JSON, the response data will automatically be parsed into a data structure.

Signature:

void httpGet(String uri, Closure closure)

void httpGet(Map params, Closure closure)

Parameters:

String uri - The URI to make the HTTP GET call to

Map params - A map of parameters for configuring the request. The valid parameters are:

Parameter Description
uri Either a URI or URL of of the endpoint to make a request from.
path Request path that is merged with the URI.
query Map of URL query parameters.
headers Map of HTTP headers.
contentType Request content type and Accept header.
requestContentType Content type for the request, if it is different from the expected response content-type.
body Request body that will be encoded based on the given contentType.

Closure - closure - The closure that will be called with the response of the request.

Example:

def params = [
    uri: "http://httpbin.org",
    path: "/get"
]

try {
    httpGet(params) { resp ->
        resp.headers.each {
        log.debug "${it.name} : ${it.value}"
    }
    log.debug "response contentType: ${resp.contentType}"
    log.debug "response data: ${resp.data}"
} catch (e) {
    log.error "something went wrong: $e"
}

httpHead()

Executes an HTTP HEAD request and passes control to the specified closure. The closure is passed one HttpResponseDecorator argument from which the response content and header information can be extracted.

Signature:

void httpHead(String uri, Closure closure)

void httpHead(Map params, Closure closure)

Parameters:

String uri - The URI to make the HTTP HEAD call to

Map params - A map of parameters for configuring the request. The valid parameters are:

Parameter Description
uri Either a URI or URL of of the endpoint to make a request from.
path Request path that is merged with the URI.
query Map of URL query parameters.
headers Map of HTTP headers.
contentType Request content type and Accept header.
requestContentType Content type for the request, if it is different from the expected response content-type.
body Request body that will be encoded based on the given contentType.

Closure closure - The closure that will be called with the response of the request.


httpPost()

Executes an HTTP POST request and passes control to the specified closure. The closure is passed one HttpResponseDecorator argument from which the response content and header information can be extracted.

If the response content type is JSON, the response data will automatically be parsed into a data structure.

Signature:

void httpPost(String uri, String body, Closure closure)

void httpPost(Map params, Closure closure)

Parameters:

String uri - The URI to make the HTTP GET call to

String body - The body of the request

Map params - A map of parameters for configuring the request. The valid parameters are:

Parameter Description
uri Either a URI or URL of of the endpoint to make a request from.
path Request path that is merged with the URI.
query Map of URL query parameters.
headers Map of HTTP headers.
contentType Request content type and Accept header.
requestContentType Content type for the request, if it is different from the expected response content-type.
body Request body that will be encoded based on the given contentType.

Closure closure - The closure that will be called with the response of the request.

Example:

try {
    httpPost("http://mysite.com/api/call", "id=XXX&value=YYY") { resp ->
        log.debug "response data: ${resp.data}"
        log.debug "response contentType: ${resp.contentType}"
    }
} catch (e) {
    log.debug "something went wrong: $e"
}

httpPostJson()

Executes an HTTP POST request with a JSON-encoded body and content type, and passes control to the specified closure. The closure is passed one HttpResponseDecorator argument from which the response content and header information can be extracted.

If the response content type is JSON, the response data will automatically be parsed into a data structure.

Signature:

void httpPostJson(String uri, String body, Closure closure)

void httpPostJson(String uri, Map body, Closure closure)

void httpPostJson(Map params, Closure closure)

Parameters:

String uri - The URI to make the HTTP POST call to

String body - The body of the request

Map params - A map of parameters for configuring the request. The valid parameters are:

Parameter Description
uri Either a URI or URL of of the endpoint to make a request from.
path Request path that is merged with the URI.
query Map of URL query parameters.
headers Map of HTTP headers.
contentType Request content type and Accept header.
requestContentType Content type for the request, if it is different from the expected response content-type.
body Request body that will be encoded based on the given contentType.

Closure closure - The closure that will be called with the response of the request.

Example:

def params = [
    uri: "http://postcatcher.in/catchers/<yourUniquePath>",
    body: [
        param1: [subparam1: "subparam 1 value",
                 subparam2: "subparam2 value"],
        param2: "param2 value"
    ]
]

try {
    httpPostJson(params) { resp ->
        resp.headers.each {
            log.debug "${it.name} : ${it.value}"
        }
        log.debug "response contentType: ${resp.    contentType}"
    }
} catch (e) {
    log.debug "something went wrong: $e"
}

httpPut()

Executes an HTTP PUT request and passes control to the specified closure. The closure is passed one HttpResponseDecorator argument from which the response content and header information can be extracted.

If the response content type is JSON, the response data will automatically be parsed into a data structure.

Signature:

void httpPut(String uri, String body, Closure closure)

void httpPut(Map params, Closure closure)

Parameters:

String uri - The URI to make the HTTP GET call to

String body - The body of the request

Map params - A map of parameters for configuring the request. The valid parameters are:

Parameter Description
uri Either a URI or URL of of the endpoint to make a request from.
path Request path that is merged with the URI.
query Map of URL query parameters.
headers Map of HTTP headers.
contentType Request content type and Accept header.
requestContentType Content type for the request, if it is different from the expected response content-type.
body Request body that will be encoded based on the given contentType.

Closure closure - The closure that will be called with the response of the request.

Example:

try {
    httpPut("http://mysite.com/api/call", "id=XXX&value=YYY") { resp ->
        log.debug "response data: ${resp.data}"
        log.debug "response contentType: ${resp.contentType}"
    }
} catch (e) {
    log.error "something went wrong: $e"
}

httpPutJson()

Executes an HTTP PUT request with a JSON-encoded boday and content type, and passes control to the specified closure. The closure is passed one HttpResponseDecorator argument from which the response content and header information can be extracted.

If the response content type is JSON, the response data will automatically be parsed into a data structure.

Signature:

void httpPutJson(String uri, String body, Closure closure)

void httpPutJson(String uri, Map body, Closure closure)

void httpPutJson(Map params, Closure closure)

Parameters:

String uri - The URI to make the HTTP PUT call to

String body - The body of the request

Map params - A map of parameters for configuring the request. The valid parameters are:

Parameter Description
uri Either a URI or URL of of the endpoint to make a request from.
path Request path that is merged with the URI.
query Map of URL query parameters.
headers Map of HTTP headers.
contentType Request content type and Accept header.
requestContentType Content type for the request, if it is different from the expected response content-type.
body Request body that will be encoded based on the given contentType.

Closure closure - The closure that will be called with the response of the request.


main()

Used to define what tile appears on the main “Things” view in the mobile application. Can be called within the tiles() method.

Signature:
void main(String tileName)
Parameters:
String tileName - the name of the tile to display as the main tile.
Returns:
void

Example:

tiles {
    standardTile("switchTile", "device.switch", width: 2, height: 2,
                 canChangeIcon: true) {
        state "off", label: '${name}', action: "switch.on",
              icon: "st.switches.switch.off", backgroundColor: "#ffffff"
        state "on", label: '${name}', action: "switch.off",
              icon: "st.switches.switch.on", backgroundColor: "#E60000"
    }
    valueTile("powerTile", "device.power", decoration: "flat") {
        state "power", label:'${currentValue} W'
    }
    standardTile("refreshTile", "device.power", decoration: "ring") {
        state "default", label:'', action:"refresh.refresh",
              icon:"st.secondary.refresh",
    }

    // The "switchTile" will be main tile, displayed in the "Things" view
    main "switchTile"
    details(["switchTile","powerTile","refreshTile"])
}

metadata()

Used to define metadata such as this Device Handler’s supported capabilities, attributes, commands, and UX information.

Signature:
void metadata(Closure closure)
Parameters:
Closure closure - a closure that defines the metadata. The closure is expected to have the following methods called in it: definition() , simulator() , and tiles() .
Returns:
void

Example:

metadata {
    definition(name: "device name", namespace: "yournamespace", author: "your name") {

        // supported capabilities, commands, attributes,
    }
    simulator {
        // simulator metadata
    }
    tiles {
        // tiles metadata
    }
}

reply()

Called in the simulator() method to model the behavior of a physical device when a virtual instance of the Device Handler is run in the IDE.

The simulator matches command strings generated by the device to those specified in the commandString argument of a reply method and, if a match is found, calls the Device Handler’s parse method with the corresponding messageDescription.

For example, the reply method reply "2001FF,2502": "command: 2503, payload: FF" models the behavior of a physical Z-Wave switch in responding to an Basic Set command followed by a Switch Binary Get command. The result will be a call to the parse method with a Switch Binary Report command with a value of 255, i.e., the turning on of the switch. Modeling turn off would be done with the reply method reply "200100,2502": "command: 2503, payload: 00".

Signature:
void reply(String commandString, String messageDescription)
Parameters:

String commandString - a String that represents the command.

String messageDescription - a String that represents the message description.

Returns:
void

Example:

 metadata {
    ...

    // simulator metadata
    simulator {
        // 'on' and 'off' will appear in the messages dropdown, and send
        // "on/off: 1 to the parse method"
        status "on": "on/off: 1"
        status "off": "on/off: 0"

        // simulate reply messages from the device
        reply "zcl on-off on": "on/off: 1"
        reply "zcl on-off off": "on/off: 0"
    }
    ...
}

runEvery1Minute()

Creates a recurring schedule that executes the specified handlerMethod every minute. Using this method will pick a random start time in the next minute, and run every minute after that.

Signature:
void runEvery1Minute(handlerMethod[, options])

Tip

This is preferred over using schedule(cronExpression, handlerMethod) for a regular schedule like this because with a cron expression all installations of a SmartApp will execute at the same time. With this method, the executions will be spread out over the 1 minute period.

Parameters:

handlerMethod - The method to call every minute. Can be the name of the method as a string, or a reference to the method.

options (optional) - A map of parameters, with the following keys supported:

Key Possible values Description
data A map of data A map of data that will be passed to the handler method.
Returns:
void

Example:

runEvery1Minute(handlerMethod1)
runEvery1Minute(handlerMethod2, [data: [key1: 'val1']])

def handlerMethod1() {
    log.debug "handlerMethod1"
}

def handlerMethod2(data) {
    log.debug "handlerMethod2, data: $data"
}

runEvery5Minutes()

Creates a recurring schedule that executes the specified handlerMethod every five minutes. Using this method will pick a random start time in the next five minutes, and run every five minutes after that.

Signature:
void runEvery5Minutes(handlerMethod[, options])

Tip

This is preferred over using schedule(cronExpression, handlerMethod) for a regular schedule like this because with a cron expression all installations of a SmartApp will execute at the same time. With this method, the executions will be spread out over the 5 minute period.

Parameters:

handlerMethod - The method to call every five minutes. Can be the name of the method as a string, or a reference to the method.

options (optional) - A map of parameters, with the following keys supported:

Key Possible values Description
data A map of data A map of data that will be passed to the handler method.
Returns:
void

Example:

runEvery5Minutes(handlerMethod1)
runEvery5Minutes(handlerMethod2, [data: [key1: 'val1']])

def handlerMethod1() {
    log.debug "handlerMethod1"
}

def handlerMethod2(data) {
    log.debug "handlerMethod2, data: $data"
}

runEvery10Minutes()

Creates a recurring schedule that executes the specified handlerMethod every ten minutes. Using this method will pick a random start time in the next ten minutes, and run every ten minutes after that.

Signature:
void runEvery10Minutes(handlerMethod[, options])

Tip

This is preferred over using schedule(cronExpression, handlerMethod) for a regular schedule like this because with a cron expression all installations of a SmartApp will execute at the same time. With this method, the executions will be spread out over the ten minute period.

Parameters:

handlerMethod - The method to call every ten minutes. Can be the name of the method as a string, or a reference to the method.

options (optional) - A map of parameters, with the following keys supported:

Key Possible values Description
data A map of data A map of data that will be passed to the handler method.
Returns:
void

Example:

runEvery10Minutes(handlerMethod1)
runEvery10Minutes(handlerMethod2, [data: [key1: 'val1']])

def handlerMethod1() {
    log.debug "handlerMethod1"
}

def handlerMethod2(data) {
    log.debug "handlerMethod2, data: $data"
}

runEvery15Minutes()

Creates a recurring schedule that executes the specified handlerMethod every fifteen minutes. Using this method will pick a random start time in the next five minutes, and run every five minutes after that.

Signature:
void runEvery15Minutes(handlerMethod[, options])

Tip

This is preferred over using schedule(cronExpression, handlerMethod) for a regular schedule like this because with a cron expression all installations of a SmartApp will execute at the same time. With this method, the executions will be spread out over the fifteen minute period.

Parameters:

handlerMethod - The method to call every fifteen minutes. Can be the name of the method as a string, or a reference to the method.

options (optional) - A map of parameters, with the following keys supported:

Key Possible values Description
data A map of data A map of data that will be passed to the handler method.
Returns:
void

Example:

runEvery15Minutes(handlerMethod1)
runEvery15Minutes(handlerMethod2, [data: [key1: 'val1']])

def handlerMethod1() {
    log.debug "handlerMethod1"
}

def handlerMethod2(data) {
    log.debug "handlerMethod2, data: $data"
}

runEvery30Minutes()

Creates a recurring schedule that executes the specified handlerMethod every thirty minutes. Using this method will pick a random start time in the next thirty minutes, and run every thirty minutes after that.

Signature:
void runEvery30Minutes(handlerMethod[, options])

Tip

This is preferred over using schedule(cronExpression, handlerMethod) for a regular schedule like this because with a cron expression all installations of a SmartApp will execute at the same time. With this method, the executions will be spread out over the thirty minute period.

Parameters:

handlerMethod - The method to call every thirty minutes. Can be the name of the method as a string, or a reference to the method.

options (optional) - A map of parameters, with the following keys supported:

Key Possible values Description
data A map of data A map of data that will be passed to the handler method.
Returns:
void

Example:

runEvery30Minutes(handlerMethod1)
runEvery30Minutes(handlerMethod2, [data: [key1: 'val1']])

def handlerMethod1() {
    log.debug "handlerMethod1"
}

def handlerMethod2(data) {
    log.debug "handlerMethod2, data: $data"
}

runEvery1Hour()

Creates a recurring schedule that executes the specified handlerMethod every hour. Using this method will pick a random start time in the next hour, and run every hour after that.

Signature:
void runEvery1Hour(handlerMethod[, options])

Tip

This is preferred over using schedule(cronExpression, handlerMethod) for a regular schedule like this because with a cron expression all installations of a SmartApp will execute at the same time. With this method, the executions will be spread out over the one hour period.

Parameters:

handlerMethod- The method to call every hour. Can be the name of the method as a string, or a reference to the method.

options (optional) - A map of parameters, with the following keys supported:

Key Possible values Description
data A map of data A map of data that will be passed to the handler method.
Returns:
void

Example:

runEvery1Hour(handlerMethod1)
runEvery1Hour(handlerMethod2, [data: [key1: 'val1']])

def handlerMethod1() {
    log.debug "handlerMethod1"
}

def handlerMethod2(data) {
    log.debug "handlerMethod2, data: $data"
}

runEvery3Hours()

Creates a recurring schedule that executes the specified handlerMethod every three hours. Using this method will pick a random start time in the next hour, and run every three hours after that.

Signature:
void runEvery3Hours(handlerMethod[, options])

Tip

This is preferred over using schedule(cronExpression, handlerMethod) for a regular schedule like this because with a cron expression all installations of a SmartApp will execute at the same time. With this method, the executions will be spread out over the three hour period.

Parameters:

handlerMethod - The method to call every three hours. Can be the name of the method as a string, or a reference to the method.

options (optional) - A map of parameters, with the following keys supported:

Key Possible values Description
data A map of data A map of data that will be passed to the handler method.
Returns:
void

Example:

runEvery3Hours(handlerMethod1)
runEvery3Hours(handlerMethod2, [data: [key1: 'val1']])

def handlerMethod1() {
    log.debug "handlerMethod1"
}

def handlerMethod2(data) {
    log.debug "handlerMethod2, data: $data"
}

runIn()

Executes a specified handlerMethod after delaySeconds have elapsed.

Signature:
void runIn(delayInSeconds, handlerMethod [, options])

Tip

It’s important to note that we will attempt to run this method at this time, but cannot guarantee exact precision. We typically expect per-minute level granularity, so if using with values less than sixty seconds, your mileage will vary.

Parameters:

delayInSeconds - The number of seconds to execute the handlerMethod after.

handlerMethod - The method to call after delayInSeconds has passed. Can be a string or a reference to the method.

options (optional) - A map of parameters, with the following keys supported:

Key Possible values Description
overwrite true or false Specify [overwrite: false] to not overwrite any existing pending schedule handler for the given method (the default behavior is to overwrite the pending schedule). Specifying [overwrite: false] can lead to multiple different schedules for the same handler method, so be sure your handler method can handle this.
data A map of data A map of data that will be passed to the handler method.
Returns:
void

Example:

runIn(300, myHandlerMethod)
runIn(400, "myOtherHandlerMethod", [data: [flag: true]])

def myHandlerMethod() {
    log.debug "handler method called"
}

def myOtherHandlerMethod(data) {
    log.debug "other handler method called with flag: $data.flag"
}

runOnce()

Executes the handlerMethod once at the specified date and time.

Signature:
void runOnce(dateTime, handlerMethod [, options])
Parameters:

dateTime - When to execute the handlerMethod. Can be either a Date object or an ISO-8601 date string. For example, new Date() + 1 would run at the current time tomorrow, and "2017-07-04T12:00:00.000Z" would run at noon GMT on July 4th, 2017.

handlerMethod - The method to execute at the specified dateTime. This can be a reference to the method, or the method name as a string.

options (optional) - A map of parameters, with the following keys supported:

Key Possible values Description
overwrite true or false Specify [overwrite: false] to not overwrite any existing pending schedule handler for the given method (the default behavior is to overwrite the pending schedule). Specifying [overwrite: false] can lead to multiple different schedules for the same handler method, so be sure your handler method can handle this.
data A map of data A map of data that will be passed to the handler method.
Returns:
void

Example:

// execute handler at 4 PM CST on October 21, 2015 (e.g., Back to the Future 2 Day!)
runOnce("2015-10-21T16:00:00.000-0600", handler)

def handler() {
    ...
}

schedule()

Creates a scheduled job that calls the handlerMethod once per day at the time specified, or according to a cron schedule.

Signature:

void schedule(dateTime, handlerMethod)

void schedule(cronExpression, handlerMethod)

Parameters:

dateTime - A Date object, an ISO-8601 formatted date time string.

String cronExpression - A cron expression that specifies the schedule to execute on.

handlerMethod - The method to call. This can be a reference to the method itself, or the method name as a string.

Returns:
void

Tip

Since calling schedule() with a dateTime argument creates a recurring scheduled job to execute every day at the specified time, the date information is ignored. Only the time portion of the argument is used.

Tip

Full documentation for the cron expression format can be found in the Quartz Cron Trigger Tutorial

Example:

preferences {
    section() {
        input "timeToRun", "time"
    }
}

...
// call handlerMethod1 at time specified by user input
schedule(timeToRun, handlerMethod1)

// call handlerMethod2 every day at 3:36 PM CST
schedule("2015-01-09T15:36:00.000-0600", handlerMethod2)

// execute handlerMethod3 every hour on the half hour (using a randomly chosen seconds field)
schedule("12 30 * * * ?", handlerMethod3)
...

def handlerMethod1() {...}
def handlerMethod2() {...}
def handlerMethod3() {...}

sendEvent()

Create and fire an Event . Typically a Device Handler will return the map returned from createEvent() , which will allow the platform to create and fire the Event. In cases where you need to fire the Event (outside of the parse() method), sendEvent() is used.

Signature:
void sendEvent(Map properties)
Parameters:

Map properties - The properties of the Event to create and send.

Here are the available properties:

Property Description
name (required) String - The name of the Event. Typically corresponds to an attribute name of a capability.
value (required) The value of the Event. The value is stored as a string, but you can pass numbers or other objects.
descriptionText String - The description of this Event. This appears in the mobile application activity for the device. If not specified, this will be created using the Event name and value.
displayed Pass true to display this Event in the mobile application activity feed, false to not display. Defaults to true.
linkText String - Name of the Event to show in the mobile application activity feed.
isStateChange true if this Event caused a device attribute to change state. Typically not used, since it will be set automatically.
unit String - a unit string, if desired. This will be used to create the descriptionText if it (the descriptionText option) is not specified.
data A map of additional information to store with the Event

Tip

Not all Event properties need to be specified. ID properties like deviceId and locationId are automatically set, as are properties like isStateChange, displayed, and linkText.

Returns:
void

Example:

sendEvent(name: "temperature", value: 72, unit: "F")

simulator()

Defines information used to simulate device interaction in the IDE. Can be called in the metadata() method.

Signature:
void simulator(Closure closure)
Parameters:
Closure closure - the closure that defines the status() and reply() messages.
Returns:
void

Example:

metadata {
    ...

    // simulator metadata
    simulator {
        // 'on' and 'off' will appear in the messages dropdown, and send
        // "on/off: 1 to the parse method"
        status "on": "on/off: 1"
        status "off": "on/off: 0"

        // simulate reply messages from the device
        reply "zcl on-off on": "on/off: 1"
        reply "zcl on-off off": "on/off: 0"
    }
    ...
}

standardTile()

Called within the tiles() method to define a tile to display current state information. For example, to show that a switch is on or off, or that there is or is not motion.

Signature:
void standardTile(String tileName, String attributeName [, Map options, Closure closure])
Returns:
void
Parameters:

String tileName - the name of the tile. This is used to identify the tile when specifying the tile layout.

String attributeName - the attribute this tile is associated with. Each tile is associated with an attribute of the device. The typical pattern is to prefix the attribute name with "device." - e.g., "device.water".

Map options (optional) - Various options for this tile. Valid options are found in the table below:

option type description
width Integer controls how wide the tile is. Default is 1.
height Integer controls how tall this tile is. Default is 1.
canChangeIcon Boolean true to allow the user to pick their own icon. Defaults to false.
canChangeBackground Boolean true to allow a user to choose their own background image for the tile. Defaults to false.
decoration String specify "flat" for the tile to render without a ring.

Closure closure (optional) - A closure that calls any state() methods to define how the tile should appear for various attribute values.

Example:

tile {
     standardTile("water", "device.water", width: 2, height: 2) {
        state "dry", icon:"st.alarm.water.dry", backgroundColor:"#ffffff"
        state "wet", icon:"st.alarm.water.wet", backgroundColor:"#53a7c0"
    }
}

state

A map of name/value pairs that a Device Handler can use to save and retrieve data across executions.

Signature:
Map state
Returns:
Map - a map of name/value pairs.
state.count = 0
state.count = state.count + 1

log.debug "state.count: ${state.count}"

// use array notation if you wish
log.debug "state['count']: ${state['count']}"

// you can store lists and maps to make more intersting structures
state.listOfMaps = [[key1: "val1", bool1: true],
                    [otherKey: ["string1", "string2"]]]

Warning

Though state can be treated as a map in most regards, certain convenience operations that you may be accustomed to in maps will not work with state. For example, state.count++ will not increment the count - use the longer form of state.count = state.count + 1.


state()

Called within any of the various tiles method’s closure to define options to be used when the current value of the tile’s attribute matches the value argument.

Signature:
void state(stateName, Map options)
Parameters:

String stateName - the name of the attribute value for which to display this state for.

Map options - a map that defines additional information for this state. The valid options are:

option type description
action String the action to take when this tile is pressed. The form is <capabilityReference>.<command>.
backgroundColor String a hexadecimal color code to use for the background color. This has no effect if the tile has decoration: “flat”.
backgroundColors String specify a list of maps of attribute values and colors. The mobile app will match and interpolate between these entries to select a color based on the value of the attribute.
defaultState Boolean specify true if this state should be the active state displayed for this tile.
icon String the identifier of the icon to use for this state. You can view the icon options here.
label String the label for this state.
Returns:
void

Example:

...
standardTile("water", "device.water", width: 2, height: 2) {
    // when the "water" attribute has the value "dry", show the
    // specified icon and background color
    state "dry", icon:"st.alarm.water.dry", backgroundColor:"#ffffff"

    // when the "water" attribute has the value "wet", show the
    // specified icon and background color
    state "wet", icon:"st.alarm.water.wet", backgroundColor:"#53a7c0"
}

valueTile("temperature", "device.temperature", width: 2, height: 2) {
    state("temperature", label:'${currentValue}°',
        backgroundColors:[
            [value: 31, color: "#153591"],
            [value: 44, color: "#1e9cbb"],
            [value: 59, color: "#90d2a7"],
            [value: 74, color: "#44b621"],
            [value: 84, color: "#f1d801"],
            [value: 95, color: "#d04e00"],
            [value: 96, color: "#bc2323"]
        ]
    )
}
...

status()

The status method is called in the simulator() method, and populates the select box that appears under virtual devices in the IDE. Can be called in the simulator() method.

Signature:
void status(String name, String messageDescription)
Parameters:

String name - any unique string and is used to refer to this status message in the select box.

String messageDescription - should be a parseable message for this Device Handler. It’s passed to the Device Handler’s parse method when select box entry is sent in the simulator. For example, status "on": "command: 2003, payload: FF" will send a Z-Wave Basic Report command to the Device Handler’s parse method when the on option is selected and sent.

Returns:
void

Example:

 metadata {
    ...

    // simulator metadata
    simulator {
        // 'on' and 'off' will appear in the messages dropdown, and send
        // "on/off: 1 to the parse method"
        status "on": "on/off: 1"
        status "off": "on/off: 0"

        // simulate reply messages from the device
        reply "zcl on-off on": "on/off: 1"
        reply "zcl on-off off": "on/off: 0"
    }
    ...
}

storeImage()

Stores an image represented by a ByteArrayInputStream and emits an Event with name “image”.

storeImage() is often in used in conjunction with the carouselTile() and cloud-connected camera devices to store and display images.

JPEG and PNG image formats are supported.

Note

Images stored using storeImage() are stored for 365 days, after which they will be permanently deleted.

The carouselTile() can display images for the past seven days.

Signature:
void storeImage(String name, ByteArrayInputStream is, String contentType = "image/jpeg") throws Exception
Parameters:

String name - The name associated with the image, consisting of alphanumeric, ‘_’, ‘-‘, and ‘.’ characters. This name must be unique per device instance, and should not include the file extension.

ByteArrayInputStream is - The input stream of bytes representing the image. The total size may not exceed 1 megabyte.

String contentType (optional) - The content type of the image. Optional, and defaults to "image/jpeg". Other supported values are "image/jpg" and "image/png".

Throws:

InvalidParameterException if the name does not solely consist of alphanumeric, ‘_’, ‘-‘, and ‘.’ characters.

InvalidParameterException if the size of total bytes to be stored exceeds one megabyte.

Exception - if the current Device Handler execution is attempting to store more than two images.

Example:

def params = [
            uri: "http://static.tvtropes.org/pmwiki/pub/images/catsbeard_9105.jpg"
    ]

try {
    httpGet(params) { response ->
        if (response.status == 200 && response.headers.'Content-Type'.contains("image/jpeg")) {
            def imageBytes = response.data
            if (imageBytes) {
                state.imgCount = state.imgCount + 1
                def name = "test$state.imgCount"

                // the response data is already a ByteArrayInputStream, no need to convert
                try {
                    storeImage(name, imageBytes)
                } catch (e) {
                    log.error "error storing image: $e"
                }
            }
        }
    }
} catch (err) {
    log.error ("Error making request: $err")
}

storeTemporaryImage()

Transfers an image temporarily stored via a HubAction request to a LAN-connected camera device to longer-lasting storage, and emits an event with name “image”. Typically used in conjunction with the carouselTile() to store and display images captured by a camera device.

Only the JPEG image format is supported.

Note

Images stored using storeTemporaryImage() are stored for 365 days, after which they will be permanently deleted.

The carouselTile() can display images for the past seven days.

Signature:
void storeTemporaryImage(String key, String name)
Parameters:

String key - The key for this image, extracted from the response map sent to the parse() method.

String name - The name associated with the image, consisting of alphanumeric, ‘_’, ‘-‘, and ‘.’ characters. This name must be unique per device instance, and should not include the file extension.

Throws:
InvalidParameterException if the name does not solely consist of alphanumeric, ‘_’, ‘-‘, and ‘.’ characters.

Example:

// take() command method from the Image Capture capability
def take() {
    def host = getHostAddress()
    def port = host.split(":")[1]

    def path = "/some/path/"

    def hubAction = new physicalgraph.device.HubAction(
        method: "GET",
        path: path,
        headers: [HOST:host]
    )

    // outputMsgToS3: true required to store this image temporarily!
    hubAction.options = [outputMsgToS3:true]

    return hubAction
}

/**
* Utility method to get the host addresses
*/
private getHostAddress() {
    def parts = device.deviceNetworkId.split(":")
    def ip = convertHexToIP(parts[0])
    def port = convertHexToInt(parts[1])
    return ip + ":" + port
}

def parse(String description) {

    def map = stringToMap(description)

    // if the message has the tempImageKey, we know it's a response from
    // an image stored via the HubAction. Need to move it to longer-lasting
    // storage with storeTemporaryImage()
    if (map.tempImageKey) {
        try {
            storeTemporaryImage(map.tempImageKey, getPictureName())
        } catch (Exception e) {
            log.error e
        }
    } else if (map.error) {
        log.error ("Error: ${map.error}")
    }

    // parse other messages too
}

/**
* Utility method to get a unique picture name
*/
private getPictureName() {
    return java.util.UUID.randomUUID().toString().replaceAll('-', '')
}

tiles()

Defines the user interface for the device in the mobile app. It’s composed of one or more standardTile() , valueTile() , carouselTile() , or controlTile() methods, as well as a main() and details() method.

Signature:
void tiles(Closure closure)
Parameters:
Closure closure - A closure that defines the various tiles and metadata.
Returns:
void

Example:

tiles {
    standardTile("switchTile", "device.switch", width: 2, height: 2,
                 canChangeIcon: true) {
        state "off", label: '${name}', action: "switch.on",
              icon: "st.switches.switch.off", backgroundColor: "#ffffff"
        state "on", label: '${name}', action: "switch.off",
              icon: "st.switches.switch.on", backgroundColor: "#E60000"
    }
    valueTile("powerTile", "device.power", decoration: "flat") {
              state "power", label:'${currentValue} W'
    }
    standardTile("refreshTile", "device.power", decoration: "ring") {
        state "default", label:'', action:"refresh.refresh",
              icon:"st.secondary.refresh",
    }

    main "switchTile"
    details(["switchTile","powerTile","refreshTile"])
}

valueTile()

Defines a tile that displays a specific value. Typical examples include temperature, humidity, or power values. Called within the tiles() method.

Signature:
void valueTile(String tileName, String attributeName [, Map options, Closure closure])
Returns:
void
Parameters:

String tileName - the name of the tile. This is used to identify the tile when specifying the tile layout.

String attributeName - the attribute this tile is associated with. Each tile is associated with an attribute of the device. The typical pattern is to prefix the attribute name with "device." - e.g., "device.power".

Map options (optional) - Various options for this tile. Valid options are found in the table below:

option type description
width Integer controls how wide the tile is. Default is 1.
height Integer controls how tall this tile is. Default is 1.
canChangeIcon Boolean true to allow the user to pick their own icon. Defaults to false.
canChangeBackground Boolean true to allow a user to choose their own background image for the tile. Defaults to false.
decoration String specify "flat" for the tile to render without a ring.

Closure closure (optional) - A closure that calls any state() methods to define how the tile should appear for various attribute values.

Example:

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

zigbee

A utility class for parsing and formatting ZigBee messages.

Signature:
Zigbee zigbee
Returns:
A reference to the ZigBee utility class.

zwave

The utility class for parsing and formatting Z-Wave command messages.

Signature:
ZWave zwave
Returns:
A reference to the ZWave helper class. See the Z-Wave Reference for more information.

Example:

// On command implementation for a Z-Wave switch
def on() {
    delayBetween([
        zwave.basicV1.basicSet(value: 0xFF).format(),
        zwave.switchBinaryV1.switchBinaryGet().format()
    ])
}