openhab-addons/bundles/org.openhab.binding.smartthings/contrib/smartthings/SmartApps/OpenHabAppV2.groovy

1053 lines
32 KiB
Groovy
Raw Normal View History

/**
* OpenHabAppV2
*
* Description
* Provides two way communications between a Smartthings Hub and OpenHAB
* Messages from OpenHAB with the following paths are supported and perform the following functions
* /state - returns the state of the specified device and attribute, i.e. on, off, 95
* /update - Updates the state of the specified device and attribute
* /discovery - Returns a list of the devices
* /error - Returns error messages to OpenHAB for logging
* Messages are sent to OpenHAB with the following paths
* /smartthings/push - When an event occurs on a monitored device the new value is sent to OpenHAB
*
* Authors
* - rjraker@gmail.com - 1/30/17 - Modified for use with Smartthings
* - st.john.johnson@gmail.com and jeremiah.wuenschel@gmail.com- original code for interface with another device
*
* Copyright 2016 - 2020
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*/
import groovy.json.JsonSlurper
import groovy.json.JsonOutput
import groovy.json.JsonBuilder
import groovy.transform.Field
// Massive lookup tree
@Field CAPABILITY_MAP = [
"accelerationSensor": [
name: "Acceleration Sensor",
capability: "capability.accelerationSensor",
attributes: [
"acceleration"
]
],
"airConditionerMode": [
name: "Air Conditioner Mode",
capability: "capability.airConditionerMode",
attributes: [
"airConditionerMode"
],
action: actionAirConditionerMode
],
"alarm": [
name: "Alarm",
capability: "capability.alarm",
attributes: [
"alarm"
],
action: "actionAlarm"
],
"battery": [
name: "Battery",
capability: "capability.battery",
attributes: [
"battery"
]
],
"beacon": [
name: "Beacon",
capability: "capability.beacon",
attributes: [
"presence"
]
],
"bulb": [
name: "Bulb",
capability: "capability.bulb",
attributes: [
"switch"
],
action: "actionOnOff"
],
"button": [
name: "Button",
capability: "capability.button",
attributes: [
"button"
]
],
"carbonDioxideMeasurement": [
name: "Carbon Dioxide Measurement",
capability: "capability.carbonDioxideMeasurement",
attributes: [
"carbonDioxide"
]
],
"carbonMonoxideDetector": [
name: "Carbon Monoxide Detector",
capability: "capability.carbonMonoxideDetector",
attributes: [
"carbonMonoxide"
]
],
"colorControl": [
name: "Color Control",
capability: "capability.colorControl",
attributes: [
"hue",
"saturation",
"color"
],
action: "actionColorControl"
],
"color": [
name: "Color (proposed)",
capability: "capability.color",
attributes: [
"colorValue"
],
action: "actionColor"
],
"colorTemperature": [
name: "Color Temperature",
capability: "capability.colorTemperature",
attributes: [
"colorTemperature"
],
action: "actionColorTemperature"
],
"consumable": [
name: "Consumable",
capability: "capability.consumable",
attributes: [
"consumable"
],
action: "actionConsumable"
],
"contactSensor": [
name: "Contact Sensor",
capability: "capability.contactSensor",
attributes: [
"contact"
]
],
"doorControl": [
name: "Door Control",
capability: "capability.doorControl",
attributes: [
"door"
],
action: "actionOpenClosed"
],
"energyMeter": [
name: "Energy Meter",
capability: "capability.energyMeter",
attributes: [
"energy"
]
],
"dryerMode": [
name: "Dryer Mode",
capability: "capability.dryerMode",
attributes: [
"dryerMode"
],
action: "actionApplianceMode"
],
"dryerOperatingState": [
name: "Dryer Operating State",
capability: "capability.dryerOperatingState",
attributes: [
"machineState",
"dryerJobState"
],
action: "actionMachineState"
],
"estimatedTimeOfArrival": [
name: "Estimated Time Of Arrival",
capability: "capability.estimatedTimeOfArrival",
attributes: [
"eta"
]
],
"garageDoorControl": [
name: "Garage Door Control",
capability: "capability.garageDoorControl",
attributes: [
"door"
],
action: "actionOpenClosed"
],
"holdableButton": [
name: "Holdable Button",
capability: "capability.holdableButton",
attributes: [
"button",
"numberOfButtons"
],
action: "actionOpenClosed"
],
"illuminanceMeasurement": [
name: "Illuminance Measurement",
capability: "capability.illuminanceMeasurement",
attributes: [
"illuminance"
]
],
"imageCapture": [
name: "Image Capture",
capability: "capability.imageCapture",
attributes: [
"image"
]
],
"indicator": [
name: "Indicator",
capability: "capability.indicator",
attributes: [
"indicatorStatus"
],
action: indicator
],
"infraredLevel": [
name: "Infrared Level",
capability: "capability.infraredLevel",
attributes: [
"infraredLevel"
],
action: "actionLevel"
],
"lock": [
name: "Lock",
capability: "capability.lock",
attributes: [
"lock"
],
action: "actionLock"
],
"lockOnly": [
name: "Lock Only",
capability: "capability.lockOnly",
attributes: [
"lock"
],
action: "actionLockOnly"
],
"mediaController": [
name: "Media Controller",
capability: "capability.mediaController",
attributes: [
"activities",
"currentActivity"
]
],
"motionSensor": [
name: "Motion Sensor",
capability: "capability.motionSensor",
attributes: [
"motion"
],
action: "actionActiveInactive"
],
"musicPlayer": [
name: "Music Player",
capability: "capability.musicPlayer",
attributes: [
"status",
"level",
"trackDescription",
"trackData",
"mute"
],
action: "actionMusicPlayer"
],
"outlet": [
name: "Outlet",
capability: "capability.outlet",
attributes: [
"switch"
],
action: "actionOnOff"
],
"pHMeasurement": [
name: "pH Measurement",
capability: "capability.pHMeasurement",
attributes: [
"pH"
]
],
"powerMeter": [
name: "Power Meter",
capability: "capability.powerMeter",
attributes: [
"power"
]
],
"powerSource": [
name: "Power Source",
capability: "capability.powerSource",
attributes: [
"powerSource"
]
],
"presenceSensor": [
name: "Presence Sensor",
capability: "capability.presenceSensor",
attributes: [
"presence"
]
],
"relativeHumidityMeasurement": [
name: "Relative Humidity Measurement",
capability: "capability.relativeHumidityMeasurement",
attributes: [
"humidity"
]
],
"relaySwitch": [
name: "Relay Switch",
capability: "capability.relaySwitch",
attributes: [
"switch"
],
action: "actionOnOff"
],
"shockSensor": [
name: "Shock Sensor",
capability: "capability.shockSensor",
attributes: [
"shock"
]
],
"signalStrength": [
name: "Signal Strength",
capability: "capability.signalStrength",
attributes: [
"lqi",
"rssi"
]
],
"sleepSensor": [
name: "Sleep Sensor",
capability: "capability.sleepSensor",
attributes: [
"sleeping"
]
],
"smokeDetector": [
name: "Smoke Detector",
capability: "capability.smokeDetector",
attributes: [
"smoke",
"carbonMonoxide"
]
],
"soundPressureLevel": [
name: "Sound Pressure Level",
capability: "capability.soundPressureLevel",
attributes: [
"soundPressureLevel"
]
],
"soundSensor": [
name: "Sound Sensor",
capability: "capability.soundSensor",
attributes: [
"phraseSpoken"
]
],
"speechRecognition": [
name: "Speech Recognition",
capability: "capability.speechRecognition",
action: [
"speak"
]
],
"stepSensor": [
name: "Step Sensor",
capability: "capability.stepSensor",
attributes: [
"steps",
"goal"
]
],
"switch": [
name: "Switch",
capability: "capability.switch",
attributes: [
"switch"
],
action: "actionOnOff"
],
"switchLevel": [
name: "Dimmer Switch",
capability: "capability.switchLevel",
attributes: [
"level"
],
action: "actionLevel"
],
"soundPressureLevel": [
name: "Sound Pressure Level",
capability: "capability.soundPressureLevel",
attributes: [
"soundPressureLevel"
]
],
"tamperAlert": [
name: "Tamper Alert",
capability: "capability.tamperAlert",
attributes: [
"tamper"
]
],
"temperatureMeasurement": [
name: "Temperature Measurement",
capability: "capability.temperatureMeasurement",
attributes: [
"temperature"
]
],
"thermostat": [
name: "Thermostat",
capability: "capability.thermostat",
attributes: [
"temperature",
"heatingSetpoint",
"coolingSetpoint",
"thermostatSetpoint",
"thermostatMode",
"thermostatFanMode",
"thermostatOperatingState"
],
action: "actionThermostat"
],
"thermostatCoolingSetpoint": [
name: "Thermostat Cooling Setpoint",
capability: "capability.thermostatCoolingSetpoint",
attributes: [
"coolingSetpoint"
],
action: "actionThermostat"
],
"thermostatFanMode": [
name: "Thermostat Fan Mode",
capability: "capability.thermostatFanMode",
attributes: [
"thermostatFanMode"
],
action: "actionThermostat"
],
"thermostatHeatingSetpoint": [
name: "Thermostat Heating Setpoint",
capability: "capability.thermostatHeatingSetpoint",
attributes: [
"heatingSetpoint"
],
action: "actionThermostat"
],
"thermostatMode": [
name: "Thermostat Mode",
capability: "capability.thermostatMode",
attributes: [
"thermostatMode"
],
action: "actionThermostat"
],
"thermostatOperatingState": [
name: "Thermostat Operating State",
capability: "capability.thermostatOperatingState",
attributes: [
"thermostatOperatingState"
]
],
"thermostatSetpoint": [
name: "Thermostat Setpoint",
capability: "capability.thermostatSetpoint",
attributes: [
"thermostatSetpoint"
]
],
"threeAxis": [
name: "Three Axis",
capability: "capability.threeAxis",
attributes: [
"threeAxis"
]
],
"timedSession": [
name: "Timed Session",
capability: "capability.timedSession",
attributes: [
"timeRemaining",
"sessionStatus"
],
action: "actionTimedSession"
],
"touchSensor": [
name: "Touch Sensor",
capability: "capability.touchSensor",
attributes: [
"touch"
]
],
"valve": [
name: "Valve",
capability: "capability.valve",
attributes: [
"valve"
],
action: "actionOpenClosed"
],
"voltageMeasurement": [
name: "Voltage Measurement",
capability: "capability.voltageMeasurement",
attributes: [
"voltage"
]
],
"washerMode": [
name: "Washer Mode",
capability: "capability.washerMode",
attributes: [
"washerMode"
],
action: "actionApplianceMode"
],
"washerOperatingState": [
name: "Washer Operating State",
capability: "capability.washerOperatingState",
attributes: [
"machineState",
"washerJobState"
],
action: "actionMachineState"
],
"waterSensor": [
name: "Water Sensor",
capability: "capability.waterSensor",
attributes: [
"water"
]
],
"windowShade": [
name: "Window Shade",
capability: "capability.windowShade",
attributes: [
"windowShade"
],
action: "actionOpenClosed"
]
]
definition(
name: "OpenHabAppV2",
namespace: "bobrak",
author: "Bob Raker",
description: "Provides two way communications between a Smartthings Hub and OpenHAB",
category: "My Apps",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Connections/Cat-Connections.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Connections/Cat-Connections@2x.png",
iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Connections/Cat-Connections@3x.png"
)
preferences {
section("Send Notifications?") {
input("recipients", "contact", title: "Send notifications to", multiple: true, required: false)
}
section ("Input") {
CAPABILITY_MAP.each { key, capability ->
input key, capability["capability"], title: capability["name"], description: capability["key"], multiple: true, required: false
}
}
section ("Device") {
input "openhabDevice", "capability.notification", title: "Notify this virtual device", required: true, multiple: false
}
}
def installed() {
log.debug "Installed with settings: ${settings}"
initialize()
}
def updated() {
log.debug "Updated with settings: ${settings}"
// Unsubscribe from all events
unsubscribe()
// Subscribe to stuff
initialize()
}
def initialize() {
// Subscribe to new events from devices
CAPABILITY_MAP.each { key, capability ->
capability["attributes"].each { attribute ->
if ( settings[key] != null ) {
subscribe(settings[key], attribute, inputHandler)
log.debug "Subscribing inputHandler to device \"${settings[key]}\" with attribute \"${attribute}\""
}
}
}
// Subscribe to events from the openhabDevice
log.debug "Subscribing to event handler ${openHabDevice}"
subscribe(openhabDevice, "message", openhabMessageHandler)
}
// Receive an event from OpenHAB via the openhabDevice
def openhabMessageHandler(evt) {
def json = new JsonSlurper().parseText(evt.value)
log.debug "Received device event from Message : ${json}"
switch (json.path) {
case "update":
openhabUpdateHandler (evt)
break
case "state":
openhabStateHandler (evt)
break
case "discovery":
openhabDiscoveryHandler (evt)
break
default:
log.debug "Received device event from Message **** UNEXPECTED **** : ${json}"
}
}
// Handler for "current" state requests
def openhabStateHandler(evt) {
def mapIn = new JsonSlurper().parseText(evt.value)
log.debug "Received state event from openhabDevice: ${mapIn}"
// Get the CAPABILITY_MAP entry for this device type
def capability = CAPABILITY_MAP[mapIn.capabilityKey]
if (capability == null) {
log.error "No capability: \"${mapIn.capabilityKey}\" exists, make sure there is a CAPABILITY_MAP entry for this capability."
sendErrorResponse "Requested current state information for CAPABILITY: \"${mapIn.capabilityKey}\" but this is not defined in the SmartApp"
return
}
// Verify the attribute is on this capability
if (! capability.attributes.contains(mapIn.capabilityAttribute) ) {
log.error "Capability \"${mapIn.capabilityKey}\" does NOT contain the expected attribute: \"${mapIn.capabilityAttribute}\", make sure the a CAPABILITY_MAP for this capability contains the missing attribte."
sendErrorResponse "Requested current state information for CAPABILITY: \"${mapIn.capabilityKey}\" with attribute: \"${mapIn.capabilityAttribute}\" but this is attribute not defined for this capability in the SmartApp"
return
}
// Look for the device associated with this capability and return the value of the specified attribute
settings[mapIn.capabilityKey].each {device ->
if (device.displayName == mapIn.deviceDisplayName) {
// Have the device, get the value and return the correct message
def currentState = device.currentValue(mapIn.capabilityAttribute)
// Have to handle special values. Ones that are not numeric or string
// This switch statement should just be considered a beginning. There are other cases that I dont have devices to test
def capabilityAttr = mapIn.capabilityAttribute
switch (capabilityAttr) {
case 'threeAxis' :
currentState = "${currentState}"
break
default :
break
}
def jsonOut = new JsonOutput().toJson([
path: "/smartthings/state",
body: [
deviceDisplayName: device.displayName,
capabilityAttribute: capabilityAttr,
value: currentState
]
])
log.debug "State Handler is returning ${jsonOut}"
openhabDevice.deviceNotification(jsonOut)
}
}
}
// Update a device when requested from OpenHAB
def openhabUpdateHandler(evt) {
def json = new JsonSlurper().parseText(evt.value)
// log.debug "Received update event from openhabDevice: ${json}"
// printSettings()
if (json.type == "notify") {
if (json.name == "Contacts") {
sendNotificationToContacts("${json.value}", recipients)
} else {
sendNotificationEvent("${json.value}")
}
return
}
// Get the CAPABILITY_MAP entry for this device type
def capability = CAPABILITY_MAP[json.capabilityKey]
if (capability == null) {
//log.error "No capability: \"${json.capabilityKey}\" exists, make sure there is a CAPABILITY_MAP entry for this capability."
sendErrorResponse "Update failed device displayName of: \"${json.deviceDisplayName}\" with CAPABILITY: \"${json.capabilityKey}\" because that CAPABILTY does not exist in the SmartApp"
return
}
// Look for the device associated with this capability and perform the requested action
settings[json.capabilityKey].each {device ->
// log.debug "openhabUpdateHandler - looking at devices with capabilityKey ${json.capabilityKey} and device{ ${device.displayName}."
if (device.displayName == json.deviceDisplayName) {
log.debug "openhabUpdateHandler - found device for ${json.deviceDisplayName}"
if (capability.containsKey("action")) {
// log.debug "openhabUpdateHandler - Capability ${capability.name} with device name ${device.displayName} changed to ${json.value} using action ${capability.action}"
def action = capability["action"]
// Yes, this is calling the method dynamically
try {
"$action"(device, json.capabilityAttribute, json.value)
} catch (e) {
sendErrorResponse "Error occured while calling action: {$action} for Capability: ${capability.name} with device name: ${device.displayName} changed to: ${json.value}. Exception ${e}"
// log.error "Error occured while calling action: {$action} for Capability: ${capability.name} with device name: ${device.displayName} changed to: ${json.value}. Exception ${e}"
}
}
}
}
}
// Debug method
def printSettings() {
log.debug "**** printSettings() ****"
String out
settings.each { key, device ->
out += " *** ${key} *** \n"
device.each { d ->
out += "[ key: ${key}, deviceName: ${d.name}, deviceLabel: ${d.label}, deviceValue: ${d.currentValue} "
/* The following does work for showing attributes bug significantly expands the output
def attributes = d.getSupportedAttributes()
out += ", attrLen: ${attributes.size()}"
attributes.each { a->
out += ", ${a}"
}
out += "], \n"
*/
}
}
log.debug "*** printSettings() done ***"
}
def sendErrorResponse (msg) {
def jsonOut = new JsonOutput().toJson([
path: "/smartthings/error",
body: [
message: msg
]
])
openhabDevice.deviceNotification(jsonOut)
log.error msg
}
// Send a list of all devices to OpenHAB - used during OpenHAB's discovery process
// The hub is only capable of sending back a buffer of ~40,000 bytes. This routine
// will send multiple responses anytime the buffer exceeds 30,000 bytes
def openhabDiscoveryHandler(evt) {
def mapIn = new JsonSlurper().parseText(evt.value)
log.debug "Entered discovery handler with mapIn: ${mapIn}"
def results = []
def bufferLength = 0
def deviceCount = 0
CAPABILITY_MAP.each { key, capability ->
capability["attributes"].each { attribute ->
settings[key].each {device ->
// The device info has to be returned as a string. It will be parsed into device data on the OpenHAB side
def deviceInfo = "{\"capability\": \"${key}\", \"attribute\": \"${attribute}\", \"name\": \"${device.displayName}\", \"id\": \"${device.id}\" }"
results.push(deviceInfo)
deviceCount++
bufferLength += deviceInfo.length()
// Check if we have close to a full buffer and if so send it
if( bufferLength > 30000 ) {
def json = new groovy.json.JsonOutput().toJson([
path: "/smartthings/discovery",
body: results
])
log.debug "Discovery is returning JSON: ${json}"
openhabDevice.deviceNotification(json)
results = []
bufferLength = 0
}
}
}
}
if( bufferLength > 0 ) {
def json = new groovy.json.JsonOutput().toJson([
path: "/smartthings/discovery",
body: results
])
log.debug "Discovery is returning FINAL JSON: ${json}"
openhabDevice.deviceNotification(json)
}
log.debug "Discovery returned data for ${deviceCount} devices."
}
// Receive an event from a device and send it onto OpenHAB
def inputHandler(evt) {
def device = evt.device
def capabilities = device.capabilities
def json = new JsonOutput().toJson([
path: "/smartthings/state",
body: [
deviceDisplayName: evt.displayName,
value: evt.value,
capabilityAttribute: evt.name,
]
])
log.debug "Forwarding device event to openhabDevice: ${json}"
openhabDevice.deviceNotification(json)
}
// +---------------------------------+
// | WARNING, BEYOND HERE BE DRAGONS |
// +---------------------------------+
// These are the functions that handle incoming messages from OpenHAB.
// I tried to put them in closures but apparently SmartThings Groovy sandbox
// restricts you from running closures from an object (it's not safe).
// This handles the basic case where there is one attribute and one action that sets the attribute.
// And, the value is always an ENUM
def actionEnum(device, attribute, value) {
log.debug "actionEnum: Setting device \"${device}\" with attribute \"${attribute}\" to value \"${value}\""
//device."${value}"() // I can't figure out why this doesn't work, but it doesn't
def converted = "set" + attribute.capitalize()
device."$converted"(value)
}
def actionAirConditionerMode(device, attribute, value) {
log.debug "actionAirConditionerMode: Setting device \"${device}\" with attribute \"${attribute}\" to value \"${value}\""
device.setAirConditionerMode(value)
}
def actionAlarm(device, attribute, value) {
switch (value) {
case "strobe":
device.strobe()
break
case "siren":
device.siren()
break
case "off":
device.off()
break
case "both":
device.both()
break
}
}
// This is the original color control
def actionColorControl(device, attribute, value) {
log.debug "actionColor: attribute \"${attribute}\", value \"${value}\""
switch (attribute) {
case "hue":
device.setHue(value as int)
break
case "saturation":
device.setSaturation(value as int)
break
case "color":
def colormap = ["hue": value[0] as int, "saturation": value[1] as int]
// log.debug "actionColor: Setting device \"${device}\" with attribute \"${attribute}\" to colormap \"${colormap}\""
device.setColor(colormap)
device.setLevel(value[2] as int)
break
}
}
// This is the new "proposed" color. Here hue is 0-360
def actionColor(device, attribute, value) {
log.debug "actionColor: attribute \"${attribute}\", value \"${value}\""
switch (attribute) {
case "hue":
device.setHue(value as int)
break
case "saturation":
device.setSaturation(value as int)
break
case "colorValue":
def colormap = ["hue": value[0] as int, "saturation": value[1] as int]
// log.debug "actionColor: Setting device \"${device}\" with attribute \"${attribute}\" to colormap \"${colormap}\""
device.setColor(colormap)
device.setLevel(value[2] as int)
break
}
}
def actionOpenClosed(device, attribute, value) {
if (value == "open") {
device.open()
} else if (value == "close") {
device.close()
}
}
def actionOnOff(device, attribute, value) {
if (value == "off") {
device.off()
} else if (value == "on") {
device.on()
}
}
def actionActiveInactive(device, attribute, value) {
if (value == "active") {
device.active()
} else if (value == "inactive") {
device.inactive()
}
}
def actionThermostat(device, attribute, value) {
log.debug "actionThermostat: Setting device \"${device}\" with attribute \"${attribute}\" to value \"${value}\""
switch(attribute) {
case "heatingSetpoint":
device.setHeatingSetpoint(value)
break
case "coolingSetpoint":
device.setCoolingSetpoint(value)
break
case "thermostatMode":
device.setThermostatMode(value)
break
case "thermostatFanMode":
device.setThermostatFanMode(value)
break
}
}
def actionMusicPlayer(device, attribute, value) {
switch(attribute) {
case "level":
device.setLevel(value)
break
case "mute":
if (value == "muted") {
device.mute()
} else if (value == "unmuted") {
device.unmute()
}
break
}
}
def actionColorTemperature(device, attribute, value) {
device.setColorTemperature(value as int)
}
def actionLevel(device, attribute, value) {
//log.debug "actionLevel: Setting device \"${device}\" with attribute \"${attribute}\" to value \"${value}\""
// OpenHAB will send on / off or a number for the percent. See what we got and acct accordingly
if (value == "off") {
device.off()
} else if (value == "on") {
device.on()
} else {
device.setLevel(value as int)
// And, set the switch to on if level > 0 otherwise off
if( value > 0 ) {
device.on()
} else {
device.off()
}
}
}
def actionConsumable(device, attribute, value) {
device.setConsumableStatus(value)
}
def actionLock(device, attribute, value) {
// log.debug "actionLock: Setting device \"${device}\" with attribute \"${attribute}\" to value \"${value}\""
if (value == "locked") {
device.lock()
} else if (value == "unlocked") {
device.unlock()
}
}
def actionLockOnly(device, attribute, value) {
// log.debug "actionLockOnly: Setting device \"${device}\" with attribute \"${attribute}\" to value \"${value}\""
if (value == "locked") {
device.lock()
}
}
def actionTimedSession(device, attribute, value) {
if (attribute == "timeRemaining") {
device.setTimeRemaining(value)
}
}
def actionApplianceMode(device, attribute, value) {
//log.debug "actionDryeMode: attribute: ${attribute} value: ${value}"
// Through trial and error I figured out that changing the dryerMode requires the following code
// Originally this was called actionDryerMode but then the washer was added I renamed and added washer modes
switch (value) {
// Modes used by both washer and dryer
case "regular":
device.regular()
break
// Dryer modes
case "lowHeat":
device.lowHeat()
break
case "highHeat":
device.highHeat()
break
// washer modes
case "heavy":
device.heavy()
break
case "rinse":
device.rinse()
break
case "spinDry":
device.spinDry()
break
}
}
def actionMachineState(device, attribute, value) {
//log.debug "actionMachineState: attribute: ${attribute} value: ${value}"
// Through trial and error I figured out that changing the machineState requires the following code
switch (value) {
case "run":
device.start()
break
case "stop":
device.stop()
break
case "pause":
device.pause()
break
// I'm not sure if unpause() is valid. I saw an error message that included unpause as a valid command but it is not included in the Capabilities for MachineState
case "unpause":
device.unpause()
break
}
}
// The following functions return the current state of a device
def switchState(device, attribute) {
device.currentValue(attribute);
}